Summary
Two related bugs triggered when pressing Load in the mobile library view, reported by @goermezer in #216.
Bug 1 - WebKit tab crash (root cause)
openTrack() calls createAudioEngine(), which fetches all stems in parallel via res.arrayBuffer() then decodes each with decodeAudioData(). This holds both the compressed MP3 and the decoded PCM in memory simultaneously for all stems at once.
Rough memory for a 5-minute, 4-stem track:
- Decoded PCM: 5 min x 4 stems x 44100 Hz x 2ch x 4 bytes = ~420 MB
- Compressed MP3 buffers in flight: ~32 MB
Mobile browser tabs get killed at 256-512 MB on most devices. The desktop player (player.js) already has a MAX_ENGINE_DECODED_BYTES = 1.2e9 guard with a streaming fallback - the mobile engine has no equivalent.
Relevant files:
static/js/audioEngine.js lines 43-63 (parallel arrayBuffer() + decodeAudioData())
static/mobile/app.js lines 262-278 (openTrack - no memory guard before createAudioEngine)
static/js/player.js lines 879-891 (desktop guard, for reference)
Bug 2 - Stuck on "Preparing audio" after reload (symptom)
After WebKit kills and reloads the tab, loadLibrary() auto-selects the first track:
// static/mobile/app.js line 893
if (!state.current && state.tracks.length) state.current = state.tracks[0];
This sets a raw track card into state.current (with loading: false, error: null) but never calls openTrack(), so engineReady stays false. The preparing condition on line 428 evaluates to true indefinitely:
const preparing = !!state.current && !state.current.loading && !state.current.error && !ready;
Pressing Load manually calls openTrack() and clears the state. That is why the workaround works.
Reproduction
- Open StemDeck mobile on a phone browser (iOS Safari or Android Chrome)
- Open the Library tab, press Load on any track
- On longer tracks or older devices: blank white page briefly, page reloads ("This website was reloaded because of a problem" on iOS)
- After reload: player shows "Preparing audio..." and never progresses
Proposed fix
- Bug 1: Add a decoded-size estimate before calling
createAudioEngine() in openTrack(). If it exceeds a mobile-appropriate threshold (e.g. 150 MB), fall back to sequential decoding or <audio> element streaming.
- Bug 2: Remove the auto-select on line 893, or only set
state.current when also triggering openTrack().
Summary
Two related bugs triggered when pressing Load in the mobile library view, reported by @goermezer in #216.
Bug 1 - WebKit tab crash (root cause)
openTrack()callscreateAudioEngine(), which fetches all stems in parallel viares.arrayBuffer()then decodes each withdecodeAudioData(). This holds both the compressed MP3 and the decoded PCM in memory simultaneously for all stems at once.Rough memory for a 5-minute, 4-stem track:
Mobile browser tabs get killed at 256-512 MB on most devices. The desktop player (
player.js) already has aMAX_ENGINE_DECODED_BYTES = 1.2e9guard with a streaming fallback - the mobile engine has no equivalent.Relevant files:
static/js/audioEngine.jslines 43-63 (parallelarrayBuffer()+decodeAudioData())static/mobile/app.jslines 262-278 (openTrack- no memory guard beforecreateAudioEngine)static/js/player.jslines 879-891 (desktop guard, for reference)Bug 2 - Stuck on "Preparing audio" after reload (symptom)
After WebKit kills and reloads the tab,
loadLibrary()auto-selects the first track:This sets a raw track card into
state.current(withloading: false,error: null) but never callsopenTrack(), soengineReadystaysfalse. Thepreparingcondition on line 428 evaluates totrueindefinitely:Pressing Load manually calls
openTrack()and clears the state. That is why the workaround works.Reproduction
Proposed fix
createAudioEngine()inopenTrack(). If it exceeds a mobile-appropriate threshold (e.g. 150 MB), fall back to sequential decoding or<audio>element streaming.state.currentwhen also triggeringopenTrack().