import Emitter from "../emitter" ; import { clamp } from "../utils" ; import { MEDIA_STATUS } from "../constants" ; import { MediaStatus } from "../types" ; export default class ElementSource { _emitter : Emitter ; _context : AudioContext ; _source : AudioNode ; _destination : AudioNode ; _audio : HTMLAudioElement ; _stalled : boolean ; _status : MediaStatus ; on ( eventType , cb ) { return this . _emitter . on ( eventType , cb ) ; } constructor ( context , destination ) { this . _emitter = new Emitter ( ) ; this . _context = context ; this . _destination = destination ; this . _audio = document . createElement ( "audio" ) ; this . _audio . crossOrigin = "anonymous" ; this . _stalled = false ; this . _status = MEDIA_STATUS . STOPPED ; this . _audio . addEventListener ( "suspend" , ( ) => { this . _setStalled ( true ) ; } ) ; this . _audio . addEventListener ( "durationchange" , ( ) => { this . _emitter . trigger ( "loaded" ) ; this . _setStalled ( false ) ; } ) ; this . _audio . addEventListener ( "ended" , ( ) => { this . _emitter . trigger ( "ended" ) ; this . _setStatus ( MEDIA_STATUS . STOPPED ) ; } ) ; this . _audio . addEventListener ( "timeupdate" , ( ) => { this . _emitter . trigger ( "positionChange" ) ; } ) ; this . _audio . addEventListener ( "error" , ( e ) => { switch ( this . _audio . error ! . code ) { case 1 : console . error ( "MEDIA_ERR_ABORTED" , e ) ; break ; case 2 : console . error ( "MEDIA_ERR_NETWORK" , e ) ; break ; case 3 : console . error ( "PIPELINE_ERROR_DECODE" , e ) ; break ; case 4 : console . error ( "MEDIA_ERR_SRC_NOT_SUPPORTED" , e ) ; break ; } this . _emitter . trigger ( "ended" ) ; this . _setStatus ( MEDIA_STATUS . STOPPED ) ; } ) ; this . _source = this . _context . createMediaElementSource ( this . _audio ) ; this . _source . connect ( destination ) ; } _setStalled ( stalled ) { this . _stalled = stalled ; this . _emitter . trigger ( "stallChanged" ) ; } disconnect ( ) { this . _source . disconnect ( ) ; } async loadUrl ( url ) { this . _audio . src = url ; } async play ( ) { if ( this . _status !== MEDIA_STATUS . PAUSED ) { this . seekToTime ( 0 ) ; } try { await this . _audio . play ( ) ; } catch ( err ) { } this . _setStatus ( MEDIA_STATUS . PLAYING ) ; } pause ( ) { this . _audio . pause ( ) ; this . _setStatus ( MEDIA_STATUS . PAUSED ) ; } stop ( ) { this . _audio . pause ( ) ; this . _audio . currentTime = 0 ; this . _setStatus ( MEDIA_STATUS . STOPPED ) ; } seekToTime ( time ) { this . _audio . currentTime = clamp ( time , 0 , this . getDuration ( ) ) ; this . _emitter . trigger ( "positionChange" ) ; } getStalled ( ) { return this . _stalled ; } getStatus ( ) { return this . _status ; } getDuration ( ) { const { duration } = this . _audio ; return isNaN ( duration ) || duration === Infinity ? 0 : duration ; } getTimeElapsed ( ) { return this . _audio . currentTime ; } _setStatus ( status ) { this . _status = status ; this . _emitter . trigger ( "statusChange" ) ; } dispose ( ) { this . stop ( ) ; this . _emitter . dispose ( ) ; } }