folder play / next song fixes

This commit is contained in:
Stefan Ostermann 2025-12-13 23:18:48 +01:00
parent 69bc259a6c
commit 6ecb54e5ee
4 changed files with 83 additions and 58 deletions

View File

@ -572,6 +572,10 @@ DirectoryNode *DirectoryNode::findPreviousMP3Globally(const String &currentGloba
void DirectoryNode::buildFlatMP3List(std::vector<std::pair<DirectoryNode *, int>> &allMP3s) void DirectoryNode::buildFlatMP3List(std::vector<std::pair<DirectoryNode *, int>> &allMP3s)
{ {
#ifdef DEBUG
Serial.println("Building flat mp3 list for folder");
#endif
// Pre-reserve to reduce reallocations // Pre-reserve to reduce reallocations
allMP3s.reserve(allMP3s.size() + mp3Files.size()); allMP3s.reserve(allMP3s.size() + mp3Files.size());
// Add all MP3 files from this directory // Add all MP3 files from this directory
@ -594,64 +598,56 @@ size_t DirectoryNode::getNumOfFiles() const
DirectoryNode *DirectoryNode::advanceToNextMP3(const String &currentGlobal) DirectoryNode *DirectoryNode::advanceToNextMP3(const String &currentGlobal)
{ {
bool useFirst = false;
Serial.println(currentGlobal.c_str()); Serial.println(currentGlobal.c_str());
if (!currentGlobal.isEmpty())
// Build a flat list of all MP3 files in order to correctly find the next one across directories
std::vector<std::pair<DirectoryNode *, int>> allMP3s;
buildFlatMP3List(allMP3s);
if (allMP3s.empty())
{ {
for (size_t i = 0; i < mp3Files.size(); i++) Serial.println(F("advanceToNextMP3: No MP3s found in tree"));
{ currentPlaying = "";
buildFullPath(mp3Files[i], buffer, buffer_size);
if (currentGlobal == String(buffer))
{
// Found the current playing MP3 file
if (i < mp3Files.size() - 1)
{
// Advance to the next MP3 file in the same directory
setCurrentPlaying(mp3Files[i + 1]);
return this; return this;
} }
useFirst = true;
// Reached the end of the MP3 files in the directory int currentIndex = -1;
if (!currentGlobal.isEmpty())
{
for (size_t i = 0; i < allMP3s.size(); i++)
{
DirectoryNode *node = allMP3s[i].first;
int fileIndex = allMP3s[i].second;
node->buildFullPath(node->mp3Files[fileIndex], buffer, buffer_size);
if (comparePathWithString(buffer, currentGlobal))
{
currentIndex = (int)i;
break; break;
} }
} }
} }
// We are either not playing, or we've exhausted all the MP3 files in this directory. // If current song found and not the last one, move to next
// Therefore, we need to recursively look in our subdirectories. if (currentIndex >= 0 && currentIndex < (int)allMP3s.size() - 1)
for (auto subdir : subdirectories)
{ {
DirectoryNode *nextNode = allMP3s[currentIndex + 1].first;
int nextFileIndex = allMP3s[currentIndex + 1].second;
if (useFirst && subdir->mp3Files.size() > 0) nextNode->setCurrentPlaying(nextNode->mp3Files[nextFileIndex]);
{ return nextNode;
subdir->setCurrentPlaying(subdir->mp3Files[0]);
return subdir;
} }
// Have each subdirectory advance its song // If not playing anything (start), play first
for (size_t i = 0; i < subdir->mp3Files.size(); i++) if (currentIndex == -1 && currentGlobal.isEmpty())
{ {
subdir->buildFullPath(subdir->mp3Files[i], buffer, buffer_size); DirectoryNode *nextNode = allMP3s[0].first;
if (currentGlobal == String(buffer)) int nextFileIndex = allMP3s[0].second;
{ nextNode->setCurrentPlaying(nextNode->mp3Files[nextFileIndex]);
// Found the current playing MP3 file return nextNode;
if (i < subdir->mp3Files.size() - 1)
{
// Advance to the next MP3 file in the same directory
subdir->setCurrentPlaying(subdir->mp3Files[i + 1]);
return subdir;
}
else
{
useFirst = true;
}
// Reached the end of the MP3 files in the directory
break;
}
}
} }
// If we get here, there were no MP3 files or subdirectories left to check // If we get here, either we are at the last song, or the current song was not found
currentPlaying = ""; currentPlaying = "";
Serial.println(F("no more nodes found")); Serial.println(F("no more nodes found"));
return this; return this;

View File

@ -492,16 +492,18 @@ void playSongByRFID(const String &id)
else else
{ {
// Find index of current playing file within the folder list // Find index of current playing file within the folder list
uint16_t targetId = currentNode->getCurrentPlayingId();
for (size_t i = 0; i < folderFlatList.size(); i++) for (size_t i = 0; i < folderFlatList.size(); i++)
{ {
DirectoryNode *node = folderFlatList[i].first; DirectoryNode *node = folderFlatList[i].first;
int fileIdx = folderFlatList[i].second; int fileIdx = folderFlatList[i].second;
if (node->getCurrentPlaying() == mp3File) if (node == currentNode && node->getFileIdAt(fileIdx) == targetId)
{ {
folderFlatIndex = (int)i; folderFlatIndex = (int)i;
break; break;
} }
} }
Serial.print(F("RFID Folder Index: ")); Serial.println(folderFlatIndex);
} }
// Compute root path for safety checks (path up to last '/') // Compute root path for safety checks (path up to last '/')
@ -1124,10 +1126,26 @@ void previous()
void audio_eof_mp3(const char *info) void audio_eof_mp3(const char *info)
{ {
Serial.println(F("audio file ended.")); Serial.println(F("audio file ended."));
#ifdef DEBUG
if (folderModeActive)
Serial.println("folder mode active");
#endif
if (prepareSleepMode) if (prepareSleepMode)
return; return;
// If folder-mode is active, advance only inside that folder. // If folder-mode is active, advance only inside that folder.
if (folderModeActive)
{
if (folderRootNode == nullptr) {
#ifdef DEBUG
Serial.println(F("DEBUG: folderRootNode was null, fixing..."));
#endif
folderRootNode = currentNode;
}
}
if (folderModeActive && folderRootNode != nullptr) if (folderModeActive && folderRootNode != nullptr)
{ {
// Ensure flat list is built // Ensure flat list is built
@ -1135,17 +1153,25 @@ void audio_eof_mp3(const char *info)
folderRootNode->buildFlatMP3List(folderFlatList); folderRootNode->buildFlatMP3List(folderFlatList);
// Try to find current index if not set // Try to find current index if not set
if (folderFlatIndex < 0) if (folderFlatIndex < 0 && currentNode != nullptr)
{ {
String cur = currentNode ? currentNode->getCurrentPlaying() : String(); uint16_t currentId = currentNode->getCurrentPlayingId();
Serial.print(F("EOF: Searching for ID ")); Serial.println(currentId);
for (size_t i = 0; i < folderFlatList.size(); i++) for (size_t i = 0; i < folderFlatList.size(); i++)
{ {
if (folderFlatList[i].first->getCurrentPlaying() == cur) if (folderFlatList[i].first == currentNode &&
folderFlatList[i].first->getFileIdAt(folderFlatList[i].second) == currentId)
{ {
folderFlatIndex = (int)i; folderFlatIndex = (int)i;
#ifdef DEBUG
Serial.print(F("EOF: Found at ")); Serial.println(folderFlatIndex);
#endif
break; break;
} }
} }
if (folderFlatIndex < 0) {
Serial.println(F("EOF: ID not found in flat list"));
}
} }
if (folderFlatIndex >= 0 && folderFlatIndex < (int)folderFlatList.size() - 1) if (folderFlatIndex >= 0 && folderFlatIndex < (int)folderFlatList.size() - 1)
@ -1270,9 +1296,10 @@ static void serveStaticFile(AsyncWebServerRequest *request,
if (ctx->f) ctx->f.close(); if (ctx->f) ctx->f.close();
sd_lock_release(); sd_lock_release();
delete ctx; delete ctx;
webreq_exit();
}); });
request->send(resp); request->send(resp);
webreq_exit();
} }
else else
{ {
@ -1535,8 +1562,7 @@ void setup()
AsyncWiFiManager wifiManager(&server, &dns); AsyncWiFiManager wifiManager(&server, &dns);
// Memory optimizations for WiFiManager wifiManager.setDebugOutput(true);
wifiManager.setDebugOutput(false); // Disable debug strings
// Reduce timeouts to free memory faster // Reduce timeouts to free memory faster
wifiManager.setTimeout(180); // Reduced from 180 wifiManager.setTimeout(180); // Reduced from 180
@ -1548,8 +1574,6 @@ void setup()
#endif #endif
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable brownout detector WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable brownout detector
// wifiManager.resetSettings();
if (wifiManager.autoConnect("HannaBox")) if (wifiManager.autoConnect("HannaBox"))
{ {
Serial.printf("Heap before init_webserver: %u\n", (unsigned)xPortGetFreeHeapSize()); Serial.printf("Heap before init_webserver: %u\n", (unsigned)xPortGetFreeHeapSize());
@ -1585,6 +1609,7 @@ void id_song_action(AsyncWebServerRequest *request)
webreq_enter(); webreq_enter();
request->onDisconnect([](){ webreq_exit(); }); request->onDisconnect([](){ webreq_exit(); });
int params = request->params(); int params = request->params();
folderModeActive = true;
for (int i = 0; i < params; i++) for (int i = 0; i < params; i++)
{ {
const AsyncWebParameter *p = request->getParam(i); const AsyncWebParameter *p = request->getParam(i);
@ -1593,6 +1618,10 @@ void id_song_action(AsyncWebServerRequest *request)
playSongById(atoi(p->value().c_str())); playSongById(atoi(p->value().c_str()));
} }
} }
if (currentNode != nullptr)
{
folderRootNode = currentNode;
}
lastInteraction = millis(); lastInteraction = millis();
request->send_P(200, txt_plain, PSTR("ok")); request->send_P(200, txt_plain, PSTR("ok"));
} }

View File

@ -212,7 +212,7 @@ std::map<String, MappingEntry> rfid_map;
// Folder-play helper: when a mapping requests "folder only" playback we keep // Folder-play helper: when a mapping requests "folder only" playback we keep
// track of the folder root node so EOF handling can advance only inside that folder. // track of the folder root node so EOF handling can advance only inside that folder.
bool folderModeActive = false; bool folderModeActive = true;
bool pendingSeek = false; bool pendingSeek = false;
uint32_t pendingSeekSeconds = 0; uint32_t pendingSeekSeconds = 0;

View File

@ -121,8 +121,8 @@
<div> <div>
<label for="mode">Mode:</label> <label for="mode">Mode:</label>
<select id="mode" name="mode"> <select id="mode" name="mode">
<option value="s">Single (play selected song / file)</option>
<option value="f">Folder (play selected folder, then stop)</option> <option value="f">Folder (play selected folder, then stop)</option>
<option value="s">Single (play selected song / file)</option>
<option value="r">Random (randomize order in folder, then stop)</option> <option value="r">Random (randomize order in folder, then stop)</option>
<option value="c">Continuous (continuous playback / loop folder)</option> <option value="c">Continuous (continuous playback / loop folder)</option>
</select> </select>