diff --git a/platformio.ini b/platformio.ini index 2879803..dd0518c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,5 +18,6 @@ lib_deps = me-no-dev/ESP Async WebServer@^1.2.3 alanswx/ESPAsyncWiFiManager@^0.31 miguelbalboa/MFRC522@^1.4.10 + bblanchon/ArduinoJson@^6.21.3 monitor_speed = 115200 board_build.partitions = huge_app.csv diff --git a/src/DirectoryNode.cpp b/src/DirectoryNode.cpp index 5a1e312..f206154 100644 --- a/src/DirectoryNode.cpp +++ b/src/DirectoryNode.cpp @@ -46,6 +46,14 @@ void DirectoryNode::addMP3File(const String &mp3File) mp3Files.push_back(mp3File); } +void DirectoryNode::setSecondsPlayed(const uint32_t seconds) { + secondsPlayed = seconds; +} + +uint32_t DirectoryNode::getSecondsPlayed() { + return secondsPlayed; +} + void DirectoryNode::buildDirectoryTree(const char *currentPath) { File rootDir = SD.open(currentPath); diff --git a/src/DirectoryNode.h b/src/DirectoryNode.h index f73c685..0716c4e 100644 --- a/src/DirectoryNode.h +++ b/src/DirectoryNode.h @@ -10,6 +10,7 @@ private: std::vector subdirectories; std::vector mp3Files; const String* currentPlaying; + uint16_t secondsPlayed = 0; public: DirectoryNode(const String& nodeName); @@ -22,6 +23,9 @@ public: void setCurrentPlaying(const String* mp3File); const String* getCurrentPlaying() const; + void setSecondsPlayed(const uint32_t seconds); + uint32_t getSecondsPlayed(); + void addSubdirectory(DirectoryNode* subdirectory); void addMP3File(const String& mp3File); void buildDirectoryTree(const char* currentPath); diff --git a/src/WebContent.h b/src/WebContent.h index 4870d15..be4cc42 100644 --- a/src/WebContent.h +++ b/src/WebContent.h @@ -9,13 +9,27 @@ const char index_html[] PROGMEM = R"rawliteral(

HannaBox

- %PLAYING%

+

- - -
+ +
+ + +
+
+ + +
-   + + +
+ +  
@@ -46,9 +60,17 @@ const char index_html[] PROGMEM = R"rawliteral( }); }); - function toggleCheckbox(x) { - var xhr = new XMLHttpRequest(); - xhr.open("GET", "/" + x, true); + function simpleGetCall(endpoint) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", "/" + endpoint, true); + xhr.send(); + } + + function postValue(endpoint,value) { + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/" + endpoint, true); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + xhr.send("value="+encodeURIComponent(value)); xhr.send(); } @@ -57,7 +79,7 @@ const char index_html[] PROGMEM = R"rawliteral( xhr.onreadystatechange = function() { if (xhr.readyState === 4) { - displayState(xhr.response); + displayState(JSON.parse(xhr.response)); } } @@ -67,14 +89,23 @@ const char index_html[] PROGMEM = R"rawliteral( } function displayState(state) { - document.getElementById("state").innerHTML = state; + document.getElementById("state").innerHTML = state['title']; + document.getElementById("progressLabel").innerHTML = state['time']; var elements = document.getElementsByClassName('play-button'); - var btn = elements[0]; - if (state=='-') { - btn.classList.remove('paused'); - } else { - btn.classList.add('paused'); - } + var btn = elements[0]; + + if (state['playing']) { + btn.classList.add('paused'); + } else { + btn.classList.remove('paused'); + } + var progress = document.getElementById('progressSlider'); + progress.value = state['time']; + progress.max = state['length']; + + var volume = document.getElementById('volumeSlider'); + volume.value = state['volume']; + } function playNamedSong(song) { diff --git a/src/globals.h b/src/globals.h index 07e445b..73b74c7 100644 --- a/src/globals.h +++ b/src/globals.h @@ -11,6 +11,10 @@ void loop2(void* parameter); void named_song_action(AsyncWebServerRequest *request); +void progress_action(AsyncWebServerRequest *request); + +void volume_action(AsyncWebServerRequest *request); + boolean buttonPressed(const uint8_t pin); /** diff --git a/src/main.cpp b/src/main.cpp index 525a367..1f5f627 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,8 @@ #include //Local WebServer used to serve the configuration portal #include //https://github.com/tzapu/WiFiManager WiFi Configuration Magic +#include + #include "Audio.h" @@ -131,6 +133,11 @@ void playNextMp3() { currentNode = rootNode.advanceToNextMP3(currentNode->getCurrentPlaying()); } + + if (currentNode!=NULL) { + currentNode->setSecondsPlayed(0); + } + Serial.print("Now advancing to "); String mp3File = currentNode->getCurrentPlayingFilePath(); Serial.println(mp3File.c_str()); @@ -163,35 +170,22 @@ void unmute() String getState() { - String state = String(); + DynamicJsonDocument jsonState(1024); + jsonState["playing"] = audio.isRunning(); + if (currentNode!=NULL) + jsonState["title"] = *currentNode->getCurrentPlaying(); + + jsonState["time"] = audio.getAudioCurrentTime(); + jsonState["volume"] = audio.getVolume(); + jsonState["length"] = audio.getAudioFileDuration(); + String output; + serializeJson(jsonState,output); + return output; - - if (audio.isRunning()) - { - state += "Playing "; - state += audio.getAudioCurrentTime(); - state += " / "; - state += audio.getAudioFileDuration(); - state += " "; - } - else - { - return "-"; - } - if (currentNode) - { - state += currentNode->getName(); - state += " "; - if (currentNode->getCurrentPlaying()) - state += *currentNode->getCurrentPlaying(); - } - return state; } String processor(const String &var) { - if (var == "PLAYING" && currentNode) - return getState(); if (var == "DIRECTORY") { return rootNode.getDirectoryStructureHTML(); @@ -203,8 +197,11 @@ void stop() { if (audio.isRunning()) { - Serial.println("stopping audio."); + Serial.println("stopping audio."); audio.stopSong(); + if (currentNode!=NULL) { + currentNode->setSecondsPlayed(0); + } } } @@ -219,6 +216,16 @@ void start() playNextMp3(); } +void togglePlayPause() +{ + if (currentNode != NULL) + { + audio.pauseResume(); + } else { + playNextMp3(); + } +} + void next() { playNextMp3(); @@ -311,7 +318,7 @@ void setup() server.on("/state", HTTP_GET, [](AsyncWebServerRequest *request) { String state = getState(); - request->send(200, "text/plain", state.c_str()); }); + request->send(200, "application/json", state.c_str()); }); server.on("/start", HTTP_GET, [](AsyncWebServerRequest *request) { @@ -319,6 +326,13 @@ void setup() request->send(200, "text/plain", "start"); start(); }); + + server.on("/toggleplaypause", HTTP_GET, [](AsyncWebServerRequest *request) + { + + request->send(200, "text/plain", "toggleplaypause"); + togglePlayPause(); }); + server.on("/stop", HTTP_GET, [](AsyncWebServerRequest *request) { @@ -333,6 +347,10 @@ void setup() server.on("/playnamed", HTTP_POST, named_song_action); + server.on("/progress", HTTP_POST, progress_action); + + server.on("/volume", HTTP_POST, volume_action); + server.begin(); xTaskCreatePinnedToCore( @@ -358,7 +376,35 @@ void named_song_action(AsyncWebServerRequest *request) { playSongByName(p->value()); } } - request->send_P(200, "text/html", index_html, processor); + request->send_P(200, "text/plain", "ok"); +} + +void progress_action(AsyncWebServerRequest *request) { + Serial.println("progress!"); + + int params = request->params(); + for (int i = 0; i < params; i++) { + AsyncWebParameter* p = request->getParam(i); + Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); + if (p->name()=="value") { + audio.setAudioPlayPosition(atoi(p->value().c_str())); + } + } + request->send_P(200, "text/plain", "ok"); +} + +void volume_action(AsyncWebServerRequest *request) { + Serial.println("volume!"); + + int params = request->params(); + for (int i = 0; i < params; i++) { + AsyncWebParameter* p = request->getParam(i); + Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str()); + if (p->name()=="value") { + audio.setVolume(atoi(p->value().c_str())); + } + } + request->send_P(200, "text/plain", "ok"); } void loop() @@ -372,6 +418,10 @@ void loop() deactivateRFID(); activateSD(); audio.loop(); + if (currentNode!=NULL) { + currentNode->setSecondsPlayed(audio.getAudioCurrentTime()); + } + deactivateSD(); activateRFID(); } else if (asyncStart) {