import { Ticker } from '@pixi/ticker' ; import { EventEmitter } from '@pixi/utils' ; import { IMediaInstance } from '../interfaces/IMediaInstance' ; import { PlayOptions } from '../Sound' ; import { HTMLAudioMedia } from './HTMLAudioMedia' ; import { Filter } from '../filters/Filter' ; let id = 0 ; memberof extends PIXI . util . EventEmitter class HTMLAudioInstance extends EventEmitter implements IMediaInstance { /** Extra padding, in seconds, to deal with low-latecy of HTMLAudio. */ public static readonly PADDING : number = 0.1 ; /** The current unique ID for this instance. */ public readonly id : number ; /** The instance of the Audio element. */ private _source : HTMLAudioElement ; /** The instance of the Audio media element. */ private _media : HTMLAudioMedia ; /** Playback rate, where 1 is 100%. */ private _end : number ; /** Current instance paused state. */ private _paused : boolean ; /** Current instance muted state. */ private _muted : boolean ; /** Current actual paused state. */ private _pausedReal : boolean ; /** Total length of the audio. */ private _duration : number ; /** Playback rate, where 1 is 100%. */ private _start : number ; /** `true` if the audio is actually playing. */ private _playing : boolean ; /** Volume for the instance. */ private _volume : number ; /** Speed for the instance. */ private _speed : number ; /** `true` for looping the playback */ private _loop : boolean ; param parent constructor ( parent ) { super ( ) ; this . id = id ++ ; this . init ( parent ) ; } param name param value public set ( name , value ) { if ( this [ name ] === undefined ) { throw new Error ( `Property with name ${ name } does not exist.` ) ; } else { switch ( name ) { case 'speed' : this . speed = value as number ; break ; case 'volume' : this . volume = value as number ; break ; case 'paused' : this . paused = value as boolean ; break ; case 'loop' : this . loop = value as boolean ; break ; case 'muted' : this . muted = value as boolean ; break ; } } return this ; } /** The current playback progress from 0 to 1. */ public get progress ( ) : number { const { currentTime } = this . _source ; return currentTime / this . _duration ; } /** Pauses the sound. */ public get paused ( ) : boolean { return this . _paused ; } public set paused ( paused ) { this . _paused = paused ; this . refreshPaused ( ) ; } private private _onPlay ( ) { this . _playing = true ; } private private _onPause ( ) { this . _playing = false ; } param { htmlaudio . HTMLAudioMedia } media public init ( media ) { this . _playing = false ; this . _duration = media . source . duration ; const source = this . _source = media . source . cloneNode ( false ) as HTMLAudioElement ; source . src = media . parent . url ; source . onplay = this . _onPlay . bind ( this ) ; source . onpause = this . _onPause . bind ( this ) ; media . context . on ( 'refresh' , this . refresh , this ) ; media . context . on ( 'refreshPaused' , this . refreshPaused , this ) ; this . _media = media ; } private private _internalStop ( ) { if ( this . _source && this . _playing ) { this . _source . onended = null ; this . _source . pause ( ) ; } } /** Stop the sound playing */ public stop ( ) { this . _internalStop ( ) ; if ( this . _source ) { this . emit ( 'stop' ) ; } } /** Set the instance speed from 0 to 1 */ public get speed ( ) : number { return this . _speed ; } public set speed ( speed ) { this . _speed = speed ; this . refresh ( ) ; } /** Get the set the volume for this instance from 0 to 1 */ public get volume ( ) : number { return this . _volume ; } public set volume ( volume ) { this . _volume = volume ; this . refresh ( ) ; } /** If the sound instance should loop playback */ public get loop ( ) : boolean { return this . _loop ; } public set loop ( loop ) { this . _loop = loop ; this . refresh ( ) ; } /** `true` if the sound is muted */ public get muted ( ) : boolean { return this . _muted ; } public set muted ( muted ) { this . _muted = muted ; this . refresh ( ) ; } /**
     * HTML Audio does not support filters, this is non-functional API.
     */ public get filters ( ) : Filter [ ] { console . warn ( 'HTML Audio does not support filters' ) ; return null ; } public set filters ( _filters ) { console . warn ( 'HTML Audio does not support filters' ) ; } /** Call whenever the loop, speed or volume changes */ public refresh ( ) { const global = this . _media . context ; const sound = this . _media . parent ; this . _source . loop = this . _loop || sound . loop ; const globalVolume = global . volume * ( global . muted ? 0 : 1 ) ; const soundVolume = sound . volume * ( sound . muted ? 0 : 1 ) ; const instanceVolume = this . _volume * ( this . _muted ? 0 : 1 ) ; this . _source . volume = instanceVolume * globalVolume * soundVolume ; this . _source . playbackRate = this . _speed * global . speed * sound . speed ; } /** Handle changes in paused state, either globally or sound or instance */ public refreshPaused ( ) { const global = this . _media . context ; const sound = this . _media . parent ; const pausedReal = this . _paused || sound . paused || global . paused ; if ( pausedReal !== this . _pausedReal ) { this . _pausedReal = pausedReal ; if ( pausedReal ) { this . _internalStop ( ) ; event this . emit ( 'paused' ) ; } else { event this . emit ( 'resumed' ) ; this . play ( { start : this . _source . currentTime , end : this . _end , volume : this . _volume , speed : this . _speed , loop : this . _loop , } ) ; } event property this . emit ( 'pause' , pausedReal ) ; } } /** Start playing the sound/ */ public play ( options ) { const { start , end , speed , loop , volume , muted } = options ; if ( end ) { console . assert ( end > start , 'End time is before start time' ) ; } this . _speed = speed ; this . _volume = volume ; this . _loop = ! ! loop ; this . _muted = muted ; this . refresh ( ) ; if ( this . loop && end !== null ) { console . warn ( 'Looping not support when specifying an "end" time' ) ; this . loop = false ; } this . _start = start ; this . _end = end || this . _duration ; this . _start = Math . max ( 0 , this . _start - HTMLAudioInstance . PADDING ) ; this . _end = Math . min ( this . _end + HTMLAudioInstance . PADDING , this . _duration ) ; this . _source . onloadedmetadata = ( ) => { if ( this . _source ) { this . _source . currentTime = start ; this . _source . onloadedmetadata = null ; this . emit ( 'progress' , start , this . _duration ) ; Ticker . shared . add ( this . _onUpdate , this ) ; } } ; this . _source . onended = this . _onComplete . bind ( this ) ; this . _source . play ( ) ; event this . emit ( 'start' ) ; } private private _onUpdate ( ) { this . emit ( 'progress' , this . progress , this . _duration ) ; if ( this . _source . currentTime >= this . _end && ! this . _source . loop ) { this . _onComplete ( ) ; } } private private _onComplete ( ) { Ticker . shared . remove ( this . _onUpdate , this ) ; this . _internalStop ( ) ; this . emit ( 'progress' , 1 , this . _duration ) ; event this . emit ( 'end' , this ) ; } /** Don't use after this. */ public destroy ( ) { Ticker . shared . remove ( this . _onUpdate , this ) ; this . removeAllListeners ( ) ; const source = this . _source ; if ( source ) { source . onended = null ; source . onplay = null ; source . onpause = null ; this . _internalStop ( ) ; } this . _source = null ; this . _speed = 1 ; this . _volume = 1 ; this . _loop = false ; this . _end = null ; this . _start = 0 ; this . _duration = 0 ; this . _playing = false ; this . _pausedReal = false ; this . _paused = false ; this . _muted = false ; if ( this . _media ) { this . _media . context . off ( 'refresh' , this . refresh , this ) ; this . _media . context . off ( 'refreshPaused' , this . refreshPaused , this ) ; this . _media = null ; } } return public toString ( ) { return `[HTMLAudioInstance id=${ this . id }]` ; } } export { HTMLAudioInstance } ;