Browse Source

Adding support for playlist from local .m3u-file

master
Torsten Stauder 4 years ago
parent
commit
263f3a530f
  1. 3
      README.md
  2. 1
      changelog.md
  3. 1
      html/management_DE.html
  4. 1
      html/management_EN.html
  5. 18
      src/AudioPlayer.cpp
  6. 1
      src/HTMLmanagement_DE.h
  7. 1
      src/HTMLmanagement_EN.h
  8. 2
      src/Led.cpp
  9. 3
      src/LogMessages_DE.cpp
  10. 3
      src/LogMessages_EN.cpp
  11. 38
      src/SdCard.cpp
  12. 2
      src/Wlan.cpp
  13. 1
      src/logmessages.h
  14. 2
      src/revision.h
  15. 1
      src/values.h

3
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). * 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 ## Changelog
Last three events: Last three events:
* 20.07.2021: Adding new playmode: multiple webradio-paylist from local .m3u-file
* 13.07.2021: Adding OTA-support via webGUI * 13.07.2021: Adding OTA-support via webGUI
* 09.07.2021: Making branch `refactoring` the new master * 09.07.2021: Making branch `refactoring` the new master
* 09.07.2021: Making master new branch `old` (not maintained any longer!)
## Known bugs ## 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). * 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? ## 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 (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 * `folder/playlist (random order)` => plays all tracks in random order from a folder forever
* `webradio` => always only one "track": plays a webstream * `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 ### 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: 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:

1
changelog.md

@ -9,6 +9,7 @@
* 09.07.2021: Making branch `refactoring` the the master * 09.07.2021: Making branch `refactoring` the the master
* 09.07.2021: Making master the new branch `old` (not maintained any longer!) * 09.07.2021: Making master the new branch `old` (not maintained any longer!)
* 13.07.2021: Adding OTA-support via webGUI * 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) ## Old (monolithic main.cpp)
* 11.07.2020: Added support for reversed Neopixel addressing. * 11.07.2020: Added support for reversed Neopixel addressing.

1
html/management_DE.html

@ -272,6 +272,7 @@
<option class="option-folder" value="7">Alle Titel eines Verzeichnis (sortiert, Endlosschleife)</option> <option class="option-folder" value="7">Alle Titel eines Verzeichnis (sortiert, Endlosschleife)</option>
<option class="option-folder" value="9">Alle Titel eines Verzeichnis (zufällig, Endlosschleife)</option> <option class="option-folder" value="9">Alle Titel eines Verzeichnis (zufällig, Endlosschleife)</option>
<option class="option-stream" value="8">Webradio</option> <option class="option-stream" value="8">Webradio</option>
<option class="option-stream" value="11">Webradioliste aus .m3u-Datei</option>
</select> </select>
</div> </div>
<div class="tab-pane " id="rfidmod" role="tabpanel"> <div class="tab-pane " id="rfidmod" role="tabpanel">

1
html/management_EN.html

@ -272,6 +272,7 @@
<option class="option-folder" value="7">All tracks of a directory (sorted alph., loop)</option> <option class="option-folder" value="7">All tracks of a directory (sorted alph., loop)</option>
<option class="option-folder" value="9">All tracks of a directory (random, loop)</option> <option class="option-folder" value="9">All tracks of a directory (random, loop)</option>
<option class="option-stream" value="8">Webradio</option> <option class="option-stream" value="8">Webradio</option>
<option class="option-stream" value="11">Webradiolist from local .m3u-File</option>
</select> </select>
</div> </div>
<div class="tab-pane " id="rfidmod" role="tabpanel"> <div class="tab-pane " id="rfidmod" role="tabpanel">

18
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)); audio->connecttohost(*(gPlayProperties.playlist + gPlayProperties.currentTrackNumber));
gPlayProperties.playlistFinished = false; gPlayProperties.playlistFinished = false;
} else { } else {
@ -866,6 +866,22 @@ void AudioPlayer_TrackQueueDispatcher(const char *_itemToPlay, const uint32_t _l
break; 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: default:
Log_Println((char *) FPSTR(modeDoesNotExist), LOGLEVEL_ERROR); Log_Println((char *) FPSTR(modeDoesNotExist), LOGLEVEL_ERROR);
gPlayProperties.playMode = NO_PLAYLIST; gPlayProperties.playMode = NO_PLAYLIST;

1
src/HTMLmanagement_DE.h

@ -272,6 +272,7 @@ static const char management_HTML[] PROGMEM = "<!DOCTYPE html>\
<option class=\"option-folder\" value=\"7\">Alle Titel eines Verzeichnis (sortiert, Endlosschleife)</option>\ <option class=\"option-folder\" value=\"7\">Alle Titel eines Verzeichnis (sortiert, Endlosschleife)</option>\
<option class=\"option-folder\" value=\"9\">Alle Titel eines Verzeichnis (zufällig, Endlosschleife)</option>\ <option class=\"option-folder\" value=\"9\">Alle Titel eines Verzeichnis (zufällig, Endlosschleife)</option>\
<option class=\"option-stream\" value=\"8\">Webradio</option>\ <option class=\"option-stream\" value=\"8\">Webradio</option>\
<option class=\"option-stream\" value=\"11\">Webradioliste aus .m3u-Datei</option>\
</select>\ </select>\
</div>\ </div>\
<div class=\"tab-pane \" id=\"rfidmod\" role=\"tabpanel\">\ <div class=\"tab-pane \" id=\"rfidmod\" role=\"tabpanel\">\

1
src/HTMLmanagement_EN.h

@ -272,6 +272,7 @@ static const char management_HTML[] PROGMEM = "<!DOCTYPE html>\
<option class=\"option-folder\" value=\"7\">All tracks of a directory (sorted alph., loop)</option>\ <option class=\"option-folder\" value=\"7\">All tracks of a directory (sorted alph., loop)</option>\
<option class=\"option-folder\" value=\"9\">All tracks of a directory (random, loop)</option>\ <option class=\"option-folder\" value=\"9\">All tracks of a directory (random, loop)</option>\
<option class=\"option-stream\" value=\"8\">Webradio</option>\ <option class=\"option-stream\" value=\"8\">Webradio</option>\
<option class=\"option-stream\" value=\"11\">Webradiolist from local .m3u-File</option>\
</select>\ </select>\
</div>\ </div>\
<div class=\"tab-pane \" id=\"rfidmod\" role=\"tabpanel\">\ <div class=\"tab-pane \" id=\"rfidmod\" role=\"tabpanel\">\

2
src/Led.cpp

@ -467,7 +467,7 @@ static void Led_Task(void *parameter) {
redrawProgress = true; redrawProgress = true;
} }
if (gPlayProperties.playMode != WEBSTREAM) {
if (gPlayProperties.playMode != WEBSTREAM && gPlayProperties.playMode != WEBSTREAMS_LOCAL_M3U) {
if (gPlayProperties.currentRelPos != lastPos || redrawProgress) { if (gPlayProperties.currentRelPos != lastPos || redrawProgress) {
redrawProgress = false; redrawProgress = false;
lastPos = gPlayProperties.currentRelPos; lastPos = gPlayProperties.currentRelPos;

3
src/LogMessages_DE.cpp

@ -31,7 +31,7 @@
const char nameOfFileFound[] PROGMEM = "Gefundenes File"; const char nameOfFileFound[] PROGMEM = "Gefundenes File";
const char reallocCalled[] PROGMEM = "Speicher reallokiert."; const char reallocCalled[] PROGMEM = "Speicher reallokiert.";
const char unableToAllocateMemForLinearPlaylist[] PROGMEM = "Speicher für lineare Playlist konnte nicht allokiert werden!"; 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 newLoudnessReceivedQueue[] PROGMEM = "Neue Lautstärke empfangen via Queue";
const char newCntrlReceivedQueue[] PROGMEM = "Kontroll-Kommando empfangen via Queue"; const char newCntrlReceivedQueue[] PROGMEM = "Kontroll-Kommando empfangen via Queue";
const char newPlaylistReceived[] PROGMEM = "Neue Playlist empfangen"; 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 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 modeAllTrackRandomLoop[] PROGMEM = "Modus: Alle Tracks eines Ordners zufällig in Endlosschleife";
const char modeWebstream[] PROGMEM = "Modus: Webstream"; 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 webstreamNotAvailable[] PROGMEM = "Aktuell kein Webstream möglich, da keine WLAN-Verbindung vorhanden!";
const char modeDoesNotExist[] PROGMEM = "Abspielmodus existiert nicht!"; const char modeDoesNotExist[] PROGMEM = "Abspielmodus existiert nicht!";
const char modeRepeatNone[] PROGMEM = "Repeatmodus: Kein Repeat"; const char modeRepeatNone[] PROGMEM = "Repeatmodus: Kein Repeat";

3
src/LogMessages_EN.cpp

@ -31,7 +31,7 @@
const char nameOfFileFound[] PROGMEM = "File found"; const char nameOfFileFound[] PROGMEM = "File found";
const char reallocCalled[] PROGMEM = "Reallocated memory."; const char reallocCalled[] PROGMEM = "Reallocated memory.";
const char unableToAllocateMemForLinearPlaylist[] PROGMEM = "Unable to allocate memory for linear playlist!"; 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 newLoudnessReceivedQueue[] PROGMEM = "New volume received via queue";
const char newCntrlReceivedQueue[] PROGMEM = "Control-command received via queue"; const char newCntrlReceivedQueue[] PROGMEM = "Control-command received via queue";
const char newPlaylistReceived[] PROGMEM = "New playlist received"; 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 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 modeAllTrackRandomLoop[] PROGMEM = "Mode: all tracks (in random order) of directory as infinite loop";
const char modeWebstream[] PROGMEM = "Mode: webstream"; 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 webstreamNotAvailable[] PROGMEM = "Unable to access webstream as no wifi-connection is available!";
const char modeDoesNotExist[] PROGMEM = "Playmode does not exist!"; const char modeDoesNotExist[] PROGMEM = "Playmode does not exist!";
const char modeRepeatNone[] PROGMEM = "Repeatmode: no repeat"; const char modeRepeatNone[] PROGMEM = "Repeatmode: no repeat";

38
src/SdCard.cpp

@ -89,6 +89,7 @@ char **SdCard_ReturnPlaylist(const char *fileName, const uint32_t _playMode) {
char cacheFileNameBuf[275]; char cacheFileNameBuf[275];
bool readFromCacheFile = false; bool readFromCacheFile = false;
bool enablePlaylistCaching = false; bool enablePlaylistCaching = false;
bool enablePlaylistFromM3u = false;
// Look if file/folder requested really exists. If not => break. // Look if file/folder requested really exists. If not => break.
File fileOrDirectory = gFSystem.open(fileName); File fileOrDirectory = gFSystem.open(fileName);
@ -97,6 +98,7 @@ char **SdCard_ReturnPlaylist(const char *fileName, const uint32_t _playMode) {
return NULL; return NULL;
} }
// Create linear playlist of caching-file
#ifdef CACHED_PLAYLIST_ENABLE #ifdef CACHED_PLAYLIST_ENABLE
strncpy(cacheFileNameBuf, fileName, sizeof(cacheFileNameBuf)); strncpy(cacheFileNameBuf, fileName, sizeof(cacheFileNameBuf));
strcat(cacheFileNameBuf, "/"); strcat(cacheFileNameBuf, "/");
@ -140,6 +142,36 @@ char **SdCard_ReturnPlaylist(const char *fileName, const uint32_t _playMode) {
} }
#endif #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()); snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *) FPSTR(freeMemory), ESP.getFreeHeap());
Log_Println(Log_Buffer, LOGLEVEL_DEBUG); 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); 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); Log_Println((char *) FPSTR(playlistGenModeUncached), LOGLEVEL_NOTICE);
// File-mode // File-mode
if (!fileOrDirectory.isDirectory()) { if (!fileOrDirectory.isDirectory()) {
@ -173,7 +205,7 @@ char **SdCard_ReturnPlaylist(const char *fileName, const uint32_t _playMode) {
return ++files; return ++files;
} }
// Directory-mode
// Directory-mode (linear-playlist)
uint16_t allocCount = 1; uint16_t allocCount = 1;
uint16_t allocSize = 4096; uint16_t allocSize = 4096;
if (psramInit()) { if (psramInit()) {

2
src/Wlan.cpp

@ -164,7 +164,7 @@ void writeWifiStatusToNVS(bool wifiStatus) {
if (!wifiStatus) { if (!wifiStatus) {
if (gPrefsSettings.putUInt("enableWifi", 0)) { // disable if (gPrefsSettings.putUInt("enableWifi", 0)) { // disable
Log_Println((char *) FPSTR(wifiDisabledAfterRestart), LOGLEVEL_NOTICE); Log_Println((char *) FPSTR(wifiDisabledAfterRestart), LOGLEVEL_NOTICE);
if (gPlayProperties.playMode == WEBSTREAM) {
if (gPlayProperties.playMode == WEBSTREAM || gPlayProperties.playMode == WEBSTREAMS_LOCAL_M3U) {
AudioPlayer_TrackControlToQueueSender(STOP); AudioPlayer_TrackControlToQueueSender(STOP);
} }
delay(300); delay(300);

1
src/logmessages.h

@ -68,6 +68,7 @@ extern const char modeAllTrackRandom[];
extern const char modeAllTrackAlphSortedLoop[]; extern const char modeAllTrackAlphSortedLoop[];
extern const char modeAllTrackRandomLoop[]; extern const char modeAllTrackRandomLoop[];
extern const char modeWebstream[]; extern const char modeWebstream[];
extern const char modeWebstreamM3u[];
extern const char webstreamNotAvailable[]; extern const char webstreamNotAvailable[];
extern const char modeDoesNotExist[]; extern const char modeDoesNotExist[];
extern const char modeRepeatNone[]; extern const char modeRepeatNone[];

2
src/revision.h

@ -1,4 +1,4 @@
#ifndef __REVISION_H__ #ifndef __REVISION_H__
#define __REVISION_H__ #define __REVISION_H__
constexpr const char softwareRevision[] PROGMEM = "Software-revision: 20210716-2";
constexpr const char softwareRevision[] PROGMEM = "Software-revision: 20210720-1";
#endif #endif

1
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_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 ALL_TRACKS_OF_DIR_RANDOM_LOOP 9 // Play all files of a directory (randomized) in infinite-loop
#define WEBSTREAM 8 // Play webradio-stream #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 #define BUSY 10 // Used if playlist is created

Loading…
Cancel
Save