Entry for: type|url|title
This commit is contained in:
parent
35c8ec13ee
commit
bcf6001c55
21
src/Entry.h
Normal file
21
src/Entry.h
Normal 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
|
||||||
120
src/audio.cpp
120
src/audio.cpp
@ -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)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Serial.printf("[%s] %s", code, url.c_str());
|
if (!icy.begin(entry.url.c_str())) {
|
||||||
running = true;
|
Serial.printf("[%s] Failed to start ICYStream.\n", entry.type.c_str());
|
||||||
|
|
||||||
if (needsWiFi) {
|
|
||||||
if (!isWifiConnected()) {
|
|
||||||
Serial.printf("[%s] WiFi not connected.\n", code);
|
|
||||||
audioStop();
|
audioStop();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (needsSD) {
|
return audioBeginMP3Stream(entry, icy);
|
||||||
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) {
|
bool audioPlaySNAP(const Entry &entry) {
|
||||||
if (!audioPlayInit("ICY", url, true, false)) {
|
if (!audioPlayInit(entry)) {
|
||||||
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)) {
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user