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 "http_client_wrapper.h"
|
||||
|
||||
enum PlaylistPersistence {
|
||||
PERSIST_NONE,
|
||||
PERSIST_TEMPORARY,
|
||||
PERSIST_PERMANENTLY
|
||||
};
|
||||
|
||||
struct PlaylistEntry {
|
||||
String filename;
|
||||
String title;
|
||||
String id;
|
||||
|
||||
bool operator<(PlaylistEntry p) { return title < p.title; }
|
||||
};
|
||||
@ -19,12 +26,14 @@ private:
|
||||
bool _shuffled = false;
|
||||
std::vector<PlaylistEntry> _files;
|
||||
String _title = "";
|
||||
String _path;
|
||||
void _add_path(String path);
|
||||
void _examine_http_url(String url);
|
||||
void _parse_rss(HTTPClientWrapper* http);
|
||||
void _parse_m3u(HTTPClientWrapper* http);
|
||||
void _parse_pls(HTTPClientWrapper* http);
|
||||
public:
|
||||
PlaylistPersistence persistence = PERSIST_TEMPORARY;
|
||||
Playlist(String path);
|
||||
void start();
|
||||
uint16_t get_file_count();
|
||||
@ -34,9 +43,12 @@ public:
|
||||
bool track_prev();
|
||||
void track_restart();
|
||||
bool set_track(uint8_t track);
|
||||
void set_track_by_id(String id);
|
||||
void reset();
|
||||
String path();
|
||||
bool is_empty();
|
||||
bool get_current_file(String* dst);
|
||||
String get_current_track_id();
|
||||
uint32_t get_position();
|
||||
void set_position(uint32_t p);
|
||||
void shuffle(uint8_t random_offset=0);
|
||||
|
@ -20,4 +20,5 @@ public:
|
||||
String json();
|
||||
bool add_mapping(String id, String folder);
|
||||
String create_mapping_txt();
|
||||
void persist(Playlist* p);
|
||||
};
|
||||
|
@ -613,6 +613,7 @@ void Player::stop(bool turn_speaker_off) {
|
||||
if (_state != playing) return;
|
||||
INFO("Stopping...\n");
|
||||
_current_playlist->set_position(_current_play_position);
|
||||
_controller->pm->persist(_current_playlist);
|
||||
|
||||
_state = stopping;
|
||||
_stop_delay = 0;
|
||||
|
@ -7,7 +7,9 @@
|
||||
#include <TinyXML.h>
|
||||
|
||||
Playlist::Playlist(String path) {
|
||||
_path = path;
|
||||
if (path.startsWith("/")) {
|
||||
persistence = PERSIST_TEMPORARY;
|
||||
_add_path(path);
|
||||
} else if (path.startsWith("http")) {
|
||||
_examine_http_url(path);
|
||||
@ -44,7 +46,7 @@ void Playlist::_add_path(String path) {
|
||||
ext.equals(".mpa"))) {
|
||||
TRACE(" Adding entry %s\n", entry.name());
|
||||
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;
|
||||
for(int i=0; i<filename.length(); i++) {
|
||||
char c = filename.charAt(i);
|
||||
@ -75,12 +77,16 @@ void Playlist::_examine_http_url(String url) {
|
||||
String ct = http->getContentType();
|
||||
DEBUG("Content-Type is %s.\n", ct.c_str());
|
||||
if (ct.startsWith("audio/x-mpegurl")) {
|
||||
|
||||
_parse_m3u(http);
|
||||
} 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")) {
|
||||
persistence = PERSIST_PERMANENTLY;
|
||||
_parse_rss(http);
|
||||
} else if (ct.startsWith("application/pls+xml")) {
|
||||
persistence = PERSIST_PERMANENTLY;
|
||||
_parse_pls(http);
|
||||
} else {
|
||||
ERROR("Unknown content type %s.\n", ct.c_str());
|
||||
@ -95,6 +101,7 @@ String xml_title = "";
|
||||
String xml_album_title = "";
|
||||
String xml_url = "";
|
||||
String xml_enclosure_url = "";
|
||||
String xml_guid = "";
|
||||
bool xml_enclosure_is_audio = false;
|
||||
|
||||
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)) {
|
||||
xml_title = "";
|
||||
xml_url = "";
|
||||
xml_guid = "";
|
||||
} else if (tag.endsWith("/item/title") && (status & STATUS_TAG_TEXT)) {
|
||||
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)) {
|
||||
// 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) {
|
||||
@ -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)) {
|
||||
if (xml_title.length()>0 && xml_url.length()>0) {
|
||||
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")) {
|
||||
if (title.length()==0) title = line;
|
||||
_files.push_back({.filename=line, .title=title});
|
||||
_files.push_back({.filename=line, .title=title, .id="none"});
|
||||
title = "";
|
||||
}
|
||||
line = "";
|
||||
@ -222,7 +232,7 @@ void Playlist::_parse_pls(HTTPClientWrapper* http) {
|
||||
}
|
||||
|
||||
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;
|
||||
title = "";
|
||||
url = "";
|
||||
@ -231,6 +241,10 @@ void Playlist::_parse_pls(HTTPClientWrapper* http) {
|
||||
// don't close http at the end
|
||||
}
|
||||
|
||||
String Playlist::path() {
|
||||
return _path;
|
||||
}
|
||||
|
||||
uint16_t Playlist::get_file_count() {
|
||||
return _files.size();
|
||||
}
|
||||
@ -274,6 +288,15 @@ bool Playlist::set_track(uint8_t track) {
|
||||
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() {
|
||||
_position = 0;
|
||||
}
|
||||
@ -310,8 +333,15 @@ void Playlist::reset() {
|
||||
_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) {
|
||||
if (_current_track < _files.size()) {
|
||||
if (_current_track > _files.size()) {
|
||||
return false;
|
||||
} else {
|
||||
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* p;
|
||||
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() {
|
||||
@ -164,3 +195,40 @@ String PlaylistManager::create_mapping_txt() {
|
||||
}
|
||||
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