Play position in stuff like podcasts can now be permanently persisted.
This commit is contained in:
parent
076f0e9dfd
commit
65118fbc42
@ -4,9 +4,16 @@
|
|||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include "http_client_wrapper.h"
|
#include "http_client_wrapper.h"
|
||||||
|
|
||||||
|
enum PlaylistPersistence {
|
||||||
|
PERSIST_NONE,
|
||||||
|
PERSIST_TEMPORARY,
|
||||||
|
PERSIST_PERMANENTLY
|
||||||
|
};
|
||||||
|
|
||||||
struct PlaylistEntry {
|
struct PlaylistEntry {
|
||||||
String filename;
|
String filename;
|
||||||
String title;
|
String title;
|
||||||
|
String id;
|
||||||
|
|
||||||
bool operator<(PlaylistEntry p) { return title < p.title; }
|
bool operator<(PlaylistEntry p) { return title < p.title; }
|
||||||
};
|
};
|
||||||
@ -19,12 +26,14 @@ private:
|
|||||||
bool _shuffled = false;
|
bool _shuffled = false;
|
||||||
std::vector<PlaylistEntry> _files;
|
std::vector<PlaylistEntry> _files;
|
||||||
String _title = "";
|
String _title = "";
|
||||||
|
String _path;
|
||||||
void _add_path(String path);
|
void _add_path(String path);
|
||||||
void _examine_http_url(String url);
|
void _examine_http_url(String url);
|
||||||
void _parse_rss(HTTPClientWrapper* http);
|
void _parse_rss(HTTPClientWrapper* http);
|
||||||
void _parse_m3u(HTTPClientWrapper* http);
|
void _parse_m3u(HTTPClientWrapper* http);
|
||||||
void _parse_pls(HTTPClientWrapper* http);
|
void _parse_pls(HTTPClientWrapper* http);
|
||||||
public:
|
public:
|
||||||
|
PlaylistPersistence persistence = PERSIST_TEMPORARY;
|
||||||
Playlist(String path);
|
Playlist(String path);
|
||||||
void start();
|
void start();
|
||||||
uint16_t get_file_count();
|
uint16_t get_file_count();
|
||||||
@ -34,9 +43,12 @@ public:
|
|||||||
bool track_prev();
|
bool track_prev();
|
||||||
void track_restart();
|
void track_restart();
|
||||||
bool set_track(uint8_t track);
|
bool set_track(uint8_t track);
|
||||||
|
void set_track_by_id(String id);
|
||||||
void reset();
|
void reset();
|
||||||
|
String path();
|
||||||
bool is_empty();
|
bool is_empty();
|
||||||
bool get_current_file(String* dst);
|
bool get_current_file(String* dst);
|
||||||
|
String get_current_track_id();
|
||||||
uint32_t get_position();
|
uint32_t get_position();
|
||||||
void set_position(uint32_t p);
|
void set_position(uint32_t p);
|
||||||
void shuffle(uint8_t random_offset=0);
|
void shuffle(uint8_t random_offset=0);
|
||||||
|
@ -20,4 +20,5 @@ public:
|
|||||||
String json();
|
String json();
|
||||||
bool add_mapping(String id, String folder);
|
bool add_mapping(String id, String folder);
|
||||||
String create_mapping_txt();
|
String create_mapping_txt();
|
||||||
|
void persist(Playlist* p);
|
||||||
};
|
};
|
||||||
|
@ -613,7 +613,8 @@ void Player::stop(bool turn_speaker_off) {
|
|||||||
if (_state != playing) return;
|
if (_state != playing) return;
|
||||||
INFO("Stopping...\n");
|
INFO("Stopping...\n");
|
||||||
_current_playlist->set_position(_current_play_position);
|
_current_playlist->set_position(_current_play_position);
|
||||||
|
_controller->pm->persist(_current_playlist);
|
||||||
|
|
||||||
_state = stopping;
|
_state = stopping;
|
||||||
_stop_delay = 0;
|
_stop_delay = 0;
|
||||||
_write_control_register(SCI_MODE, _read_control_register(SCI_MODE) | SM_CANCEL);
|
_write_control_register(SCI_MODE, _read_control_register(SCI_MODE) | SM_CANCEL);
|
||||||
|
@ -7,7 +7,9 @@
|
|||||||
#include <TinyXML.h>
|
#include <TinyXML.h>
|
||||||
|
|
||||||
Playlist::Playlist(String path) {
|
Playlist::Playlist(String path) {
|
||||||
|
_path = path;
|
||||||
if (path.startsWith("/")) {
|
if (path.startsWith("/")) {
|
||||||
|
persistence = PERSIST_TEMPORARY;
|
||||||
_add_path(path);
|
_add_path(path);
|
||||||
} else if (path.startsWith("http")) {
|
} else if (path.startsWith("http")) {
|
||||||
_examine_http_url(path);
|
_examine_http_url(path);
|
||||||
@ -44,7 +46,7 @@ void Playlist::_add_path(String path) {
|
|||||||
ext.equals(".mpa"))) {
|
ext.equals(".mpa"))) {
|
||||||
TRACE(" Adding entry %s\n", entry.name());
|
TRACE(" Adding entry %s\n", entry.name());
|
||||||
String title = filename.substring(0, filename.length() - 4);
|
String title = filename.substring(0, filename.length() - 4);
|
||||||
_files.push_back({.filename=entry.name(), .title=title});
|
_files.push_back({.filename=entry.name(), .title=title, .id=String(_files.size())});
|
||||||
bool non_ascii_chars = false;
|
bool non_ascii_chars = false;
|
||||||
for(int i=0; i<filename.length(); i++) {
|
for(int i=0; i<filename.length(); i++) {
|
||||||
char c = filename.charAt(i);
|
char c = filename.charAt(i);
|
||||||
@ -75,12 +77,16 @@ void Playlist::_examine_http_url(String url) {
|
|||||||
String ct = http->getContentType();
|
String ct = http->getContentType();
|
||||||
DEBUG("Content-Type is %s.\n", ct.c_str());
|
DEBUG("Content-Type is %s.\n", ct.c_str());
|
||||||
if (ct.startsWith("audio/x-mpegurl")) {
|
if (ct.startsWith("audio/x-mpegurl")) {
|
||||||
|
|
||||||
_parse_m3u(http);
|
_parse_m3u(http);
|
||||||
} else if (ct.startsWith("audio/")) {
|
} else if (ct.startsWith("audio/")) {
|
||||||
_files.push_back({.filename=url, .title=url});
|
persistence = PERSIST_NONE;
|
||||||
|
_files.push_back({.filename=url, .title=url, .id="none"});
|
||||||
} else if (ct.startsWith("application/rss+xml")) {
|
} else if (ct.startsWith("application/rss+xml")) {
|
||||||
|
persistence = PERSIST_PERMANENTLY;
|
||||||
_parse_rss(http);
|
_parse_rss(http);
|
||||||
} else if (ct.startsWith("application/pls+xml")) {
|
} else if (ct.startsWith("application/pls+xml")) {
|
||||||
|
persistence = PERSIST_PERMANENTLY;
|
||||||
_parse_pls(http);
|
_parse_pls(http);
|
||||||
} else {
|
} else {
|
||||||
ERROR("Unknown content type %s.\n", ct.c_str());
|
ERROR("Unknown content type %s.\n", ct.c_str());
|
||||||
@ -95,6 +101,7 @@ String xml_title = "";
|
|||||||
String xml_album_title = "";
|
String xml_album_title = "";
|
||||||
String xml_url = "";
|
String xml_url = "";
|
||||||
String xml_enclosure_url = "";
|
String xml_enclosure_url = "";
|
||||||
|
String xml_guid = "";
|
||||||
bool xml_enclosure_is_audio = false;
|
bool xml_enclosure_is_audio = false;
|
||||||
|
|
||||||
void xmlcb(uint8_t status, char* tagName, uint16_t tagLen, char* data, uint16_t dataLen) {
|
void xmlcb(uint8_t status, char* tagName, uint16_t tagLen, char* data, uint16_t dataLen) {
|
||||||
@ -106,8 +113,11 @@ void xmlcb(uint8_t status, char* tagName, uint16_t tagLen, char* data, uint16_t
|
|||||||
} else if (tag.endsWith("/item") && (status & STATUS_START_TAG)) {
|
} else if (tag.endsWith("/item") && (status & STATUS_START_TAG)) {
|
||||||
xml_title = "";
|
xml_title = "";
|
||||||
xml_url = "";
|
xml_url = "";
|
||||||
|
xml_guid = "";
|
||||||
} else if (tag.endsWith("/item/title") && (status & STATUS_TAG_TEXT)) {
|
} else if (tag.endsWith("/item/title") && (status & STATUS_TAG_TEXT)) {
|
||||||
xml_title = String(data);
|
xml_title = String(data);
|
||||||
|
} else if (tag.endsWith("/item/guid") && (status & STATUS_TAG_TEXT)) {
|
||||||
|
xml_guid = data;
|
||||||
//} else if (xml_last_tag.endsWith("/item/enclosure") && (status & STATUS_ATTR_TEXT)) {
|
//} else if (xml_last_tag.endsWith("/item/enclosure") && (status & STATUS_ATTR_TEXT)) {
|
||||||
// DEBUG("tag: %s, data: %s\n", tag.c_str(), data);
|
// DEBUG("tag: %s, data: %s\n", tag.c_str(), data);
|
||||||
} else if (xml_last_tag.endsWith("/item/enclosure") && tag.equals("type") && (status & STATUS_ATTR_TEXT) && String(data).indexOf("audio/")>=0) {
|
} else if (xml_last_tag.endsWith("/item/enclosure") && tag.equals("type") && (status & STATUS_ATTR_TEXT) && String(data).indexOf("audio/")>=0) {
|
||||||
@ -126,7 +136,7 @@ void xmlcb(uint8_t status, char* tagName, uint16_t tagLen, char* data, uint16_t
|
|||||||
} else if (tag.endsWith("/item") && (status & STATUS_END_TAG)) {
|
} else if (tag.endsWith("/item") && (status & STATUS_END_TAG)) {
|
||||||
if (xml_title.length()>0 && xml_url.length()>0) {
|
if (xml_title.length()>0 && xml_url.length()>0) {
|
||||||
DEBUG("Adding playlist entry: '%s' => '%s'\n", xml_title.c_str(), xml_url.c_str());
|
DEBUG("Adding playlist entry: '%s' => '%s'\n", xml_title.c_str(), xml_url.c_str());
|
||||||
xml_files_ptr->push_back({xml_url, xml_title});
|
xml_files_ptr->insert(xml_files_ptr->begin(), {.filename=xml_url, .title=xml_title, .id=xml_guid});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,7 +190,7 @@ void Playlist::_parse_m3u(HTTPClientWrapper* http) {
|
|||||||
}
|
}
|
||||||
} else if (line.startsWith("http")) {
|
} else if (line.startsWith("http")) {
|
||||||
if (title.length()==0) title = line;
|
if (title.length()==0) title = line;
|
||||||
_files.push_back({.filename=line, .title=title});
|
_files.push_back({.filename=line, .title=title, .id="none"});
|
||||||
title = "";
|
title = "";
|
||||||
}
|
}
|
||||||
line = "";
|
line = "";
|
||||||
@ -222,7 +232,7 @@ void Playlist::_parse_pls(HTTPClientWrapper* http) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (title.length()>0 && url.length()>0) {
|
if (title.length()>0 && url.length()>0) {
|
||||||
_files.push_back({.filename=url, .title=title});
|
_files.push_back({.filename=url, .title=title, .id="none"});
|
||||||
last_index = -1;
|
last_index = -1;
|
||||||
title = "";
|
title = "";
|
||||||
url = "";
|
url = "";
|
||||||
@ -231,6 +241,10 @@ void Playlist::_parse_pls(HTTPClientWrapper* http) {
|
|||||||
// don't close http at the end
|
// don't close http at the end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String Playlist::path() {
|
||||||
|
return _path;
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t Playlist::get_file_count() {
|
uint16_t Playlist::get_file_count() {
|
||||||
return _files.size();
|
return _files.size();
|
||||||
}
|
}
|
||||||
@ -274,6 +288,15 @@ bool Playlist::set_track(uint8_t track) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Playlist::set_track_by_id(String id) {
|
||||||
|
for (int i=0; i<_files.size(); i++) {
|
||||||
|
if (id.equals(_files[i].id)) {
|
||||||
|
set_track(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Playlist::track_restart() {
|
void Playlist::track_restart() {
|
||||||
_position = 0;
|
_position = 0;
|
||||||
}
|
}
|
||||||
@ -310,8 +333,15 @@ void Playlist::reset() {
|
|||||||
_started = false;
|
_started = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String Playlist::get_current_track_id() {
|
||||||
|
if (_current_track > _files.size()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return _files[_current_track].id;
|
||||||
|
}
|
||||||
|
|
||||||
bool Playlist::get_current_file(String* dst) {
|
bool Playlist::get_current_file(String* dst) {
|
||||||
if (_current_track < _files.size()) {
|
if (_current_track > _files.size()) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
dst->concat(_files[_current_track].filename);
|
dst->concat(_files[_current_track].filename);
|
||||||
|
@ -104,10 +104,41 @@ Playlist* PlaylistManager::get_playlist_for_id(String id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Playlist* PlaylistManager::get_playlist_for_folder(String folder) {
|
Playlist* PlaylistManager::get_playlist_for_folder(String folder) {
|
||||||
|
Playlist* p;
|
||||||
if (!_playlists.count(folder)) {
|
if (!_playlists.count(folder)) {
|
||||||
_playlists[folder] = new Playlist(folder);
|
p = new Playlist(folder);
|
||||||
|
_playlists[folder] = p;
|
||||||
|
if (p->persistence == PERSIST_PERMANENTLY) {
|
||||||
|
// TODO Load persistence from file
|
||||||
|
String search = folder;
|
||||||
|
search += "=";
|
||||||
|
SPIMaster::select_sd();
|
||||||
|
if (SD.exists("/_positions.txt")) {
|
||||||
|
File f = SD.open("/_positions.txt", "r");
|
||||||
|
while (true) {
|
||||||
|
String s = f.readStringUntil('\n');
|
||||||
|
if (s.length()==0) break;
|
||||||
|
if (s.startsWith(search)) {
|
||||||
|
s = s.substring(search.length());
|
||||||
|
int idx = s.indexOf(',');
|
||||||
|
String title_index = s.substring(0, idx);
|
||||||
|
uint32_t position = s.substring(idx+1).toInt();
|
||||||
|
p->set_track_by_id(title_index);
|
||||||
|
p->set_position(position);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
SPIMaster::select_sd(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p = _playlists[folder];
|
||||||
|
if (p->persistence == PERSIST_NONE) {
|
||||||
|
p->reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return _playlists[folder];
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaylistManager::dump_ids() {
|
void PlaylistManager::dump_ids() {
|
||||||
@ -164,3 +195,40 @@ String PlaylistManager::create_mapping_txt() {
|
|||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlaylistManager::persist(Playlist* p) {
|
||||||
|
if (p->persistence != PERSIST_PERMANENTLY) return;
|
||||||
|
|
||||||
|
String search = p->path();
|
||||||
|
search += '=';
|
||||||
|
|
||||||
|
bool old_file_existed = false;
|
||||||
|
|
||||||
|
SPIMaster::select_sd();
|
||||||
|
if (SD.exists("_positions.txt")) {
|
||||||
|
SD.rename("/_positions.txt", "/_positions.temp.txt");
|
||||||
|
old_file_existed = true;
|
||||||
|
}
|
||||||
|
File dst = SD.open("/_positions.txt", "w");
|
||||||
|
|
||||||
|
if (old_file_existed) {
|
||||||
|
File src = SD.open("/_positions.temp.txt", "r");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
String line = src.readStringUntil('\n');
|
||||||
|
line.trim();
|
||||||
|
if (line.startsWith(search)) continue;
|
||||||
|
dst.println(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
src.close();
|
||||||
|
SD.remove("/_positions.temp.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.print(search);
|
||||||
|
dst.print(p->get_current_track_id());
|
||||||
|
dst.print(',');
|
||||||
|
dst.println(p->get_position());
|
||||||
|
dst.close();
|
||||||
|
SPIMaster::select_sd(false);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user