70 lines
2.6 KiB
JavaScript
70 lines
2.6 KiB
JavaScript
/* One lifetime owner for the player's realtime AudioContext and media sources. */
|
|
(function (root) {
|
|
'use strict';
|
|
|
|
function create(options = {}) {
|
|
const getConstructor = options.getConstructor || (() => root.AudioContext || root.webkitAudioContext);
|
|
let context = null;
|
|
const sources = new Map();
|
|
const routes = new Map();
|
|
const failed = new Map();
|
|
|
|
function getContext(contextOptions) {
|
|
if (context) return context;
|
|
const Constructor = getConstructor();
|
|
if (typeof Constructor !== 'function') throw new Error('Web Audio is unavailable');
|
|
context = contextOptions === undefined ? new Constructor() : new Constructor(contextOptions);
|
|
return context;
|
|
}
|
|
|
|
function source(element) {
|
|
if (!element) throw new TypeError('A media element is required');
|
|
if (sources.has(element)) return sources.get(element);
|
|
if (failed.has(element)) throw failed.get(element);
|
|
try {
|
|
const created = getContext().createMediaElementSource(element);
|
|
sources.set(element, created);
|
|
return created;
|
|
} catch (error) {
|
|
// MediaElementSource cannot be undone. Remember failure so a later
|
|
// route change can never attempt a second source for this element.
|
|
failed.set(element, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function connect(element, destination) {
|
|
const mediaSource = source(element);
|
|
if (!destination) throw new TypeError('An audio destination is required');
|
|
if (routes.get(element) === destination) return mediaSource;
|
|
if (routes.has(element)) mediaSource.disconnect();
|
|
mediaSource.connect(destination);
|
|
routes.set(element, destination);
|
|
return mediaSource;
|
|
}
|
|
|
|
function routeAll(elements, destination) {
|
|
for (const element of elements || []) connect(element, destination);
|
|
}
|
|
|
|
function snapshot() {
|
|
return {
|
|
contextCreated: !!context,
|
|
sampleRate: context?.sampleRate || null,
|
|
sourceCount: sources.size,
|
|
failedCount: failed.size,
|
|
routedCount: routes.size,
|
|
};
|
|
}
|
|
|
|
return { context: getContext, source, connect, routeAll, snapshot };
|
|
}
|
|
|
|
const singleton = create();
|
|
const api = { create, context: (...args) => singleton.context(...args), source: (...args) => singleton.source(...args),
|
|
connect: (...args) => singleton.connect(...args), routeAll: (...args) => singleton.routeAll(...args),
|
|
snapshot: () => singleton.snapshot() };
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
else root.AudioGraph = api;
|
|
})(typeof window !== 'undefined' ? window : globalThis);
|