Skip to main content

loadStream

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

Load a long audio file as a stream instead of decoding it into memory.

loadSound fetches the whole file and decodes it into an AudioBuffer. That is what sprites, overlapping instances and sample-accurate scheduling need, and it is the wrong shape for a podcast. An hour of stereo audio at 44.1 kHz costs roughly 600 MB once decoded, and nothing plays until the download and the decode have both finished.

loadStream takes the other route. An HTMLAudioElement fetches as it plays, and a MediaElementAudioSourceNode drops the result into the same audio graph your buffered sounds use, so master volume, panning and the master limiter still apply. Only the metadata is read up front, so playback starts within a second on a file of any length.

ParameterTypeDescription
idstringThe id you will address this stream by, like any other sound
urlstringURL of the audio file
optionsStreamOptions (optional)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)
loopbooleanconfig loopSoundsStart over at the end
playbackRatenumberconfig defaultPlaybackRate0.5 to 4
startTimenumberconfig defaultStartTimeSecond to start from
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

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');

// Everything you already know works the same way
soundHub.setPlaybackRate('episode-42', 1.5);
soundHub.seek('episode-42', 1800); // half an hour in
soundHub.setSoundVolume('episode-42', 0.4);
soundHub.fadeIn('episode-42', 2);

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

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 a clear error. Use loadSound for sprite sheets
createNewInstanceNot available. One id is one playing stream
seamlessLoopNot available. The browser handles looping
loop_completed eventNever fires, and maxLoops has nothing to count

The rest behaves exactly as it does for a buffered sound: playback, seeking, volume, mute, fades, panning, spatial position, playback rate, looping, getSoundState, progress events and groups.


Drawing a loading barโ€‹

getStreamElement hands you the media element, which is what knows how much of the file has actually 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}%`;
}

Cross-origin filesโ€‹

The media element is created with crossOrigin set from your SoundHubConfig, which defaults to 'anonymous'. A file served from another domain has to send Access-Control-Allow-Origin. Without it the browser refuses to route the audio through Web Audio, and you get silence rather than an error. Files from your own origin need nothing.


Try itโ€‹

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

0:00stopped ยท loading metadataโ€ฆ0:00