web audio decoding #
decodeAudioData costs memory by duration, not by file size #
a compressed file is small; the PCM the browser produces from it is not. stereo float32 at 44.1 kHz is about 21 MB per minute of audio, and the decoder's working memory on top of that makes the transient several times larger. measured in chromium, the browser process tree grows by roughly 100 MB per minute of audio for the duration of the decode, then frees it: a 4-minute m4a (5.8 MB) cost about +550 MB, an hour-long one (85 MB) about +6.9 GB. that transient, not the retained buffer, is what kills a mobile tab.
so a waveform-from-file feature needs a cap on duration, read cheaply
from the container header first (an <audio> element's loadedmetadata
answered in under 80 ms for the 85 MB file), and a size cap only as a
backstop.
decoding through a low-rate OfflineAudioContext shrinks what is kept, not what is spent #
new OfflineAudioContext(1, 1, 8000).decodeAudioData(buf) returns a buffer
resampled to 8 kHz, so the retained PCM is a fraction of the full-rate
one (the same hour-long file: 220 MB kept instead of 1.2 GB). but the
browser still decodes at the file's own rate before resampling, so the
transient stays the same order (+5.5 GB vs +6.9 GB above) and the decode
takes longer (3.1 s vs 2.0 s). use it to keep long-lived buffers small;
do not use it as the reason to raise a duration cap.
the numberOfChannels argument to the constructor does not apply to
decoded buffers: a stereo file still decodes to two channels.
test browsers do not share the platform's codecs #
playwright's webkit build fails decodeAudioData on AAC with
EncodingError: Decoding failed, where Safari proper decodes it through
the OS. a headless measurement of AAC or ALAC handling in webkit says
nothing about Safari; only the <audio> metadata path behaved the same.
sources #
- plyr.fm —
frontend/src/lib/audio/probe.ts,peaks.ts; measurement script and numbers in PR #1954 (2026-09-01)