Skip to content

feat(mobile): chunked audio engine - faster start, no RAM limit #236

Description

@thcp

Problem

The mobile player currently:

  • Downloads all stems as MP3 files and decodes them entirely into RAM before playback starts
  • Holds up to ~420 MB for a 5-minute 4-stem track
  • Crashes or shows an error for tracks longer than ~14 minutes (600 MB guard)
  • Makes users wait for the full download + decode before hearing anything

Proposed solution

A chunked audio engine that fetches WAV stems in 10-second windows via HTTP Range requests and chains AudioBufferSourceNodes back-to-back on the AudioContext clock.

Key properties:

  • Audio starts after the first chunk downloads (~7 MB for 4 stems on WiFi) instead of the full file
  • Peak RAM: ~28 MB (2 chunks x 4 stems) vs. up to 420 MB
  • No track length limit (removes the 14-minute cap)
  • Same glitch-free behavior as the current engine (no streaming elements, no HTTP/1.1 connection-cap underruns)

Why WAV range requests work here:

  • Backend: Starlette's FileResponse already handles Range headers natively - zero backend changes needed
  • WAV PCM is uncompressed, so byte offsets map directly to audio positions (no frame boundary issues like MP3)
  • Confirmed format: 16-bit PCM, 44100 Hz, stereo (pcm_s16le) - consistent output from Demucs pipeline

Implementation scope

Two files, no backend changes:

  1. New static/js/chunkedAudioEngine.js - same interface as createAudioEngine so the rest of the app needs no changes

    • WAV header parser (Range fetch of first 1024 bytes)
    • PCM deinterleave to AudioBuffer (typed array fast path for stereo 16-bit)
    • Fetch + decode chunks in parallel across all stems
    • RAF-driven lookahead scheduler (pre-fetches next chunk while current plays)
    • Clean seek: flush scheduled nodes, fetch from new position
  2. static/mobile/app.js - three-line change in openTrack():

    • Import createChunkedAudioEngine instead of createAudioEngine
    • Use WAV URLs (remove the .replace(.wav -> .mp3) call)
    • Remove the 600 MB guard block

Complexity estimate

3-5 days for a solid implementation. The main complexity is the RAF-driven scheduler, multi-stem coordination (all stems' chunk N must be ready before any starts), and seeking into an uncached chunk position.

Related

Discussed in PR #235 (mobile load OOM fixes). This is the follow-up improvement once the immediate OOM crash fix is merged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions