Compare commits

..

No commits in common. "710b8a2cdca3184f1e35428caa6ecec6e277b450" and "01f513c97bfb60e24261d5680cf1e2a8b1c4b646" have entirely different histories.

12 changed files with 86 additions and 242 deletions

View File

@ -37,9 +37,7 @@ and dashes an alike are okay. Put the SD card into the SD card slot.
Copy `include/config.sample.h` to `include/config.h`. Modify it to at Copy `include/config.sample.h` to `include/config.h`. Modify it to at
least contain the correct login details for your WiFi. least contain the correct login details for your WiFi.
The code then should compile in PlatformIO without errors. Upload it The code then should compile in PlatformIO without errors.
to your ESP32. After that, upload static files using PlatformIO's task
"Upload file system image".
The serial console in PlatformIO should give you more or less useful The serial console in PlatformIO should give you more or less useful
messages about what's going on. There will also be a line saying messages about what's going on. There will also be a line saying
@ -144,4 +142,4 @@ will only play in December. On December 1st, only track 1
will play. On December 2nd, track 2 followed by track 1. On will play. On December 2nd, track 2 followed by track 1. On
December 3rd, tracks 3, 1 and 2. From December 24th on, track December 3rd, tracks 3, 1 and 2. From December 24th on, track
24 followed by tracks 1-23. So your kid will get the "daily track" 24 followed by tracks 1-23. So your kid will get the "daily track"
first, followed by all previous tags in the right order. first, followed by all previous tags in the right order.

View File

@ -18,6 +18,7 @@ class Controller {
private: private:
MFRC522* _rfid; MFRC522* _rfid;
HTTPServer* _http_server; HTTPServer* _http_server;
PlaylistManager* _pm;
ControllerState _state = NORMAL; ControllerState _state = NORMAL;
bool _rfid_enabled = true; bool _rfid_enabled = true;
void _check_rfid(); void _check_rfid();
@ -29,7 +30,7 @@ private:
bool _rfid_present = false; bool _rfid_present = false;
String _last_rfid_uid = ""; String _last_rfid_uid = "";
String _last_rfid_data = ""; String _last_rfid_data = "";
Player* _player;
unsigned long _last_rfid_scan_at = 0; unsigned long _last_rfid_scan_at = 0;
unsigned long _last_position_info_at = 0; unsigned long _last_position_info_at = 0;
String _serial_buffer = String(); String _serial_buffer = String();
@ -41,8 +42,6 @@ private:
bool _check_button(uint8_t btn); bool _check_button(uint8_t btn);
public: public:
Controller(Player* p, PlaylistManager* pm); Controller(Player* p, PlaylistManager* pm);
PlaylistManager* pm;
Player* player;
void register_http_server(HTTPServer* h); void register_http_server(HTTPServer* h);
void loop(); void loop();
void send_controller_status(); void send_controller_status();

View File

@ -1,55 +0,0 @@
#pragma once
#include <Arduino.h>
#include <SD.h>
#include "config.h"
#include <HTTPClient.h>
class DataSource {
private:
public:
DataSource() {};
virtual ~DataSource() {};
virtual size_t read(uint8_t* buf, size_t len) = 0;
virtual uint8_t read() = 0;
virtual size_t position() = 0;
virtual void seek(size_t position) = 0;
virtual size_t size() = 0;
virtual void close() = 0;
virtual void skip_id3_tag() {};
virtual bool usable() = 0;
};
class SDDataSource : public DataSource {
private:
File _file;
public:
SDDataSource(String file);
~SDDataSource();
size_t read(uint8_t* buf, size_t len);
uint8_t read();
size_t position();
void seek(size_t position);
size_t size();
void close();
void skip_id3_tag();
bool usable();
};
class HTTPSDataSource : public DataSource {
private:
WiFiClient* _stream = NULL;
HTTPClient* _http = NULL;
uint32_t _length;
uint32_t _position;
public:
HTTPSDataSource(String url, uint32_t offset=0);
~HTTPSDataSource();
size_t read(uint8_t* buf, size_t len);
uint8_t read();
size_t position();
void seek(size_t position);
size_t size();
void close();
bool usable();
};

View File

@ -4,7 +4,6 @@
#include <SD.h> #include <SD.h>
#include "spi_master.h" #include "spi_master.h"
#include "playlist.h" #include "playlist.h"
#include "data_sources.h"
class Player; class Player;
@ -56,6 +55,7 @@ private:
void _flush_and_cancel(); void _flush_and_cancel();
int8_t _get_endbyte(); int8_t _get_endbyte();
void _flush(uint count, int8_t fill_byte); void _flush(uint count, int8_t fill_byte);
uint32_t _id3_tag_offset(File f);
void _play_file(String filename, uint32_t offset); void _play_file(String filename, uint32_t offset);
void _finish_playing(); void _finish_playing();
void _finish_stopping(bool turn_speaker_off); void _finish_stopping(bool turn_speaker_off);
@ -72,7 +72,7 @@ private:
SPISettings _spi_settings_fast = SPISettings(4000000, MSBFIRST, SPI_MODE0); SPISettings _spi_settings_fast = SPISettings(4000000, MSBFIRST, SPI_MODE0);
SPISettings* _spi_settings = &_spi_settings_slow; SPISettings* _spi_settings = &_spi_settings_slow;
DataSource* _file; File _file;
uint32_t _file_size = 0; uint32_t _file_size = 0;
uint8_t _buffer[32]; uint8_t _buffer[32];
uint32_t _current_play_position = 0; uint32_t _current_play_position = 0;

View File

@ -11,7 +11,7 @@ private:
bool _shuffled = false; bool _shuffled = false;
std::vector<String> _files; std::vector<String> _files;
public: public:
Playlist(String path, bool is_url=false); Playlist(String path);
void start(); void start();
bool has_track_next(); bool has_track_next();
bool has_track_prev(); bool has_track_prev();

View File

@ -19,5 +19,4 @@ public:
void scan_files(); void scan_files();
String json(); String json();
bool add_mapping(String id, String folder); bool add_mapping(String id, String folder);
String create_mapping_txt();
}; };

