@firstcoders/hls-web-audio
Version:
`@firstcoders/hls-web-audio` plays multiple streamed audio tracks in sync using the Web Audio API. It is designed for stem-like playback where each track can be independently loaded, buffered, scheduled, and mixed.
88 lines (78 loc) • 2.36 kB
JavaScript
import Track from './Track.js';
import Segment from '../io/AudioSegment.js';
import ManifestLoader from '../io/ManifestLoader.js';
/**
* HLS-backed track that loads a manifest and creates schedulable audio segments.
*/
export default class HLS extends Track {
/**
* @param {Object} [options]
* @param {import('../core/AudioController.js').default} [options.controller]
* @param {number} [options.volume=1]
* @param {typeof fetch|null} [options.fetch=null]
* @param {RequestInit} [options.fetchOptions={}]
* @param {number} [options.start=0]
* @param {number} [options.duration]
*/
constructor({
controller,
volume = 1,
fetch = null,
fetchOptions = {},
start = 0,
duration = undefined,
} = {}) {
super({ controller, volume, start, duration });
this.manifestLoader = new ManifestLoader(fetch);
this.fetchOptions = fetchOptions;
}
/**
* Loads an HLS manifest from a URL and builds segment instances from it.
*
* @param {string} src
* @returns {{ promise: Promise<Array<{src: string, duration: number}>>, cancel: Function }}
*/
load(src) {
this.src = src;
const loadHandle = this.manifestLoader.load(src, {
...this.fetchOptions,
headers: {
Accept: 'application/x-mpegURL, application/vnd.apple.mpegurl',
...this.fetchOptions?.headers,
},
});
this.cancel = loadHandle.cancel;
const promise = loadHandle.promise
.then((sources) => {
this.buildSegments(sources);
this.controller?.notify('init', this);
return sources;
})
.catch((error) => {
if (error.name === 'AbortError') return;
this.controller?.notify('error', error);
throw error;
});
return { promise, cancel: this.cancel };
}
/**
* Builds segments from an in-memory manifest payload.
*
* @param {string} manifest
* @param {string} src
*/
loadFromM3u8(manifest, src) {
const sources = ManifestLoader.parse(manifest, src);
this.buildSegments(sources);
}
/**
* Converts manifest entries into audio segment instances.
*
* @param {Array<{src: string, duration: number}>} sources
*/
buildSegments(sources) {
this.stack?.push(
...sources.map((source) => new Segment({ ...source, fetchOptions: this.fetchOptions })),
);
}
}