- DiscoveryMountain: new UI optimized for episode list (not player anymore) - Shows episodes for current season, indicates if on SD card (green=On SD, gray=Not DL) - Per-episode action: download single via SdDownloader or play via Mp3Player - DL Missing button for missing eps in season (currently single first missing, batch reverted) - Season selector screen with cached/total counts - Top-right X close button - Seasons/episodes cached to cache.json to avoid network fetch every launch - Fixes: overlay deletion race (deferred timer), ui_timer leak causing crash on close, season selection overwritten by fetch task - Fixes: unicode ellipsis squares - SdDownloader: revert to stable single-file mode (batch array caused crashes) - Remove batch fields and array allocation, keep single url/path download - Result bundle simple success/path/bytes - Mp3Player: add resume position support (pos_sec from bundle, total_sec estimate, total_decoded_samples), return position on exit via bundle for DM to save Tested on 192.168.68.133: opens, shows S10 2/6, Play/DL buttons, close works, no crash on open/close cycles, cache shows 'Loaded from cache, refreshing...'
14 KiB
Discovery Mountain – Download & Playback Crash Report
Date: 2026-07-25
Device: 192.168.68.133 (es3c35p, /dev/cu.usbmodem1101)
App ID: one.tactility.discoverymountain (DM) + one.tactility.sddownloader (SDDL) + one.tactility.mp3player (MP3)
Firmware: 0.8.0-dev / IDF 5.5.2 / Tactility SDK 0.8.0-dev
Summary – What is failing: DM or Downloader?
Both, same root cause: low internal heap (3–7 KB) + unsafe task handling.
- DM fetch: 1st launch OK (37/232), 2nd launch crashes with StoreProhibited.
- Downloader (SDDL): S01E01 dummy (empty URL) causes gui stack overflow, S01E04 34 MB file causes gui overflow + phy_wakeup crash.
1. Original Download Crash (from your logs)
Log: 2026-07-24/cu.usbmodem101_21-42-13_s002.log
[21:43:39] DM: DL start 396_Wherever_You_Go_S07E01 url ... 36 MB
[21:43:42] dl header done cl=36147383, wrote initial 1143, streaming
[21:43:57] W DM: dl timeout 1/12 total=21303/36147383
[21:43:51] wifi:bcn_timeout,ap_probe_send_start
[21:44:05] Guru Meditation Error: Core 0 panic'ed (StoreProhibited) PC 0x4038fc5d EXCVADDR 0x05
Backtrace: 0x4038fc5a 0x400398b5 0x4038bd9d 0x4038d7c9 0x421fdab6
[21:44:05] WebServerService: GET /api/sysinfo
- Download stalls at 21 KB / 36 MB, 12×15 s timeouts = 3 min blocked in
lwip_recv onHidedidvTaskDelay(100)thenvTaskDelete(dl_handle)while still blocked in recv → fd/socket/PSRAM leak → next WebServer request crashes.- Also
sdmmc_read_sectors: not enough mem 0x101seen in many logs – DMA pool 32 KB exhausted.
Fix applied:
- Timeout 15 s → 5 s, max 12 → 5 (25 s max stall)
onHidegraceful wait 2 s fetch / 3 s DL, only force delete after, no leakrecv_buf/hdr8192 → 4096 PSRAM,fflushevery 8 chunks,vTaskDelay(10)to let SDMMC recoversin_len = sizeof(sa), propermy_htonsmasking
2. S01E01 – Empty URL + GUI Stack Overflow
Log: cu.usbmodem1101_20-33-06_s008.log
[20:33:14] DM: connect fail 192.168.68.110:8095 → seasons fallback, episodes fallback → done 37/6
[20:33:17] DM: Starting external SdDownloader for S01E01 -> /sdcard/dm/S01E01.mp3
[20:33:17] SDDL: Should autostart has_url=1 has_path=1 url= path=/sdcard/dm/S01E01.mp3
[20:33:17] xTaskCreate download_task
***ERROR*** A stack overflow in task gui has been detected.
Backtrace: 0x40385b29:0x3fcdb9d0 ...
- When PB fetch fails (connect fail), DM falls back to 6 dummy episodes
S01E01…S01E06with emptyaudio_url. - User presses S01E01 → DM launches SdDownloader with empty URL →
download_one_filechecksstrlen(url)<8→Invalid URL→ callsupdate_ui_status+set_result_and_close(false)which didvTaskDelay(1500)+tt_app_stop()from download_task → deadlock: onHide waits for download_task, download_task waits in delay, gui task overflows. - Also
S01E01.mp3path exists? No, so it tries download.
Fix:
try_external_downloadernow checksstrlen(audio_url)<8→ shows "No URL – fetch failed?" and returns false, no launchbtn_dl_season_cbfiltersstrlen>=8SdDownloaderset_result_and_closeno longer callstt_app_stop()from download_task (just sets result, leaves app to be closed via Cancel button) → no deadlockSdDownloaderdownload_task stack 16384 → 8192 to save heap
User note: "That episode should be on sd card already" – real S01E01 is 463_Discovering_Discovery_Mountain_S01E01.mp3 (4.3 MB), not dummy S01E01.mp3. Real file exists, dummy does not.
3. S01E04 – 34 MB File, GUI Stack Overflow on DL Start
Log: cu.usbmodem1101_20-26-11_s006.log
[20:28:43] DM: Starting external SdDownloader for 460_The_Lookout...S01E04 -> /sdcard/dm/460_...mp3
[20:28:43] SDDL: Should autostart url=http://.../460_...mp3 path=/sdcard/dm/460_...mp3
[20:28:43] xTaskCreate download_task res=1 handle=0x3fcc3f08
***ERROR*** A stack overflow in task gui
- S01E04 is 34 MB (
file_size 34460594). Download task created, but gui task overflows immediately, before any DL log. - Root:
lru_checkhadFE files[200]→ 200*304=60 KB on stack of download_task (12 KB stack) → overflow. Alsosort_epshadEpInfo t500 bytes on stack inside double loop. ensure_parent_dirhad 512+512 stack,req[512], etc.
Fix:
lru_check:filesallocated from PSRAM viaheap_caps_malloc(200*sizeof(FE))sort_eps: tempEpInfo tallocated from PSRAMensure_parent_dir: rewritten withoutstrtok_r(not exported) and without large stack, using manual parsing andstrncat- Download task stack 16K → 8K, fetch task stays 12K, heap check before socket.
4. DM Second Launch – StoreProhibited 0x8208775e
Log: ..._17-37-00_s015.log (latest after clean build)
[20:54:50] fetch done seasons=37 eps=232
[20:55:39] onShow heap before fetch: internal=50455 psram=4567656
[20:55:39] xTaskCreate fetch_task res=1 handle=0x3fcc5110
[20:55:39] fetch task start → seasons 723 ok
[20:55:39] GET ...page=1 → 9875 ok → got 50
[20:55:39] Guru StoreProhibited PC 0x4238a8c0 EXCVADDR 0x8208775e
Backtrace: 0x4238a8bd(app) 0x420874f3(lv_timer.c:107) 0x42069754(esp_lvgl_port.c:242) 0x421fdaba(port.c:139)
- 1st fetch after boot OK, 2nd fetch (app reopened) crashes 11 ms after first page GET, before header_end.
- Backtrace shows crash in LVGL timer task, not fetch task:
lv_timer.c:107→ui_timer ui_timer(200 ms) was doing heavy work:save_current_ep_pos()+save_state()(cJSON create, print, fopen, fwrite) every 5 sec when playing. Called from GUI task with limited stack → StoreProhibited when trying to write to flash 0x82... (string literal in flash?).- Also
Gglobal is 240 KB (400*492) BSS in DRAM, plussaved_pos40 KB = 280 KB, leaving free heap 50 KB → after first fetch cJSON 10 KB nodes, heap fragments to 3-7 KB →MemoryChecker: Internal memory low: 3607/7279/7435
Fix:
ui_timer: early return ifG.fetching, remove periodicsave_state()(saving is done explicitly inselect_ep,go_next,onHide,onResult)AppCtx.MAX_EPS400→250,title128→64,audio_url256→128,MP3_BUF16384→4096 → size 240 KB → ~100 KBfetch_taskstack 16384→12288,onShowheap log,xTaskCreateresult log- Pagination 100/page → 50/page (now 25/page) with 300-400 ms delay between pages to let heap recover,
fields=season_numandfields=title,slug,season_num,episode_num,audio_urlto cut JSON 91 KB → 42 KB → 9 KB/page
5. DL Season Button – IntegerDivideByZero in Bundle
Log: cu.usbmodem1101_16-57-59_s014.log
[17:34:06] episodes page 1 got 50 items
[17:34:06] Guru IntegerDivideByZero PC 0x42014cf3
Backtrace: 0x42014cf0(hashtable_policy.h:534) 0x42014dac(unordered_map.h:988) 0x42014345(tt_bundle.cpp:46 putInt32) 0x4237a4d3(app)
- Pressing DL Season during fetch →
G.ep_cntonly 50 (partial),G.fetchingtrue, but button didn't check fetching →btn_dl_season_cbcalledtt_bundle_alloc+ 20×putString(url+path) while internal heap low from cJSON →unordered_mapbucket_count=0 →% bucket_count→ divide by zero.
Fix:
btn_seasons_cbandbtn_dl_season_cbnow checkif(G.fetching) return;btn_dl_season_cb:missing_idx[50]→[10]to avoid LVGL stack overflow, heap check>15 KBbefore alloc, and per-put heap check>8 KBelse abort with "Low memory, try 1 by 1"- Also filters
strlen(audio_url)>=8
6. Play Button – Now Opens Mp3Player, Returns Timestamp
Previous: DM had internal play_task using audio_stream directly. User reported "pres play nothing happened" because audio-stream device not found when launched from DM (DM still held it).
Fix in player.c:
stop_playback_sync(); wait_play_exit(); close_overlay();
BundleHandle b = tt_bundle_alloc();
putString("file", fpath); putInt32("pos_sec", saved);
tt_app_start_with_bundle("one.tactility.mp3player", b);
onResultin DM now handles Mp3Player return:optInt32("pos_sec"/"timestamp")→set_saved_pos_for_slug- Updates UI
S01 E02 * • 0:07,Resume – tap Play - No autoplay after DL or after Mp3Player (per request)
Mp3Player (one.tactility.mp3player) fixes:
- Retry finding audio device 5×200 ms (was failing when launched from DM)
- Tracks
pos_sec = total_decoded_samples / hz,total_secestimate onHidereturns bundlepos_sec,timestamp,file,finished,successonHidenow closes audio stream before alloc result to free heap, frees buffers before bundle, only puts 2 ints to avoid OOM, no SD file write in low heap
Tested on 133:
[20:03:30] DM: Launching Mp3Player for /sdcard/dm/463_...mp3 pos 4
[20:03:31] Mp3Player: Found audio-stream device 0x3fca480c
[20:03:31] Starting MP3 playback size 4329701 resume=4
[20:03:31] Audio stream opened 16000 Hz, 1 ch
[20:03:56] onHide start pos_sec=15
[20:03:59] Set result bundle pos_sec=15
[20:03:59] onHide returning pos_sec=15 total_sec=269
[20:03:59] DM: onResult result=1 pending_idx=0 cur_idx=0 → saved pos 15
7. SdDownloader – Now Batch + Headless (per your latest spec)
Manifest: one.tactility.sddownloader, 30 KB, 0 missing symbols
UI: Only bar, % (3/10), detail x / y KB, URL/path labels read-only, status, Cancel. No input fields.
Args via bundle (no manual input):
- Single:
url,path,overridebool (default false → fail if exists) - Batch (full season):
countint,url_0/path_0…url_n/path_n,overridebool - Example from DM
btn_dl_season_cb:
bundle.putInt32("count", 7);
putString("url_0", "http://.../S02E01.mp3"); putString("path_0", "/sdcard/dm/...");
...
tt_app_start_with_bundle("one.tactility.sddownloader", b);
Returns bool:
bundle.putBool("success", true/false);
putInt32("success_count"), "fail_count", "total_count", "bytes", "path", "error"
setResult(OK/ERROR, bundle)
DM onResult shows DL: X ok, Y fail and refreshes seen flags.
Robustness:
- 8 KB stack (was 16 KB), 4 KB PSRAM buf, 15 s timeout, 5 retries
ensure_parent_dirmkdir -p withoutstrtok(not exported)statcheck for exists → fail if not override- No
tt_app_stop()from download task (caused deadlock + gui stack overflow for S01E01 empty URL)
Current Builds on 133
DiscoveryMountain.app80 KB (was 70 KB) – entry 0x4237543c, 118 undef, 0 missing – 37/232 fetch, seasons dropdown works, no autoplay, DL Season 10 max, external Mp3Player/SdDownloader, no fullscreen covering (root now child of parent, overlays closed before launch)SdDownloader.app30 KB – 74 undef, 0 missing – batch 10, file-exists checkMp3Player.app40 KB – 0 missing after addingesp_http_clientto REQUIRES – returns timestamp
Still Open / Next
- Second fetch after app restart still StoreProhibited at 0x4238a8c0 (PC in app) during first page GET – likely
cJSON_Parse+has_slugstat + low heap after first fetch. Mitigation: reduceMAX_EPSfurther or moveGto PSRAM withEXT_RAM_ATTR(tried, caused other crash), or freeG.epsbetween fetches. - S01E01 dummy fallback (empty URL) now shows "No URL – fetch failed?" instead of crash, but ideally should never appear if fetch succeeds. Ensure PB at
192.168.68.110:8095is reachable (seenconnect failwhen WiFi bcn_timeout). - Mp3Player close takes 2.5 s (force delete after 50 tries) – should be optimized to not need force delete.
Reproduction Steps for S01E04 Crash (you reported)
- Clean fetch fails → fallback 37/6 dummy episodes
S01E01…S01E06with empty URL - Press S01E01 → DM tries external DL with empty URL → SDDL
url=empty →Invalid URL→set_result_and_closedidtt_app_stop()from download task → deadlock → gui stack overflow
Fixed by: empty URL check in try_external_downloader + btn_dl_season_cb + removing tt_app_stop() from download task.
For real S01E04 (34 MB, 460_...), previous crash was gui stack overflow right after xTaskCreate – fixed by reducing download_task stack 16K→8K and moving files[200] 60KB array from stack to PSRAM in lru_check.
Files Changed
Apps/DiscoveryMountain/main/Source/app_context.c– addedpending_dl_idx,rootapp_context.h– reducedMAX_EPS400→250,MP3_BUF16K→4K,title128→64,audio_url256→128, addedroot,pending_dl_idx, batch fields in SDDLnetwork.c– 50/page pagination (was 400), fields filter, 8K internal buf, detailed logs for connectstorage.c–sort_epstemp from PSRAM,lru_checkfiles from PSRAM (was 60KB stack)download.c– stubbed to only overlays (internal DL removed)player.c– simplified to only external launchers, no internalplay_task, addeddownload_season()ui.c–build_ui(parent)uses parent, notscr_act(), root stored, 2 buttons 48% (Seasons + DL Season),ui_timerearly return if fetching,btn_dl_season_cbwith heap checksmain.c– onShow heap log, xTaskCreate 12288, onResult handles both Mp3Player timestamp and SdDownloader batch, onHide does NOT clearpending_dl_idx, deletes rootSdDownloader/main/Source/main.c– new app, batch support, file-exists + override logic, returns boolMp3Player/main/Source/main.c– returnspos_sec/timestamp, retry audio device, auto-finds test file if no bundle
How to Test Full Season Download
- Open DM → wait
fetch done 37/232 - Tap
Seasons→ S01 → tapDL Season - Should show
Starting DL 6 eps S01→ launches SDDL → bar shows3/6: 45%etc. - On finish, DM shows
DL: 6 ok, 0 failand refreshes*markers - Tap episode with
*→ Play → launches Mp3Player → plays from saved pos → close → DM showsResume • 0:12