diff --git a/src/DirectoryNode.cpp b/src/DirectoryNode.cpp
index 730e3fa..2e3180f 100644
--- a/src/DirectoryNode.cpp
+++ b/src/DirectoryNode.cpp
@@ -578,6 +578,44 @@ String DirectoryNode::getDirectoryStructureHTML() const {
return html;
}
+void DirectoryNode::streamDirectoryHTML(Print &out) const {
+ if (name == "/") {
+ out.println(F("
"));
+ delay(0); // yield to WiFi/other tasks
+ }
+
+ if (name != "/") {
+ out.print(F("- "));
+ out.print(name);
+ out.println(F("
"));
+ delay(0); // yield periodically while streaming
+ }
+
+ for (size_t i = 0; i < mp3Files.size(); i++) {
+ out.print(F("- "));
+ out.print(mp3Files[i]);
+ out.println(F("
"));
+ if ((i & 0x0F) == 0) { // yield every ~16 items
+ delay(0);
+ }
+ }
+
+ for (DirectoryNode* child : subdirectories) {
+ delay(0); // yield before descending
+ child->streamDirectoryHTML(out);
+ delay(0); // and after returning
+ }
+
+ if (name == "/") {
+ out.println(F("
"));
+ delay(0);
+ }
+}
+
// NEW: Calculate exact required size first
size_t DirectoryNode::calculateHTMLSize() const {
size_t size = 0;
diff --git a/src/DirectoryNode.h b/src/DirectoryNode.h
index 6ef825e..6ba20be 100644
--- a/src/DirectoryNode.h
+++ b/src/DirectoryNode.h
@@ -1,5 +1,6 @@
#ifndef DIRECTORYNODE_H_
#define DIRECTORYNODE_H_
+class Print;
#include
#include
@@ -55,6 +56,7 @@ public:
DirectoryNode* advanceToMP3(const uint16_t id);
void advanceToFirstMP3InThisNode();
String getDirectoryStructureHTML() const;
+ void streamDirectoryHTML(Print &out) const;
size_t calculateHTMLSize() const;
void appendIndentation(String& html, int level) const;
DirectoryNode* findFirstDirectoryWithMP3s();
diff --git a/src/main.cpp b/src/main.cpp
index 0caed3d..4832fde 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1217,15 +1217,14 @@ server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{
webreq_enter();
request->onDisconnect([](){ webreq_exit(); });
- // Stream the response to avoid lifetime issues with temporary Strings and to prevent wrong Content-Length
+ // Stream the response directly from the directory tree to avoid large temporary Strings
AsyncResponseStream* stream = request->beginResponseStream("text/html; charset=UTF-8");
stream->addHeader("Cache-Control", "no-store");
stream->addHeader("Connection", "close");
- // Build HTML under lock, then print into stream
+ // Generate HTML directly into the stream under lock
dir_lock_acquire();
- String html = rootNode.getDirectoryStructureHTML();
+ rootNode.streamDirectoryHTML(*stream);
dir_lock_release();
- stream->print(html);
request->send(stream);
});
@@ -1508,12 +1507,8 @@ void loop()
if (webreq_cnt > 0 && webrequest_blockings > MAX_WEBREQUEST_BLOCKINGS) {
Serial.println("excessive webrequest blocking - suppress reset");
// Avoid resetting server mid-response to prevent mixing headers/body or truncation
- server.reset();
- init_webserver();
-
webreq_cnt = 0;
webrequest_blockings = 0;
- server.begin();
}
if (audio.isRunning())
diff --git a/web/script.js b/web/script.js
index 2931b22..794d6b5 100644
--- a/web/script.js
+++ b/web/script.js
@@ -58,9 +58,9 @@ setInterval(updateProgress, 500); // Update progress every second
if ((this.__url || '').indexOf('/upload') !== -1) {
timeoutMs = 600000; // 10 minutes for uploads
} else if (this.__method === 'GET') {
- timeoutMs = 3500;
- } else {
timeoutMs = 6000;
+ } else {
+ timeoutMs = 8000;
}
if (isIdempotentGET && inflightKeys.has(key)) {