@pixi/sound
Version:
WebAudio API playback library with filters
1 lines • 16.9 kB
Source Map (JSON)
{"version":3,"file":"WebAudioContext.mjs","sources":["../../src/webaudio/WebAudioContext.ts"],"sourcesContent":["import { EventEmitter } from 'pixi.js';\nimport { Filterable } from '../Filterable';\nimport { IMediaContext } from '../interfaces';\n\n/**\n * Main class to handle WebAudio API. There's a simple chain\n * of AudioNode elements: analyser > compressor > context.destination.\n * any filters that are added are inserted between the analyser and compressor nodes\n * @memberof webaudio\n */\nclass WebAudioContext extends Filterable implements IMediaContext\n{\n /**\n * Context Compressor node\n * @readonly\n */\n public compressor: DynamicsCompressorNode;\n\n /**\n * Context Analyser node\n * @readonly\n */\n public analyser: AnalyserNode;\n\n /**\n * Global speed of all sounds\n * @readonly\n */\n public speed: number;\n\n /**\n * Sets the muted state.\n * @default false\n */\n public muted: boolean;\n\n /**\n * Sets the volume from 0 to 1.\n * @default 1\n */\n public volume: number;\n\n /**\n * Handle global events\n * @type {PIXI.EventEmitter}\n */\n public events: EventEmitter;\n\n /** The instance of the AudioContext for WebAudio API. */\n private _ctx: AudioContext;\n\n /** The instance of the OfflineAudioContext for fast decoding audio. */\n private _offlineCtx: OfflineAudioContext;\n\n /** Current paused status */\n private _paused: boolean;\n\n /**\n * Indicated whether audio on iOS has been unlocked, which requires a touchend/mousedown event that plays an\n * empty sound.\n */\n private _locked: boolean;\n\n /** The paused state when blurring the current window */\n private _pausedOnBlur: boolean;\n\n /** Set to false ignore suspending when window is blurred */\n public autoPause = true;\n\n constructor()\n {\n const win: any = window as any;\n const ctx = new WebAudioContext.AudioContext();\n const compressor: DynamicsCompressorNode = ctx.createDynamicsCompressor();\n const analyser: AnalyserNode = ctx.createAnalyser();\n\n // setup the end of the node chain\n analyser.connect(compressor);\n compressor.connect(ctx.destination);\n\n super(analyser, compressor);\n\n this._ctx = ctx;\n // ios11 safari's webkitOfflineAudioContext allows only 44100 Hz sample rate\n //\n // For the sample rate value passed to OfflineAudioContext constructor,\n // all browsers are required to support a range of 8000 to 96000.\n // Reference:\n // https://www.w3.org/TR/webaudio/#dom-offlineaudiocontext-offlineaudiocontext-numberofchannels-length-samplerate\n this._offlineCtx = new WebAudioContext.OfflineAudioContext(1, 2,\n (win.OfflineAudioContext) ? Math.max(8000, Math.min(96000, ctx.sampleRate)) : 44100);\n\n this.compressor = compressor;\n this.analyser = analyser;\n this.events = new EventEmitter();\n\n // Set the defaults\n this.volume = 1;\n this.speed = 1;\n this.muted = false;\n this.paused = false;\n\n this._locked = ctx.state === 'suspended' && ('ontouchstart' in globalThis || 'onclick' in globalThis);\n\n // Listen for document level clicks to unlock WebAudio. See the _unlock method.\n if (this._locked)\n {\n this._unlock(); // When played inside of a touch event, this will enable audio on iOS immediately.\n this._unlock = this._unlock.bind(this);\n document.addEventListener('mousedown', this._unlock, true);\n document.addEventListener('touchstart', this._unlock, true);\n document.addEventListener('touchend', this._unlock, true);\n }\n\n this.onFocus = this.onFocus.bind(this);\n this.onBlur = this.onBlur.bind(this);\n globalThis.addEventListener('focus', this.onFocus);\n globalThis.addEventListener('blur', this.onBlur);\n }\n\n /** Handle mobile WebAudio context resume */\n private onFocus(): void\n {\n if (!this.autoPause)\n {\n return;\n }\n // Safari uses the non-standard \"interrupted\" state in some cases\n // such as when the app loses focus because the screen is locked\n // or when the user switches to another app.\n const state = this._ctx.state as 'suspended' | 'interrupted';\n\n if (state === 'suspended' || state === 'interrupted' || !this._locked)\n {\n this.paused = this._pausedOnBlur;\n this.refreshPaused();\n }\n }\n\n /** Handle mobile WebAudio context suspend */\n private onBlur(): void\n {\n if (!this.autoPause)\n {\n return;\n }\n if (!this._locked)\n {\n this._pausedOnBlur = this._paused;\n this.paused = true;\n this.refreshPaused();\n }\n }\n\n /**\n * Try to unlock audio on iOS. This is triggered from either WebAudio plugin setup (which will work if inside of\n * a `mousedown` or `touchend` event stack), or the first document touchend/mousedown event. If it fails (touchend\n * will fail if the user presses for too long, indicating a scroll event instead of a click event.\n *\n * Note that earlier versions of iOS supported `touchstart` for this, but iOS9 removed this functionality. Adding\n * a `touchstart` event to support older platforms may preclude a `mousedown` even from getting fired on iOS9, so we\n * stick with `mousedown` and `touchend`.\n */\n private _unlock(): void\n {\n if (!this._locked)\n {\n return;\n }\n this.playEmptySound();\n if (this._ctx.state === 'running')\n {\n document.removeEventListener('mousedown', this._unlock, true);\n document.removeEventListener('touchend', this._unlock, true);\n document.removeEventListener('touchstart', this._unlock, true);\n this._locked = false;\n }\n }\n\n /**\n * Plays an empty sound in the web audio context. This is used to enable web audio on iOS devices, as they\n * require the first sound to be played inside of a user initiated event (touch/click).\n */\n public playEmptySound(): void\n {\n const source = this._ctx.createBufferSource();\n\n source.buffer = this._ctx.createBuffer(1, 1, 22050);\n source.connect(this._ctx.destination);\n source.start(0, 0, 0);\n if (source.context.state === 'suspended')\n {\n (source.context as AudioContext).resume();\n }\n }\n\n /**\n * Get AudioContext class, if not supported returns `null`\n * @type {AudioContext}\n * @readonly\n */\n public static get AudioContext(): typeof AudioContext\n {\n const win: any = window as any;\n\n return (\n win.AudioContext\n || win.webkitAudioContext\n || null\n );\n }\n\n /**\n * Get OfflineAudioContext class, if not supported returns `null`\n * @type {OfflineAudioContext}\n * @readonly\n */\n public static get OfflineAudioContext(): typeof OfflineAudioContext\n {\n const win: any = window as any;\n\n return (\n win.OfflineAudioContext\n || win.webkitOfflineAudioContext\n || null\n );\n }\n\n /** Destroy this context. */\n public destroy(): void\n {\n super.destroy();\n\n const ctx: any = this._ctx as any;\n // check if browser supports AudioContext.close()\n\n if (typeof ctx.close !== 'undefined')\n {\n ctx.close();\n }\n globalThis.removeEventListener('focus', this.onFocus);\n globalThis.removeEventListener('blur', this.onBlur);\n this.events.removeAllListeners();\n this.analyser.disconnect();\n this.compressor.disconnect();\n this.analyser = null;\n this.compressor = null;\n this.events = null;\n this._offlineCtx = null;\n this._ctx = null;\n }\n\n /**\n * The WebAudio API AudioContext object.\n * @readonly\n * @type {AudioContext}\n */\n public get audioContext(): AudioContext\n {\n return this._ctx;\n }\n\n /**\n * The WebAudio API OfflineAudioContext object.\n * @readonly\n * @type {OfflineAudioContext}\n */\n public get offlineContext(): OfflineAudioContext\n {\n return this._offlineCtx;\n }\n\n /**\n * Pauses all sounds, even though we handle this at the instance\n * level, we'll also pause the audioContext so that the\n * time used to compute progress isn't messed up.\n * @default false\n */\n public set paused(paused: boolean)\n {\n if (paused && this._ctx.state === 'running')\n {\n this._ctx.suspend();\n }\n else if (!paused && this._ctx.state === 'suspended')\n {\n this._ctx.resume();\n }\n this._paused = paused;\n }\n public get paused(): boolean\n {\n return this._paused;\n }\n\n /** Emit event when muted, volume or speed changes */\n public refresh(): void\n {\n this.events.emit('refresh');\n }\n\n /** Emit event when muted, volume or speed changes */\n public refreshPaused(): void\n {\n this.events.emit('refreshPaused');\n }\n\n /**\n * Toggles the muted state.\n * @return The current muted state.\n */\n public toggleMute(): boolean\n {\n this.muted = !this.muted;\n this.refresh();\n\n return this.muted;\n }\n\n /**\n * Toggles the paused state.\n * @return The current muted state.\n */\n public togglePause(): boolean\n {\n this.paused = !this.paused;\n this.refreshPaused();\n\n return this._paused;\n }\n\n /**\n * Decode the audio data\n * @param arrayBuffer - Buffer from loader\n * @param callback - When completed, error and audioBuffer are parameters.\n */\n public decode(arrayBuffer: ArrayBuffer, callback: (err?: Error, buffer?: AudioBuffer) => void): void\n {\n const handleError = (err: Error) =>\n {\n callback(new Error(err?.message || 'Unable to decode file'));\n };\n const result = this._offlineCtx.decodeAudioData(\n arrayBuffer, (buffer: AudioBuffer) =>\n {\n callback(null, buffer);\n },\n handleError,\n );\n // Reference: https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData\n // decodeAudioData return value: Void, or a Promise object that fulfills with the decodedData.\n\n if (result)\n {\n result.catch(handleError);\n }\n }\n}\n\nexport { WebAudioContext };\n"],"names":[],"mappings":"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"}