Skip to main content

loadStream

Load a long audio file as a stream instead of decoding it into memory. Reach for it for podcasts, audiobooks, radio and hour-long background music.

loadStream(id: string, url: string, options?: StreamOptions): Promise<void>;

loadSound fetches the whole file and decodes it into an AudioBuffer. Sprites, overlapping instances and sample-accurate scheduling need that, but it is the wrong shape for a podcast. An hour of stereo audio at 44.1 kHz takes about 1.3 GB once decoded, and nothing plays until the download and the decode have both finished.

loadStream takes the other route. An HTMLAudioElement fetches the file as it plays, and a MediaElementAudioSourceNode puts the result into the same audio graph your buffered sounds use. Master volume, master pan and the master limiter still apply. With the default preload: 'metadata', only the duration and headers are fetched before the promise resolves.

Parameters​

ParameterTypeDefaultDescription
idstringrequiredThe id you will address the stream by, like any other sound.
urlstringrequiredUrl of the audio file.
optionsStreamOptions{}Initial volume, pan, loop, playback rate, start time, progress tracking and preload strategy.

StreamOptions​

PropertyTypeDefaultDescription
volumenumberconfig defaultVolume0 to 1.
pannumberconfig defaultPan-1 (left) to 1 (right). Stereo panning only.
loopbooleanconfig loopSoundsStart over at the end.
playbackRatenumberconfig defaultPlaybackRate0.25 to 4.
startTimenumberconfig defaultStartTimeSecond to start from when play() starts the stream from stopped.
trackProgressbooleanconfig trackProgressDispatch progress events while playing.
preload'none' | 'metadata' | 'auto''metadata'How much the browser fetches before playback. Leave it on metadata for long files.

Returns​

Promise<void>: resolves once the browser has the file's metadata. It rejects when the element reports a load error.

Example​

import { SoundHub, SoundEventsEnum } from 'soundhub';

const soundHub = new SoundHub();

await soundHub.loadStream('episode-42', '/audio/episode-42.mp3', {
volume: 0.8,
trackProgress: true,
});

soundHub.play('episode-42');

// The usual methods work on a stream
soundHub.setPlaybackRate('episode-42', 1.5);
soundHub.seek('episode-42', 1800); // half an hour in
soundHub.setSoundVolume('episode-42', 0.4);

soundHub.addEventListener(SoundEventsEnum.PROGRESS, (event) => {
progressBar.value = event.progressInfo!.progress;
}, { soundId: 'episode-42' });

Drawing a loading bar​

getStreamElement hands you the media element, which knows how much of the file has arrived:

const element = soundHub.getStreamElement('episode-42');

if (element?.buffered.length) {
const loadedUpTo = element.buffered.end(element.buffered.length - 1);
bufferBar.style.width = `${(loadedUpTo / soundHub.getDuration('episode-42')) * 100}%`;
}

Try it​

The demo streams a short track so the page stays quick to load, but the code path is the same for a two-hour recording. The darker bar behind the playhead shows how much of the file has been downloaded so far.

Try it

Stream a long file

Idle
0:00 / 0:00
1.00×
Stateloading metadata
Buffered0:00

The lighter bar is what the browser has downloaded so far. A stream starts playing before the file is complete.

Code
Your clicks show up here as soundhub calls

What a stream cannot do​

Anything that needs random access to the samples, because they are never all in memory at once:

FeatureOn a stream
setSoundSprite and playSpriteThrows an error. Use loadSound for sprite sheets.
overlapNot available. One id is one playing stream.
seamlessLoopNot available. The browser handles looping.
loop_completed eventNever fires, and maxLoops has nothing to count.
setSpatialPositionDoes nothing. A stream has a stereo panner only.

Playback, seeking, volume, mute, fades, stereo panning, playback rate, looping, getSoundState and progress events behave as they do for a buffered sound.

Good to know​

  • An id that already exists, as a sound or a stream, is skipped and the promise resolves without loading anything.
  • A loaded event with the duration is dispatched when the stream is ready. There is no loading event, and getLoadState only reports loaded once the promise has resolved.
  • The media element gets crossOrigin from your SoundHubConfig, and 'anonymous' when that is not set. A file from another domain has to send Access-Control-Allow-Origin. Without it the browser will not route the audio through Web Audio, and you hear silence rather than get an error.
  • A live stream or a file without a known length reports a duration of 0, so a progress bar does not divide by Infinity.
  • play() on a stream returns undefined rather than a Sound. Errors that happen during playback are stored for getLastError and dispatched as error events.

See also​