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();
}
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();
bool audioPlayICY(const Entry &entry) {
if (!audioPlayInit(entry)) {
return false;
}
Serial.printf("[%s] %s", code, url.c_str());
running = true;
if (needsWiFi) {
if (!isWifiConnected()) {
Serial.printf("[%s] WiFi not connected.\n", code);
if (!icy.begin(entry.url.c_str())) {
Serial.printf("[%s] Failed to start ICYStream.\n", entry.type.c_str());
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;
return audioBeginMP3Stream(entry, icy);
}
bool audioPlayICY(const String &url) {
if (!audioPlayInit("ICY", url, true, false)) {
return false;
}
if (!icy.begin(url.c_str())) {
Serial.println("[ICY] Failed to start ICYStream.");
audioStop();
return false;
}
return audioBeginMP3Stream("ICY", 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;
}

View File

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

View File

@ -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() {

View File

@ -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 {

View File

@ -1,9 +1,9 @@
#include "playlist.h"
#include <vector>
#include <SD.h>
#include "AudioTools/AudioLibs/I2SCodecStream.h"
#include <vector>
bool playlistRepeatOne = false;
@ -15,22 +15,53 @@ String playlistTitle = "";
size_t playlistIndex = 0;
std::vector<String> playlistEntries;
std::vector<Entry> playlistEntries;
void playlistClear() {
playlistTitle = "";
playlistIndex = 0;
playlistEntries.clear();
std::vector<String>().swap(playlistEntries);
std::vector<Entry>().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);
}

View File

@ -1,20 +1,20 @@
#ifndef PLAYLIST_H
#define PLAYLIST_H
#include <WString.h>
#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