Changed the playing code to use Playlists managed by a PlaylistManager. This allows you to have randomized playlists and stuff. Also, you can now access special functions via the contents of RFID tags. See the README for a list of available modes.

This commit is contained in:
2019-11-14 20:42:02 +01:00
parent 6e05900b5a
commit e471a57578
11 changed files with 412 additions and 344 deletions

View File

@ -1,10 +1,11 @@
#include "controller.h"
#include "spi_master.h"
#include "config.h"
#include "playlist.h"
Controller::Controller(Player* p, SPIMaster* s) {
Controller::Controller(Player* p, PlaylistManager* pm) {
_player = p;
_spi = s;
_pm = pm;
_rfid = new MFRC522(17, MFRC522::UNUSED_PIN);
BTN_NEXT_SETUP();
@ -12,13 +13,13 @@ Controller::Controller(Player* p, SPIMaster* s) {
BTN_VOL_UP_SETUP();
BTN_VOL_DOWN_SETUP();
_spi->select_rc522();
SPIMaster::select_rc522();
DEBUG("Initializing RC522...\n");
_rfid->PCD_Init();
#ifdef SHOW_DEBUG
_rfid->PCD_DumpVersionToSerial();
#endif
_spi->select_rc522(false);
SPIMaster::select_rc522(false);
INFO("RC522 initialized.\n");
for (uint8_t i=0; i<NUM_BUTTONS; i++) _button_last_pressed_at[i]=0;
@ -44,7 +45,7 @@ void Controller::loop() {
}
uint32_t Controller::_get_rfid_card_uid() {
_spi->select_rc522();
SPIMaster::select_rc522();
if (!_rfid->PICC_ReadCardSerial()) {
if (!_rfid->PICC_IsNewCardPresent()) {
return 0;
@ -53,7 +54,7 @@ uint32_t Controller::_get_rfid_card_uid() {
return 0;
}
}
_spi->select_rc522(false);
SPIMaster::select_rc522(false);
uint32_t uid = _rfid->uid.uidByte[0]<<24 | _rfid->uid.uidByte[1]<<16 | _rfid->uid.uidByte[2]<<8 | _rfid->uid.uidByte[3];
return uid;
}
@ -63,19 +64,21 @@ void Controller::_check_rfid() {
if (_rfid_present) {
byte buffer[2];
byte buffer_size = 2;
_spi->select_rc522();
SPIMaster::select_rc522();
status = _rfid->PICC_WakeupA(buffer, &buffer_size);
if (status == MFRC522::STATUS_OK) {
// Card is still present.
_rfid->PICC_HaltA();
_spi->select_rc522(false);
SPIMaster::select_rc522(false);
return;
}
_spi->select_rc522(false);
SPIMaster::select_rc522(false);
// Card is now gone
_rfid_present = false;
INFO("No more RFID card.\n");
_player->stop();
if (_state != LOCKED) {
_player->stop();
}
} else {
uint32_t uid = _get_rfid_card_uid();
if (uid > 0) {
@ -92,23 +95,63 @@ void Controller::_check_rfid() {
String data = _read_rfid_data();
_player->play_id(s_uid);
Playlist* pl = _pm->get_playlist_for_id(s_uid);
if (data.indexOf("[lock]") != -1) {
if (_state == LOCKED) {
_state = NORMAL;
DEBUG("ControllerState is now UNLOCKED\n");
} else {
DEBUG("ControllerState is now LOCKING\n");
_state = LOCKING;
}
}
if (pl==NULL) {
INFO("Could not find album for id '%s'.", s_uid.c_str());
return;
}
int index;
if (data.indexOf("[random]") != -1 && pl->is_fresh()) {
pl->shuffle();
} else if ((index=data.indexOf("[random:")) != -1 && pl->is_fresh()) {
String temp = data.substring(index + 8);
index = temp.indexOf("]");
TRACE("temp: %s, temp.substring(0, %d): %s\n", temp.c_str(), index, temp.substring(0, index).c_str());
if (index>0) {
uint8_t random_offset = temp.substring(0, index).toInt();
pl->shuffle(random_offset);
}
}
if (_state == LOCKED) {
DEBUG("ControllerState is LOCKED, ignoring card.\n");
return;
}
if (_state == LOCKING) {
_state = LOCKED;
DEBUG("ControllerState is now LOCKED.\n");
}
_player->play(pl);
}
}
}
String Controller::_read_rfid_data() {
_spi->select_rc522();
static MFRC522::MIFARE_Key keys[8] = {
{{0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7}}, // D3 F7 D3 F7 D3 F7
{{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, // FF FF FF FF FF FF = factory default
{{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5}}, // A0 A1 A2 A3 A4 A5
{{0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5}}, // B0 B1 B2 B3 B4 B5
{{0x4d, 0x3a, 0x99, 0xc3, 0x51, 0xdd}}, // 4D 3A 99 C3 51 DD
{{0x1a, 0x98, 0x2c, 0x7e, 0x45, 0x9a}}, // 1A 98 2C 7E 45 9A
{{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}}, // AA BB CC DD EE FF
{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}} // 00 00 00 00 00 00
};
SPIMaster::select_rc522();
DEBUG("Trying to read RFID data...\n");
String data = "";
MFRC522::MIFARE_Key key;
key.keyByte[0] = 0xD3;
key.keyByte[1] = 0xF7;
key.keyByte[2] = 0xD3;
key.keyByte[3] = 0xF7;
key.keyByte[4] = 0xD3;
key.keyByte[5] = 0xF7;
MFRC522::PICC_Type type = _rfid->PICC_GetType(_rfid->uid.sak);
uint8_t sectors = 0;
@ -119,35 +162,45 @@ String Controller::_read_rfid_data() {
default: INFO("Unknown PICC type %s\n", String(MFRC522::PICC_GetTypeName(type)).c_str());
}
int good_key_index = -1;
for (uint8_t sector=1; sector<sectors; sector++) {
uint8_t blocks = (sector < 32) ? 4 : 16;
uint8_t block_offset = (sector < 32) ? sector * 4 : 128 + (sector - 32) * 16;
MFRC522::StatusCode status;
status = _rfid->PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block_offset, &key, &_rfid->uid);
if (status != MFRC522::STATUS_OK) {
DEBUG("PCD_Authenticate() for sector %d failed: %s\n", sector, String(_rfid->GetStatusCodeName(status)).c_str());
continue;
}
for (uint8_t block=0; block<blocks-1; block++) {
byte buffer[18];
uint8_t byte_count = 18;
status = _rfid->MIFARE_Read(block_offset + block, buffer, &byte_count);
if (status != MFRC522::STATUS_OK) {
DEBUG("MIFARE_Read() failed: %s\n", String(_rfid->GetStatusCodeName(status)).c_str());
continue;
for (int i=0; i<8; i++) {
MFRC522::MIFARE_Key *k = &keys[i];
TRACE("Trying MIFARE key %02X %02X %02X %02X %02X %02X...\n", k->keyByte[0], k->keyByte[1], k->keyByte[2], k->keyByte[3], k->keyByte[4], k->keyByte[5]);
status = _rfid->PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block_offset, k, &_rfid->uid);
if (status == MFRC522::STATUS_OK) {
TRACE("Authentication succeeded with key #%d\n", i);
good_key_index = i;
break;
}
for (int i=0; i<16; i++) {
if (buffer[i]>=0x20 && buffer[i]<0x7F) data.concat((char)buffer[i]);
}
if (good_key_index == -1) {
TRACE("Could not find a valid MIFARE key.\n");
} else {
for (uint8_t block=0; block<blocks-1; block++) {
byte buffer[18];
uint8_t byte_count = 18;
status = _rfid->MIFARE_Read(block_offset + block, buffer, &byte_count);
if (status != MFRC522::STATUS_OK) {
DEBUG("MIFARE_Read() failed: %s\n", String(_rfid->GetStatusCodeName(status)).c_str());
continue;
}
for (int i=0; i<16; i++) {
if (buffer[i]>=0x20 && buffer[i]<0x7F) data.concat((char)buffer[i]);
}
}
}
}
_rfid->PICC_HaltA();
_rfid->PCD_StopCrypto1();
DEBUG("Data from RFID: %s", data.c_str());
_spi->select_rc522(false);
DEBUG("Data from RFID: %s\n", data.c_str());
SPIMaster::select_rc522(false);
return data;
}
@ -169,16 +222,15 @@ void Controller::_check_serial() {
void Controller::_execute_serial_command(String cmd) {
DEBUG("Executing command: %s\n", cmd.c_str());
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_random_album();
} else if (cmd.startsWith("play ")) {
_player->play_id(cmd.substring(5));
} else if (cmd.startsWith("sys ")) {
_player->play_system_sound(cmd.substring(4));
if (cmd.startsWith("play ")) {
Playlist* p = _pm->get_playlist_for_id(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_random_album();
} else if (cmd.equals("stop")) {
_player->stop();
} else if (cmd.equals("help")) {
@ -192,7 +244,7 @@ void Controller::_execute_serial_command(String cmd) {
} else if (cmd.equals("n")) {
_player->track_next();
} else if (cmd.equals("ids")) {
_execute_command_ids();
_pm->dump_ids();
} else {
ERROR("Unknown command: %s\n", cmd.c_str());
}
@ -201,25 +253,19 @@ void Controller::_execute_serial_command(String cmd) {
void Controller::_execute_command_ls(String path) {
INFO("Listing contents of %s:\n", path.c_str());
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());
}
}
void Controller::_execute_command_ids() {
for (std::map<String, String>::iterator it = _player->id_to_folder_map.begin(); it!=_player->id_to_folder_map.end(); ++it) {
INFO(" %s -> %s\n", it->first.c_str(), it->second.c_str());
}
// TODO
//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());
//}
}
void Controller::_execute_command_help() {
INFO("Valid commands are:");
INFO(" help - Displays this help\n");
INFO(" ls [dir] - Lists the contents of [dir] or, if not given, of /\n");
//INFO(" ls [dir] - Lists the contents of [dir] or, if not given, of /\n");
INFO(" ids - Lists all known ID-to-folder mappings\n");
INFO(" play [id] - Plays the album with the given id\n");
INFO(" sys [file]- Plays the file as system sound\n");
INFO(" stop - Stops playback\n");
INFO(" - / + - Decrease or increase the volume\n");
INFO(" p / n - Previous or next track\n");
@ -227,13 +273,21 @@ void Controller::_execute_command_help() {
void Controller::_check_buttons() {
if (BTN_PREV() && _debounce_button(0)) {
_player->track_prev();
if (_state == NORMAL) {
_player->track_prev();
} else {
DEBUG("Ignoring btn_prev because state is LOCKED.\n");
}
} else if (BTN_VOL_UP() && _debounce_button(1)) {
_player->vol_up();
} else if (BTN_VOL_DOWN() && _debounce_button(2)) {
_player->vol_down();
} else if (BTN_NEXT() && _debounce_button(3)) {
_player->track_next();
if (_state == NORMAL) {
_player->track_next();
} else {
DEBUG("Ignoring btn_next because state is LOCKED.\n");
}
}
}
@ -252,13 +306,14 @@ String Controller::get_status_json() {
response.concat(_player->is_playing() ? "playing" : "idle");
response.concat("\", ");
if (_player->is_playing()) {
response.concat("\"album\": \"");
response.concat(_player->album());
response.concat("\", \"track\": ");
response.concat(_player->track());
response.concat(", \"position\": ");
response.concat(_player->position());
response.concat(", ");
// TODO
//response.concat("\"album\": \"");
//response.concat(_player->album());
//response.concat("\", \"track\": ");
//response.concat(_player->track());
//response.concat(", \"position\": ");
//response.concat(_player->position());
//response.concat(", ");
}
response.concat("\"volume\": ");
response.concat(_player->volume());

View File

@ -8,10 +8,12 @@
#include "spi_master.h"
#include "http_server.h"
#include "mqtt_client.h"
#include "playlist_manager.h"
#include <ESP8266FtpServer.h>
Controller* controller;
Player* player;
PlaylistManager* pm;
//HTTPServer* http_server;
FtpServer* ftp_server;
MQTTClient* mqtt_client;
@ -33,6 +35,7 @@ void setup() {
DEBUG("Setting up SPI...\n");
SPI.begin();
SPI.setHwCs(false);
SPIMaster::init();
SPIMaster* spi = new SPIMaster();
INFO("SPI initialized.\n");
@ -45,9 +48,13 @@ void setup() {
}
spi->select_sd(false);
DEBUG("Initializing PlaylistManager...\n");
pm = new PlaylistManager();
DEBUG("done.\n");
DEBUG("Initializing Player and Controller...\n");
player = new Player(spi);
controller = new Controller(player, spi);
controller = new Controller(player, pm);
INFO("Player and controller initialized.\n");
DEBUG("Connecting to wifi \"%s\"...\n", WIFI_SSID);

View File

@ -15,8 +15,6 @@ Player::Player(SPIMaster* s) {
_spi->disable();
PIN_VS1053_DREQ_SETUP();
_fill_id_to_folder_map();
_init();
}
@ -70,14 +68,6 @@ void Player::_init() {
INFO("VS1053 initialization completed.\n");
INFO("Checking system sounds...\n");
_spi->select_sd();
_check_system_sound("no_prev_song.mp3");
_check_system_sound("no_next_song.mp3");
_check_system_sound("volume_max.mp3");
_check_system_sound("volume_min.mp3");
_spi->select_sd(false);
_state = idle;
}
@ -149,15 +139,6 @@ void Player::_record() {
_state = recording;
}
void Player::_check_system_sound(String filename) {
String path = String("/system/") + filename;
if (!SD.exists(path)) {
ERROR("System sound %s is missing on the sd card!\n", path.c_str());
} else {
DEBUG("%s found.\n", path.c_str());
}
}
inline void Player::_wait() {
while(!PIN_VS1053_DREQ());
}
@ -476,15 +457,15 @@ void Player::set_volume(uint8_t vol, bool save) {
}
void Player::vol_up() {
if (_volume == VOLUME_MAX) play_system_sound("volume_max.mp3");
else if (_volume + VOLUME_STEP > VOLUME_MAX) set_volume(VOLUME_MAX);
else set_volume(_volume + VOLUME_STEP);
uint8_t vol = _volume + VOLUME_STEP;
if (vol > VOLUME_MAX) vol=VOLUME_MAX;
set_volume(vol);
}
void Player::vol_down() {
if (_volume >= VOLUME_MIN + VOLUME_STEP) set_volume(_volume - VOLUME_STEP);
else if (_volume == VOLUME_MIN) play_system_sound("volume_min.mp3");
else set_volume(VOLUME_MIN);
uint8_t vol = _volume - VOLUME_STEP;
if (vol < VOLUME_MIN) vol=VOLUME_MIN;
set_volume(vol);
}
void Player::_mute() {
@ -501,26 +482,27 @@ void Player::_unmute() {
void Player::track_next() {
if (_state != playing) return;
if (_playing_index + 1 >= _playing_album_songs) {
play_system_sound("no_next_song.mp3");
if (!_current_playlist->has_track_next()) {
return;
}
stop();
play_song(_playing_album, _playing_index + 1);
_current_playlist->track_next();
play();
}
void Player::track_prev() {
if (_state != playing) return;
if (_current_play_position > 100000) {
stop();
play_song(_playing_album, _playing_index);
_current_playlist->track_restart();
play();
} else {
if (_playing_index == 0) {
play_system_sound("no_prev_song.mp3");
if (!_current_playlist->has_track_prev()) {
return;
}
stop();
play_song(_playing_album, _playing_index - 1);
_current_playlist->track_prev();
play();
}
}
@ -528,195 +510,21 @@ bool Player::is_playing() {
return _state == playing;
}
std::list<String> Player::ls(String path, bool withFiles, bool withDirs, bool withHidden) {
_spi->select_sd();
std::list<String> result;
if (!SD.exists(path)) return result;
File dir = SD.open(path);
File entry;
while (entry = dir.openNextFile()) {
if (!withDirs && entry.isDirectory()) continue;
if (!withFiles && !entry.isDirectory()) continue;
String filename = entry.name();
if (!withHidden && filename.startsWith(".")) continue;
if (entry.isDirectory()) filename.concat("/");
result.push_back(filename);
}
_spi->select_sd(false);
result.sort();
return result;
bool Player::play(Playlist* p) {
_current_playlist = p;
return play();
}
String Player::_find_album_dir(String id) {
_spi->select_sd();
if (id.endsWith("/")) id = id.substring(0, id.length() - 1);
String id_with_divider = id + " - ";
File root = SD.open("/");
File entry;
String result = String("");
while ((result.length()==0) && (entry = root.openNextFile())) {
String name = entry.name() + 1;
TRACE("Checking if '%s' startsWith '%s'...\n", name.c_str(), id.c_str());
if (entry.isDirectory() && (name.startsWith(id_with_divider) || name.equals(id))) {
result = name;
}
entry.close();
}
root.close();
_spi->select_sd(false);
return result;
}
std::list<String> Player::_files_in_dir(String path) {
_spi->select_sd();
TRACE("Examining folder %s...\n", path.c_str());
if (!path.startsWith("/")) path = String("/") + path;
//if (!path.endsWith("/")) path.concat("/");
std::list<String> result;
if (!SD.exists(path)) {
DEBUG("Could not open path '%s'.\n", path.c_str());
_spi->select_sd(false);
return result;
}
File dir = SD.open(path);
File entry;
while (entry = dir.openNextFile()) {
String filename = entry.name();
filename = filename.substring(path.length() + 1);
if (!entry.isDirectory() &&
!filename.startsWith(".") &&
( filename.endsWith(".mp3") ||
filename.endsWith(".ogg") ||
filename.endsWith(".wma") ||
filename.endsWith(".mp4") ||
filename.endsWith(".mpa"))) {
TRACE(" Adding entry %s\n", entry.name());
result.push_back(entry.name());
} else {
TRACE(" Ignoring entry %s\n", filename.c_str());
}
entry.close();
}
dir.close();
_spi->select_sd(false);
result.sort();
return result;
}
void Player::_fill_id_to_folder_map() {
DEBUG("_fill_id_to_folder_map() running...");
_spi->select_sd();
File root = SD.open("/");
File entry;
while (entry = root.openNextFile()) {
String foldername = entry.name();
// Remove trailing slash
foldername.remove(foldername.length());
TRACE("Looking at %s...\n", foldername.c_str());
if (!entry.isDirectory() || foldername.startsWith("/.")) continue;
if (!SD.exists(foldername + "/ids.txt")) {
TRACE("Folder %s does not contain ids.txt -> ignoring\n", foldername.c_str());
continue;
}
TRACE("Reading contents of %s...\n", (foldername + "/ids.txt").c_str());
File f = SD.open(foldername + "/ids.txt");
String buffer = "";
while (f.available()) {
char c = f.read();
if (c=='\n' || c=='\r') {
if (buffer.length() > 0) {
id_to_folder_map[buffer] = foldername;
DEBUG("Adding mapping '%s'=>'%s'\n", buffer.c_str(), foldername.c_str());
buffer = "";
}
} else {
buffer.concat(c);
}
}
f.close();
if (buffer.length() > 0) {
id_to_folder_map[buffer] = foldername;
DEBUG("Adding mapping '%s'=>'%s'\n", buffer.c_str(), foldername.c_str());
}
entry.close();
}
root.close();
DEBUG("fill_id_to_folder_map done.\n");
_spi->select_sd(false);
}
String Player::_random_album() {
std::list<String> albums = ls("/", false, true, false);
uint8_t rnd = random(albums.size());
std::list<String>::iterator it = albums.begin();
for (int i=0; i<rnd; i++) { it++; }
return *it;
}
void Player::play_random_album() {
play_album(_random_album());
}
bool Player::play_id(String id) {
String folder = _foldername_for_id(id);
if (folder.length()==0) return false;
return play_album(folder);
}
String Player::_foldername_for_id(String id) {
DEBUG("Searching for id %s...\n", id.c_str());
std::map<String, String>::iterator it = id_to_folder_map.find(id);
if (it != id_to_folder_map.end()) {
DEBUG("Found folder '%s' for id %s.\n", it->first.c_str(), it->second.c_str());
return it->second;
}
DEBUG("No folder found for id %s.\n", id.c_str());
return "";
}
bool Player::play_album(String album) {
album_state s = _last_tracks[album.c_str()];
DEBUG("Last index for album '%s' was %d,%d\n", album.c_str(), s.index, s.position);
return play_song(album, s.index, s.position);
}
bool Player::play_song(String album, uint8_t index, uint32_t skip_to) {
bool Player::play() {
if (_state == sleeping || _state == recording) _wakeup();
if (_state != idle) return false;
DEBUG("Trying to play song at index %d, offset %d of album %s\n", index, skip_to, album.c_str());
std::list<String> files = _files_in_dir(album);
_playing_album_songs = files.size();
DEBUG("Found %d songs in album\n", files.size());
if (index >= files.size()) {
ERROR("No matching file found - not playing.\n");
return false;
}
String file = *(std::next(files.begin(), index));
String file = _current_playlist->get_current_file();
uint32_t position = _current_playlist->get_position();
_state = playing;
_playing_album = album;
_playing_index = index;
_set_last_track(album.c_str(), index, skip_to);
_play_file(file, skip_to);
_play_file(file, position);
return true;
}
void Player::play_system_sound(String filename) {
String file = filename;
if (!SD.exists(file)) {
ERROR("File %s does not exist!\n", file.c_str());
return;
}
if (_state == playing) {
stop();
_state = system_sound_while_playing;
} else {
_state = system_sound_while_stopped;
}
_play_file(file, 0);
}
void Player::_play_file(String file, uint32_t file_offset) {
INFO("play_file('%s', %d)\n", file.c_str(), file_offset);
_spi->select_sd();
@ -797,11 +605,10 @@ void Player::_finish_playing() {
}
void Player::stop(bool turn_speaker_off) {
if (_state != playing /* && _state != system_sound_while_playing && _state != system_sound_while_stopped*/) return;
if (_state != playing) return;
INFO("Stopping...\n");
if (_state == playing) {
_set_last_track(_playing_album.c_str(), _playing_index, (uint32_t)_file.position());
}
_current_playlist->set_position(_current_play_position);
_state = stopping;
_stop_delay = 0;
_write_control_register(SCI_MODE, _read_control_register(SCI_MODE) | SM_CANCEL);
@ -842,18 +649,12 @@ void Player::_refill() {
DEBUG("EOF reached.\n");
_skip_to = 0;
_finish_playing();
if (_state == system_sound_while_playing) {
_finish_stopping(false);
play_album(_playing_album);
return;
} else if (_state == system_sound_while_stopped) {
_finish_stopping(true);
return;
}
_finish_stopping(false);
bool result = play_song(_playing_album, _playing_index + 1);
if (!result) {
_set_last_track(_playing_album.c_str(), 0, 0);
if (_current_playlist->has_track_next()) {
_current_playlist->track_next();
play();
} else {
_current_playlist->reset();
}
return;
}
@ -880,10 +681,7 @@ void Player::_refill() {
}
bool Player::_refill_needed() {
return _state==playing ||
_state==stopping ||
_state==system_sound_while_playing ||
_state==system_sound_while_stopped;
return _state==playing || _state==stopping;
}
bool Player::loop() {
@ -914,8 +712,3 @@ bool Player::loop() {
}
return false;
}
void Player::_set_last_track(const char* album, uint8_t index, uint32_t position) {
DEBUG("Setting _last_track[%s]=%d,%d.\n", album, index, position);
_last_tracks[album] = {index, position};
}

108
src/playlist.cpp Normal file
View File

@ -0,0 +1,108 @@
#include <playlist.h>
#include "spi_master.h"
#include "config.h"
#include <SD.h>
#include <algorithm>
Playlist::Playlist(String path) {
// Add files to _files
SPIMaster::select_sd();
TRACE("Examining folder %s...\n", path.c_str());
if (!path.startsWith("/")) path = String("/") + path;
if (!SD.exists(path)) {
DEBUG("Could not open path '%s'.\n", path.c_str());
SPIMaster::select_sd(false);
return;
}
File dir = SD.open(path);
File entry;
while (entry = dir.openNextFile()) {
String filename = entry.name();
filename = filename.substring(path.length() + 1);
String ext = filename.substring(filename.length() - 4);
if (!entry.isDirectory() &&
!filename.startsWith(".") &&
( ext.equals(".mp3") ||
ext.equals(".ogg") ||
ext.equals(".wma") ||
ext.equals(".mp4") ||
ext.equals(".mpa"))) {
TRACE(" Adding entry %s\n", entry.name());
_files.push_back(entry.name());
} else {
TRACE(" Ignoring entry %s\n", filename.c_str());
}
entry.close();
}
dir.close();
SPIMaster::select_sd(false);
std::sort(_files.begin(), _files.end());
}
bool Playlist::has_track_prev() {
return _current_track > 0;
}
bool Playlist::has_track_next() {
return _current_track < _files.size()-1;
}
bool Playlist::track_prev() {
if (_current_track > 0) {
_current_track--;
_position = 0;
return true;
}
return false;
}
bool Playlist::track_next() {
if (_current_track < _files.size()-1) {
_current_track++;
_position = 0;
return true;
}
return false;
}
void Playlist::track_restart() {
_position = 0;
}
void Playlist::shuffle(uint8_t random_offset) {
DEBUG("Shuffling the playlist with an offset of %d...\n", random_offset);
for (int i=random_offset; i<_files.size(); i++) {
int j = random(random_offset, _files.size()-1);
if (i!=j) {
TRACE(" Swapping elements %d and %d.\n", i, j);
String temp = _files[i];
_files[i] = _files[j];
_files[j] = temp;
}
}
_shuffled = true;
TRACE("Done.\n");
}
void Playlist::reset() {
std::sort(_files.begin(), _files.end());
_current_track = 0;
_position = 0;
_shuffled = false;
}
String Playlist::get_current_file() {
return _files[_current_track];
}
uint32_t Playlist::get_position() {
return _position;
}
void Playlist::set_position(uint32_t p) {
_position = p;
}
bool Playlist::is_fresh() {
return !_shuffled && _position==0 && _current_track==0;
}

60
src/playlist_manager.cpp Normal file
View File

@ -0,0 +1,60 @@
#include "playlist_manager.h"
#include <SD.h>
#include "spi_master.h"
PlaylistManager::PlaylistManager() {
SPIMaster::select_sd();
File root = SD.open("/");
File entry;
while (entry = root.openNextFile()) {
String foldername = entry.name();
// Remove trailing slash
foldername.remove(foldername.length());
TRACE("Looking at %s...", foldername.c_str());
if (!entry.isDirectory() || foldername.startsWith("/.")) continue;
if (!SD.exists(foldername + "/ids.txt")) {
TRACE("No ids.txt -> ignoring\n");
continue;
}
File f = SD.open(foldername + "/ids.txt");
String buffer = "";
if (f.available()) {
do {
char c = f.read();
if (!f.available() && c!='\n' && c!='\r') {
buffer.concat(c);
c='\n';
}
if (c=='\n' || c=='\r') {
if (buffer.length() > 0) {
_map[buffer] = foldername;
TRACE(" ID %s", buffer.c_str());
buffer="";
}
} else {
buffer.concat(c);
}
} while(f.available());
}
f.close();
entry.close();
}
root.close();
SPIMaster::select_sd(false);
}
Playlist* PlaylistManager::get_playlist_for_id(String id) {
if (!_map.count(id)) return NULL;
String folder = _map[id];
if (!_playlists.count(folder)) {
_playlists[folder] = new Playlist(folder);
}
return _playlists[folder];
}
void PlaylistManager::dump_ids() {
for (std::map<String, String>::iterator it = _map.begin(); it!=_map.end(); it++) {
INFO(" %s -> %s\n", it->first.c_str(), it->second.c_str());
}
}