diff --git a/html/management_DE.html b/html/management_DE.html index c39f820d..7eb8acac 100644 --- a/html/management_DE.html +++ b/html/management_DE.html @@ -824,7 +824,7 @@ if(data.dir) { type = "folder"; - } else if ((/\.(mp3|MP3|ogg|wav|WAV|OGG|wma|WMA|acc|ACC|flac|FLAC)$/i).test(data.name)) { + } else if ((/\.(mp3|MP3|ogg|wav|WAV|OGG|wma|WMA|acc|ACC|m4a|M4A|flac|FLAC)$/i).test(data.name)) { type = "audio"; } else { type = "file"; @@ -904,7 +904,14 @@ items.play = { label: "Abspielen", action: function(x) { - var playMode = node.data.directory?"5":"1"; + var playMode = 1; + if (node.data.directory) { + playMode = 5; + } else { + if ((/\.(m3u|M3U)$/i).test(node.data.path)) { + playMode = 11; + } + } postData("/exploreraudio?path=" + node.data.path + "&playmode=" + playMode); } }; diff --git a/src/AudioPlayer.cpp b/src/AudioPlayer.cpp index f336b653..49681ea0 100644 --- a/src/AudioPlayer.cpp +++ b/src/AudioPlayer.cpp @@ -101,7 +101,7 @@ void AudioPlayer_Init(void) { AudioPlayer_SetupVolume(); // delete cover image - gFSystem.remove("/.cover"); + gPlayProperties.coverFileName = NULL; if (System_GetOperationMode() == OPMODE_NORMAL) { // Don't start audio-task in BT-mode! xTaskCreatePinnedToCore( AudioPlayer_Task, /* Function to implement the task */ @@ -363,10 +363,10 @@ void AudioPlayer_Task(void *parameter) { if (gPlayProperties.title) { free(gPlayProperties.title); gPlayProperties.title = NULL; - } + } Web_SendWebsocketData(0, 30); // delete cover image - gFSystem.remove("/.cover"); + gPlayProperties.coverFileName = NULL; Web_SendWebsocketData(0, 40); continue; @@ -458,9 +458,9 @@ void AudioPlayer_Task(void *parameter) { if (gPlayProperties.title) { free(gPlayProperties.title); gPlayProperties.title = NULL; - } + } // delete cover image - gFSystem.remove("/.cover"); + gPlayProperties.coverFileName = NULL; Web_SendWebsocketData(0, 40); audioReturnCode = audio->connecttoFS(gFSystem, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber)); // consider track as finished, when audio lib call was not successful @@ -598,9 +598,9 @@ void AudioPlayer_Task(void *parameter) { if (gPlayProperties.title) { free(gPlayProperties.title); gPlayProperties.title = NULL; - } + } // delete cover image - gFSystem.remove("/.cover"); + gPlayProperties.coverFileName = NULL; Web_SendWebsocketData(0, 40); audioReturnCode = audio->connecttohost(*(gPlayProperties.playlist + gPlayProperties.currentTrackNumber)); gPlayProperties.playlistFinished = false; @@ -617,9 +617,9 @@ void AudioPlayer_Task(void *parameter) { if (gPlayProperties.title) { free(gPlayProperties.title); gPlayProperties.title = NULL; - } + } // delete cover image - gFSystem.remove("/.cover"); + gPlayProperties.coverFileName = NULL; Web_SendWebsocketData(0, 40); audioReturnCode = audio->connecttoFS(gFSystem, *(gPlayProperties.playlist + gPlayProperties.currentTrackNumber)); // consider track as finished, when audio lib call was not successful @@ -1104,11 +1104,11 @@ void audio_id3data(const char *info) { //id3 metadata // copy title if (!gPlayProperties.title) { gPlayProperties.title = (char *) x_malloc(sizeof(char) * 255); - } + } strncpy(gPlayProperties.title, info + 6, 255); // notify web ui Web_SendWebsocketData(0, 30); - } + } } void audio_eof_mp3(const char *info) { //end of file @@ -1170,19 +1170,11 @@ void audio_lasthost(const char *info) { //stream URL played } // id3 tag: save cover image -void audio_id3image(File& file, const size_t pos, const size_t size) { - - // save raw image data to file "/.cover" - snprintf(Log_Buffer, Log_BufferLength, "save album cover image: \"%s\"", (char *) file.name()); - Log_Println(Log_Buffer, LOGLEVEL_INFO); - file.seek(pos); - File coverFile = gFSystem.open("/.cover", FILE_WRITE); - uint8_t buf[255]; - while(file.position() < (pos + size)) { - int bytesRead = file.read(buf, sizeof(buf)); - coverFile.write( buf, bytesRead); - } - coverFile.close(); +void audio_id3image(File& file, const size_t pos, const size_t size) { + // save cover image file and position/size for later use + gPlayProperties.coverFileName = (char *)(file.name()); + gPlayProperties.coverFilePos = pos; + gPlayProperties.coverFileSize = size; // websocket notify cover image has changed Web_SendWebsocketData(0, 40); } diff --git a/src/AudioPlayer.h b/src/AudioPlayer.h index c10a5e93..63c97a4c 100644 --- a/src/AudioPlayer.h +++ b/src/AudioPlayer.h @@ -25,6 +25,9 @@ typedef struct { // Bit field bool tellIpAddress: 1; // If true current IP-address is spoken bool currentSpeechActive: 1; // If speech-play is active bool lastSpeechActive: 1; // If speech-play was active + char *coverFileName; // current coverfile + size_t coverFilePos; // current cover file position + size_t coverFileSize; // current cover file size } playProps; extern playProps gPlayProperties; diff --git a/src/HTMLmanagement_DE.h b/src/HTMLmanagement_DE.h index b7454ff8..6d57c498 100644 --- a/src/HTMLmanagement_DE.h +++ b/src/HTMLmanagement_DE.h @@ -824,7 +824,7 @@ static const char management_HTML[] PROGMEM = " \ \ if(data.dir) {\ type = \"folder\";\ - } else if ((/\\.(mp3|MP3|ogg|wav|WAV|OGG|wma|WMA|acc|ACC|flac|FLAC)$/i).test(data.name)) {\ + } else if ((/\\.(mp3|MP3|ogg|wav|WAV|OGG|wma|WMA|acc|ACC|m4a|M4A|flac|FLAC)$/i).test(data.name)) {\ type = \"audio\";\ } else {\ type = \"file\";\ @@ -904,7 +904,14 @@ static const char management_HTML[] PROGMEM = " \ items.play = {\ label: \"Abspielen\",\ action: function(x) {\ - var playMode = node.data.directory?\"5\":\"1\";\ + var playMode = 1;\ + if (node.data.directory) {\ + playMode = 5; \ + } else {\ + if ((/\\.(m3u|M3U)$/i).test(node.data.path)) {\ + playMode = 11;\ + }\ + } \ postData(\"/exploreraudio?path=\" + node.data.path + \"&playmode=\" + playMode);\ }\ };\ diff --git a/src/Web.cpp b/src/Web.cpp index 7a239f05..c63f2b1d 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -577,8 +577,13 @@ void Web_SendWebsocketData(uint32_t client, uint8_t code) { entry["volume"] = AudioPlayer_GetCurrentVolume(); if (gPlayProperties.title) { // show current audio title from id3 metadata + if (gPlayProperties.numberOfTracks > 1) { + snprintf(Log_Buffer, Log_BufferLength, "(%u / %u): %s", gPlayProperties.currentTrackNumber+1, gPlayProperties.numberOfTracks, gPlayProperties.title); + } else { + snprintf(Log_Buffer, Log_BufferLength, "%s", gPlayProperties.title); + }; char utf8Buffer[200]; - convertAsciiToUtf8(gPlayProperties.title, utf8Buffer); + convertAsciiToUtf8(Log_Buffer, utf8Buffer); entry["name"] = utf8Buffer; } else if (gPlayProperties.playMode == NO_PLAYLIST) { // no active playlist @@ -1135,10 +1140,10 @@ bool Web_DumpNvsToSd(const char *_namespace, const char *_destFile) { // handle album cover image request static void handleCoverImageRequest(AsyncWebServerRequest *request) { - if (!gFSystem.exists("/.cover")) { + if (!gPlayProperties.coverFileName) { // empty image: // request->send(200, "image/svg+xml", ""); - if (gPlayProperties.isWebstream) { + if (gPlayProperties.playMode == WEBSTREAM) { // no cover -> send placeholder icon for webstream (fa-soundcloud) snprintf(Log_Buffer, Log_BufferLength, "no cover image for webstream"); Log_Println(Log_Buffer, LOGLEVEL_NOTICE); @@ -1152,10 +1157,9 @@ static void handleCoverImageRequest(AsyncWebServerRequest *request) { return; } - File coverFile = gFSystem.open("/.cover", FILE_READ); - - // skip 1 byte encoding - coverFile.seek(1); + File coverFile = gFSystem.open(gPlayProperties.coverFileName, FILE_READ); + // seek to start position, skip 1 byte encoding + coverFile.seek(gPlayProperties.coverFilePos + 1); // mime-type (null terminated) char mimeType[255]; for (uint8_t i = 0u; i < 255; i++) { @@ -1163,7 +1167,7 @@ static void handleCoverImageRequest(AsyncWebServerRequest *request) { if (uint8_t(mimeType[i]) == 0) break; } - snprintf(Log_Buffer, Log_BufferLength, "serve cover image (%s)", (char *) mimeType); + snprintf(Log_Buffer, Log_BufferLength, "serve cover image (%s): %s", (char *) mimeType, gPlayProperties.coverFileName); Log_Println(Log_Buffer, LOGLEVEL_NOTICE); // skip image type (1 Byte) @@ -1174,7 +1178,7 @@ static void handleCoverImageRequest(AsyncWebServerRequest *request) { break; } - int imageSize = coverFile.size() - coverFile.position(); + int imageSize = gPlayProperties.coverFileSize - coverFile.position(); AsyncWebServerResponse *response = request->beginResponse( mimeType, @@ -1187,6 +1191,8 @@ static void handleCoverImageRequest(AsyncWebServerRequest *request) { file.close(); Log_Println("cover image serving finished, close file", LOGLEVEL_DEBUG); } + // do not consume too much cpu time + vTaskDelay(portTICK_RATE_MS * 50u); return max(0, bytes); // return 0 even when no bytes were loaded } );