import { EventEmitter } from '@pixi/utils' ; import { Filterable } from '../Filterable' ; import { IMediaContext } from '../interfaces' ; memberof class WebAudioContext extends Filterable implements IMediaContext { readonly public compressor : DynamicsCompressorNode ; readonly public analyser : AnalyserNode ; readonly public speed : number ; default public muted : boolean ; default public volume : number ; type { PIXI . utils . EventEmitter } public events : EventEmitter ; /** The instance of the AudioContext for WebAudio API. */ private _ctx : AudioContext ; /** The instance of the OfflineAudioContext for fast decoding audio. */ private _offlineCtx : OfflineAudioContext ; /** Current paused status */ private _paused : boolean ; /**
     * Indicated whether audio on iOS has been unlocked, which requires a touchend/mousedown event that plays an
     * empty sound.
     */ private _unlocked : boolean ; constructor ( ) { const win = window as any ; const ctx = new WebAudioContext . AudioContext ( ) ; const compressor = ctx . createDynamicsCompressor ( ) ; const analyser = ctx . createAnalyser ( ) ; analyser . connect ( compressor ) ; compressor . connect ( ctx . destination ) ; super ( analyser , compressor ) ; this . _ctx = ctx ; this . _offlineCtx = new WebAudioContext . OfflineAudioContext ( 1 , 2 , ( win . OfflineAudioContext ) ? ctx . sampleRate : 44100 ) ; this . _unlocked = false ; this . compressor = compressor ; this . analyser = analyser ; this . events = new EventEmitter ( ) ; this . volume = 1 ; this . speed = 1 ; this . muted = false ; this . paused = false ; if ( ctx . state !== 'running' ) { this . _unlock ( ) ; this . _unlock = this . _unlock . bind ( this ) ; document . addEventListener ( 'mousedown' , this . _unlock , true ) ; document . addEventListener ( 'touchstart' , this . _unlock , true ) ; document . addEventListener ( 'touchend' , this . _unlock , true ) ; } } /**
     * Try to unlock audio on iOS. This is triggered from either WebAudio plugin setup (which will work if inside of
     * a `mousedown` or `touchend` event stack), or the first document touchend/mousedown event. If it fails (touchend
     * will fail if the user presses for too long, indicating a scroll event instead of a click event.
     *
     * Note that earlier versions of iOS supported `touchstart` for this, but iOS9 removed this functionality. Adding
     * a `touchstart` event to support older platforms may preclude a `mousedown` even from getting fired on iOS9, so we
     * stick with `mousedown` and `touchend`.
     */ private _unlock ( ) { if ( this . _unlocked ) { return ; } this . playEmptySound ( ) ; if ( this . _ctx . state === 'running' ) { document . removeEventListener ( 'mousedown' , this . _unlock , true ) ; document . removeEventListener ( 'touchend' , this . _unlock , true ) ; document . removeEventListener ( 'touchstart' , this . _unlock , true ) ; this . _unlocked = true ; } } /**
     * Plays an empty sound in the web audio context.  This is used to enable web audio on iOS devices, as they
     * require the first sound to be played inside of a user initiated event (touch/click).
     */ public playEmptySound ( ) { const source = this . _ctx . createBufferSource ( ) ; source . buffer = this . _ctx . createBuffer ( 1 , 1 , 22050 ) ; source . connect ( this . _ctx . destination ) ; source . start ( 0 , 0 , 0 ) ; if ( source . context . state === 'suspended' ) { ( source . context as AudioContext ) . resume ( ) ; } } type { AudioContext } readonly public static get AudioContext ( ) : typeof AudioContext { const win = window as any ; return ( win . AudioContext || win . webkitAudioContext || null ) ; } type { OfflineAudioContext } readonly public static get OfflineAudioContext ( ) : typeof OfflineAudioContext { const win = window as any ; return ( win . OfflineAudioContext || win . webkitOfflineAudioContext || null ) ; } /** Destroy this context. */ public destroy ( ) { super . destroy ( ) ; const ctx = this . _ctx as any ; if ( typeof ctx . close !== 'undefined' ) { ctx . close ( ) ; } this . events . removeAllListeners ( ) ; this . analyser . disconnect ( ) ; this . compressor . disconnect ( ) ; this . analyser = null ; this . compressor = null ; this . events = null ; this . _offlineCtx = null ; this . _ctx = null ; } readonly type { AudioContext } public get audioContext ( ) : AudioContext { return this . _ctx ; } readonly type { OfflineAudioContext } public get offlineContext ( ) : OfflineAudioContext { return this . _offlineCtx ; } default public set paused ( paused ) { if ( paused && this . _ctx . state === 'running' ) { ( this . _ctx as any ) . suspend ( ) ; } else if ( ! paused && this . _ctx . state === 'suspended' ) { ( this . _ctx as any ) . resume ( ) ; } this . _paused = paused ; } public get paused ( ) : boolean { return this . _paused ; } /** Emit event when muted, volume or speed changes */ public refresh ( ) { this . events . emit ( 'refresh' ) ; } /** Emit event when muted, volume or speed changes */ public refreshPaused ( ) { this . events . emit ( 'refreshPaused' ) ; } return public toggleMute ( ) { this . muted = ! this . muted ; this . refresh ( ) ; return this . muted ; } return public togglePause ( ) { this . paused = ! this . paused ; this . refreshPaused ( ) ; return this . _paused ; } param arrayBuffer param callback public decode ( arrayBuffer , callback ) { const handleError = ( err ) => { callback ( new Error ( err . message || 'Unable to decode file' ) ) ; } ; const result = this . _offlineCtx . decodeAudioData ( arrayBuffer , ( buffer ) => { callback ( null , buffer ) ; } , handleError ) ; if ( result ) { result . catch ( handleError ) ; } } } export { WebAudioContext } ;