Files
tactility_apps/Apps/DiscoveryMountain/CRASH_REPORT.md
T
Adolfo 3778cb3399 feat(DM): library UI with episode list, SD status, cache, close button, stable single-file DL
- 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...'
2026-07-25 22:06:43 -04:00

14 KiB
Raw Blame History

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 (37 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
  • onHide did vTaskDelay(100) then vTaskDelete(dl_handle) while still blocked in recv → fd/socket/PSRAM leak → next WebServer request crashes.
  • Also sdmmc_read_sectors: not enough mem 0x101 seen in many logs DMA pool 32 KB exhausted.

Fix applied:

  • Timeout 15 s → 5 s, max 12 → 5 (25 s max stall)
  • onHide graceful wait 2 s fetch / 3 s DL, only force delete after, no leak
  • recv_buf/hdr 8192 → 4096 PSRAM, fflush every 8 chunks, vTaskDelay(10) to let SDMMC recover
  • sin_len = sizeof(sa), proper my_htons masking

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 S01E01S01E06 with empty audio_url.
  • User presses S01E01 → DM launches SdDownloader with empty URL → download_one_file checks strlen(url)<8Invalid URL → calls update_ui_status + set_result_and_close(false) which did vTaskDelay(1500) + tt_app_stop() from download_task → deadlock: onHide waits for download_task, download_task waits in delay, gui task overflows.
  • Also S01E01.mp3 path exists? No, so it tries download.

Fix:

  • try_external_downloader now checks strlen(audio_url)<8 → shows "No URL fetch failed?" and returns false, no launch
  • btn_dl_season_cb filters strlen>=8
  • SdDownloader set_result_and_close no longer calls tt_app_stop() from download_task (just sets result, leaves app to be closed via Cancel button) → no deadlock
  • SdDownloader download_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_check had FE files[200] → 200*304=60 KB on stack of download_task (12 KB stack) → overflow. Also sort_eps had EpInfo t 500 bytes on stack inside double loop.
  • ensure_parent_dir had 512+512 stack, req[512], etc.

Fix:

  • lru_check: files allocated from PSRAM via heap_caps_malloc(200*sizeof(FE))
  • sort_eps: temp EpInfo t allocated from PSRAM
  • ensure_parent_dir: rewritten without strtok_r (not exported) and without large stack, using manual parsing and strncat
  • 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:107ui_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 G global is 240 KB (400*492) BSS in DRAM, plus saved_pos 40 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 if G.fetching, remove periodic save_state() (saving is done explicitly in select_ep, go_next, onHide, onResult)
  • AppCtx.MAX_EPS 400→250, title 128→64, audio_url 256→128, MP3_BUF 16384→4096 → size 240 KB → ~100 KB
  • fetch_task stack 16384→12288, onShow heap log, xTaskCreate result log
  • Pagination 100/page → 50/page (now 25/page) with 300-400 ms delay between pages to let heap recover, fields=season_num and fields=title,slug,season_num,episode_num,audio_url to 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_cnt only 50 (partial), G.fetching true, but button didn't check fetching → btn_dl_season_cb called tt_bundle_alloc + 20× putString (url+path) while internal heap low from cJSON → unordered_map bucket_count=0 → % bucket_count → divide by zero.

Fix:

  • btn_seasons_cb and btn_dl_season_cb now check if(G.fetching) return;
  • btn_dl_season_cb: missing_idx[50][10] to avoid LVGL stack overflow, heap check >15 KB before alloc, and per-put heap check >8 KB else 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);
  • onResult in 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_sec estimate
  • onHide returns bundle pos_sec, timestamp, file, finished, success
  • onHide now 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, override bool (default false → fail if exists)
  • Batch (full season): count int, url_0/path_0url_n/path_n, override bool
  • 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_dir mkdir -p without strtok (not exported)
  • stat check 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.app 80 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.app 30 KB 74 undef, 0 missing batch 10, file-exists check
  • Mp3Player.app 40 KB 0 missing after adding esp_http_client to 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_slug stat + low heap after first fetch. Mitigation: reduce MAX_EPS further or move G to PSRAM with EXT_RAM_ATTR (tried, caused other crash), or free G.eps between 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:8095 is reachable (seen connect fail when 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)

  1. Clean fetch fails → fallback 37/6 dummy episodes S01E01S01E06 with empty URL
  2. Press S01E01 → DM tries external DL with empty URL → SDDL url= empty → Invalid URLset_result_and_close did tt_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 added pending_dl_idx, root
  • app_context.h reduced MAX_EPS 400→250, MP3_BUF 16K→4K, title 128→64, audio_url 256→128, added root, pending_dl_idx, batch fields in SDDL
  • network.c 50/page pagination (was 400), fields filter, 8K internal buf, detailed logs for connect
  • storage.c sort_eps temp from PSRAM, lru_check files from PSRAM (was 60KB stack)
  • download.c stubbed to only overlays (internal DL removed)
  • player.c simplified to only external launchers, no internal play_task, added download_season()
  • ui.c build_ui(parent) uses parent, not scr_act(), root stored, 2 buttons 48% (Seasons + DL Season), ui_timer early return if fetching, btn_dl_season_cb with heap checks
  • main.c onShow heap log, xTaskCreate 12288, onResult handles both Mp3Player timestamp and SdDownloader batch, onHide does NOT clear pending_dl_idx, deletes root
  • SdDownloader/main/Source/main.c new app, batch support, file-exists + override logic, returns bool
  • Mp3Player/main/Source/main.c returns pos_sec/timestamp, retry audio device, auto-finds test file if no bundle

How to Test Full Season Download

  1. Open DM → wait fetch done 37/232
  2. Tap Seasons → S01 → tap DL Season
  3. Should show Starting DL 6 eps S01 → launches SDDL → bar shows 3/6: 45% etc.
  4. On finish, DM shows DL: 6 ok, 0 fail and refreshes * markers
  5. Tap episode with * → Play → launches Mp3Player → plays from saved pos → close → DM shows Resume • 0:12