Compare commits

..

No commits in common. "fbb800c47279986d0a3f1c46e1e49dea4a8b15ee" and "f421b77700742685b9f7d8bd6caa65aeb8d3bc12" have entirely different histories.

10 changed files with 58 additions and 290 deletions

View File

@ -7,19 +7,10 @@
#include <SPI.h>
#include <SD.h>
#include <SnapClient.h>
#include "player.h"
#include "SnapClient.h"
#include "wifi.h"
bool audioMute = true;
bool audioMute2 = true; // keep last applied mute state because we cannot retrieve it from library
float audioVolume = 0.1;
int copyErrors = 0;
AudioBoardStream board(AudioKitEs8388V1);
File file;
@ -43,9 +34,8 @@ bool running = false;
void audioStop() {
if (running) {
running = false;
Serial.println("[AUDIO ] STOP");
Serial.println("[STOP]");
}
copyErrors = 0;
copier.end();
mp3Stream.end();
mp3Decoder.end();
@ -58,12 +48,12 @@ void audioStop() {
bool audioPlayInit(const Entry &entry) {
audioStop();
Serial.printf("[AUDIO ] [%-4s] Start: %s\n", entry.type.c_str(), entry.url.c_str());
Serial.printf("[%s] %s\n", entry.type.c_str(), entry.url.c_str());
running = true;
if (entry.type == "ICY" || entry.type == "SNAP") {
if (!isWifiConnected()) {
Serial.printf("[AUDIO ] [%-4s] WiFi not connected.\n", entry.type.c_str());
Serial.printf("[%s] WiFi not connected.\n", entry.type.c_str());
audioStop();
return false;
}
@ -71,7 +61,7 @@ bool audioPlayInit(const Entry &entry) {
if (entry.type == "SD") {
if (!SD.begin(PIN_AUDIO_KIT_SD_CARD_CS)) {
Serial.printf("[AUDIO ] [%-4s] Failed to mount SD-card.\n", entry.type.c_str());
Serial.printf("[%s] Failed to mount SD-card.\n", entry.type.c_str());
audioStop();
return false;
}
@ -82,13 +72,13 @@ bool audioPlayInit(const Entry &entry) {
bool audioBeginMP3Stream(const Entry &entry, Stream &source) {
if (!mp3Decoder.begin()) {
Serial.printf("[AUDIO ] [%-4s] Failed to start MP3DecoderHelix.", entry.type.c_str());
Serial.printf("[%s] Failed to start MP3DecoderHelix.", entry.type.c_str());
audioStop();
return false;
}
if (!mp3Stream.begin()) {
Serial.printf("[AUDIO ] [%-4s] Failed to start EncodedAudioStream.", entry.type.c_str());
Serial.printf("[%s] Failed to start EncodedAudioStream.", entry.type.c_str());
audioStop();
return false;
}
@ -96,7 +86,7 @@ bool audioBeginMP3Stream(const Entry &entry, Stream &source) {
copier.begin(mp3Stream, source);
if (copier.copy() == 0) {
Serial.printf("[AUDIO ] [%-4s] Failed to copy initial data.", entry.type.c_str());
Serial.printf("[%s] Failed to copy initial data.", entry.type.c_str());
audioStop();
return false;
}
@ -110,7 +100,7 @@ bool audioPlayICY(const Entry &entry) {
}
if (!icy.begin(entry.url.c_str())) {
Serial.printf("[AUDIO ] [%-4s] Failed to start ICYStream.\n", entry.type.c_str());
Serial.printf("[%s] Failed to start ICYStream.\n", entry.type.c_str());
audioStop();
return false;
}
@ -125,26 +115,26 @@ bool audioPlaySNAP(const Entry &entry) {
IPAddress ip;
if (!WiFiClass::hostByName(entry.url.c_str(), ip)) {
Serial.printf("[AUDIO ] [%-4s] Failed to resolve host.\n", entry.type.c_str());
Serial.printf("[%s] Failed to resolve host.\n", entry.type.c_str());
audioStop();
return false;
}
snap.setServerIP(ip);
if (!snap.begin()) {
Serial.printf("[AUDIO ] [%-4s] Failed to connect.\n", entry.type.c_str());
Serial.printf("[%s] Failed to connect.\n", entry.type.c_str());
audioStop();
return false;
}
if (!opusDecoder.begin()) {
Serial.printf("[AUDIO ] [%-4s] Failed to start OpusAudioDecoder.\n", entry.type.c_str());
Serial.printf("[%s] Failed to start OpusAudioDecoder.\n", entry.type.c_str());
audioStop();
return false;
}
if (!snap.doLoop()) {
Serial.printf("[AUDIO ] [%-4s] Failed to copy initial data.\n", entry.type.c_str());
Serial.printf("[%s] Failed to copy initial data.\n", entry.type.c_str());
audioStop();
return false;
}
@ -158,14 +148,14 @@ bool audioPlaySD(const Entry &entry) {
}
if (!SD.exists(entry.url.c_str())) {
Serial.printf("[AUDIO ] [%-4s] File not found.\n", entry.type.c_str());
Serial.printf("[%s] File not found.\n", entry.type.c_str());
audioStop();
return false;
}
file = SD.open(entry.url.c_str(), FILE_READ);
if (!file) {
Serial.printf("[AUDIO ] [%-4s] Failed to open file.\n", entry.type.c_str());
Serial.printf("[%s] Failed to open file.\n", entry.type.c_str());
audioStop();
return false;
}
@ -187,39 +177,22 @@ bool audioPlay(const Entry &entry) {
if (entry.type == "SNAP") {
return audioPlaySNAP(entry);
}
Serial.printf("[AUDIO ] Unknown type: %s\n", entry.type.c_str());
Serial.printf("[ERROR] unknown type: %s\n", entry.type.c_str());
return false;
}
void audioSetup() {
AudioToolsLogger.setLogLevel(AudioToolsLogLevel::Error);
Serial.println("[AUDIO ] Initializing board...");
Serial.println("Initializing board...");
board.begin();
audioMute2 = !audioMute;
}
bool audioLoop() {
if (abs(board.volume() - audioVolume) >= 0.01f) {
Serial.printf("[AUDIO ] volume = %.2f\n", audioVolume);
board.setVolume(audioVolume);
stateBufferUpdateRequest();
}
if (audioMute2 != audioMute) {
audioMute2 = audioMute;
Serial.printf("[AUDIO ] mute = %s\n", audioMute ? "MUTED" : "no");
board.setMute(audioMute);
stateBufferUpdateRequest();
}
if (copier.copy() > 0) {
copyErrors = 0;
return true;
}
if (running) {
copyErrors++;
if (copyErrors < 100) {
return true;
}
Serial.println("[AUDIO ] Stream ended!");
Serial.println("Stream ended!");
audioStop();
}
return false;

View File

@ -3,10 +3,6 @@
#include "playlist.h"
extern bool audioMute;
extern float audioVolume;
bool audioPlay(const Entry &entry);
void audioStop();

View File

@ -1,93 +0,0 @@
#include "http.h"
#include <ArduinoJson.h>
#include "audio.h"
#include "ESPAsyncWebServer.h"
#include "player.h"
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
void onWebSocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsEventType type, void *arg, const uint8_t *data, const size_t len) {
if (type == WS_EVT_CONNECT) {
Serial.printf("[%-9s] Connected: %s\n", "WEBSOCKET", client->remoteIP().toString().c_str());
client->text(stateBuffer);
} else if (type == WS_EVT_DISCONNECT) {
Serial.printf("[%-9s] Disconnected: %s\n", "WEBSOCKET", client->remoteIP().toString().c_str());
}
}
void httpSetup() {
Serial.println("[HTTP ] Starting HTTP server");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
server.onNotFound([](AsyncWebServerRequest *request) {
const auto path = request->url();
if (path.endsWith("/") && path.length() > 1) {
request->redirect(path.substring(0, path.length() - 1));
} else {
request->send(404, "text/plain", "Not Found");
}
});
server.on("/state", [](AsyncWebServerRequest *request) {
Serial.println("[HTTP ] " + request->url());
request->send(200, "application/json", stateBuffer);
});
server.on("/stop", [](AsyncWebServerRequest *request) {
Serial.println("[HTTP ] " + request->url());
playerState = PLAYER_STOP;
request->send(200);
});
server.on("/play", [](AsyncWebServerRequest *request) {
Serial.println("[HTTP ] " + request->url());
playerState = PLAYER_PLAY;
request->send(200);
});
server.on("/pause", [](AsyncWebServerRequest *request) {
Serial.println("[HTTP ] " + request->url());
playerState = PLAYER_PAUSE;
request->send(200);
});
server.on("/mute", [](AsyncWebServerRequest *request) {
Serial.println("[HTTP ] " + request->url());
audioMute = true;
request->send(200);
});
server.on("/unmute", [](AsyncWebServerRequest *request) {
Serial.println("[HTTP ] " + request->url());
audioMute = false;
request->send(200);
});
server.on("/up", [](AsyncWebServerRequest *request) {
Serial.println("[HTTP ] " + request->url());
audioVolume = max(0.0f, min(audioVolume + 0.05f, 1.0f));
request->send(200);
});
server.on("/down", [](AsyncWebServerRequest *request) {
Serial.println("[HTTP ] " + request->url());
audioVolume = max(0.0f, min(audioVolume - 0.05f, 1.0f));
request->send(200);
});
server.on("/next", [](AsyncWebServerRequest *request) {
Serial.println("[HTTP ] " + request->url());
playerSkip += 1;
request->send(200);
});
server.on("/back", [](AsyncWebServerRequest *request) {
Serial.println("[HTTP ] " + request->url());
playerSkip -= 1;
request->send(200);
});
ws.onEvent(onWebSocketEvent);
server.addHandler(&ws);
server.begin();
}
void httpLoop() {
ws.cleanupClients();
}
void websocketSendAll() {
ws.textAll(stateBuffer);
}

View File

@ -1,10 +0,0 @@
#ifndef HTTP_H
#define HTTP_H
void httpSetup();
void httpLoop();
void websocketSendAll();
#endif

View File

@ -1,7 +1,5 @@
#include <Arduino.h>
#include "audio.h"
#include "http.h"
#include "wifi.h"
#include "player.h"
#include "playlist.h"
@ -11,20 +9,13 @@ void setup() {
Serial.begin(115200);
Serial.println("\n\n\nStartup!");
audioSetup();
playerSetup();
playlistClear();
playlistAdd("ICY|http://liveradio.sr.de/sr/sr1/mp3/128/stream.mp3|SR1"); // 48000 Hz
playlistAdd("ICY|http://uk3.internet-radio.com:8082/live|1940s Radio"); // 44100 Hz TODO stuttering
playlistAdd("ICY|https://das-edge16-live365-dal02.cdnstream.com/a22508|Mistletoe Radio"); // 44100 Hz TODO stuttering
playlistAdd("ICY|https://stream.rockantenne.de/rockantenne/stream/mp3|Rockantenne Classic Perlen"); // 44100 Hz TODO stuttering
wifiLoop();
httpSetup();
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() {
wifiLoop();
playerLoop();
httpLoop();
}

View File

@ -1,9 +1,8 @@
#include "player.h"
#include <ArduinoJson.h>
#include <Arduino.h>
#include "audio.h"
#include "http.h"
#include "playlist.h"
#define DELAY_MS_ADD 250UL
@ -13,93 +12,25 @@ unsigned long errorMs = 0;
unsigned long delayMs = 0;
int playerSkip = 0;
PlayerState playerState = PLAYER_PLAY;
char stateBuffer0[200] = "{}";
char stateBuffer1[200];
char *stateBuffer = stateBuffer0;
bool stateBufferUpdateNeeded = true;
const char *getPlayerStateString() {
switch (playerState) {
case PLAYER_STOP:
return "STOP";
case PLAYER_PLAY:
return "PLAY";
case PLAYER_PAUSE:
return "PAUSE";
default: return "[???]";
}
void playerSetup() {
audioSetup();
}
void stateBufferUpdateRequest() {
stateBufferUpdateNeeded = true;
}
void updateStateBufferIfNeeded() {
if (!stateBufferUpdateNeeded) {
void playerLoop() {
if (audioLoop()) {
errorMs = 0;
return;
}
stateBufferUpdateNeeded = false;
auto json = JsonDocument();
const auto entry = playlistCurrent();
json["state"] = getPlayerStateString();
json["entry"]["type"] = entry.type;
json["entry"]["url"] = entry.url;
json["entry"]["title"] = entry.title;
json["volume"] = audioVolume;
json["mute"] = audioMute;
json["playlist"]["count"] = playlistGetCount();
json["playlist"]["index"] = playlistGetIndex();
if (stateBuffer == stateBuffer0) {
serializeJson(json, stateBuffer1, sizeof(stateBuffer1));
stateBuffer = stateBuffer1;
} else {
serializeJson(json, stateBuffer0, sizeof(stateBuffer0));
stateBuffer = stateBuffer0;
if (errorMs > 0 && millis() - errorMs < delayMs) {
return;
}
websocketSendAll();
}
void playerStart(const Entry &entry) {
if (entry.type == "") {
Serial.println("[PLAYER ] Not playing empty Entry => STOP");
playerState = PLAYER_STOP;
} else if (audioPlay(entry)) {
const auto entry = playlistNextOrRepeatOneIfEnabled();
if (audioPlay(entry)) {
errorMs = 0;
delayMs = 0;
} else {
errorMs = max(1UL, millis());
delayMs = min(DELAY_MS_MAX, delayMs + DELAY_MS_ADD);
Serial.printf("[PLAYER ] Retry delay: %d ms\n", delayMs);
Serial.printf("retry delay: %d ms\n", delayMs);
}
stateBufferUpdateRequest();
}
void playerLoop() {
if (playerSkip != 0) {
playerState = PLAYER_PLAY;
playerStart(playlistNext(playerSkip));
playerSkip = 0;
} else {
const auto playing = audioLoop();
if (playing) {
errorMs = 0;
}
if (playing && playerState == PLAYER_STOP) {
errorMs = 0;
audioStop();
stateBufferUpdateRequest();
}
if (!playing && playerState == PLAYER_PLAY && (errorMs == 0 || millis() - errorMs >= delayMs)) {
playerStart(playlistNextOrRepeatOneIfEnabled());
}
}
updateStateBufferIfNeeded();
}

View File

@ -1,20 +1,8 @@
#ifndef PLAYER_H
#define PLAYER_H
enum PlayerState {
PLAYER_STOP,
PLAYER_PLAY,
PLAYER_PAUSE,
};
extern PlayerState playerState;
extern int playerSkip;
extern char *stateBuffer;
void playerSetup();
void playerLoop();
void stateBufferUpdateRequest();
#endif

View File

@ -3,8 +3,6 @@
#include <vector>
#include <SD.h>
#include "AudioTools/CoreAudio/AudioBasic/Collections/Vector.h" // needed for following import (library mistake???=
#include "AudioTools/CoreAudio/BaseConverter.h" // needed for following import (library mistake???=
#include "AudioTools/AudioLibs/I2SCodecStream.h"
bool playlistRepeatOne = false;
@ -20,7 +18,7 @@ size_t playlistIndex = 0;
std::vector<Entry> playlistEntries;
void playlistClear() {
Serial.println("[PLAYLIST ] clear");
Serial.println("[PLAYLIST] clear");
playlistTitle = "";
playlistIndex = 0;
playlistEntries.clear();
@ -29,17 +27,17 @@ void playlistClear() {
void playlistAdd(String entry) {
entry.trim();
Serial.println("[PLAYLIST ] [ADD] " + entry);
Serial.println("[PLAYLIST] add: " + entry);
const auto index_type_url = entry.indexOf('|');
if (index_type_url < 0) {
Serial.println("[PLAYLIST ] type/url-delimiter not found: " + entry);
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);
Serial.println("[PLAYLIST] Unknown type: " + type);
}
entry = entry.substring(index_type_url + 1);
@ -52,13 +50,13 @@ void playlistAdd(String entry) {
const auto index_url_title = entry.indexOf('|');
if (index_url_title < 0) {
Serial.println("[PLAYLIST ] url/title-delimiter not found: " + entry);
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.");
Serial.println("[PLAYLIST] url is empty.");
return;
}
@ -70,18 +68,18 @@ void playlistAdd(String entry) {
void playlistLoad(const String &path) {
playlistClear();
Serial.println("[PLAYLIST ] Loading playlist: " + path);
Serial.println("[PLAYLIST] Loading playlist: " + path);
if (!SD.begin(PIN_AUDIO_KIT_SD_CARD_CS)) {
Serial.println("[PLAYLIST ] Failed to initialize SD card.");
Serial.println("[PLAYLIST] Failed to initialize SD card.");
return;
}
if (!SD.exists(path)) {
Serial.println("[PLAYLIST ] File not found.");
Serial.println("[PLAYLIST] File not found.");
return;
}
auto file = SD.open(path, FILE_READ);
if (!file) {
Serial.println("[PLAYLIST ] Failed to open file.");
Serial.println("[PLAYLIST] Failed to open file.");
}
while (file.available() > 0) {
@ -104,27 +102,23 @@ Entry playlistCurrent() {
return playlistSet(playlistIndex);
}
Entry playlistNext(const int amount) {
if (amount < 0 && playlistIndex <= 0 && !playlistRepeatAll) {
Entry playlistNext() {
if (playlistIndex >= playlistEntries.size() - 1 && !playlistRepeatAll) {
return Entry();
}
if (amount > 0 && playlistIndex >= playlistEntries.size() - 1 && !playlistRepeatAll) {
return Entry();
}
return playlistSet(playlistIndex + amount);
return playlistSet(playlistIndex + 1);
}
Entry playlistNextOrRepeatOneIfEnabled() {
if (playlistRepeatOne) {
return playlistCurrent();
}
return playlistNext(+1);
return playlistNext();
}
size_t playlistGetCount() {
return playlistEntries.size();
}
size_t playlistGetIndex() {
return playlistIndex;
Entry playlistPrevious() {
if (playlistIndex <= 0 && !playlistRepeatAll) {
return Entry();
}
return playlistSet(playlistIndex - 1);
}

View File

@ -11,12 +11,10 @@ void playlistLoad(const String &path);
Entry playlistCurrent();
Entry playlistNext(int amount);
Entry playlistNext();
Entry playlistNextOrRepeatOneIfEnabled();
size_t playlistGetCount();
size_t playlistGetIndex();
Entry playlistPrevious();
#endif

View File

@ -12,21 +12,21 @@ void wifiLoop() {
if (connected) {
// still connected
} else {
Serial.printf("[WIFI ] Disconnected!\n");
Serial.printf("WIFI disconnected!\n");
}
} else {
if (connected) {
wifiLastMillis = 0;
Serial.printf("[WIFI ] Connected: %s\n", WiFi.localIP().toString().c_str());
Serial.printf("WIFI connected: %s\n", WiFi.localIP().toString().c_str());
} else if (wifiLastMillis == 0 || millis() - wifiLastMillis > 10000) {
WiFi.disconnect();
yield();
if (wifiLastMillis != 0) {
Serial.printf("[WIFI ] Connect timeout!\n");
Serial.printf("WIFI connect timeout!\n");
}
wifiLastMillis = max(1UL, millis());
const auto ssid = "HappyNet";
Serial.printf("[WIFI ] Connecting: %s\n", ssid);
Serial.printf("WIFI connecting: %s\n", ssid);
WiFi.begin(ssid, "1Grausame!Sackratte7");
yield();
} else {