Compare commits
8 Commits
01f513c97b
...
710b8a2cdc
Author | SHA1 | Date | |
---|---|---|---|
710b8a2cdc | |||
b989784fb9 | |||
94489618ca | |||
82d8f07eea | |||
20041dd483 | |||
4f9174d362 | |||
68ecc05712 | |||
5fad39ee0e |
@ -37,7 +37,9 @@ 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
|
||||
least contain the correct login details for your WiFi.
|
||||
|
||||
The code then should compile in PlatformIO without errors.
|
||||
The code then should compile in PlatformIO without errors. Upload it
|
||||
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
|
||||
messages about what's going on. There will also be a line saying
|
||||
@ -142,4 +144,4 @@ will only play in December. On December 1st, only track 1
|
||||
will play. On December 2nd, track 2 followed by track 1. On
|
||||
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"
|
||||
first, followed by all previous tags in the right order.
|
||||
first, followed by all previous tags in the right order.
|
||||
|
@ -18,7 +18,6 @@ class Controller {
|
||||
private:
|
||||
MFRC522* _rfid;
|
||||
HTTPServer* _http_server;
|
||||
PlaylistManager* _pm;
|
||||
ControllerState _state = NORMAL;
|
||||
bool _rfid_enabled = true;
|
||||
void _check_rfid();
|
||||
@ -30,7 +29,7 @@ private:
|
||||
bool _rfid_present = false;
|
||||
String _last_rfid_uid = "";
|
||||
String _last_rfid_data = "";
|
||||
Player* _player;
|
||||
|
||||
unsigned long _last_rfid_scan_at = 0;
|
||||
unsigned long _last_position_info_at = 0;
|
||||
String _serial_buffer = String();
|
||||
@ -42,6 +41,8 @@ private:
|
||||
bool _check_button(uint8_t btn);
|
||||
public:
|
||||
Controller(Player* p, PlaylistManager* pm);
|
||||
PlaylistManager* pm;
|
||||
Player* player;
|
||||
void register_http_server(HTTPServer* h);
|
||||
void loop();
|
||||
void send_controller_status();
|
||||
|
55
include/data_sources.h
Normal file
55
include/data_sources.h
Normal file
@ -0,0 +1,55 @@
|
||||
#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();
|
||||
};
|
@ -4,6 +4,7 @@
|
||||
#include <SD.h>
|
||||
#include "spi_master.h"
|
||||
#include "playlist.h"
|
||||
#include "data_sources.h"
|
||||
|
||||
class Player;
|
||||
|
||||
@ -55,7 +56,6 @@ private:
|
||||
void _flush_and_cancel();
|
||||
int8_t _get_endbyte();
|
||||
void _flush(uint count, int8_t fill_byte);
|
||||
uint32_t _id3_tag_offset(File f);
|
||||
void _play_file(String filename, uint32_t offset);
|
||||
void _finish_playing();
|
||||
void _finish_stopping(bool turn_speaker_off);
|
||||
@ -72,7 +72,7 @@ private:
|
||||
SPISettings _spi_settings_fast = SPISettings(4000000, MSBFIRST, SPI_MODE0);
|
||||
SPISettings* _spi_settings = &_spi_settings_slow;
|
||||
|
||||
File _file;
|
||||
DataSource* _file;
|
||||
uint32_t _file_size = 0;
|
||||
uint8_t _buffer[32];
|
||||
uint32_t _current_play_position = 0;
|
||||
|
@ -11,7 +11,7 @@ private:
|
||||
bool _shuffled = false;
|
||||
std::vector<String> _files;
|
||||
public:
|
||||
Playlist(String path);
|
||||
Playlist(String path, bool is_url=false);
|
||||
void start();
|
||||
bool has_track_next();
|
||||
bool has_track_prev();
|
||||
|
@ -19,4 +19,5 @@ public:
|
||||
void scan_files();
|
||||
String json();
|
||||
bool add_mapping(String id, String folder);
|
||||
String create_mapping_txt();
|
||||
};
|
||||
|
@ -5,12 +5,12 @@
|
||||
#include "http_server.h"
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
Controller::Controller(Player* p, PlaylistManager* pm) {
|
||||
_player = p;
|
||||
_pm = pm;
|
||||
Controller::Controller(Player* p, PlaylistManager* playlist_manager) {
|
||||
player = p;
|
||||
pm = playlist_manager;
|
||||
_rfid = new MFRC522(17, MFRC522::UNUSED_PIN);
|
||||
|
||||
_player->register_controller(this);
|
||||
player->register_controller(this);
|
||||
|
||||
BTN_NEXT_SETUP();
|
||||
BTN_PREV_SETUP();
|
||||
@ -87,7 +87,7 @@ void Controller::_check_rfid() {
|
||||
_rfid_present = false;
|
||||
INFO("No more RFID card.\n");
|
||||
if (_state != LOCKED) {
|
||||
_player->stop();
|
||||
player->stop();
|
||||
}
|
||||
send_controller_status();
|
||||
} else {
|
||||
@ -106,7 +106,7 @@ void Controller::_check_rfid() {
|
||||
String data = _read_rfid_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 (_state == LOCKED) {
|
||||
_state = NORMAL;
|
||||
@ -152,7 +152,7 @@ void Controller::_check_rfid() {
|
||||
DEBUG("ControllerState is now LOCKED.\n");
|
||||
}
|
||||
|
||||
_player->play(pl);
|
||||
player->play(pl);
|
||||
//send_playlist_manager_status();
|
||||
send_controller_status();
|
||||
}
|
||||
@ -248,38 +248,38 @@ bool Controller::process_message(String cmd) {
|
||||
DEBUG("Executing command: %s\n", cmd.c_str());
|
||||
|
||||
if (cmd.startsWith("play ")) {
|
||||
Playlist* p = _pm->get_playlist_for_folder(cmd.substring(5));
|
||||
_player->play(p);
|
||||
Playlist* p = pm->get_playlist_for_folder(cmd.substring(5));
|
||||
player->play(p);
|
||||
//} else if (cmd.equals("ls")) {
|
||||
// _execute_command_ls("/");
|
||||
//} else if (cmd.startsWith("ls ")) {
|
||||
// _execute_command_ls(cmd.substring(3));
|
||||
} else if (cmd.equals("play")) {
|
||||
_player->play();
|
||||
player->play();
|
||||
|
||||
} else if (cmd.equals("stop")) {
|
||||
_player->stop();
|
||||
player->stop();
|
||||
} else if (cmd.equals("help")) {
|
||||
_execute_command_help();
|
||||
} else if (cmd.equals("-")) {
|
||||
_player->vol_down();
|
||||
player->vol_down();
|
||||
} else if (cmd.equals("+")) {
|
||||
_player->vol_up();
|
||||
player->vol_up();
|
||||
} else if (cmd.startsWith("volume=")) {
|
||||
uint8_t vol = cmd.substring(7).toInt();
|
||||
_player->set_volume(vol);
|
||||
player->set_volume(vol);
|
||||
} else if (cmd.equals("track_prev")) {
|
||||
_player->track_prev();
|
||||
player->track_prev();
|
||||
} else if (cmd.equals("track_next")) {
|
||||
_player->track_next();
|
||||
player->track_next();
|
||||
} else if (cmd.startsWith("track=")) {
|
||||
uint8_t track = cmd.substring(6).toInt();
|
||||
_player->set_track(track);
|
||||
player->set_track(track);
|
||||
} else if (cmd.equals("ids")) {
|
||||
_pm->dump_ids();
|
||||
pm->dump_ids();
|
||||
} else if (cmd.equals("reset_vs1053")) {
|
||||
_player->stop();
|
||||
_player->init();
|
||||
player->stop();
|
||||
player->init();
|
||||
} else if (cmd.equals("reboot")) {
|
||||
ESP.restart();
|
||||
} else if (cmd.startsWith("add_mapping=")) {
|
||||
@ -287,7 +287,7 @@ bool Controller::process_message(String cmd) {
|
||||
uint8_t idx = rest.indexOf('=');
|
||||
String id = rest.substring(0, idx);
|
||||
String folder = rest.substring(idx + 1);
|
||||
_pm->add_mapping(id, folder);
|
||||
pm->add_mapping(id, folder);
|
||||
send_playlist_manager_status();
|
||||
} else {
|
||||
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) {
|
||||
INFO("Listing contents of %s:\n", path.c_str());
|
||||
// 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) {
|
||||
// INFO(" %s\n", (*it).c_str());
|
||||
//}
|
||||
@ -321,17 +321,17 @@ void Controller::_check_buttons() {
|
||||
|
||||
if (BTN_PREV() && _debounce_button(0)) {
|
||||
if (_state == NORMAL) {
|
||||
_player->track_prev();
|
||||
player->track_prev();
|
||||
} else {
|
||||
DEBUG("Ignoring btn_prev because state is LOCKED.\n");
|
||||
}
|
||||
} else if (BTN_VOL_UP() && _debounce_button(1)) {
|
||||
_player->vol_up();
|
||||
player->vol_up();
|
||||
} else if (BTN_VOL_DOWN() && _debounce_button(2)) {
|
||||
_player->vol_down();
|
||||
player->vol_down();
|
||||
} else if (BTN_NEXT() && _debounce_button(3)) {
|
||||
if (_state == NORMAL) {
|
||||
_player->track_next();
|
||||
player->track_next();
|
||||
} else {
|
||||
DEBUG("Ignoring btn_next because state is LOCKED.\n");
|
||||
}
|
||||
@ -360,6 +360,8 @@ String Controller::json() {
|
||||
JsonObject rfid = json.createNestedObject("last_rfid");
|
||||
rfid["uid"] = _last_rfid_uid;
|
||||
rfid["data"] = _last_rfid_data;
|
||||
json["uptime"] = millis() / 1000;
|
||||
json["free_heap"] = ESP.getFreeHeap();
|
||||
return json.as<String>();
|
||||
}
|
||||
|
||||
@ -367,22 +369,22 @@ void Controller::send_player_status() {
|
||||
TRACE("In send_player_status()...\n");
|
||||
|
||||
if (_http_server->ws->count() > 0) {
|
||||
_http_server->ws->textAll(_player->json());
|
||||
_http_server->ws->textAll(_player->position_json());
|
||||
_http_server->ws->textAll(player->json());
|
||||
_http_server->ws->textAll(player->position_json());
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::send_playlist_manager_status() {
|
||||
TRACE("In send_playlist_manager_status()...\n");
|
||||
if (_http_server->ws->count() > 0) {
|
||||
_http_server->ws->textAll(_pm->json());
|
||||
_http_server->ws->textAll(pm->json());
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::send_position() {
|
||||
TRACE("In send_position()...\n");
|
||||
if (_http_server->ws->count() > 0 && _player->is_playing()) {
|
||||
_http_server->ws->textAll(_player->position_json());
|
||||
if (_http_server->ws->count() > 0) {
|
||||
_http_server->ws->textAll(player->position_json());
|
||||
}
|
||||
_last_position_info_at = millis();
|
||||
}
|
||||
@ -396,11 +398,11 @@ void Controller::send_controller_status() {
|
||||
|
||||
void Controller::inform_new_client(AsyncWebSocketClient* client) {
|
||||
String s;
|
||||
s += _pm->json();
|
||||
s += pm->json();
|
||||
s += '\n';
|
||||
s += _player->json();
|
||||
s += player->json();
|
||||
s += '\n';
|
||||
s += _player->position_json();
|
||||
s += player->position_json();
|
||||
s += '\n';
|
||||
s += json();
|
||||
client->text(s);
|
||||
@ -412,6 +414,6 @@ void Controller::queue_command(String s) {
|
||||
}
|
||||
|
||||
void Controller::update_playlist_manager() {
|
||||
_pm->scan_files();
|
||||
pm->scan_files();
|
||||
send_playlist_manager_status();
|
||||
}
|
||||
}
|
||||
|
93
src/data_sources.cpp
Normal file
93
src/data_sources.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
#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(); }
|
@ -10,8 +10,15 @@ HTTPServer::HTTPServer(Player* p, Controller* c) {
|
||||
ws = new AsyncWebSocket("/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);});
|
||||
_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("/_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();
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
}
|
||||
@ -133,4 +140,4 @@ void HTTPServer::_onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client
|
||||
_controller->queue_command((char*)data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -463,12 +463,14 @@ void Player::set_volume(uint8_t vol, bool save) {
|
||||
}
|
||||
|
||||
void Player::vol_up() {
|
||||
if (!is_playing()) return;
|
||||
uint8_t vol = _volume + VOLUME_STEP;
|
||||
if (vol > VOLUME_MAX) vol=VOLUME_MAX;
|
||||
set_volume(vol);
|
||||
}
|
||||
|
||||
void Player::vol_down() {
|
||||
if (!is_playing()) return;
|
||||
uint8_t vol = _volume - VOLUME_STEP;
|
||||
if (vol < VOLUME_MIN) vol=VOLUME_MIN;
|
||||
set_volume(vol);
|
||||
@ -543,10 +545,16 @@ bool Player::play() {
|
||||
void Player::_play_file(String file, uint32_t file_offset) {
|
||||
INFO("play_file('%s', %d)\n", file.c_str(), file_offset);
|
||||
_spi->select_sd();
|
||||
_file = SD.open(file);
|
||||
_file_size = _file.size();
|
||||
if (file.startsWith("/")) {
|
||||
_file = new SDDataSource(file);
|
||||
} else if (file.startsWith("https://")) {
|
||||
_file = new HTTPSDataSource(file);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
_file_size = _file->size();
|
||||
_spi->select_sd(false);
|
||||
if (!_file) {
|
||||
if (!_file || !_file->usable()) {
|
||||
DEBUG("Could not open file %s", file.c_str());
|
||||
return;
|
||||
}
|
||||
@ -559,10 +567,10 @@ void Player::_play_file(String file, uint32_t file_offset) {
|
||||
|
||||
_spi->select_sd();
|
||||
if (file_offset == 0) {
|
||||
_file.seek(_id3_tag_offset(_file));
|
||||
_file->skip_id3_tag();
|
||||
}
|
||||
_refills = 0;
|
||||
_current_play_position = _file.position();
|
||||
_current_play_position = _file->position();
|
||||
_spi->select_sd(false);
|
||||
_skip_to = file_offset;
|
||||
if (_skip_to>0) _mute();
|
||||
@ -571,30 +579,6 @@ void Player::_play_file(String file, uint32_t file_offset) {
|
||||
_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) {
|
||||
_spi->select_vs1053_xdcs();
|
||||
SPI.beginTransaction(*_spi_settings);
|
||||
@ -650,7 +634,8 @@ void Player::_finish_stopping(bool turn_speaker_off) {
|
||||
_state = idle;
|
||||
_stopped_at = millis();
|
||||
if (_file) {
|
||||
_file.close();
|
||||
_file->close();
|
||||
delete _file;
|
||||
}
|
||||
_current_play_position = 0;
|
||||
_file_size = 0;
|
||||
@ -662,7 +647,7 @@ void Player::_refill() {
|
||||
_spi->select_sd();
|
||||
_refills++;
|
||||
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);
|
||||
if (result == 0) {
|
||||
// File is over.
|
||||
@ -684,13 +669,13 @@ void Player::_refill() {
|
||||
_write_data(_buffer);
|
||||
|
||||
if (_skip_to > 0) {
|
||||
if (_skip_to > _file.position()) {
|
||||
if (_skip_to > _file->position()) {
|
||||
uint16_t status = _read_control_register(SCI_STATUS);
|
||||
if ((status & SS_DO_NOT_JUMP) == 0) {
|
||||
DEBUG("Skipping to %d.\n", _skip_to);
|
||||
_flush(2048, _get_endbyte());
|
||||
_spi->select_sd();
|
||||
_file.seek(_skip_to);
|
||||
_file->seek(_skip_to);
|
||||
_spi->select_sd(false);
|
||||
_skip_to = 0;
|
||||
_unmute();
|
||||
@ -755,6 +740,7 @@ String Player::json() {
|
||||
}
|
||||
|
||||
String Player::position_json() {
|
||||
if (!is_playing()) return "null";
|
||||
DynamicJsonDocument json(200);
|
||||
json["_type"] = "position";
|
||||
json["position"] = _current_play_position;
|
||||
|
@ -5,7 +5,11 @@
|
||||
#include <algorithm>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
Playlist::Playlist(String path) {
|
||||
Playlist::Playlist(String path, bool is_url) {
|
||||
if (is_url) {
|
||||
_files.push_back(path);
|
||||
return;
|
||||
}
|
||||
// Add files to _files
|
||||
SPIMaster::select_sd();
|
||||
TRACE("Examining folder %s...\n", path.c_str());
|
||||
|
@ -143,6 +143,16 @@ bool PlaylistManager::add_mapping(String id, String folder) {
|
||||
void PlaylistManager::_save_mapping() {
|
||||
SPIMaster::select_sd();
|
||||
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;
|
||||
for(std::map<String, String>::iterator it = _map.begin(); it != _map.end(); it++) {
|
||||
s += it->first;
|
||||
@ -150,10 +160,5 @@ void PlaylistManager::_save_mapping() {
|
||||
s += it->second;
|
||||
s += '\n';
|
||||
}
|
||||
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;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user