You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
11 KiB
300 lines
11 KiB
#include <Arduino.h>
|
|
#include "settings.h"
|
|
#include "SdCard.h"
|
|
#include "Common.h"
|
|
#include "Led.h"
|
|
#include "Log.h"
|
|
#include "MemX.h"
|
|
#include "System.h"
|
|
|
|
#ifdef SD_MMC_1BIT_MODE
|
|
fs::FS gFSystem = (fs::FS)SD_MMC;
|
|
#else
|
|
SPIClass spiSD(HSPI);
|
|
fs::FS gFSystem = (fs::FS)SD;
|
|
#endif
|
|
|
|
void SdCard_Init(void) {
|
|
#ifndef SINGLE_SPI_ENABLE
|
|
#ifdef SD_MMC_1BIT_MODE
|
|
pinMode(2, INPUT_PULLUP);
|
|
while (!SD_MMC.begin("/sdcard", true)) {
|
|
#else
|
|
pinMode(SPISD_CS, OUTPUT);
|
|
digitalWrite(SPISD_CS, HIGH);
|
|
spiSD.begin(SPISD_SCK, SPISD_MISO, SPISD_MOSI, SPISD_CS);
|
|
spiSD.setFrequency(1000000);
|
|
while (!SD.begin(SPISD_CS, spiSD)) {
|
|
#endif
|
|
#else
|
|
#ifdef SD_MMC_1BIT_MODE
|
|
pinMode(2, INPUT_PULLUP);
|
|
while (!SD_MMC.begin("/sdcard", true)) {
|
|
#else
|
|
while (!SD.begin(SPISD_CS)) {
|
|
#endif
|
|
#endif
|
|
Log_Println((char *) FPSTR(unableToMountSd), LOGLEVEL_ERROR);
|
|
delay(500);
|
|
#ifdef SHUTDOWN_IF_SD_BOOT_FAILS
|
|
if (millis() >= deepsleepTimeAfterBootFails * 1000) {
|
|
Log_Println((char *) FPSTR(sdBootFailedDeepsleep), LOGLEVEL_ERROR);
|
|
esp_deep_sleep_start();
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void SdCard_Exit(void) {
|
|
// SD card goto idle mode
|
|
#ifdef SD_MMC_1BIT_MODE
|
|
SD_MMC.end();
|
|
#endif
|
|
}
|
|
|
|
sdcard_type_t SdCard_GetType(void) {
|
|
sdcard_type_t cardType;
|
|
#ifdef SD_MMC_1BIT_MODE
|
|
Log_Println((char *) FPSTR(sdMountedMmc1BitMode), LOGLEVEL_NOTICE);
|
|
cardType = SD_MMC.cardType();
|
|
#else
|
|
Log_Println((char *) FPSTR(sdMountedSpiMode), LOGLEVEL_NOTICE);
|
|
cardType = SD.cardType();
|
|
#endif
|
|
return cardType;
|
|
}
|
|
|
|
// Check if file-type is correct
|
|
bool fileValid(const char *_fileItem) {
|
|
const char ch = '/';
|
|
char *subst;
|
|
subst = strrchr(_fileItem, ch); // Don't use files that start with .
|
|
|
|
return (!startsWith(subst, (char *) "/.")) &&
|
|
(endsWith(_fileItem, ".mp3") || endsWith(_fileItem, ".MP3") ||
|
|
endsWith(_fileItem, ".aac") || endsWith(_fileItem, ".AAC") ||
|
|
endsWith(_fileItem, ".m3u") || endsWith(_fileItem, ".M3U") ||
|
|
endsWith(_fileItem, ".m4a") || endsWith(_fileItem, ".M4A") ||
|
|
endsWith(_fileItem, ".wav") || endsWith(_fileItem, ".WAV") ||
|
|
endsWith(_fileItem, ".flac") || endsWith(_fileItem, ".FLAC") ||
|
|
endsWith(_fileItem, ".asx") || endsWith(_fileItem, ".ASX"));
|
|
}
|
|
|
|
/* Puts SD-file(s) or directory into a playlist
|
|
First element of array always contains the number of payload-items. */
|
|
char **SdCard_ReturnPlaylist(const char *fileName, const uint32_t _playMode) {
|
|
static char **files;
|
|
char *serializedPlaylist;
|
|
char fileNameBuf[255];
|
|
char cacheFileNameBuf[275];
|
|
bool readFromCacheFile = false;
|
|
bool enablePlaylistCaching = false;
|
|
bool enablePlaylistFromM3u = false;
|
|
|
|
// Look if file/folder requested really exists. If not => break.
|
|
File fileOrDirectory = gFSystem.open(fileName);
|
|
if (!fileOrDirectory) {
|
|
Log_Println((char *) FPSTR(dirOrFileDoesNotExist), LOGLEVEL_ERROR);
|
|
return NULL;
|
|
}
|
|
|
|
// Create linear playlist of caching-file
|
|
#ifdef CACHED_PLAYLIST_ENABLE
|
|
strncpy(cacheFileNameBuf, fileName, sizeof(cacheFileNameBuf));
|
|
strcat(cacheFileNameBuf, "/");
|
|
strcat(cacheFileNameBuf, (const char*) FPSTR(playlistCacheFile)); // Build absolute path of cacheFile
|
|
|
|
// Decide if to use cacheFile. It needs to exist first...
|
|
if (gFSystem.exists(cacheFileNameBuf)) { // Check if cacheFile (already) exists
|
|
readFromCacheFile = true;
|
|
}
|
|
|
|
// ...and playmode has to be != random/single (as random along with caching doesn't make sense at all)
|
|
if (_playMode == SINGLE_TRACK ||
|
|
_playMode == SINGLE_TRACK_LOOP) {
|
|
readFromCacheFile = false;
|
|
} else {
|
|
enablePlaylistCaching = true;
|
|
}
|
|
|
|
// Read linear playlist (csv with #-delimiter) from cachefile (faster!)
|
|
if (readFromCacheFile) {
|
|
File cacheFile = gFSystem.open(cacheFileNameBuf);
|
|
if (cacheFile) {
|
|
uint32_t cacheFileSize = cacheFile.size();
|
|
|
|
if (!(cacheFileSize >= 1)) { // Make sure it's greater than 0 bytes
|
|
Log_Println((char *) FPSTR(playlistCacheFoundBut0), LOGLEVEL_ERROR);
|
|
readFromCacheFile = false;
|
|
} else {
|
|
Log_Println((char *) FPSTR(playlistGenModeCached), LOGLEVEL_NOTICE);
|
|
serializedPlaylist = (char *) x_calloc(cacheFileSize+10, sizeof(char));
|
|
|
|
char buf;
|
|
uint32_t fPos = 0;
|
|
while (cacheFile.available() > 0) {
|
|
buf = cacheFile.read();
|
|
serializedPlaylist[fPos++] = buf;
|
|
}
|
|
}
|
|
cacheFile.close();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Parse m3u-playlist and create linear-playlist out of it
|
|
if (_playMode == WEBSTREAMS_LOCAL_M3U) {
|
|
enablePlaylistFromM3u = true;
|
|
if (fileOrDirectory && !fileOrDirectory.isDirectory()) {
|
|
serializedPlaylist = (char *) x_calloc(2048, sizeof(char));
|
|
if (serializedPlaylist == NULL) {
|
|
Log_Println((char *) FPSTR(unableToAllocateMemForLinearPlaylist), LOGLEVEL_ERROR);
|
|
System_IndicateError();
|
|
return files;
|
|
}
|
|
char buf;
|
|
uint32_t fPos = 1;
|
|
|
|
serializedPlaylist[0] = '#';
|
|
while (fileOrDirectory.available() > 0) {
|
|
buf = fileOrDirectory.read();
|
|
if (buf != '\n' && buf != '\r') {
|
|
serializedPlaylist[fPos++] = buf;
|
|
} else {
|
|
serializedPlaylist[fPos++] = '#';
|
|
}
|
|
}
|
|
if (serializedPlaylist[fPos-1] == '#') { // Remove trailing delimiter if set
|
|
serializedPlaylist[fPos-1] = '\0';
|
|
}
|
|
} else {
|
|
return files;
|
|
}
|
|
}
|
|
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *) FPSTR(freeMemory), ESP.getFreeHeap());
|
|
Log_Println(Log_Buffer, LOGLEVEL_DEBUG);
|
|
|
|
if (files != NULL) { // If **ptr already exists, de-allocate its memory
|
|
Log_Println((char *) FPSTR(releaseMemoryOfOldPlaylist), LOGLEVEL_DEBUG);
|
|
--files;
|
|
freeMultiCharArray(files, strtoul(*files, NULL, 10));
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: %u", (char *) FPSTR(freeMemoryAfterFree), ESP.getFreeHeap());
|
|
Log_Println(Log_Buffer, LOGLEVEL_DEBUG);
|
|
}
|
|
|
|
// Don't read from cachefile or m3u-file. Means: read filenames from SD and make playlist of it
|
|
if (!readFromCacheFile && !enablePlaylistFromM3u) {
|
|
Log_Println((char *) FPSTR(playlistGenModeUncached), LOGLEVEL_NOTICE);
|
|
// File-mode
|
|
if (!fileOrDirectory.isDirectory()) {
|
|
files = (char **) x_malloc(sizeof(char *) * 2);
|
|
if (files == NULL) {
|
|
Log_Println((char *) FPSTR(unableToAllocateMemForPlaylist), LOGLEVEL_ERROR);
|
|
System_IndicateError();
|
|
return NULL;
|
|
}
|
|
Log_Println((char *) FPSTR(fileModeDetected), LOGLEVEL_INFO);
|
|
strncpy(fileNameBuf, (char *) fileOrDirectory.name(), sizeof(fileNameBuf) / sizeof(fileNameBuf[0]));
|
|
if (fileValid(fileNameBuf)) {
|
|
files = (char **) x_malloc(sizeof(char *) * 2);
|
|
files[1] = x_strdup(fileNameBuf);
|
|
}
|
|
files[0] = x_strdup("1"); // Number of files is always 1 in file-mode
|
|
|
|
return ++files;
|
|
}
|
|
|
|
// Directory-mode (linear-playlist)
|
|
uint16_t allocCount = 1;
|
|
uint16_t allocSize = 4096;
|
|
if (psramInit()) {
|
|
allocSize = 65535; // There's enough PSRAM. So we don't have to care...
|
|
}
|
|
|
|
serializedPlaylist = (char *) x_calloc(allocSize, sizeof(char));
|
|
File cacheFile;
|
|
if (enablePlaylistCaching) {
|
|
cacheFile = gFSystem.open(cacheFileNameBuf, FILE_WRITE);
|
|
}
|
|
|
|
while (true) {
|
|
File fileItem = fileOrDirectory.openNextFile();
|
|
if (!fileItem) {
|
|
break;
|
|
}
|
|
if (fileItem.isDirectory()) {
|
|
continue;
|
|
} else {
|
|
strncpy(fileNameBuf, (char *) fileItem.name(), sizeof(fileNameBuf) / sizeof(fileNameBuf[0]));
|
|
|
|
// Don't support filenames that start with "." and only allow .mp3
|
|
if (fileValid(fileNameBuf)) {
|
|
/*snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *) FPSTR(nameOfFileFound), fileNameBuf);
|
|
Log_Println(Log_Buffer, LOGLEVEL_INFO);*/
|
|
if ((strlen(serializedPlaylist) + strlen(fileNameBuf) + 2) >= allocCount * allocSize) {
|
|
serializedPlaylist = (char *) realloc(serializedPlaylist, ++allocCount * allocSize);
|
|
Log_Println((char *) FPSTR(reallocCalled), LOGLEVEL_DEBUG);
|
|
if (serializedPlaylist == NULL) {
|
|
Log_Println((char *) FPSTR(unableToAllocateMemForLinearPlaylist), LOGLEVEL_ERROR);
|
|
System_IndicateError();
|
|
return files;
|
|
}
|
|
}
|
|
strcat(serializedPlaylist, stringDelimiter);
|
|
strcat(serializedPlaylist, fileNameBuf);
|
|
if (cacheFile && enablePlaylistCaching) {
|
|
cacheFile.print(stringDelimiter);
|
|
cacheFile.print(fileNameBuf); // Write linear playlist to cacheFile
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cacheFile && enablePlaylistCaching) {
|
|
cacheFile.close();
|
|
}
|
|
}
|
|
|
|
// Get number of elements out of serialized playlist
|
|
uint32_t cnt = 0;
|
|
for (uint32_t k = 0; k < (strlen(serializedPlaylist)); k++) {
|
|
if (serializedPlaylist[k] == '#') {
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
// Alloc only necessary number of playlist-pointers
|
|
files = (char **) x_malloc(sizeof(char *) * cnt + 1);
|
|
|
|
if (files == NULL) {
|
|
Log_Println((char *) FPSTR(unableToAllocateMemForPlaylist), LOGLEVEL_ERROR);
|
|
System_IndicateError();
|
|
free(serializedPlaylist);
|
|
return NULL;
|
|
}
|
|
|
|
// Extract elements out of serialized playlist and copy to playlist
|
|
char *token;
|
|
token = strtok(serializedPlaylist, stringDelimiter);
|
|
uint32_t pos = 1;
|
|
while (token != NULL) {
|
|
files[pos++] = x_strdup(token);
|
|
token = strtok(NULL, stringDelimiter);
|
|
}
|
|
|
|
free(serializedPlaylist);
|
|
|
|
files[0] = (char *) x_malloc(sizeof(char) * 5);
|
|
|
|
if (files[0] == NULL) {
|
|
Log_Println((char *) FPSTR(unableToAllocateMemForPlaylist), LOGLEVEL_ERROR);
|
|
System_IndicateError();
|
|
return NULL;
|
|
}
|
|
sprintf(files[0], "%u", cnt);
|
|
snprintf(Log_Buffer, Log_BufferLength, "%s: %d", (char *) FPSTR(numberOfValidFiles), cnt);
|
|
Log_Println(Log_Buffer, LOGLEVEL_NOTICE);
|
|
|
|
return ++files; // return ptr+1 (starting at 1st payload-item); ptr+0 contains number of items
|
|
}
|