UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

258 lines 9.56 kB
import { Observable } from "../../Misc/observable.js"; import { AudioEngineV2 } from "../abstractAudio/audioEngineV2.js"; import { _HasSpatialAudioListenerOptions } from "../abstractAudio/subProperties/abstractSpatialAudioListener.js"; import { _CreateSpatialAudioListener } from "./subProperties/spatialWebAudioListener.js"; import { _WebAudioMainOut } from "./webAudioMainOut.js"; import { _WebAudioUnmuteUI } from "./webAudioUnmuteUI.js"; /** * Creates a new v2 audio engine that uses the WebAudio API. * @param options - The options for creating the audio engine. * @returns A promise that resolves with the created audio engine. */ export async function CreateAudioEngineAsync(options = {}) { const engine = new _WebAudioEngine(options); await engine._init(options); return engine; } const FormatMimeTypes = { aac: "audio/aac", ac3: "audio/ac3", flac: "audio/flac", m4a: "audio/mp4", mp3: 'audio/mpeg; codecs="mp3"', mp4: "audio/mp4", ogg: 'audio/ogg; codecs="vorbis"', wav: "audio/wav", webm: 'audio/webm; codecs="vorbis"', }; /** @internal */ export class _WebAudioEngine extends AudioEngineV2 { /** @internal */ constructor(options = {}) { super(options); this._audioContextStarted = false; this._invalidFormats = new Set(); this._isUsingOfflineAudioContext = false; this._listener = null; this._pauseCalled = false; this._resumeOnInteraction = true; this._resumeOnPause = true; this._resumeOnPauseRetryInterval = 1000; this._resumeOnPauseTimerId = null; this._resumePromise = null; this._listenerAutoUpdate = true; this._listenerMinUpdateTime = 0; this._unmuteUI = null; this._validFormats = new Set(); this._volume = 1; /** @internal */ this.isReadyPromise = new Promise((resolve) => { this._resolveIsReadyPromise = resolve; }); /** @internal */ this.stateChangedObservable = new Observable(); /** @internal */ this.userGestureObservable = new Observable(); this._initAudioContext = async () => { this._audioContext.addEventListener("statechange", this._onAudioContextStateChange); this._mainOut = new _WebAudioMainOut(this); this._mainOut.volume = this._volume; await this.createMainBusAsync("default"); }; this._onAudioContextStateChange = () => { if (this.state === "running") { clearInterval(this._resumeOnPauseTimerId); this._audioContextStarted = true; this._resumePromise = null; } if (this.state === "suspended" || this.state === "interrupted") { if (this._audioContextStarted && this._resumeOnPause && !this._pauseCalled) { clearInterval(this._resumeOnPauseTimerId); this._resumeOnPauseTimerId = setInterval(() => { this.resumeAsync(); }, this._resumeOnPauseRetryInterval); } } this.stateChangedObservable.notifyObservers(this.state); }; this._onUserGesture = async () => { if (this._resumeOnInteraction) { await this._audioContext.resume(); } this.userGestureObservable.notifyObservers(); }; if (typeof options.listenerAutoUpdate === "boolean") { this._listenerAutoUpdate = options.listenerAutoUpdate; } if (typeof options.listenerMinUpdateTime === "number") { this._listenerMinUpdateTime = options.listenerMinUpdateTime; } this._volume = options.volume ?? 1; if (options.audioContext) { this._isUsingOfflineAudioContext = options.audioContext instanceof OfflineAudioContext; this._audioContext = options.audioContext; } else { this._audioContext = new AudioContext(); } if (!options.disableDefaultUI) { this._unmuteUI = new _WebAudioUnmuteUI(this, options.defaultUIParentElement); } } /** @internal */ async _init(options) { this._resumeOnInteraction = typeof options.resumeOnInteraction === "boolean" ? options.resumeOnInteraction : true; this._resumeOnPause = typeof options.resumeOnPause === "boolean" ? options.resumeOnPause : true; this._resumeOnPauseRetryInterval = options.resumeOnPauseRetryInterval ?? 1000; document.addEventListener("click", this._onUserGesture); await this._initAudioContext(); if (_HasSpatialAudioListenerOptions(options)) { this._listener = _CreateSpatialAudioListener(this, this._listenerAutoUpdate, this._listenerMinUpdateTime); this._listener.setOptions(options); } this._resolveIsReadyPromise(); } /** @internal */ get currentTime() { return this._audioContext.currentTime ?? 0; } /** @internal */ get _inNode() { return this._audioContext.destination; } /** @internal */ get mainOut() { return this._mainOut; } /** @internal */ get listener() { return this._listener ?? (this._listener = _CreateSpatialAudioListener(this, this._listenerAutoUpdate, this._listenerMinUpdateTime)); } /** @internal */ get state() { // Always return "running" for OfflineAudioContext so sound `play` calls work while the context is suspended. return this._isUsingOfflineAudioContext ? "running" : this._audioContext.state; } /** @internal */ get volume() { return this._volume; } /** @internal */ set volume(value) { if (this._volume === value) { return; } this._volume = value; if (this._mainOut) { this._mainOut.volume = value; } } /** @internal */ async createBusAsync(name, options = {}) { const module = await import("./webAudioBus.js"); const bus = new module._WebAudioBus(name, this, options); await bus._init(options); return bus; } /** @internal */ async createMainBusAsync(name, options = {}) { const module = await import("./webAudioMainBus.js"); const bus = new module._WebAudioMainBus(name, this); await bus._init(options); return bus; } /** @internal */ async createSoundAsync(name, source, options = {}) { const module = await import("./webAudioStaticSound.js"); const sound = new module._WebAudioStaticSound(name, this, options); await sound._init(source, options); return sound; } /** @internal */ async createSoundBufferAsync(source, options = {}) { const module = await import("./webAudioStaticSound.js"); const soundBuffer = new module._WebAudioStaticSoundBuffer(this); await soundBuffer._init(source, options); return soundBuffer; } /** @internal */ async createStreamingSoundAsync(name, source, options = {}) { const module = await import("./webAudioStreamingSound.js"); const sound = new module._WebAudioStreamingSound(name, this, options); await sound._init(source, options); return sound; } /** @internal */ dispose() { super.dispose(); this._listener?.dispose(); this._listener = null; // Note that OfflineAudioContext does not have a `close` method. if (this._audioContext.state !== "closed" && !this._isUsingOfflineAudioContext) { this._audioContext.close(); } document.removeEventListener("click", this._onUserGesture); this._audioContext.removeEventListener("statechange", this._onAudioContextStateChange); this._unmuteUI?.dispose(); this._unmuteUI = null; } /** @internal */ flagInvalidFormat(format) { this._invalidFormats.add(format); } /** @internal */ isFormatValid(format) { if (this._validFormats.has(format)) { return true; } if (this._invalidFormats.has(format)) { return false; } const mimeType = FormatMimeTypes[format]; if (mimeType === undefined) { return false; } const audio = new Audio(); if (audio.canPlayType(mimeType) === "") { this._invalidFormats.add(format); return false; } this._validFormats.add(format); return true; } /** @internal */ async pauseAsync() { await this._audioContext.suspend(); this._pauseCalled = true; } /** @internal */ resumeAsync() { this._pauseCalled = false; if (this._resumePromise) { return this._resumePromise; } this._resumePromise = this._audioContext.resume(); return this._resumePromise; } /** @internal */ _addMainBus(mainBus) { super._addMainBus(mainBus); } /** @internal */ _removeMainBus(mainBus) { super._removeMainBus(mainBus); } /** @internal */ _addNode(node) { super._addNode(node); } /** @internal */ _removeNode(node) { super._removeNode(node); } /** @internal */ _setAudioParam(audioParam, value) { audioParam.linearRampToValueAtTime(value, this.currentTime + this.parameterRampDuration); } } //# sourceMappingURL=webAudioEngine.js.map