import { AudioContext , IAudioNode , IAudioDestinationNode } from 'standardized-audio-context' ; import { Movie } from '../movie' ; import { subscribe } from '../event' ; import { applyOptions , val } from '../util' ; import { Base , BaseOptions } from './base' ; type Constructor < T > = new ( ... args ) => T ; interface AudioSource extends Base { readonly source : HTMLMediaElement ; readonly audioNode : IAudioNode < AudioContext > ; playbackRate : number ; /** The audio source node for the media */ sourceStartTime : number ; } interface AudioSourceOptions extends BaseOptions { source : HTMLMediaElement ; sourceStartTime ? : number ; muted ? : boolean ; volume ? : number ; playbackRate : number ; onload ? : ( source : HTMLMediaElement , options : AudioSourceOptions ) => void ; } mixin function AudioSourceMixin < OptionsSuperclass extends BaseOptions > ( superclass ) { type MixedAudioSourceOptions = OptionsSuperclass & AudioSourceOptions ; class MixedAudioSource extends superclass { /**
         * The raw html media element
         */ readonly source : HTMLMediaElement ; private __startTime : number ; private _audioNode : IAudioNode < AudioContext > ; private _sourceStartTime : number ; private _unstretchedDuration : number ; private _playbackRate : number ; private _initialized : boolean ; private _connectedToDestination : boolean ; param options param options . source param options . onload param options . sourceStartTime param options . duration param options . muted param options . volume param options . playbackRate constructor ( options ) { const onload = options . onload ; delete options . onload ; super ( options ) ; this . _initialized = false ; this . _sourceStartTime = options . sourceStartTime || 0 ; applyOptions ( options , this ) ; const load = ( ) => { if ( ( options . duration || ( this . source . duration - this . sourceStartTime ) ) < 0 ) throw new Error ( 'Invalid options.duration or options.sourceStartTime' ) ; this . _unstretchedDuration = options . duration || ( this . source . duration - this . sourceStartTime ) ; this . duration = this . _unstretchedDuration / ( this . playbackRate ) ; onload && onload . bind ( this ) ( this . source , options ) ; } ; if ( this . source . readyState >= 2 ) load ( ) ; else this . source . addEventListener ( 'loadedmetadata' , load ) ; this . source . addEventListener ( 'durationchange' , ( ) => { this . duration = options . duration || ( this . source . duration - this . sourceStartTime ) ; } ) ; } attach ( movie ) { super . attach ( movie ) ; subscribe ( movie , 'movie.seek' , ( ) => { if ( this . currentTime < 0 || this . currentTime >= this . duration ) return ; this . source . currentTime = this . currentTime + this . sourceStartTime ; } ) ; subscribe ( movie , 'movie.audiodestinationupdate' , event => { if ( this . _connectedToDestination ) { this . audioNode . disconnect ( movie . actx . destination ) ; this . audioNode . connect ( event . destination ) ; } } ) ; this . _audioNode = this . audioNode || movie . actx . createMediaElementSource ( this . source ) ; const oldConnect = this . _audioNode . connect . bind ( this . audioNode ) ; this . _audioNode . connect = < T extends IAudioDestinationNode < AudioContext > > ( destination , outputIndex ? , inputIndex ? ) : AudioNode => { this . _connectedToDestination = destination === movie . actx . destination ; return oldConnect ( destination , outputIndex , inputIndex ) ; } ; const oldDisconnect = this . _audioNode . disconnect . bind ( this . audioNode ) ; this . _audioNode . disconnect = < T extends IAudioDestinationNode < AudioContext > > ( destination ? , output ? , input ? ) : AudioNode => { if ( this . _connectedToDestination && destination === movie . actx . destination ) this . _connectedToDestination = false ; return oldDisconnect ( destination , output , input ) ; } ; this . audioNode . connect ( movie . actx . destination ) ; } detach ( ) { this . audioNode . disconnect ( this . movie . actx . destination ) ; } start ( ) { this . source . currentTime = this . currentTime + this . sourceStartTime ; this . source . play ( ) ; } render ( ) { super . render ( ) ; this . source . muted = val ( this , 'muted' , this . currentTime ) ; this . source . volume = val ( this , 'volume' , this . currentTime ) ; this . source . playbackRate = val ( this , 'playbackRate' , this . currentTime ) ; } stop ( ) { this . source . pause ( ) ; } /**
         * The audio source node for the media
         */ get audioNode ( ) { return this . _audioNode ; } get playbackRate ( ) { return this . _playbackRate ; } set playbackRate ( value ) { this . _playbackRate = value ; if ( this . _unstretchedDuration !== undefined ) this . duration = this . _unstretchedDuration / value ; } get startTime ( ) { return this . __startTime ; } set startTime ( val ) { this . __startTime = val ; if ( this . _initialized ) { const mediaProgress = this . movie . currentTime - this . startTime ; this . source . currentTime = this . sourceStartTime + mediaProgress ; } } set sourceStartTime ( val ) { this . _sourceStartTime = val ; if ( this . _initialized ) { const mediaProgress = this . movie . currentTime - this . startTime ; this . source . currentTime = mediaProgress + this . sourceStartTime ; } } /**
         * Time in the media at which the layer starts
         */ get sourceStartTime ( ) { return this . _sourceStartTime ; } getDefaultOptions ( ) { return { ... superclass . prototype . getDefaultOptions ( ) , source : undefined , sourceStartTime : 0 , duration : undefined , muted : false , volume : 1 , playbackRate : 1 } ; } } return MixedAudioSource ; } export { AudioSource , AudioSourceOptions , AudioSourceMixin } ;