Compare commits
3 Commits
129fa8e624
...
54e3100867
| Author | SHA1 | Date |
|---|---|---|
|
|
54e3100867 | |
|
|
29316ffd13 | |
|
|
67ded19c93 |
|
|
@ -9,6 +9,7 @@ SD Card Root/
|
||||||
├── system/
|
├── system/
|
||||||
│ ├── style.css (Web interface CSS - copy from web/style.css)
|
│ ├── style.css (Web interface CSS - copy from web/style.css)
|
||||||
│ ├── script.js (Web interface JavaScript - copy from web/script.js)
|
│ ├── script.js (Web interface JavaScript - copy from web/script.js)
|
||||||
|
| ├── index.html (homepage - copy from web/index.html)
|
||||||
│ ├── start.mp3 (Startup sound)
|
│ ├── start.mp3 (Startup sound)
|
||||||
│ └── sleep.mp3 (Sleep sound)
|
│ └── sleep.mp3 (Sleep sound)
|
||||||
├── [your music folders]/
|
├── [your music folders]/
|
||||||
|
|
@ -25,6 +26,7 @@ SD Card Root/
|
||||||
3. **Copy the web interface files**:
|
3. **Copy the web interface files**:
|
||||||
- Copy the file `web/style.css` from this project to `system/style.css` on your SD card
|
- Copy the file `web/style.css` from this project to `system/style.css` on your SD card
|
||||||
- Copy the file `web/script.js` from this project to `system/script.js` on your SD card
|
- Copy the file `web/script.js` from this project to `system/script.js` on your SD card
|
||||||
|
- Copy the file `web/index.html` from this project to `system/index.html` on your SD card
|
||||||
- These files contain all the styling and functionality for the web interface
|
- These files contain all the styling and functionality for the web interface
|
||||||
|
|
||||||
4. **Add sound files** (optional):
|
4. **Add sound files** (optional):
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ cp web/style.css "$SD_MOUNT_POINT/system/"
|
||||||
echo "Copying script.js..."
|
echo "Copying script.js..."
|
||||||
cp web/script.js "$SD_MOUNT_POINT/system/"
|
cp web/script.js "$SD_MOUNT_POINT/system/"
|
||||||
|
|
||||||
|
echo "Copying index.html..."
|
||||||
|
cp web/index.html "$SD_MOUNT_POINT/system/"
|
||||||
|
|
||||||
echo "Files copied successfully!"
|
echo "Files copied successfully!"
|
||||||
echo "Your SD card system directory now contains:"
|
echo "Your SD card system directory now contains:"
|
||||||
ls -la "$SD_MOUNT_POINT/system/"
|
ls -la "$SD_MOUNT_POINT/system/"
|
||||||
|
|
|
||||||
|
|
@ -4694,18 +4694,9 @@ uint32_t Audio::getAudioCurrentTime() { // return current time in seconds
|
||||||
}
|
}
|
||||||
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
bool Audio::setAudioPlayPosition(uint16_t sec) {
|
bool Audio::setAudioPlayPosition(uint16_t sec) {
|
||||||
if(!m_f_psramFound) { log_w("PSRAM must be activated"); return false;} // guard
|
|
||||||
if(m_dataMode != AUDIO_LOCALFILE /* && m_streamType == ST_WEBFILE */) return false; // guard
|
|
||||||
if(!m_avr_bitrate) return false; // guard
|
|
||||||
//if(m_codec == CODEC_OPUS) return false; // not impl. yet
|
|
||||||
//if(m_codec == CODEC_VORBIS) return false; // not impl. yet
|
|
||||||
// Jump to an absolute position in time within an audio file
|
|
||||||
// e.g. setAudioPlayPosition(300) sets the pointer at pos 5 min
|
|
||||||
if(sec > getAudioFileDuration()) sec = getAudioFileDuration();
|
if(sec > getAudioFileDuration()) sec = getAudioFileDuration();
|
||||||
uint32_t filepos = m_audioDataStart + (m_avr_bitrate * sec / 8);
|
uint32_t filepos = m_audioDataStart + (m_avr_bitrate * sec / 8);
|
||||||
if(m_dataMode == AUDIO_LOCALFILE) return setFilePos(filepos);
|
return setFilePos(filepos);
|
||||||
// if(m_streamType == ST_WEBFILE) return httpRange(m_lastHost, filepos);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
void Audio::setVolumeSteps(uint8_t steps) {
|
void Audio::setVolumeSteps(uint8_t steps) {
|
||||||
|
|
@ -4737,23 +4728,15 @@ bool Audio::setTimeOffset(int sec) { // fast forward or rewind the current posit
|
||||||
}
|
}
|
||||||
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
bool Audio::setFilePos(uint32_t pos) {
|
bool Audio::setFilePos(uint32_t pos) {
|
||||||
if(!m_f_psramFound) { log_w("PSRAM must be activated"); return false;} // guard
|
if(m_codec == CODEC_OPUS) return false; // not impl. yet
|
||||||
if(m_dataMode != AUDIO_LOCALFILE /* && m_streamType == ST_WEBFILE */) return false; // guard
|
if(m_codec == CODEC_VORBIS) return false; // not impl. yet
|
||||||
if(m_dataMode == AUDIO_LOCALFILE && !audiofile) return false; // guard
|
if(!audiofile) return false;
|
||||||
if(m_codec == CODEC_AAC) return false; // guard, not impl. yet
|
if(pos < m_audioDataStart) pos = m_audioDataStart; // issue #96
|
||||||
|
if(pos > m_fileSize) pos = m_fileSize;
|
||||||
uint32_t startAB = m_audioDataStart; // audioblock begin
|
m_resumeFilePos = pos;
|
||||||
uint32_t endAB = m_audioDataStart + m_audioDataSize; // audioblock end
|
memset(m_outBuff, 0, 2048 * 2 * sizeof(int16_t));
|
||||||
if(pos < (int32_t)startAB) {pos = startAB;}
|
|
||||||
if(pos >= (int32_t)endAB) {pos = endAB;}
|
|
||||||
|
|
||||||
|
|
||||||
m_validSamples = 0;
|
m_validSamples = 0;
|
||||||
if(m_dataMode == AUDIO_LOCALFILE /* || m_streamType == ST_WEBFILE */) {
|
return true;
|
||||||
m_resumeFilePos = pos; // used in processLocalFile()
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
bool Audio::setSampleRate(uint32_t sampRate) {
|
bool Audio::setSampleRate(uint32_t sampRate) {
|
||||||
|
|
|
||||||
|
|
@ -262,39 +262,103 @@ DirectoryNode *DirectoryNode::advanceToMP3(const String *currentGlobal)
|
||||||
*/
|
*/
|
||||||
DirectoryNode *DirectoryNode::goToPreviousMP3(uint32_t thresholdSeconds)
|
DirectoryNode *DirectoryNode::goToPreviousMP3(uint32_t thresholdSeconds)
|
||||||
{
|
{
|
||||||
if (secondsPlayed > thresholdSeconds || currentPlaying == nullptr)
|
// Safety check for null pointer
|
||||||
|
if (currentPlaying == nullptr)
|
||||||
{
|
{
|
||||||
// Restart the current song if it's been playing for more than thresholdSeconds
|
Serial.println("goToPreviousMP3: currentPlaying is null");
|
||||||
// Or if there is no current song (at the start of the list)
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've been playing for more than threshold seconds, restart current song
|
||||||
|
if (secondsPlayed > thresholdSeconds)
|
||||||
|
{
|
||||||
|
Serial.println("goToPreviousMP3: Restarting current song (played > threshold)");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Find the previous song
|
|
||||||
for (size_t i = 0; i < mp3Files.size(); i++)
|
|
||||||
{
|
|
||||||
if (currentPlaying != nullptr && *currentPlaying == mp3Files[i] && i > 0)
|
|
||||||
{
|
|
||||||
// Move to the previous song
|
|
||||||
setCurrentPlaying(&mp3Files[i - 1]);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the first song in the directory or no song was playing, move to the previous directory, if any
|
// Find the current song index in this directory
|
||||||
for (auto subdir : subdirectories)
|
int currentIndex = -1;
|
||||||
|
for (size_t i = 0; i < mp3Files.size(); i++)
|
||||||
|
{
|
||||||
|
if (*currentPlaying == mp3Files[i])
|
||||||
{
|
{
|
||||||
DirectoryNode *previousNode = subdir->goToPreviousMP3(thresholdSeconds);
|
currentIndex = i;
|
||||||
if (previousNode != nullptr)
|
break;
|
||||||
{
|
|
||||||
return previousNode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// No previous song available
|
|
||||||
|
// If current song found and not the first song, move to previous
|
||||||
|
if (currentIndex > 0)
|
||||||
|
{
|
||||||
|
Serial.print("goToPreviousMP3: Moving to previous song in same directory: ");
|
||||||
|
Serial.println(mp3Files[currentIndex - 1]);
|
||||||
|
setCurrentPlaying(&mp3Files[currentIndex - 1]);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're at the first song or song not found in current directory,
|
||||||
|
// we need to find the previous song globally
|
||||||
|
Serial.println("goToPreviousMP3: At first song or song not found, looking for previous globally");
|
||||||
|
return nullptr; // Let the caller handle global previous logic
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryNode *DirectoryNode::findPreviousMP3Globally(const String *currentGlobal)
|
||||||
|
{
|
||||||
|
if (currentGlobal == nullptr)
|
||||||
|
{
|
||||||
|
Serial.println("findPreviousMP3Globally: currentGlobal is null");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a flat list of all MP3 files in order
|
||||||
|
std::vector<std::pair<DirectoryNode*, int>> allMP3s;
|
||||||
|
buildFlatMP3List(allMP3s);
|
||||||
|
|
||||||
|
// Find current song in the flat list
|
||||||
|
int currentGlobalIndex = -1;
|
||||||
|
for (size_t i = 0; i < allMP3s.size(); i++)
|
||||||
|
{
|
||||||
|
DirectoryNode* node = allMP3s[i].first;
|
||||||
|
int fileIndex = allMP3s[i].second;
|
||||||
|
if (node->mp3Files[fileIndex] == *currentGlobal)
|
||||||
|
{
|
||||||
|
currentGlobalIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If current song found and not the first globally, move to previous
|
||||||
|
if (currentGlobalIndex > 0)
|
||||||
|
{
|
||||||
|
DirectoryNode* prevNode = allMP3s[currentGlobalIndex - 1].first;
|
||||||
|
int prevFileIndex = allMP3s[currentGlobalIndex - 1].second;
|
||||||
|
|
||||||
|
Serial.print("findPreviousMP3Globally: Moving to previous song globally: ");
|
||||||
|
Serial.println(prevNode->mp3Files[prevFileIndex]);
|
||||||
|
|
||||||
|
prevNode->setCurrentPlaying(&prevNode->mp3Files[prevFileIndex]);
|
||||||
|
return prevNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("findPreviousMP3Globally: No previous song found globally");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DirectoryNode::buildFlatMP3List(std::vector<std::pair<DirectoryNode*, int>>& allMP3s)
|
||||||
|
{
|
||||||
|
// Add all MP3 files from this directory
|
||||||
|
for (size_t i = 0; i < mp3Files.size(); i++)
|
||||||
|
{
|
||||||
|
allMP3s.push_back(std::make_pair(this, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively add MP3 files from subdirectories
|
||||||
|
for (DirectoryNode* subdir : subdirectories)
|
||||||
|
{
|
||||||
|
subdir->buildFlatMP3List(allMP3s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DirectoryNode *DirectoryNode::advanceToNextMP3(const String *currentGlobal)
|
DirectoryNode *DirectoryNode::advanceToNextMP3(const String *currentGlobal)
|
||||||
{
|
{
|
||||||
bool useFirst = false;
|
bool useFirst = false;
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ public:
|
||||||
DirectoryNode* advanceToMP3(const String* currentGlobal);
|
DirectoryNode* advanceToMP3(const String* currentGlobal);
|
||||||
DirectoryNode* advanceToNextMP3(const String* currentGlobal);
|
DirectoryNode* advanceToNextMP3(const String* currentGlobal);
|
||||||
DirectoryNode* goToPreviousMP3(uint32_t thresholdSeconds = 3);
|
DirectoryNode* goToPreviousMP3(uint32_t thresholdSeconds = 3);
|
||||||
|
DirectoryNode* findPreviousMP3Globally(const String* currentGlobal);
|
||||||
|
void buildFlatMP3List(std::vector<std::pair<DirectoryNode*, int>>& allMP3s);
|
||||||
DirectoryNode* advanceToMP3(const uint16_t id);
|
DirectoryNode* advanceToMP3(const uint16_t id);
|
||||||
void advanceToFirstMP3InThisNode();
|
void advanceToFirstMP3InThisNode();
|
||||||
String getDirectoryStructureHTML() const;
|
String getDirectoryStructureHTML() const;
|
||||||
|
|
|
||||||
98
src/main.cpp
98
src/main.cpp
|
|
@ -592,33 +592,85 @@ void next()
|
||||||
|
|
||||||
void previous()
|
void previous()
|
||||||
{
|
{
|
||||||
if (currentNode != NULL) {
|
Serial.println("=== PREVIOUS FUNCTION CALLED ===");
|
||||||
|
|
||||||
|
if (currentNode == NULL) {
|
||||||
|
Serial.println("previous(): currentNode is null, cannot go to previous");
|
||||||
|
lastInteraction = millis();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate current state
|
||||||
|
const String* currentSong = currentNode->getCurrentPlaying();
|
||||||
|
if (currentSong == NULL) {
|
||||||
|
Serial.println("previous(): currentPlaying is null, cannot go to previous");
|
||||||
|
lastInteraction = millis();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print("previous(): Current song: ");
|
||||||
|
Serial.println(*currentSong);
|
||||||
|
|
||||||
|
// Use audio library's current time instead of tracked seconds for more accuracy
|
||||||
|
uint32_t currentAudioTime = audio.getAudioCurrentTime();
|
||||||
|
Serial.print("previous(): Current audio time: ");
|
||||||
|
Serial.print(currentAudioTime);
|
||||||
|
Serial.println(" seconds");
|
||||||
|
|
||||||
|
// Try to go to previous within current directory first
|
||||||
|
DirectoryNode* newNode = currentNode->goToPreviousMP3(2); // Use 2 second threshold
|
||||||
|
|
||||||
|
if (newNode != NULL) {
|
||||||
|
// Check if we're restarting the same song or moving to a different song
|
||||||
|
const String* newSong = newNode->getCurrentPlaying();
|
||||||
|
|
||||||
const String* curr = currentNode->getCurrentPlaying();
|
if (newSong != NULL && currentSong == newSong && currentAudioTime > 2) {
|
||||||
DirectoryNode* newNode = currentNode->goToPreviousMP3();
|
// Restart current song if it's been playing for more than 2 seconds
|
||||||
|
Serial.println("previous(): Restarting current song");
|
||||||
|
audio.setAudioPlayPosition(0);
|
||||||
|
currentNode->setSecondsPlayed(0);
|
||||||
|
} else if (newSong != NULL && currentSong != newSong) {
|
||||||
|
// Move to previous song in same directory
|
||||||
|
Serial.print("previous(): Moving to previous song in directory: ");
|
||||||
|
Serial.println(*newSong);
|
||||||
|
currentNode = newNode;
|
||||||
|
stop();
|
||||||
|
deactivateRFID();
|
||||||
|
activateSD();
|
||||||
|
playFile(currentNode->getCurrentPlayingFilePath().c_str());
|
||||||
|
activateRFID();
|
||||||
|
deactivateSD();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Need to find previous song globally (across directories)
|
||||||
|
Serial.println("previous(): Looking for previous song globally");
|
||||||
|
DirectoryNode* globalPrevNode = rootNode.findPreviousMP3Globally(currentSong);
|
||||||
|
|
||||||
if (newNode != NULL) {
|
if (globalPrevNode != NULL) {
|
||||||
if (curr == newNode->getCurrentPlaying()) {
|
const String* globalPrevSong = globalPrevNode->getCurrentPlaying();
|
||||||
// reset to 0 seconds playtime:
|
if (globalPrevSong != NULL) {
|
||||||
audio.setAudioPlayPosition(0);
|
Serial.print("previous(): Found previous song globally: ");
|
||||||
|
Serial.println(*globalPrevSong);
|
||||||
|
currentNode = globalPrevNode;
|
||||||
|
stop();
|
||||||
|
deactivateRFID();
|
||||||
|
activateSD();
|
||||||
|
playFile(currentNode->getCurrentPlayingFilePath().c_str());
|
||||||
|
activateRFID();
|
||||||
|
deactivateSD();
|
||||||
} else {
|
} else {
|
||||||
// Update the current node and start playing the previous song
|
Serial.println("previous(): Global previous song is null");
|
||||||
Serial.println("");
|
|
||||||
Serial.print("Playing previous song: ");
|
|
||||||
Serial.println(currentNode->getCurrentPlayingFilePath());
|
|
||||||
currentNode = newNode;
|
|
||||||
stop();
|
|
||||||
deactivateRFID();
|
|
||||||
activateSD();
|
|
||||||
playSongByPath(currentNode->getCurrentPlayingFilePath());
|
|
||||||
deactivateSD();
|
|
||||||
activateRFID();
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Serial.println("previous(): No previous song found globally - at beginning of playlist");
|
||||||
|
// Optionally restart current song or do nothing
|
||||||
|
audio.setAudioPlayPosition(0);
|
||||||
|
currentNode->setSecondsPlayed(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastInteraction = millis();
|
|
||||||
|
lastInteraction = millis();
|
||||||
|
Serial.println("=== PREVIOUS FUNCTION COMPLETED ===");
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio_eof_mp3(const char *info)
|
void audio_eof_mp3(const char *info)
|
||||||
|
|
@ -766,8 +818,6 @@ void setup()
|
||||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
|
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||||
{
|
{
|
||||||
webrequestActive = true;
|
webrequestActive = true;
|
||||||
deactivateRFID();
|
|
||||||
activateSD();
|
|
||||||
String htmlPath = "/system/index.html";
|
String htmlPath = "/system/index.html";
|
||||||
if (SD.exists(htmlPath)) {
|
if (SD.exists(htmlPath)) {
|
||||||
AsyncWebServerResponse *response = request->beginResponse(SD, htmlPath, "text/html", false, processor);
|
AsyncWebServerResponse *response = request->beginResponse(SD, htmlPath, "text/html", false, processor);
|
||||||
|
|
@ -784,8 +834,6 @@ void setup()
|
||||||
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request)
|
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||||
{
|
{
|
||||||
webrequestActive = true;
|
webrequestActive = true;
|
||||||
deactivateRFID();
|
|
||||||
activateSD();
|
|
||||||
String cssPath = "/system/style.css";
|
String cssPath = "/system/style.css";
|
||||||
if (SD.exists(cssPath)) {
|
if (SD.exists(cssPath)) {
|
||||||
request->send(SD, cssPath, "text/css");
|
request->send(SD, cssPath, "text/css");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue