class HighResMetadataTextTrackCueList extends Array < TextTrackCue > implements TextTrackCueList { public addCue ( cue ) { this . push ( cue ) ; } public removeCue ( cue ) { const index = this . findIndex ( ( c ) => c === cue ) ; if ( index < 0 ) { return ; } this . splice ( index , 1 ) ; } public getCueById ( id ) { return this . find ( ( c ) => c . id === id ) ?? null ; } } export default class HighResMetadataTextTrack implements TextTrack { private media : HTMLMediaElement ; private all : HighResMetadataTextTrackCueList = new HighResMetadataTextTrackCueList ( ) ; private active : HighResMetadataTextTrackCueList = new HighResMetadataTextTrackCueList ( ) ; private readonly polling_handler : ( ( ) => any ) = this . polling . bind ( this ) ; private polling_id : number | null = null ; public constructor ( media ) { this . media = media ; } public startPolling ( ) { this . polling_id = window . requestAnimationFrame ( this . polling_handler ) ; } public stopPolling ( ) { if ( this . polling_id == null ) { return ; } window . cancelAnimationFrame ( this . polling_id ) ; this . polling_id = null ; } private polling ( ) { const old_active = this . active ; const new_active = this . activeCues ; if ( old_active . length !== new_active . length ) { let event = null ; try { event = new CustomEvent ( 'cuechange' ) ; } catch { event = document . createEvent ( 'CustomEvent' ) ; event . initCustomEvent ( 'cuechange' , false , false , { } ) ; } if ( event != null ) { this . dispatchEvent ( event ) ; if ( this . oncuechange ) { this . oncuechange . call ( this , event ) ; } } } else { for ( let i = 0 ; i < new_active . length ; i ++ ) { if ( old_active [ i ] !== new_active [ i ] ) { let event = null ; try { event = new CustomEvent ( 'cuechange' ) ; } catch { event = document . createEvent ( 'CustomEvent' ) ; event . initCustomEvent ( 'cuechange' , false , false , { } ) ; } if ( event != null ) { this . dispatchEvent ( event ) ; if ( this . oncuechange ) { this . oncuechange . call ( this , event ) ; } break ; } } } } this . polling_id = window . requestAnimationFrame ( this . polling_handler ) ; } public readonly cues : TextTrackCueList = this . all ; public get activeCues ( ) : TextTrackCueList { const in_range_cues = new HighResMetadataTextTrackCueList ( ... this . all . filter ( ( cue ) => { return ( cue . startTime <= this . media . currentTime && this . media . currentTime <= cue . endTime ) ; } ) ) ; in_range_cues . sort ( ( a , b ) => { if ( a . startTime === b . startTime ) { return - ( a . endTime - b . endTime ) ; } else { return ( a . startTime - b . startTime ) ; } } ) ; this . active = in_range_cues ; return this . active ; } public getCueById ( id ) { return this . all . getCueById ( id ) ; } public addCue ( cue ) { this . all . addCue ( cue ) ; } public removeCue ( cue ) { this . all . removeCue ( cue ) ; } public oncuechange : ( ( this : TextTrack , ev : Event ) => any ) | null = null ; public readonly id : string = "" ; public readonly kind : TextTrackKind = "metadata" ; public readonly label : string = "" ; public readonly language : string = "ja-JP" ; public readonly mode : TextTrackMode = "hidden" ; public readonly inBandMetadataTrackDispatchType : string = "" ; public readonly sourceBuffer = null ; private listeners : ( ( this : TextTrack , ev : Event ) => any ) [ ] = [ ] ; public addEventListener ( type , listener ) { this . listeners . push ( listener ) ; } public removeEventListener ( type , listener ) { const index = this . listeners . findIndex ( ( cb ) => cb === listener ) ; if ( index < 0 ) { return ; } this . listeners . splice ( index , 1 ) ; } public dispatchEvent ( ev ) { if ( ev . type !== 'cuechange' ) { return true ; } this . listeners . forEach ( ( listener ) => listener . call ( this , ev ) ) ; return true ; } }