Skip to content

Instantly share code, notes, and snippets.

@biologist79
Created December 20, 2022 23:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save biologist79/8bdda5885830238d5272b433e08ffdb3 to your computer and use it in GitHub Desktop.
Save biologist79/8bdda5885830238d5272b433e08ffdb3 to your computer and use it in GitHub Desktop.
AudioPlayer.cpp
#include <Arduino.h>
#include "settings.h"
#include "SdCard.h"
#include "Common.h"
#include "Led.h"
#include "Log.h"
#include "MemX.h"
#include "System.h"
#include "Wlan.h"
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.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;
}
uint64_t SdCard_GetSize() {
#ifdef SD_MMC_1BIT_MODE
return SD_MMC.cardSize();
#else
return SD.cardSize();
#endif
}
uint64_t SdCard_GetFreeSize() {
#ifdef SD_MMC_1BIT_MODE
return SD_MMC.cardSize() - SD_MMC.usedBytes();
#else
return SD.cardSize() - SD.usedBytes();
#endif
}
void SdCard_PrintInfo() {
// show SD card type
sdcard_type_t cardType = SdCard_GetType();
Log_Print((char *) F("SD card type: "), LOGLEVEL_DEBUG, true );
if (cardType == CARD_MMC) {
Log_Println((char *) F("MMC"), LOGLEVEL_DEBUG);
} else if (cardType == CARD_SD) {
Log_Println((char *) F("SDSC"), LOGLEVEL_DEBUG);
} else if (cardType == CARD_SDHC) {
Log_Println((char *) F("SDHC"), LOGLEVEL_DEBUG);
} else {
Log_Println((char *) F("UNKNOWN"), LOGLEVEL_DEBUG);
}
// show SD card size / free space
uint64_t cardSize = SdCard_GetSize() / (1024 * 1024);
uint64_t freeSize = SdCard_GetFreeSize() / (1024 * 1024);;
snprintf(Log_Buffer, Log_BufferLength, "%s: %llu MB / %llu MB", (char *) FPSTR(sdInfo), cardSize, freeSize);
Log_Println(Log_Buffer, LOGLEVEL_NOTICE);
}
// 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"));
}
// Takes a directory as input and returns a random subdirectory from it
char *SdCard_pickRandomSubdirectory(char *_directory) {
// Look if file/folder requested really exists. If not => break.
File directory = gFSystem.open(_directory);
if (!directory) {
Log_Println((char *) FPSTR(dirOrFileDoesNotExist), LOGLEVEL_ERROR);
return NULL;
}
snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *) FPSTR(tryToPickRandomDir), _directory);
Log_Println(Log_Buffer, LOGLEVEL_NOTICE);
static uint8_t allocCount = 1;
uint16_t allocSize = psramInit() ? 65535 : 1024; // There's enough PSRAM. So we don't have to care...
uint16_t directoryCount = 0;
char *buffer = _directory; // input char* is reused as it's content no longer needed
char *subdirectoryList = (char *) x_calloc(allocSize, sizeof(char));
if (subdirectoryList == NULL) {
Log_Println((char *) FPSTR(unableToAllocateMemForLinearPlaylist), LOGLEVEL_ERROR);
System_IndicateError();
return NULL;
}
// Create linear list of subdirectories with #-delimiters
while (true) {
File fileItem = directory.openNextFile();
if (!fileItem) {
break;
}
if (!fileItem.isDirectory()) {
continue;
} else {
#if ESP_ARDUINO_VERSION_MAJOR >= 2
strncpy(buffer, (char *) fileItem.path(), 255);
#else
strncpy(buffer, (char *) fileItem.name(), 255);
#endif
/*snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *) FPSTR(nameOfFileFound), buffer);
Log_Println(Log_Buffer, LOGLEVEL_INFO);*/
if ((strlen(subdirectoryList) + strlen(buffer) + 2) >= allocCount * allocSize) {
subdirectoryList = (char *) realloc(subdirectoryList, ++allocCount * allocSize);
Log_Println((char *) FPSTR(reallocCalled), LOGLEVEL_DEBUG);
if (subdirectoryList == NULL) {
Log_Println((char *) FPSTR(unableToAllocateMemForLinearPlaylist), LOGLEVEL_ERROR);
System_IndicateError();
return NULL;
}
}
strcat(subdirectoryList, stringDelimiter);
strcat(subdirectoryList, buffer);
directoryCount++;
}
}
strcat(subdirectoryList, stringDelimiter);
if (!directoryCount) {
free(subdirectoryList);
return NULL;
}
uint16_t randomNumber = random(directoryCount) + 1; // Create random-number with max = subdirectory-count
uint16_t delimiterFoundCount = 0;
uint32_t a=0;
uint8_t b=0;
// Walk through subdirectory-array and extract randomized subdirectory
while (subdirectoryList[a] != '\0') {
if (subdirectoryList[a] == '#') {
delimiterFoundCount++;
} else {
if (delimiterFoundCount == randomNumber) { // Pick subdirectory of linear char* according to random number
buffer[b++] = subdirectoryList[a];
}
}
if (delimiterFoundCount > randomNumber || (b == 254)) { // It's over when next delimiter is found or buffer is full
buffer[b] = '\0';
free(subdirectoryList);
snprintf(Log_Buffer, Log_BufferLength, "%s: %s", (char *) FPSTR(pickedRandomDir), _directory);
Log_Println(Log_Buffer, LOGLEVEL_NOTICE);
return buffer; // Full path of random subdirectory
}
a++;
}
free(subdirectoryList);
return NULL;
}
class HTTPClientCustom: public HTTPClient {
public:
void *operator new(size_t size) {
return psramFound() ? ps_malloc(size) : malloc(size);
}
};
class WifiClientCustomSecure: public WiFiClientSecure {
public:
void *operator new(size_t size) {
return psramFound() ? ps_malloc(size) : malloc(size);
}
};
/* Puts SD-file(s) or directory into a playlist
First element of array always contains the number of payload-items. */
char **SdCard_ReturnPlaylist(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;
String fileOrUrl(fileName);
// Look if .m3u-file. If so: necessary to fetch from remote server?
if ((fileOrUrl.startsWith("http") || fileOrUrl.startsWith("https")) && fileOrUrl.endsWith(".m3u")) {
if (!Wlan_IsConnected()) {
System_IndicateError();
Log_Println((char *) FPSTR(cantConnectToWifi), LOGLEVEL_ERROR);
return NULL;
}
bool useTls = false;
if (fileOrUrl.startsWith("https")) {
useTls = true;
}
// Downloaded .m3u-files will be stored later in a tempfile
File m3uFile = gFSystem.open("/__tempFile.m3u", FILE_WRITE);
if (!useTls) {
HTTPClientCustom *httpclient = new HTTPClientCustom();
if (httpclient == NULL) {
System_IndicateError();
return NULL;
}
httpclient->begin(fileName);
int httpCode = httpclient->GET();
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
m3uFile.print(httpclient->getString());
} else {
System_IndicateError();
return NULL;
}
httpclient->end();
free(httpclient);
} else {
WifiClientCustomSecure *httpsclient = new WifiClientCustomSecure();
if (httpsclient == NULL) {
System_IndicateError();
return NULL;
}
HTTPClientCustom *https = new HTTPClientCustom();
if (https == NULL) {
System_IndicateError();
return NULL;
}
https->begin(fileName);
int httpCode = https->GET();
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
m3uFile.print(https->getString());
} else {
System_IndicateError();
return NULL;
}
https->end();
free(https);
free(httpsclient);
}
m3uFile.close();
strcpy(fileName,"/__tempFile.m3u"); // Reinject tempfile as regular .m3u-file
}
// 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
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);
}
// Parse m3u-playlist and create linear-playlist out of it
if (_playMode == LOCAL_M3U) {
if (fileOrDirectory && !fileOrDirectory.isDirectory() && fileOrDirectory.size() >= 0) {
enablePlaylistFromM3u = true;
uint16_t allocCount = 1;
uint16_t allocSize = psramInit() ? 65535 : 1024; // There's enough PSRAM. So we don't have to care...
serializedPlaylist = (char *) x_calloc(allocSize, sizeof(char));
if (serializedPlaylist == NULL) {
Log_Println((char *) FPSTR(unableToAllocateMemForLinearPlaylist), LOGLEVEL_ERROR);
System_IndicateError();
return files;
}
char buf;
char lastBuf = '#';
uint32_t fPos = 1;
serializedPlaylist[0] = '#';
while (fileOrDirectory.available() > 0) {
buf = fileOrDirectory.read();
if (fPos+1 >= 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();
free(serializedPlaylist);
return files;
}
}
if (buf != '\n' && buf != '\r') {
serializedPlaylist[fPos++] = buf;
lastBuf = buf;
} else {
if (lastBuf != '#') { // Strip empty lines from m3u
serializedPlaylist[fPos++] = '#';
lastBuf = '#';
}
}
}
if (serializedPlaylist[fPos-1] == '#') { // Remove trailing delimiter if set
serializedPlaylist[fPos-1] = '\0';
}
} else {
return files;
}
}
// 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);
#if ESP_ARDUINO_VERSION_MAJOR >= 2
strncpy(fileNameBuf, (char *) fileOrDirectory.path(), sizeof(fileNameBuf) / sizeof(fileNameBuf[0]));
#else
strncpy(fileNameBuf, (char *) fileOrDirectory.name(), sizeof(fileNameBuf) / sizeof(fileNameBuf[0]));
#endif
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 {
#if ESP_ARDUINO_VERSION_MAJOR >= 2
strncpy(fileNameBuf, (char *) fileItem.path(), sizeof(fileNameBuf) / sizeof(fileNameBuf[0]));
#else
strncpy(fileNameBuf, (char *) fileItem.name(), sizeof(fileNameBuf) / sizeof(fileNameBuf[0]));
#endif
// 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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment