Entry for: type|url|title

This commit is contained in:
Patrick Haßel 2025-06-05 15:25:02 +02:00
parent 35c8ec13ee
commit bcf6001c55
7 changed files with 142 additions and 88 deletions

21
src/Entry.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef ENTRY_H
#define ENTRY_H
#include <WString.h>
#include <utility>
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

View File

@ -45,15 +45,40 @@ void audioStop() {
snap.end(); 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()) { if (!mp3Decoder.begin()) {
Serial.printf("[%s] Failed to start MP3DecoderHelix.", code); Serial.printf("[%s] Failed to start MP3DecoderHelix.", entry.type.c_str());
audioStop(); audioStop();
return false; return false;
} }
if (!mp3Stream.begin()) { if (!mp3Stream.begin()) {
Serial.printf("[%s] Failed to start EncodedAudioStream.", code); Serial.printf("[%s] Failed to start EncodedAudioStream.", entry.type.c_str());
audioStop(); audioStop();
return false; return false;
} }
@ -61,7 +86,7 @@ bool audioBeginMP3Stream(const char *code, Stream &source) {
copier.begin(mp3Stream, source); copier.begin(mp3Stream, source);
if (copier.copy() == 0) { 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(); audioStop();
return false; return false;
} }
@ -69,72 +94,47 @@ bool audioBeginMP3Stream(const char *code, Stream &source) {
return true; return true;
} }
bool audioPlayInit(const char *code, const String &url, const bool needsWiFi, const bool needsSD) { bool audioPlayICY(const Entry &entry) {
audioStop(); if (!audioPlayInit(entry)) {
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)) {
return false; return false;
} }
if (!icy.begin(url.c_str())) { if (!icy.begin(entry.url.c_str())) {
Serial.println("[ICY] Failed to start ICYStream."); Serial.printf("[%s] Failed to start ICYStream.\n", entry.type.c_str());
audioStop(); audioStop();
return false; return false;
} }
return audioBeginMP3Stream("ICY", icy); return audioBeginMP3Stream(entry, icy);
} }
bool audioPlaySNAP(const String &url) { bool audioPlaySNAP(const Entry &entry) {
if (!audioPlayInit("SNAP", url, true, false)) { if (!audioPlayInit(entry)) {
return false; return false;
} }
IPAddress ip; IPAddress ip;
if (!WiFiClass::hostByName(url.c_str(), ip)) { if (!WiFiClass::hostByName(entry.url.c_str(), ip)) {
Serial.println("[SNAP] Failed to resolve host."); Serial.printf("[%s] Failed to resolve host.\n", entry.type.c_str());
audioStop(); audioStop();
return false; return false;
} }
snap.setServerIP(ip); snap.setServerIP(ip);
if (!snap.begin()) { if (!snap.begin()) {
Serial.println("[SNAP] Failed to connect."); Serial.printf("[%s] Failed to connect.\n", entry.type.c_str());
audioStop(); audioStop();
return false; return false;
} }
if (!opusDecoder.begin()) { if (!opusDecoder.begin()) {
Serial.println("[SNAP] Failed to start OpusAudioDecoder."); Serial.printf("[%s] Failed to start OpusAudioDecoder.\n", entry.type.c_str());
audioStop(); audioStop();
return false; return false;
} }
if (!snap.doLoop()) { 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(); audioStop();
return false; return false;
} }
@ -142,38 +142,42 @@ bool audioPlaySNAP(const String &url) {
return true; return true;
} }
bool audioPlaySD(const String &url) { bool audioPlaySD(const Entry &entry) {
if (!audioPlayInit("SD", url, false, true)) { if (!audioPlayInit(entry)) {
return false; return false;
} }
if (!SD.exists(url.c_str())) { if (!SD.exists(entry.url.c_str())) {
Serial.println("[SD] File not found."); Serial.printf("[%s] File not found.\n", entry.type.c_str());
audioStop(); audioStop();
return false; return false;
} }
file = SD.open(url.c_str(), FILE_READ); file = SD.open(entry.url.c_str(), FILE_READ);
if (!file) { if (!file) {
Serial.println("[SD] Failed to open file."); Serial.printf("[%s] Failed to open file.\n", entry.type.c_str());
audioStop(); audioStop();
return false; return false;
} }
return audioBeginMP3Stream("SD", file); return audioBeginMP3Stream(entry, file);
} }
bool audioPlay(const String &url) { bool audioPlay(const Entry &entry) {
if (url.startsWith("http://") || url.startsWith("https://")) { audioStop();
return audioPlayICY(url); if (entry.type == "") {
return false;
} }
if (url.startsWith("sd://")) { if (entry.type == "ICY") {
return audioPlaySD(url.substring(5)); return audioPlayICY(entry);
} }
if (url.startsWith("snap://") < 0) { if (entry.type == "SD") {
return audioPlaySNAP(url.substring(7)); 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; return false;
} }

View File

@ -1,9 +1,9 @@
#ifndef AUDIO_H #ifndef AUDIO_H
#define AUDIO_H #define AUDIO_H
#include <WString.h> #include "playlist.h"
bool audioPlay(const String &url); bool audioPlay(const Entry &entry);
void audioStop(); void audioStop();

View File

@ -9,7 +9,8 @@ void setup() {
Serial.begin(115200); Serial.begin(115200);
playerSetup(); playerSetup();
playlistClear(); 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() { void loop() {

View File

@ -24,11 +24,8 @@ void playerLoop() {
if (errorMs > 0 && millis() - errorMs < delayMs) { if (errorMs > 0 && millis() - errorMs < delayMs) {
return; return;
} }
const auto url = playlistNextOrRepeatOneIfEnabled(); const auto entry = playlistNextOrRepeatOneIfEnabled();
if (url.isEmpty()) { if (audioPlay(entry)) {
return;
}
if (audioPlay(url)) {
errorMs = 0; errorMs = 0;
delayMs = 0; delayMs = 0;
} else { } else {

View File

@ -1,9 +1,9 @@
#include "playlist.h" #include "playlist.h"
#include <vector>
#include <SD.h> #include <SD.h>
#include "AudioTools/AudioLibs/I2SCodecStream.h" #include "AudioTools/AudioLibs/I2SCodecStream.h"
#include <vector>
bool playlistRepeatOne = false; bool playlistRepeatOne = false;
@ -15,22 +15,53 @@ String playlistTitle = "";
size_t playlistIndex = 0; size_t playlistIndex = 0;
std::vector<String> playlistEntries; std::vector<Entry> playlistEntries;
void playlistClear() { void playlistClear() {
playlistTitle = ""; playlistTitle = "";
playlistIndex = 0; playlistIndex = 0;
playlistEntries.clear(); playlistEntries.clear();
std::vector<String>().swap(playlistEntries); std::vector<Entry>().swap(playlistEntries);
} }
void playlistAdd(String url) { void playlistAdd(String entry) {
url.trim(); entry.trim();
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("sd://") || url.startsWith("snap://")) {
playlistEntries.push_back(url); const auto index_type_url = entry.indexOf('|');
} else if (url.startsWith("title://")) { if (index_type_url < 0) {
playlistTitle = url.substring(8); 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) { void playlistLoad(const String &path) {
@ -50,42 +81,42 @@ void playlistLoad(const String &path) {
} }
while (file.available() > 0) { while (file.available() > 0) {
String entry = file.readStringUntil('\n'); const String entry = file.readStringUntil('\n');
playlistAdd(entry); playlistAdd(entry);
} }
file.close(); file.close();
} }
String playlistSet(const size_t index) { Entry playlistSet(const size_t index) {
const auto size = playlistEntries.size(); const auto size = playlistEntries.size();
if (size == 0) { if (size == 0) {
return ""; return Entry();
} }
playlistIndex = (index % size + size) % size; playlistIndex = (index % size + size) % size;
return playlistEntries[playlistIndex]; return playlistEntries[playlistIndex];
} }
String playlistCurrent() { Entry playlistCurrent() {
return playlistSet(playlistIndex); return playlistSet(playlistIndex);
} }
String playlistNext() { Entry playlistNext() {
if (playlistIndex >= playlistEntries.size() - 1 && !playlistRepeatAll) { if (playlistIndex >= playlistEntries.size() - 1 && !playlistRepeatAll) {
return ""; return Entry();
} }
return playlistSet(playlistIndex + 1); return playlistSet(playlistIndex + 1);
} }
String playlistNextOrRepeatOneIfEnabled() { Entry playlistNextOrRepeatOneIfEnabled() {
if (playlistRepeatOne) { if (playlistRepeatOne) {
return playlistCurrent(); return playlistCurrent();
} }
return playlistNext(); return playlistNext();
} }
String playlistPrevious() { Entry playlistPrevious() {
if (playlistIndex <= 0 && !playlistRepeatAll) { if (playlistIndex <= 0 && !playlistRepeatAll) {
return ""; return Entry();
} }
return playlistSet(playlistIndex - 1); return playlistSet(playlistIndex - 1);
} }

View File

@ -1,20 +1,20 @@
#ifndef PLAYLIST_H #ifndef PLAYLIST_H
#define PLAYLIST_H #define PLAYLIST_H
#include <WString.h> #include "Entry.h"
void playlistClear(); void playlistClear();
void playlistAdd(String url); void playlistAdd(String entry);
void playlistLoad(const String &path); void playlistLoad(const String &path);
String playlistCurrent(); Entry playlistCurrent();
String playlistNext(); Entry playlistNext();
String playlistNextOrRepeatOneIfEnabled(); Entry playlistNextOrRepeatOneIfEnabled();
String playlistPrevious(); Entry playlistPrevious();
#endif #endif