{"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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"}