Play position in stuff like podcasts can now be permanently persisted.

This commit is contained in:
Fabian Schlenz 2019-11-29 17:41:16 +01:00
parent 076f0e9dfd
commit 65118fbc42
5 changed files with 121 additions and 9 deletions

View File

@ -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);

View File

@ -20,4 +20,5 @@ public:
String json();
bool add_mapping(String id, String folder);
String create_mapping_txt();
void persist(Playlist* p);
};

View File

@ -613,7 +613,8 @@ 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;
_write_control_register(SCI_MODE, _read_control_register(SCI_MODE) | SM_CANCEL);

View File

@ -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);

View File

@ -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);
}