1
0
forked from iarv/Meck

m4b incompatibility workaround by renaming file extension to m4a when playing

This commit is contained in:
pelgraine
2026-02-14 01:49:25 +11:00
parent d51ca6db0b
commit ddfe05ad20
2 changed files with 66 additions and 10 deletions

View File

@@ -178,6 +178,12 @@ private:
uint32_t _pendingSeekSec; // 0 = no pending seek
bool _streamReady; // Set true once library reports duration
// M4B rename workaround — the audio library only recognises .m4a,
// so we temporarily rename .m4b files on the SD card for playback.
bool _m4bRenamed; // true if file was renamed for playback
String _m4bOrigPath; // original path (with .m4b extension)
String _m4bTempPath; // temporary path (with .m4a extension)
// UI state
int _transportSel;
bool _showingInfo;
@@ -199,6 +205,21 @@ private:
_dacPowered = false;
}
// Restore an M4B file that was temporarily renamed to .m4a for playback.
void restoreM4bRename() {
if (!_m4bRenamed) return;
if (SD.rename(_m4bTempPath.c_str(), _m4bOrigPath.c_str())) {
Serial.printf("AB: Restored '%s' -> '%s'\n",
_m4bTempPath.c_str(), _m4bOrigPath.c_str());
} else {
Serial.printf("AB: Warning - failed to restore '%s' to '%s'\n",
_m4bTempPath.c_str(), _m4bOrigPath.c_str());
}
_m4bRenamed = false;
_m4bOrigPath = "";
_m4bTempPath = "";
}
void ensureI2SInit() {
if (!_i2sInitialized && _audio) {
bool ok = _audio->setPinout(BOARD_I2S_BCLK, BOARD_I2S_LRC, BOARD_I2S_DOUT, 0);
@@ -246,21 +267,28 @@ private:
return false;
}
JPEGDEC jpeg;
JPEGDEC* jpeg = new JPEGDEC();
if (!jpeg) {
Serial.println("AB: Failed to allocate JPEGDEC");
free(jpegBuf);
freeCoverBitmap();
return false;
}
CoverDecodeCtx ctx;
ctx.bitmap = _coverBitmap;
ctx.bitmapW = _coverW;
ctx.bitmapH = _coverH;
if (!jpeg.openRAM(jpegBuf, _metadata.coverSize, coverDrawCallback)) {
if (!jpeg->openRAM(jpegBuf, _metadata.coverSize, coverDrawCallback)) {
Serial.println("AB: JPEGDEC failed to open cover image");
delete jpeg;
free(jpegBuf);
freeCoverBitmap();
return false;
}
int srcW = jpeg.getWidth();
int srcH = jpeg.getHeight();
int srcW = jpeg->getWidth();
int srcH = jpeg->getHeight();
int scale = 0;
if (srcW > _coverW * 6 || srcH > _coverH * 6) scale = 3;
@@ -276,17 +304,18 @@ private:
ctx.offsetX = (scaledW > _coverW) ? (scaledW - _coverW) / 2 : 0;
ctx.offsetY = (scaledH > _coverH) ? (scaledH - _coverH) / 2 : 0;
jpeg.setUserPointer(&ctx);
jpeg.setPixelType(RGB565_BIG_ENDIAN);
jpeg->setUserPointer(&ctx);
jpeg->setPixelType(RGB565_BIG_ENDIAN);
int scaleFlags[] = { JPEG_SCALE_HALF, JPEG_SCALE_QUARTER, JPEG_SCALE_EIGHTH };
if (scale > 0) {
jpeg.decode(0, 0, scaleFlags[scale - 1]);
jpeg->decode(0, 0, scaleFlags[scale - 1]);
} else {
jpeg.decode(0, 0, 0);
jpeg->decode(0, 0, 0);
}
jpeg.close();
jpeg->close();
delete jpeg;
free(jpegBuf);
_hasCover = true;
@@ -489,6 +518,7 @@ private:
if (_isPlaying || _isPaused) {
stopPlayback();
}
restoreM4bRename(); // Safety: ensure rename is restored even if state was odd
saveBookmark();
freeCoverBitmap();
_metadata.clear();
@@ -510,6 +540,28 @@ private:
String fullPath = String(AUDIOBOOKS_FOLDER) + "/" + _currentFile;
// M4B workaround: the ESP32-audioI2S library only recognises .m4a
// for MP4/AAC container parsing. M4B is identical but the extension
// isn't checked, so the library treats it as raw AAC and fails.
// Temporarily rename the file on the SD card (FAT32 rename is instant).
_m4bRenamed = false;
String lower = _currentFile;
lower.toLowerCase();
if (lower.endsWith(".m4b")) {
String m4aFile = _currentFile.substring(0, _currentFile.length() - 1) + "a";
_m4bOrigPath = fullPath;
_m4bTempPath = String(AUDIOBOOKS_FOLDER) + "/" + m4aFile;
if (SD.rename(_m4bOrigPath.c_str(), _m4bTempPath.c_str())) {
Serial.printf("AB: Renamed '%s' -> '%s' for playback\n",
_m4bOrigPath.c_str(), _m4bTempPath.c_str());
fullPath = _m4bTempPath;
_m4bRenamed = true;
} else {
Serial.println("AB: Warning - failed to rename .m4b to .m4a");
}
}
// Connect to file — library parses headers asynchronously via loop()
_audio->connecttoFS(SD, fullPath.c_str());
_audio->setVolume(_volume);
@@ -542,6 +594,9 @@ private:
_streamReady = false;
saveBookmark();
// Restore .m4b filename if we renamed it for playback
restoreM4bRename();
// Power down the PCM5102A DAC to save battery
disableDAC();
@@ -835,6 +890,7 @@ public:
_currentPosSec(0), _durationSec(0), _currentChapter(-1),
_lastPositionSave(0), _lastPosUpdate(0),
_pendingSeekSec(0), _streamReady(false),
_m4bRenamed(false),
_transportSel(2), _showingInfo(false) {}
~AudiobookPlayerScreen() {

View File

@@ -81,7 +81,7 @@ build_flags =
-D PIN_USER_BTN=0
-D CST328_PIN_RST=38
-D FIRMWARE_VERSION='"Meck v0.8.7A"'
-D ARDUINO_LOOP_STACK_SIZE=16384
-D ARDUINO_LOOP_STACK_SIZE=32768
build_src_filter = ${esp32_base.build_src_filter}
+<../variants/LilyGo_TDeck_Pro>
+<helpers/sensors/*.cpp>