From 263f3a530f66238ac4d38d8c5b0fbd22b5767104 Mon Sep 17 00:00:00 2001 From: Torsten Stauder Date: Tue, 20 Jul 2021 22:20:51 +0200 Subject: [PATCH] Adding support for playlist from local .m3u-file --- README.md | 3 ++- changelog.md | 1 + html/management_DE.html | 1 + html/management_EN.html | 1 + src/AudioPlayer.cpp | 18 +++++++++++++++++- src/HTMLmanagement_DE.h | 1 + src/HTMLmanagement_EN.h | 1 + src/Led.cpp | 2 +- src/LogMessages_DE.cpp | 3 ++- src/LogMessages_EN.cpp | 3 ++- src/SdCard.cpp | 38 +++++++++++++++++++++++++++++++++++--- src/Wlan.cpp | 2 +- src/logmessages.h | 1 + src/revision.h | 2 +- src/values.h | 1 + 15 files changed, 68 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1430809..afa1da4 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ I started this project back in october 2019 and never expected it to become that * Partition-layout for ESP32 is changed along with this branch. This step was necessary in order to resize (enlarge) the memory-region where especially the assignments for the RFID-tags are saved. As all permanent settings (e.g. WiFi-settings) are saved there too, it's necessary to re-enter WiFi-credentials after update. But the most important thing is to recover the assignments for the RFID-tags. Please consult my [migration-document](https://forum.espuino.de/t/wechsel-zum-refactoring-branch-was-ist-zu-beachten/510). ## Changelog Last three events: +* 20.07.2021: Adding new playmode: multiple webradio-paylist from local .m3u-file * 13.07.2021: Adding OTA-support via webGUI * 09.07.2021: Making branch `refactoring` the new master -* 09.07.2021: Making master new branch `old` (not maintained any longer!) ## Known bugs * Some webstreams don't run. Guess it's a combination of saturated connection-pool and lack of heap-memory. Works probably better if ESP32-WROVER (e.g. Lolin D32 pro) is used, as this chip has PSRAM. Advice: Don't enable modules (e.g. MQTT) if you don't need them as this could save memory (and trouble). ## ESPuino - what's that? @@ -278,6 +278,7 @@ It's not just simply playing music; different playmodes are supported: * `folder/playlist (alph. sorted)` => plays all tracks in alph. order from a folder forever * `folder/playlist (random order)` => plays all tracks in random order from a folder forever * `webradio` => always only one "track": plays a webstream +* `Webradiolist from local .m3u-File` => can be one or more webradio-stations with local .m3u as sourcefile ### Modification RFID-tags There are special RFID-tags, that don't start music by themself but can modify things. If applied a second time, it's previous action/modification will be reversed. Please note: all sleep-modes do dimming (Neopixel) automatically because it's supposed to be used in the evening when going to bed. Well, at least that's my children's indication :-) So first make sure to start the music then use a modification-card in order to apply your desired modification: diff --git a/changelog.md b/changelog.md index 550f974..6fd32ca 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ * 09.07.2021: Making branch `refactoring` the the master * 09.07.2021: Making master the new branch `old` (not maintained any longer!) * 13.07.2021: Adding OTA-support via webGUI +* 20.07.2021: Adding new playmode: multiple webradio-paylist from local .m3u-file ## Old (monolithic main.cpp) * 11.07.2020: Added support for reversed Neopixel addressing. diff --git a/html/management_DE.html b/html/management_DE.html index 0058c10..ae0fc49 100644 --- a/html/management_DE.html +++ b/html/management_DE.html @@ -272,6 +272,7 @@ +
diff --git a/html/management_EN.html b/html/management_EN.html index 3d5961a..3404b64 100644 --- a/html/management_EN.html +++ b/html/management_EN.html @@ -272,6 +272,7 @@ +
diff --git a/src/AudioPlayer.cpp b/src/AudioPlayer.cpp index 060f4a8..3e0a342 100644 --- a/src/AudioPlayer.cpp +++ b/src/AudioPlayer.cpp @@ -553,7 +553,7 @@ void AudioPlayer_Task(void *parameter) { } } - if (gPlayProperties.playMode == WEBSTREAM) { // Webstream + if (gPlayProperties.playMode == WEBSTREAM || gPlayProperties.playMode == WEBSTREAMS_LOCAL_M3U) { // Webstream audio->connecttohost(*(gPlayProperties.playlist + gPlayProperties.currentTrackNumber)); gPlayProperties.playlistFinished = false; } else { @@ -866,6 +866,22 @@ void AudioPlayer_TrackQueueDispatcher(const char *_itemToPlay, const uint32_t _l break; } + case WEBSTREAMS_LOCAL_M3U: { // This is always just one "track" + Log_Println((char *) FPSTR(modeWebstreamM3u), LOGLEVEL_NOTICE); + if (Wlan_IsConnected()) { + xQueueSend(gTrackQueue, &(musicFiles), 0); + #ifdef MQTT_ENABLE + publishMqtt((char *) FPSTR(topicPlaymodeState), gPlayProperties.playMode, false); + publishMqtt((char *) FPSTR(topicRepeatModeState), NO_REPEAT, false); + #endif + } else { + Log_Println((char *) FPSTR(webstreamNotAvailable), LOGLEVEL_ERROR); + System_IndicateError(); + gPlayProperties.playMode = NO_PLAYLIST; + } + break; + } + default: Log_Println((char *) FPSTR(modeDoesNotExist), LOGLEVEL_ERROR); gPlayProperties.playMode = NO_PLAYLIST; diff --git a/src/HTMLmanagement_DE.h b/src/HTMLmanagement_DE.h index 66a2b2d..7cdce4e 100644 --- a/src/HTMLmanagement_DE.h +++ b/src/HTMLmanagement_DE.h @@ -272,6 +272,7 @@ static const char management_HTML[] PROGMEM = "\ \ \ \ + \ \
\
\ diff --git a/src/HTMLmanagement_EN.h b/src/HTMLmanagement_EN.h index 4275ff6..140e198 100644 --- a/src/HTMLmanagement_EN.h +++ b/src/HTMLmanagement_EN.h @@ -272,6 +272,7 @@ static const char management_HTML[] PROGMEM = "\ \ \ \ + \ \
\
\ diff --git a/src/Led.cpp b/src/Led.cpp index 18da742..c373db8 100644 --- a/src/Led.cpp +++ b/src/Led.cpp @@ -467,7 +467,7 @@ static void Led_Task(void *parameter) { redrawProgress = true; } - if (gPlayProperties.playMode != WEBSTREAM) { + if (gPlayProperties.playMode != WEBSTREAM && gPlayProperties.playMode != WEBSTREAMS_LOCAL_M3U) { if (gPlayProperties.currentRelPos != lastPos || redrawProgress) { redrawProgress = false; lastPos = gPlayProperties.currentRelPos; diff --git a/src/LogMessages_DE.cpp b/src/LogMessages_DE.cpp index be95bd8..9d2561e 100644 --- a/src/LogMessages_DE.cpp +++ b/src/LogMessages_DE.cpp @@ -31,7 +31,7 @@ const char nameOfFileFound[] PROGMEM = "Gefundenes File"; const char reallocCalled[] PROGMEM = "Speicher reallokiert."; const char unableToAllocateMemForLinearPlaylist[] PROGMEM = "Speicher für lineare Playlist konnte nicht allokiert werden!"; - const char numberOfValidFiles[] PROGMEM = "Anzahl gültiger Files"; + const char numberOfValidFiles[] PROGMEM = "Anzahl gültiger Files/Webstreams"; const char newLoudnessReceivedQueue[] PROGMEM = "Neue Lautstärke empfangen via Queue"; const char newCntrlReceivedQueue[] PROGMEM = "Kontroll-Kommando empfangen via Queue"; const char newPlaylistReceived[] PROGMEM = "Neue Playlist empfangen"; @@ -72,6 +72,7 @@ const char modeAllTrackAlphSortedLoop[] PROGMEM = "Modus: Alle Tracks eines Ordners sortiert (alphabetisch) in Endlosschleife"; const char modeAllTrackRandomLoop[] PROGMEM = "Modus: Alle Tracks eines Ordners zufällig in Endlosschleife"; const char modeWebstream[] PROGMEM = "Modus: Webstream"; + const char modeWebstreamM3u[] PROGMEM = "Modus: Webstream (lokale .m3u-Datei)"; const char webstreamNotAvailable[] PROGMEM = "Aktuell kein Webstream möglich, da keine WLAN-Verbindung vorhanden!"; const char modeDoesNotExist[] PROGMEM = "Abspielmodus existiert nicht!"; const char modeRepeatNone[] PROGMEM = "Repeatmodus: Kein Repeat"; diff --git a/src/LogMessages_EN.cpp b/src/LogMessages_EN.cpp index 93bd9f3..459c6d5 100644 --- a/src/LogMessages_EN.cpp +++ b/src/LogMessages_EN.cpp @@ -31,7 +31,7 @@ const char nameOfFileFound[] PROGMEM = "File found"; const char reallocCalled[] PROGMEM = "Reallocated memory."; const char unableToAllocateMemForLinearPlaylist[] PROGMEM = "Unable to allocate memory for linear playlist!"; - const char numberOfValidFiles[] PROGMEM = "Number of valid files"; + const char numberOfValidFiles[] PROGMEM = "Number of valid files/webstreams"; const char newLoudnessReceivedQueue[] PROGMEM = "New volume received via queue"; const char newCntrlReceivedQueue[] PROGMEM = "Control-command received via queue"; const char newPlaylistReceived[] PROGMEM = "New playlist received"; @@ -72,6 +72,7 @@ const char modeAllTrackAlphSortedLoop[] PROGMEM = "Mode: all tracks (in alph. order) of directory as infinite loop"; const char modeAllTrackRandomLoop[] PROGMEM = "Mode: all tracks (in random order) of directory as infinite loop"; const char modeWebstream[] PROGMEM = "Mode: webstream"; + const char modeWebstreamM3u[] PROGMEM = "Mode: Webstream (local .m3u-file)"; const char webstreamNotAvailable[] PROGMEM = "Unable to access webstream as no wifi-connection is available!"; const char modeDoesNotExist[] PROGMEM = "Playmode does not exist!"; const char modeRepeatNone[] PROGMEM = "Repeatmode: no repeat"; diff --git a/src/SdCard.cpp b/src/SdCard.cpp index 1433d07..7bda195 100644 --- a/src/SdCard.cpp +++ b/src/SdCard.cpp @@ -89,6 +89,7 @@ char **SdCard_ReturnPlaylist(const char *fileName, const uint32_t _playMode) { char cacheFileNameBuf[275]; bool readFromCacheFile = false; bool enablePlaylistCaching = false; + bool enablePlaylistFromM3u = false; // Look if file/folder requested really exists. If not => break. File fileOrDirectory = gFSystem.open(fileName); @@ -97,6 +98,7 @@ char **SdCard_ReturnPlaylist(const char *fileName, const uint32_t _playMode) { return NULL; } + // Create linear playlist of caching-file #ifdef CACHED_PLAYLIST_ENABLE strncpy(cacheFileNameBuf, fileName, sizeof(cacheFileNameBuf)); strcat(cacheFileNameBuf, "/"); @@ -140,6 +142,36 @@ char **SdCard_ReturnPlaylist(const char *fileName, const uint32_t _playMode) { } #endif + // Parse m3u-playlist and create linear-playlist out of it + if (_playMode == WEBSTREAMS_LOCAL_M3U) { + enablePlaylistFromM3u = true; + if (fileOrDirectory && !fileOrDirectory.isDirectory()) { + serializedPlaylist = (char *) x_calloc(2048, sizeof(char)); + if (serializedPlaylist == NULL) { + Log_Println((char *) FPSTR(unableToAllocateMemForLinearPlaylist), LOGLEVEL_ERROR); + System_IndicateError(); + return files; + } + char buf; + uint32_t fPos = 1; + + serializedPlaylist[0] = '#'; + while (fileOrDirectory.available() > 0) { + buf = fileOrDirectory.read(); + if (buf != '\n' && buf != '\r') { + serializedPlaylist[fPos++] = buf; + } else { + serializedPlaylist[fPos++] = '#'; + } + } + if (serializedPlaylist[fPos-1] == '#') { // Remove trailing delimiter if set + serializedPlaylist[fPos-1] = '\0'; + } + } else { + return files; + } + } + snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *) FPSTR(freeMemory), ESP.getFreeHeap()); Log_Println(Log_Buffer, LOGLEVEL_DEBUG); @@ -151,8 +183,8 @@ char **SdCard_ReturnPlaylist(const char *fileName, const uint32_t _playMode) { Log_Println(Log_Buffer, LOGLEVEL_DEBUG); } - // Don't read from cachefile (if cachefile doesn't exist, playmode doesn't fit or caching isn't desired) - if (!readFromCacheFile) { + // Don't read from cachefile or m3u-file. Means: read filenames from SD and make playlist of it + if (!readFromCacheFile && !enablePlaylistFromM3u) { Log_Println((char *) FPSTR(playlistGenModeUncached), LOGLEVEL_NOTICE); // File-mode if (!fileOrDirectory.isDirectory()) { @@ -173,7 +205,7 @@ char **SdCard_ReturnPlaylist(const char *fileName, const uint32_t _playMode) { return ++files; } - // Directory-mode + // Directory-mode (linear-playlist) uint16_t allocCount = 1; uint16_t allocSize = 4096; if (psramInit()) { diff --git a/src/Wlan.cpp b/src/Wlan.cpp index 8cd5df1..e7382c6 100644 --- a/src/Wlan.cpp +++ b/src/Wlan.cpp @@ -164,7 +164,7 @@ void writeWifiStatusToNVS(bool wifiStatus) { if (!wifiStatus) { if (gPrefsSettings.putUInt("enableWifi", 0)) { // disable Log_Println((char *) FPSTR(wifiDisabledAfterRestart), LOGLEVEL_NOTICE); - if (gPlayProperties.playMode == WEBSTREAM) { + if (gPlayProperties.playMode == WEBSTREAM || gPlayProperties.playMode == WEBSTREAMS_LOCAL_M3U) { AudioPlayer_TrackControlToQueueSender(STOP); } delay(300); diff --git a/src/logmessages.h b/src/logmessages.h index 883e31d..bd8cdee 100644 --- a/src/logmessages.h +++ b/src/logmessages.h @@ -68,6 +68,7 @@ extern const char modeAllTrackRandom[]; extern const char modeAllTrackAlphSortedLoop[]; extern const char modeAllTrackRandomLoop[]; extern const char modeWebstream[]; +extern const char modeWebstreamM3u[]; extern const char webstreamNotAvailable[]; extern const char modeDoesNotExist[]; extern const char modeRepeatNone[]; diff --git a/src/revision.h b/src/revision.h index 76fe93f..8996247 100644 --- a/src/revision.h +++ b/src/revision.h @@ -1,4 +1,4 @@ #ifndef __REVISION_H__ #define __REVISION_H__ - constexpr const char softwareRevision[] PROGMEM = "Software-revision: 20210716-2"; + constexpr const char softwareRevision[] PROGMEM = "Software-revision: 20210720-1"; #endif \ No newline at end of file diff --git a/src/values.h b/src/values.h index 591acbb..bb84985 100644 --- a/src/values.h +++ b/src/values.h @@ -25,6 +25,7 @@ #define ALL_TRACKS_OF_DIR_SORTED_LOOP 7 // Play all files of a directory (alph. sorted) in infinite-loop #define ALL_TRACKS_OF_DIR_RANDOM_LOOP 9 // Play all files of a directory (randomized) in infinite-loop #define WEBSTREAM 8 // Play webradio-stream + #define WEBSTREAMS_LOCAL_M3U 11 // Plays webreadio-streams with addresses from a local m3u-file #define BUSY 10 // Used if playlist is created