View File

@ -5,12 +5,12 @@
#include "http_server.h" #include "http_server.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
Controller::Controller(Player* p, PlaylistManager* playlist_manager) { Controller::Controller(Player* p, PlaylistManager* pm) {
player = p; _player = p;
pm = playlist_manager; _pm = pm;
_rfid = new MFRC522(17, MFRC522::UNUSED_PIN); _rfid = new MFRC522(17, MFRC522::UNUSED_PIN);
player->register_controller(this); _player->register_controller(this);
BTN_NEXT_SETUP(); BTN_NEXT_SETUP();
BTN_PREV_SETUP(); BTN_PREV_SETUP();
@ -87,7 +87,7 @@ void Controller::_check_rfid() {
_rfid_present = false; _rfid_present = false;
INFO("No more RFID card.\n"); INFO("No more RFID card.\n");
if (_state != LOCKED) { if (_state != LOCKED) {
player->stop(); _player->stop();
} }
send_controller_status(); send_controller_status();
} else { } else {
@ -106,7 +106,7 @@ void Controller::_check_rfid() {
String data = _read_rfid_data(); String data = _read_rfid_data();
_last_rfid_data = data; _last_rfid_data = data;
Playlist* pl = pm->get_playlist_for_id(s_uid); Playlist* pl = _pm->get_playlist_for_id(s_uid);
if (data.indexOf("[lock]") != -1) { if (data.indexOf("[lock]") != -1) {
if (_state == LOCKED) { if (_state == LOCKED) {
_state = NORMAL; _state = NORMAL;
@ -152,7 +152,7 @@ void Controller::_check_rfid() {
DEBUG("ControllerState is now LOCKED.\n"); DEBUG("ControllerState is now LOCKED.\n");
} }
player->play(pl); _player->play(pl);
//send_playlist_manager_status(); //send_playlist_manager_status();
send_controller_status(); send_controller_status();
} }
@ -248,38 +248,38 @@ bool Controller::process_message(String cmd) {
DEBUG("Executing command: %s\n", cmd.c_str()); DEBUG("Executing command: %s\n", cmd.c_str());
if (cmd.startsWith("play ")) { if (cmd.startsWith("play ")) {
Playlist* p = pm->get_playlist_for_folder(cmd.substring(5)); Playlist* p = _pm->get_playlist_for_folder(cmd.substring(5));
player->play(p); _player->play(p);
//} else if (cmd.equals("ls")) { //} else if (cmd.equals("ls")) {
// _execute_command_ls("/"); // _execute_command_ls("/");
//} else if (cmd.startsWith("ls ")) { //} else if (cmd.startsWith("ls ")) {
// _execute_command_ls(cmd.substring(3)); // _execute_command_ls(cmd.substring(3));
} else if (cmd.equals("play")) { } else if (cmd.equals("play")) {
player->play(); _player->play();
} else if (cmd.equals("stop")) { } else if (cmd.equals("stop")) {
player->stop(); _player->stop();
} else if (cmd.equals("help")) { } else if (cmd.equals("help")) {
_execute_command_help(); _execute_command_help();
} else if (cmd.equals("-")) { } else if (cmd.equals("-")) {
player->vol_down(); _player->vol_down();
} else if (cmd.equals("+")) { } else if (cmd.equals("+")) {
player->vol_up(); _player->vol_up();
} else if (cmd.startsWith("volume=")) { } else if (cmd.startsWith("volume=")) {
uint8_t vol = cmd.substring(7).toInt(); uint8_t vol = cmd.substring(7).toInt();
player->set_volume(vol); _player->set_volume(vol);
} else if (cmd.equals("track_prev")) { } else if (cmd.equals("track_prev")) {
player->track_prev(); _player->track_prev();
} else if (cmd.equals("track_next")) { } else if (cmd.equals("track_next")) {
player->track_next(); _player->track_next();
} else if (cmd.startsWith("track=")) { } else if (cmd.startsWith("track=")) {
uint8_t track = cmd.substring(6).toInt(); uint8_t track = cmd.substring(6).toInt();
player->set_track(track); _player->set_track(track);
} else if (cmd.equals("ids")) { } else if (cmd.equals("ids")) {
pm->dump_ids(); _pm->dump_ids();
} else if (cmd.equals("reset_vs1053")) { } else if (cmd.equals("reset_vs1053")) {
player->stop(); _player->stop();
player->init(); _player->init();
} else if (cmd.equals("reboot")) { } else if (cmd.equals("reboot")) {
ESP.restart(); ESP.restart();
} else if (cmd.startsWith("add_mapping=")) { } else if (cmd.startsWith("add_mapping=")) {
@ -287,7 +287,7 @@ bool Controller::process_message(String cmd) {
uint8_t idx = rest.indexOf('='); uint8_t idx = rest.indexOf('=');
String id = rest.substring(0, idx); String id = rest.substring(0, idx);
String folder = rest.substring(idx + 1); String folder = rest.substring(idx + 1);
pm->add_mapping(id, folder); _pm->add_mapping(id, folder);
send_playlist_manager_status(); send_playlist_manager_status();
} else { } else {
ERROR("Unknown command: %s\n", cmd.c_str()); ERROR("Unknown command: %s\n", cmd.c_str());
@ -299,7 +299,7 @@ bool Controller::process_message(String cmd) {
void Controller::_execute_command_ls(String path) { void Controller::_execute_command_ls(String path) {
INFO("Listing contents of %s:\n", path.c_str()); INFO("Listing contents of %s:\n", path.c_str());
// TODO // TODO
//std::list<String> files = player->ls(path); //std::list<String> files = _player->ls(path);
//for(std::list<String>::iterator it=files.begin(); it!=files.end(); ++it) { //for(std::list<String>::iterator it=files.begin(); it!=files.end(); ++it) {
// INFO(" %s\n", (*it).c_str()); // INFO(" %s\n", (*it).c_str());
//} //}
@ -321,17 +321,17 @@ void Controller::_check_buttons() {
if (BTN_PREV() && _debounce_button(0)) { if (BTN_PREV() && _debounce_button(0)) {
if (_state == NORMAL) { if (_state == NORMAL) {
player->track_prev(); _player->track_prev();
} else { } else {
DEBUG("Ignoring btn_prev because state is LOCKED.\n"); DEBUG("Ignoring btn_prev because state is LOCKED.\n");
} }
} else if (BTN_VOL_UP() && _debounce_button(1)) { } else if (BTN_VOL_UP() && _debounce_button(1)) {
player->vol_up(); _player->vol_up();
} else if (BTN_VOL_DOWN() && _debounce_button(2)) { } else if (BTN_VOL_DOWN() && _debounce_button(2)) {
player->vol_down(); _player->vol_down();
} else if (BTN_NEXT() && _debounce_button(3)) { } else if (BTN_NEXT() && _debounce_button(3)) {
if (_state == NORMAL) { if (_state == NORMAL) {
player->track_next(); _player->track_next();
} else { } else {
DEBUG("Ignoring btn_next because state is LOCKED.\n"); DEBUG("Ignoring btn_next because state is LOCKED.\n");
} }
@ -360,8 +360,6 @@ String Controller::json() {
JsonObject rfid = json.createNestedObject("last_rfid"); JsonObject rfid = json.createNestedObject("last_rfid");
rfid["uid"] = _last_rfid_uid; rfid["uid"] = _last_rfid_uid;
rfid["data"] = _last_rfid_data; rfid["data"] = _last_rfid_data;
json["uptime"] = millis() / 1000;
json["free_heap"] = ESP.getFreeHeap();
return json.as<String>(); return json.as<String>();
} }
@ -369,22 +367,22 @@ void Controller::send_player_status() {
TRACE("In send_player_status()...\n"); TRACE("In send_player_status()...\n");
if (_http_server->ws->count() > 0) { if (_http_server->ws->count() > 0) {
_http_server->ws->textAll(player->json()); _http_server->ws->textAll(_player->json());
_http_server->ws->textAll(player->position_json()); _http_server->ws->textAll(_player->position_json());
} }
} }
void Controller::send_playlist_manager_status() { void Controller::send_playlist_manager_status() {
TRACE("In send_playlist_manager_status()...\n"); TRACE("In send_playlist_manager_status()...\n");
if (_http_server->ws->count() > 0) { if (_http_server->ws->count() > 0) {
_http_server->ws->textAll(pm->json()); _http_server->ws->textAll(_pm->json());
} }
} }
void Controller::send_position() { void Controller::send_position() {
TRACE("In send_position()...\n"); TRACE("In send_position()...\n");
if (_http_server->ws->count() > 0) { if (_http_server->ws->count() > 0 && _player->is_playing()) {
_http_server->ws->textAll(player->position_json()); _http_server->ws->textAll(_player->position_json());
} }
_last_position_info_at = millis(); _last_position_info_at = millis();
} }
@ -398,11 +396,11 @@ void Controller::send_controller_status() {
void Controller::inform_new_client(AsyncWebSocketClient* client) { void Controller::inform_new_client(AsyncWebSocketClient* client) {
String s; String s;
s += pm->json(); s += _pm->json();
s += '\n'; s += '\n';
s += player->json(); s += _player->json();
s += '\n'; s += '\n';
s += player->position_json(); s += _player->position_json();
s += '\n'; s += '\n';
s += json(); s += json();
client->text(s); client->text(s);
@ -414,6 +412,6 @@ void Controller::queue_command(String s) {
} }
void Controller::update_playlist_manager() { void Controller::update_playlist_manager() {
pm->scan_files(); _pm->scan_files();
send_playlist_manager_status(); send_playlist_manager_status();
} }

View File

@ -1,93 +0,0 @@
#include "data_sources.h"
////////////// SDDataSource //////////////
SDDataSource::SDDataSource(String file) { _file = SD.open(file, "r"); }
SDDataSource::~SDDataSource() { if (_file) _file.close(); }
size_t SDDataSource::read(uint8_t* buf, size_t len) { return _file.read(buf, len); }
uint8_t SDDataSource::read() { return _file.read(); }
size_t SDDataSource::position() { return _file.position(); }
void SDDataSource::seek(size_t position) { _file.seek(position); }
size_t SDDataSource::size() { return _file.size(); }
void SDDataSource::close() { _file.close(); }
bool SDDataSource::usable() { return _file; }
void SDDataSource::skip_id3_tag() {
uint32_t original_position = _file.position();
uint32_t offset = 0;
if (_file.read()=='I' && _file.read()=='D' && _file.read()=='3') {
DEBUG("ID3 tag found\n");
// Skip ID3 tag version
_file.read(); _file.read();
byte tags = _file.read();
bool footer_present = tags & 0x10;
DEBUG("ID3 footer found: %d\n", footer_present);
for (byte i=0; i<4; i++) {
offset <<= 7;
offset |= (0x7F & _file.read());
}
offset += 10;
if (footer_present) offset += 10;
DEBUG("ID3 tag length is %d bytes.\n", offset);
_file.seek(offset);
} else {
DEBUG("No ID3 tag found\n");
_file.seek(original_position);
}
}
////////////// HTTPSDataSource //////////////
HTTPSDataSource::HTTPSDataSource(String url, uint32_t offset) {
uint8_t tries_left = 5;
int status;
do {
if (tries_left == 0) {
ERROR("Redirection loop? Cancelling!\n");
return;
}
tries_left--;
DEBUG("Connecting to %s...\n", url.c_str());
if (_http) delete _http;
_http = new HTTPClient();
_http->setUserAgent("PodBox 0.1");
const char* headers[] = {"Location"};
_http->collectHeaders(headers, 1);
bool result = _http->begin(url);
DEBUG("HTTP->begin result: %d\n", result);
if (!result) return;
status = _http->GET();
DEBUG("Status code: %d\n", status);
if (status == HTTP_CODE_FOUND || status==HTTP_CODE_MOVED_PERMANENTLY || status==HTTP_CODE_TEMPORARY_REDIRECT) {
if (_http->hasHeader("Location")) {
url = _http->header("Location");
} else {
ERROR("Got redirection HTTP code, but could not find Location header.\n");
for(int i=0; i<_http->headers(); i++) {
DEBUG(" Header: %s=%s\n", _http->headerName(i).c_str(), _http->header(i).c_str());
}
return;
}
} else if (status != HTTP_CODE_OK) {
DEBUG("Unexpected HTTP return code. Cancelling.\n");
return;
}
} while (status != HTTP_CODE_OK);
_length = _http->getSize();
DEBUG("Content-Length: %d\n", _length);
_stream = _http->getStreamPtr();
}
HTTPSDataSource::~HTTPSDataSource() {
if (_stream) _stream->stop();
_http->end();
delete _stream;
delete _http;
}
bool HTTPSDataSource::usable() { return _http && _stream; }
size_t HTTPSDataSource::read(uint8_t* buf, size_t len) { size_t result = _stream->read(buf, len); _position += result; return result; }
uint8_t HTTPSDataSource::read() { _position++; return _stream->read(); }
size_t HTTPSDataSource::position() { return _position; }
void HTTPSDataSource::seek(size_t position) { return; /* TODO */ }
size_t HTTPSDataSource::size() { return _length; }
void HTTPSDataSource::close() { _stream->stop(); }

View File

@ -10,15 +10,8 @@ HTTPServer::HTTPServer(Player* p, Controller* c) {
ws = new AsyncWebSocket("/ws"); ws = new AsyncWebSocket("/ws");
_server->addHandler(ws); _server->addHandler(ws);
ws->onEvent([&](AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){this->_onEvent(server, client, type, arg, data, len);}); ws->onEvent([&](AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){this->_onEvent(server, client, type, arg, data, len);});
_server->on("/", [&](AsyncWebServerRequest* req) {req->send(SPIFFS, "/index.html", "text/html");});
_server->on("/", HTTP_GET, [&](AsyncWebServerRequest* req) {req->send(SPIFFS, "/index.html", "text/html");});
_server->on("/upload", HTTP_POST, [](AsyncWebServerRequest* req) {req->send(200); }, ([&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){this->_handle_upload(request, filename, index, data, len, final);})); _server->on("/upload", HTTP_POST, [](AsyncWebServerRequest* req) {req->send(200); }, ([&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){this->_handle_upload(request, filename, index, data, len, final);}));
_server->on("/_mapping.txt", HTTP_GET, [&](AsyncWebServerRequest* req) {req->send(200, "text/plain", _controller->pm->create_mapping_txt());});
_server->on("/player.json", HTTP_GET, [&](AsyncWebServerRequest* req) {req->send(200, "application/json", _controller->player->json());});
_server->on("/playlist_manager.json", HTTP_GET, [&](AsyncWebServerRequest* req) {req->send(200, "application/json", _controller->pm->json());});
_server->on("/controller.json", HTTP_GET, [&](AsyncWebServerRequest* req) {req->send(200, "application/json", _controller->json());});
_server->on("/position.json", HTTP_GET, [&](AsyncWebServerRequest* req) {req->send(200, "application/json", _controller->player->position_json());});
_server->on("/cmd", HTTP_POST, [&](AsyncWebServerRequest *req) {req->send(200); }, NULL, [&](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {_controller->queue_command((char*)data);});
_server->begin(); _server->begin();
MDNS.addService("http", "tcp", 80); MDNS.addService("http", "tcp", 80);
} }
@ -140,4 +133,4 @@ void HTTPServer::_onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client
_controller->queue_command((char*)data); _controller->queue_command((char*)data);
} }
} }
} }

View File

@ -463,14 +463,12 @@ void Player::set_volume(uint8_t vol, bool save) {
} }
void Player::vol_up() { void Player::vol_up() {
if (!is_playing()) return;
uint8_t vol = _volume + VOLUME_STEP; uint8_t vol = _volume + VOLUME_STEP;
if (vol > VOLUME_MAX) vol=VOLUME_MAX; if (vol > VOLUME_MAX) vol=VOLUME_MAX;
set_volume(vol); set_volume(vol);
} }
void Player::vol_down() { void Player::vol_down() {
if (!is_playing()) return;
uint8_t vol = _volume - VOLUME_STEP; uint8_t vol = _volume - VOLUME_STEP;
if (vol < VOLUME_MIN) vol=VOLUME_MIN; if (vol < VOLUME_MIN) vol=VOLUME_MIN;
set_volume(vol); set_volume(vol);
@ -545,16 +543,10 @@ bool Player::play() {
void Player::_play_file(String file, uint32_t file_offset) { void Player::_play_file(String file, uint32_t file_offset) {
INFO("play_file('%s', %d)\n", file.c_str(), file_offset); INFO("play_file('%s', %d)\n", file.c_str(), file_offset);
_spi->select_sd(); _spi->select_sd();
if (file.startsWith("/")) { _file = SD.open(file);
_file = new SDDataSource(file); _file_size = _file.size();
} else if (file.startsWith("https://")) {
_file = new HTTPSDataSource(file);
} else {
return;
}
_file_size = _file->size();
_spi->select_sd(false); _spi->select_sd(false);
if (!_file || !_file->usable()) { if (!_file) {
DEBUG("Could not open file %s", file.c_str()); DEBUG("Could not open file %s", file.c_str());
return; return;
} }
@ -567,10 +559,10 @@ void Player::_play_file(String file, uint32_t file_offset) {
_spi->select_sd(); _spi->select_sd();
if (file_offset == 0) { if (file_offset == 0) {
_file->skip_id3_tag(); _file.seek(_id3_tag_offset(_file));
} }
_refills = 0; _refills = 0;
_current_play_position = _file->position(); _current_play_position = _file.position();
_spi->select_sd(false); _spi->select_sd(false);
_skip_to = file_offset; _skip_to = file_offset;
if (_skip_to>0) _mute(); if (_skip_to>0) _mute();
@ -579,6 +571,30 @@ void Player::_play_file(String file, uint32_t file_offset) {
_controller->send_player_status(); _controller->send_player_status();
} }
uint32_t Player::_id3_tag_offset(File f) {
uint32_t original_position = f.position();
uint32_t offset = 0;
if (f.read()=='I' && f.read()=='D' && f.read()=='3') {
DEBUG("ID3 tag found\n");
// Skip ID3 tag version
f.read(); f.read();
byte tags = f.read();
bool footer_present = tags & 0x10;
DEBUG("ID3 footer found: %d\n", footer_present);
for (byte i=0; i<4; i++) {
offset <<= 7;
offset |= (0x7F & f.read());
}
offset += 10;
if (footer_present) offset += 10;
DEBUG("ID3 tag length is %d bytes.\n", offset);
} else {
DEBUG("No ID3 tag found\n");
}
f.seek(original_position);
return offset;
}
void Player::_flush(uint count, int8_t byte) { void Player::_flush(uint count, int8_t byte) {
_spi->select_vs1053_xdcs(); _spi->select_vs1053_xdcs();
SPI.beginTransaction(*_spi_settings); SPI.beginTransaction(*_spi_settings);
@ -634,8 +650,7 @@ void Player::_finish_stopping(bool turn_speaker_off) {
_state = idle; _state = idle;
_stopped_at = millis(); _stopped_at = millis();
if (_file) { if (_file) {
_file->close(); _file.close();
delete _file;
} }
_current_play_position = 0; _current_play_position = 0;
_file_size = 0; _file_size = 0;
@ -647,7 +662,7 @@ void Player::_refill() {
_spi->select_sd(); _spi->select_sd();
_refills++; _refills++;
if (_refills % 1000 == 0) DEBUG("."); if (_refills % 1000 == 0) DEBUG(".");
uint8_t result = _file->read(_buffer, sizeof(_buffer)); uint8_t result = _file.read(_buffer, sizeof(_buffer));
_spi->select_sd(false); _spi->select_sd(false);
if (result == 0) { if (result == 0) {
// File is over. // File is over.
@ -669,13 +684,13 @@ void Player::_refill() {
_write_data(_buffer); _write_data(_buffer);
if (_skip_to > 0) { if (_skip_to > 0) {
if (_skip_to > _file->position()) { if (_skip_to > _file.position()) {
uint16_t status = _read_control_register(SCI_STATUS); uint16_t status = _read_control_register(SCI_STATUS);
if ((status & SS_DO_NOT_JUMP) == 0) { if ((status & SS_DO_NOT_JUMP) == 0) {
DEBUG("Skipping to %d.\n", _skip_to); DEBUG("Skipping to %d.\n", _skip_to);
_flush(2048, _get_endbyte()); _flush(2048, _get_endbyte());
_spi->select_sd(); _spi->select_sd();
_file->seek(_skip_to); _file.seek(_skip_to);
_spi->select_sd(false); _spi->select_sd(false);
_skip_to = 0; _skip_to = 0;
_unmute(); _unmute();
@ -740,7 +755,6 @@ String Player::json() {
} }
String Player::position_json() { String Player::position_json() {
if (!is_playing()) return "null";
DynamicJsonDocument json(200); DynamicJsonDocument json(200);
json["_type"] = "position"; json["_type"] = "position";
json["position"] = _current_play_position; json["position"] = _current_play_position;

View File

@ -5,11 +5,7 @@
#include <algorithm> #include <algorithm>
#include <ArduinoJson.h> #include <ArduinoJson.h>
Playlist::Playlist(String path, bool is_url) { Playlist::Playlist(String path) {
if (is_url) {
_files.push_back(path);
return;
}
// Add files to _files // Add files to _files
SPIMaster::select_sd(); SPIMaster::select_sd();
TRACE("Examining folder %s...\n", path.c_str()); TRACE("Examining folder %s...\n", path.c_str());

View File

@ -143,16 +143,6 @@ bool PlaylistManager::add_mapping(String id, String folder) {
void PlaylistManager::_save_mapping() { void PlaylistManager::_save_mapping() {
SPIMaster::select_sd(); SPIMaster::select_sd();
File f = SD.open("/_mapping.txt", "w"); File f = SD.open("/_mapping.txt", "w");
String s = create_mapping_txt();
unsigned char* buf = new unsigned char[s.length()];
s.getBytes(buf, s.length());
f.write(buf, s.length()-1);
f.close();
SPIMaster::select_sd(false);
delete buf;
}
String PlaylistManager::create_mapping_txt() {
String s; String s;
for(std::map<String, String>::iterator it = _map.begin(); it != _map.end(); it++) { for(std::map<String, String>::iterator it = _map.begin(); it != _map.end(); it++) {
s += it->first; s += it->first;
@ -160,5 +150,10 @@ String PlaylistManager::create_mapping_txt() {
s += it->second; s += it->second;
s += '\n'; s += '\n';
} }
return s; unsigned char* buf = new unsigned char[s.length()];
} s.getBytes(buf, s.length());
f.write(buf, s.length()-1);
f.close();
SPIMaster::select_sd(false);
delete buf;
}