diff --git a/src/Entry.h b/src/Entry.h new file mode 100644 index 0000000..eaa83ee --- /dev/null +++ b/src/Entry.h @@ -0,0 +1,21 @@ +#ifndef ENTRY_H +#define ENTRY_H + +#include +#include + +struct Entry { + + const String type; + + const String url; + + const String title; + + explicit Entry(String type = "", String url = "", String title = "") : type(std::move(type)), url(std::move(url)), title(std::move(title)) { + // + } + +}; + +#endif diff --git a/src/audio.cpp b/src/audio.cpp index 28b42b2..e7729ca 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -45,15 +45,40 @@ void audioStop() { snap.end(); } -bool audioBeginMP3Stream(const char *code, Stream &source) { +bool audioPlayInit(const Entry &entry) { + audioStop(); + + Serial.printf("[%s] %s", entry.type.c_str(), entry.url.c_str()); + running = true; + + if (entry.type == "ICY" || entry.type == "SNAP") { + if (!isWifiConnected()) { + Serial.printf("[%s] WiFi not connected.\n", entry.type.c_str()); + audioStop(); + return false; + } + } + + if (entry.type == "SD") { + if (!SD.begin(PIN_AUDIO_KIT_SD_CARD_CS)) { + Serial.printf("[%s] Failed to mount SD-card.\n", entry.type.c_str()); + audioStop(); + return false; + } + } + + return true; +} + +bool audioBeginMP3Stream(const Entry &entry, Stream &source) { if (!mp3Decoder.begin()) { - Serial.printf("[%s] Failed to start MP3DecoderHelix.", code); + Serial.printf("[%s] Failed to start MP3DecoderHelix.", entry.type.c_str()); audioStop(); return false; } if (!mp3Stream.begin()) { - Serial.printf("[%s] Failed to start EncodedAudioStream.", code); + Serial.printf("[%s] Failed to start EncodedAudioStream.", entry.type.c_str()); audioStop(); return false; } @@ -61,7 +86,7 @@ bool audioBeginMP3Stream(const char *code, Stream &source) { copier.begin(mp3Stream, source); if (copier.copy() == 0) { - Serial.printf("[%s] Failed to copy initial data.", code); + Serial.printf("[%s] Failed to copy initial data.", entry.type.c_str()); audioStop(); return false; } @@ -69,72 +94,47 @@ bool audioBeginMP3Stream(const char *code, Stream &source) { return true; } -bool audioPlayInit(const char *code, const String &url, const bool needsWiFi, const bool needsSD) { - audioStop(); - - Serial.printf("[%s] %s", code, url.c_str()); - running = true; - - if (needsWiFi) { - if (!isWifiConnected()) { - Serial.printf("[%s] WiFi not connected.\n", code); - audioStop(); - return false; - } - } - - if (needsSD) { - if (!SD.begin(PIN_AUDIO_KIT_SD_CARD_CS)) { - Serial.printf("[%s] Failed to mount SD-card.\n", code); - audioStop(); - return false; - } - } - - return true; -} - -bool audioPlayICY(const String &url) { - if (!audioPlayInit("ICY", url, true, false)) { +bool audioPlayICY(const Entry &entry) { + if (!audioPlayInit(entry)) { return false; } - if (!icy.begin(url.c_str())) { - Serial.println("[ICY] Failed to start ICYStream."); + if (!icy.begin(entry.url.c_str())) { + Serial.printf("[%s] Failed to start ICYStream.\n", entry.type.c_str()); audioStop(); return false; } - return audioBeginMP3Stream("ICY", icy); + return audioBeginMP3Stream(entry, icy); } -bool audioPlaySNAP(const String &url) { - if (!audioPlayInit("SNAP", url, true, false)) { +bool audioPlaySNAP(const Entry &entry) { + if (!audioPlayInit(entry)) { return false; } IPAddress ip; - if (!WiFiClass::hostByName(url.c_str(), ip)) { - Serial.println("[SNAP] Failed to resolve host."); + if (!WiFiClass::hostByName(entry.url.c_str(), ip)) { + Serial.printf("[%s] Failed to resolve host.\n", entry.type.c_str()); audioStop(); return false; } snap.setServerIP(ip); if (!snap.begin()) { - Serial.println("[SNAP] Failed to connect."); + Serial.printf("[%s] Failed to connect.\n", entry.type.c_str()); audioStop(); return false; } if (!opusDecoder.begin()) { - Serial.println("[SNAP] Failed to start OpusAudioDecoder."); + Serial.printf("[%s] Failed to start OpusAudioDecoder.\n", entry.type.c_str()); audioStop(); return false; } if (!snap.doLoop()) { - Serial.println("[SNAP] Failed to copy initial data."); + Serial.printf("[%s] Failed to copy initial data.\n", entry.type.c_str()); audioStop(); return false; } @@ -142,38 +142,42 @@ bool audioPlaySNAP(const String &url) { return true; } -bool audioPlaySD(const String &url) { - if (!audioPlayInit("SD", url, false, true)) { +bool audioPlaySD(const Entry &entry) { + if (!audioPlayInit(entry)) { return false; } - if (!SD.exists(url.c_str())) { - Serial.println("[SD] File not found."); + if (!SD.exists(entry.url.c_str())) { + Serial.printf("[%s] File not found.\n", entry.type.c_str()); audioStop(); return false; } - file = SD.open(url.c_str(), FILE_READ); + file = SD.open(entry.url.c_str(), FILE_READ); if (!file) { - Serial.println("[SD] Failed to open file."); + Serial.printf("[%s] Failed to open file.\n", entry.type.c_str()); audioStop(); return false; } - return audioBeginMP3Stream("SD", file); + return audioBeginMP3Stream(entry, file); } -bool audioPlay(const String &url) { - if (url.startsWith("http://") || url.startsWith("https://")) { - return audioPlayICY(url); +bool audioPlay(const Entry &entry) { + audioStop(); + if (entry.type == "") { + return false; } - if (url.startsWith("sd://")) { - return audioPlaySD(url.substring(5)); + if (entry.type == "ICY") { + return audioPlayICY(entry); } - if (url.startsWith("snap://") < 0) { - return audioPlaySNAP(url.substring(7)); + if (entry.type == "SD") { + return audioPlaySD(entry); } - Serial.printf("[ERROR] unknown protocol: %s\n", url.c_str()); + if (entry.type == "SNAP") { + return audioPlaySNAP(entry); + } + Serial.printf("[ERROR] unknown type: %s\n", entry.type.c_str()); return false; } diff --git a/src/audio.h b/src/audio.h index 83e7488..50ce6bf 100644 --- a/src/audio.h +++ b/src/audio.h @@ -1,9 +1,9 @@ #ifndef AUDIO_H #define AUDIO_H -#include +#include "playlist.h" -bool audioPlay(const String &url); +bool audioPlay(const Entry &entry); void audioStop(); diff --git a/src/main.cpp b/src/main.cpp index 6acbfdb..168ff11 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,7 +9,8 @@ void setup() { Serial.begin(115200); playerSetup(); playlistClear(); - playlistAdd("http://liveradio.sr.de/sr/sr1/mp3/128/stream.mp3"); + playlistAdd("ICY|http://liveradio.sr.de/sr/sr1/mp3/128/stream.mp3|SR1"); + playlistAdd("ICY|https://stream.rockantenne.de/rockantenne/stream/mp3|Rockantenne Classic Perlen"); } void loop() { diff --git a/src/player.cpp b/src/player.cpp index 4962ed3..108468d 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -24,11 +24,8 @@ void playerLoop() { if (errorMs > 0 && millis() - errorMs < delayMs) { return; } - const auto url = playlistNextOrRepeatOneIfEnabled(); - if (url.isEmpty()) { - return; - } - if (audioPlay(url)) { + const auto entry = playlistNextOrRepeatOneIfEnabled(); + if (audioPlay(entry)) { errorMs = 0; delayMs = 0; } else { diff --git a/src/playlist.cpp b/src/playlist.cpp index 881d025..64ad7a6 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -1,9 +1,9 @@ #include "playlist.h" +#include #include #include "AudioTools/AudioLibs/I2SCodecStream.h" -#include bool playlistRepeatOne = false; @@ -15,22 +15,53 @@ String playlistTitle = ""; size_t playlistIndex = 0; -std::vector playlistEntries; +std::vector playlistEntries; void playlistClear() { playlistTitle = ""; playlistIndex = 0; playlistEntries.clear(); - std::vector().swap(playlistEntries); + std::vector().swap(playlistEntries); } -void playlistAdd(String url) { - url.trim(); - if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("sd://") || url.startsWith("snap://")) { - playlistEntries.push_back(url); - } else if (url.startsWith("title://")) { - playlistTitle = url.substring(8); +void playlistAdd(String entry) { + entry.trim(); + + const auto index_type_url = entry.indexOf('|'); + if (index_type_url < 0) { + Serial.println("[PLAYLIST] type/url-delimiter not found: " + entry); + return; } + auto type = entry.substring(0, index_type_url); + type.trim(); + if (type != "TITLE" && type != "ICY" && type != "SD" && type != "SNAP") { + Serial.println("[PLAYLIST] Unknown type: " + type); + } + + entry = entry.substring(index_type_url + 1); + entry.trim(); + + if (type == "TITLE") { + playlistTitle = entry; + return; + } + + const auto index_url_title = entry.indexOf('|'); + if (index_url_title < 0) { + Serial.println("[PLAYLIST] url/title-delimiter not found: " + entry); + return; + } + auto url = entry.substring(0, index_url_title); + url.trim(); + if (url.isEmpty()) { + Serial.println("[PLAYLIST] url is empty."); + return; + } + + auto title = entry.substring(index_url_title + 1); + title.trim(); + + playlistEntries.emplace_back(type, url, title); } void playlistLoad(const String &path) { @@ -50,42 +81,42 @@ void playlistLoad(const String &path) { } while (file.available() > 0) { - String entry = file.readStringUntil('\n'); + const String entry = file.readStringUntil('\n'); playlistAdd(entry); } file.close(); } -String playlistSet(const size_t index) { +Entry playlistSet(const size_t index) { const auto size = playlistEntries.size(); if (size == 0) { - return ""; + return Entry(); } playlistIndex = (index % size + size) % size; return playlistEntries[playlistIndex]; } -String playlistCurrent() { +Entry playlistCurrent() { return playlistSet(playlistIndex); } -String playlistNext() { +Entry playlistNext() { if (playlistIndex >= playlistEntries.size() - 1 && !playlistRepeatAll) { - return ""; + return Entry(); } return playlistSet(playlistIndex + 1); } -String playlistNextOrRepeatOneIfEnabled() { +Entry playlistNextOrRepeatOneIfEnabled() { if (playlistRepeatOne) { return playlistCurrent(); } return playlistNext(); } -String playlistPrevious() { +Entry playlistPrevious() { if (playlistIndex <= 0 && !playlistRepeatAll) { - return ""; + return Entry(); } return playlistSet(playlistIndex - 1); } diff --git a/src/playlist.h b/src/playlist.h index 8e37571..c0278d6 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -1,20 +1,20 @@ #ifndef PLAYLIST_H #define PLAYLIST_H -#include +#include "Entry.h" void playlistClear(); -void playlistAdd(String url); +void playlistAdd(String entry); void playlistLoad(const String &path); -String playlistCurrent(); +Entry playlistCurrent(); -String playlistNext(); +Entry playlistNext(); -String playlistNextOrRepeatOneIfEnabled(); +Entry playlistNextOrRepeatOneIfEnabled(); -String playlistPrevious(); +Entry playlistPrevious(); #endif