import { getElement , writeTask } from '@stencil/core' ; import { Disposal } from '../../../utils/Disposal' ; import { listen } from '../../../utils/dom' ; import { createStencilHook , wrapStencilHook } from '../../../utils/stencil' ; import { isUndefined } from '../../../utils/unit' ; import { MediaProviderAdapter } from '../../providers/MediaProvider' ; import { PROVIDER_CACHE_KEY } from '../../providers/ProviderConnect' ; import { MediaPlayer } from './MediaPlayer' ; import { STATE_CHANGE_EVENT , StateChange } from './PlayerDispatcher' ; import { LOAD_START_EVENT } from './PlayerEvents' ; import { initialState , isWritableProp , PlayerProp , shouldPropResetOnMediaChange , WritableProps , } from './PlayerProps' ; const immediateAdapterCall = new Set < PlayerProp > ( [ 'currentTime' , 'paused' ] ) ; export type SafeAdapterCall = < P extends keyof WritableProps > ( prop : P , method : keyof MediaProviderAdapter ) => Promise < void > ; export function withPlayerScheduler ( player ) { const el = getElement ( player ) ; const disposal = new Disposal ( ) ; const cache = new Map < PlayerProp , unknown > ( ) ; function initCache ( ) { ( Object . keys ( initialState ) as PlayerProp [ ] ) . forEach ( prop => { cache . set ( prop , player [ prop ] ) ; } ) ; } let adapterCalls = [ ] ; async function flushAdapterCalls ( ) { const adapter = await player . adapter ; if ( isUndefined ( adapter ) ) return ; for ( let i = 0 ; i < adapterCalls . length ; i += 1 ) { await adapterCalls [ i ] ( adapter ) ; } adapterCalls = [ ] ; } let hasMediaChanged = false ; function onMediaChange ( e ? ) { e ?. stopImmediatePropagation ( ) ; if ( ! hasMediaChanged ) { hasMediaChanged = true ; return ; } adapterCalls = [ ] ; writeTask ( ( ) => { ( Object . keys ( initialState ) as PlayerProp [ ] ) . filter ( shouldPropResetOnMediaChange ) . forEach ( prop => { player [ prop as keyof MediaPlayer ] = initialState [ prop ] ; } ) ; } ) ; } async function onStateChange ( event ) { event . stopImmediatePropagation ( ) ; const { by , prop , value } = event . detail ; if ( ! isWritableProp ( prop ) ) { player . logger ?. warn ( `${ by . nodeName } tried to change \`${ prop }\` but it is readonly.` ) ; return ; } if ( ! player . playbackStarted && immediateAdapterCall . has ( prop ) ) { const adapter = await player . adapter ; if ( prop === 'paused' && ! value ) { adapter ?. play ( ) ; } if ( prop === 'currentTime' ) { adapter ?. play ( ) ; adapter ?. setCurrentTime ( value as number ) ; } } writeTask ( ( ) => { player [ prop as keyof MediaPlayer ] = value ; } ) ; } const { onProviderDisconnect } = player ; player . onProviderDisconnect = function ( ) { onMediaChange ( ) ; if ( onProviderDisconnect ) onProviderDisconnect . call ( player ) ; } ; createStencilHook ( player , ( ) => { initCache ( ) ; disposal . add ( listen ( el , LOAD_START_EVENT , onMediaChange ) ) ; disposal . add ( listen ( el , STATE_CHANGE_EVENT , onStateChange ) ) ; } , ( ) => { cache . clear ( ) ; disposal . empty ( ) ; } ) ; wrapStencilHook ( player , 'componentWillRender' , async ( ) => { if ( player . playbackReady && adapterCalls . length > 0 ) await flushAdapterCalls ( ) ; } ) ; function isAdapterCallRequired < P extends keyof WritableProps > ( prop , value ) { return value !== player [ PROVIDER_CACHE_KEY ] ?. get ( prop ) ; } return async function safeAdapterCall ( prop , method ) { if ( ! isAdapterCallRequired ( prop , player [ prop ] ) ) return ; const value = player [ prop ] ; const safeCall = async ( adapter ? ) => { try { await adapter ?. [ method ] ?. ( value ) ; } catch ( e ) { el . dispatchEvent ( new CustomEvent ( 'vmError' , { detail : e } ) ) ; } } ; if ( player . playbackReady ) { await safeCall ( await player . adapter ) ; } else { adapterCalls . push ( safeCall ) ; } } ; }