Extract the shared realtime audio graph owner
This commit is contained in:
69
frontend/audio-graph.js
Normal file
69
frontend/audio-graph.js
Normal file
@@ -0,0 +1,69 @@
|
||||
/* 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);
|
||||
Reference in New Issue
Block a user