import { Component , Event , EventEmitter , h , Listen , Method , Prop , State , Watch , } from '@stencil/core' ; import { Disposal } from '../../../utils/Disposal' ; import { listen } from '../../../utils/dom' ; import { loadSDK } from '../../../utils/network' ; import { isString , isUndefined } from '../../../utils/unit' ; import { MediaType } from '../../core/player/MediaType' ; import { PlayerProps } from '../../core/player/PlayerProps' ; import { withComponentRegistry } from '../../core/player/withComponentRegistry' ; import { withPlayerContext } from '../../core/player/withPlayerContext' ; import { MediaCrossOriginOption , MediaFileProvider , MediaPreloadOption , } from '../file/MediaFileProvider' ; import { dashRegex } from '../file/utils' ; import { withProviderConnect } from '../ProviderConnect' ; import { createProviderDispatcher , ProviderDispatcher , } from '../ProviderDispatcher' ; /**
 * Enables loading, playing and controlling
 * [MPEG DASH](https://en.wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP) based media. It
 * uses [`dashjs`](https://github.com/Dash-Industry-Forum/dash.js.md) under the hood.
 *
 * > You don't interact with this component for passing player properties, controlling playback,
 * listening to player events and so on, that is all done through the `vime-player` component.
 */ @ Component ( { tag : 'vm-dash' , styleUrl : 'dash.css' , shadow : true , } ) export class Dash implements MediaFileProvider < any > { private dash ? : any ; private dispatch ! : ProviderDispatcher ; private mediaEl ? : HTMLVideoElement ; private videoProvider ! : HTMLVmVideoElement ; private textTracksDisposal = new Disposal ( ) ; @ State ( ) hasAttached = false ; /**
     * The URL of the `manifest.mpd` file to use.
     */ @ Prop ( ) src ! : string ; @ Watch ( 'src' ) @ Watch ( 'hasAttached' ) onSrcChange ( ) { if ( ! this . hasAttached ) return ; this . vmLoadStart . emit ( ) ; this . dash ?. attachSource ( this . src ) ; } /**
     * The NPM package version of the `dashjs` library to download and use.
     */ @ Prop ( ) version = 'latest' ; /**
     * The URL where the `dashjs` library source can be found. If this property is used, then the
     * `version` property is ignored.
     */ @ Prop ( ) libSrc ? : string ; /**
     * The `dashjs` configuration.
     */ @ Prop ( { attribute : 'config' } ) config : Record < string , any > = { } ; internal @ Prop ( ) autoplay = false ; inheritdoc @ Prop ( ) crossOrigin ? : MediaCrossOriginOption ; inheritdoc @ Prop ( ) preload ? : MediaPreloadOption = 'metadata' ; inheritdoc @ Prop ( ) poster ? : string ; inheritdoc @ Prop ( ) controlsList ? : string ; inheritdoc @ Prop ( { attribute : 'auto-pip' } ) autoPiP ? : boolean ; inheritdoc @ Prop ( { attribute : 'disable-pip' } ) disablePiP ? : boolean ; inheritdoc @ Prop ( ) disableRemotePlayback ? : boolean ; /**
     * The title of the current media.
     */ @ Prop ( ) mediaTitle ? : string ; /**
     * Are text tracks enabled by default.
     */ @ Prop ( ) enableTextTracksByDefault = true ; internal @ Prop ( ) shouldRenderNativeTextTracks = true ; @ Watch ( 'shouldRenderNativeTextTracks' ) onShouldRenderNativeTextTracks ( ) { if ( this . shouldRenderNativeTextTracks ) { this . textTracksDisposal . empty ( ) ; } else { this . hideCurrentTextTrack ( ) ; } this . dash ?. enableForcedTextStreaming ( ! this . shouldRenderNativeTextTracks ) ; } internal @ Prop ( ) isTextTrackVisible = true ; internal @ Prop ( ) currentTextTrack = - 1 ; @ Watch ( 'isTextTrackVisible' ) @ Watch ( 'currentTextTrack' ) onTextTrackChange ( ) { if ( ! this . shouldRenderNativeTextTracks || isUndefined ( this . dash ) ) return ; this . dash . setTextTrack ( ! this . isTextTrackVisible ? - 1 : this . currentTextTrack ) ; if ( ! this . isTextTrackVisible ) { const track = Array . from ( this . mediaEl ?. textTracks ?? [ ] ) [ this . currentTextTrack ] ; if ( track ?. mode === 'hidden' ) this . dispatch ( 'currentTextTrack' , - 1 ) ; } } internal @ Event ( ) vmLoadStart ! : EventEmitter < void > ; /**
     * Emitted when an error has occurred.
     */ @ Event ( ) vmError ! : EventEmitter < any > ; constructor ( ) { withComponentRegistry ( this ) ; withProviderConnect ( this ) ; withPlayerContext ( this , [ 'autoplay' , 'shouldRenderNativeTextTracks' , 'isTextTrackVisible' , 'currentTextTrack' , ] ) ; } connectedCallback ( ) { this . dispatch = createProviderDispatcher ( this ) ; if ( this . mediaEl ) this . setupDash ( ) ; } disconnectedCallback ( ) { this . textTracksDisposal . empty ( ) ; this . destroyDash ( ) ; } private async setupDash ( ) { try { const url = this . libSrc || `https://cdn.jsdelivr.net/npm/dashjs@${ this . version }/dist/dash.all.min.js` ; const DashSDK = ( await loadSDK ( url , 'dashjs' ) ) as any ; this . dash = DashSDK . MediaPlayer ( this . config ) . create ( ) ; this . dash . initialize ( this . mediaEl , null , this . autoplay ) ; this . dash . setTextDefaultEnabled ( this . enableTextTracksByDefault ) ; this . dash . enableForcedTextStreaming ( ! this . shouldRenderNativeTextTracks ) ; this . dash . on ( DashSDK . MediaPlayer . events . PLAYBACK_METADATA_LOADED , ( ) => { this . dispatch ( 'mediaType' , MediaType . Video ) ; this . dispatch ( 'currentSrc' , this . src ) ; this . dispatchLevels ( ) ; this . listenToTextTracksForChanges ( ) ; this . dispatch ( 'playbackReady' , true ) ; } ) ; this . dash . on ( DashSDK . MediaPlayer . events . TRACK_CHANGE_RENDERED , ( ) => { if ( ! this . shouldRenderNativeTextTracks ) this . hideCurrentTextTrack ( ) ; } ) ; this . dash . on ( DashSDK . MediaPlayer . events . ERROR , ( e ) => { this . vmError . emit ( e ) ; } ) ; this . hasAttached = true ; } catch ( e ) { this . vmError . emit ( e ) ; } } private async destroyDash ( ) { this . dash ?. reset ( ) ; this . hasAttached = false ; } @ Listen ( 'vmMediaElChange' ) async onMediaElChange ( event ) { this . destroyDash ( ) ; if ( isUndefined ( event . detail ) ) return ; this . mediaEl = event . detail ; await this . setupDash ( ) ; } private levelToPlaybackQuality ( level ) { return level === - 1 ? 'Auto' : `${ level . height }p` ; } private findLevelIndexFromQuality ( quality ) { return this . dash . getBitrateInfoListFor ( 'video' ) . findIndex ( ( level ) => this . levelToPlaybackQuality ( level ) === quality ) ; } private dispatchLevels ( ) { try { const levels = this . dash . getBitrateInfoListFor ( 'video' ) ; if ( levels ?. length > 0 ) { this . dispatch ( 'playbackQualities' , [ 'Auto' , ... levels . map ( this . levelToPlaybackQuality ) , ] ) ; this . dispatch ( 'playbackQuality' , 'Auto' ) ; } } catch ( e ) { this . vmError . emit ( e ) ; } } private listenToTextTracksForChanges ( ) { this . textTracksDisposal . empty ( ) ; if ( isUndefined ( this . mediaEl ) || this . shouldRenderNativeTextTracks ) return ; const currentTrack = this . dash ?. getCurrentTrackFor ( 'text' ) ?. index - 1 ?? - 1 ; this . currentTextTrack = currentTrack ; this . dispatch ( 'currentTextTrack' , currentTrack ) ; this . textTracksDisposal . add ( listen ( this . mediaEl . textTracks , 'change' , this . onTextTracksChange . bind ( this ) ) ) ; } private getTextTracks ( ) { return Array . from ( this . mediaEl ?. textTracks ?? [ ] ) ; } private hideCurrentTextTrack ( ) { const textTracks = this . getTextTracks ( ) ; if ( textTracks [ this . currentTextTrack ] && this . isTextTrackVisible ) { textTracks [ this . currentTextTrack ] . mode = 'hidden' ; } } private onTextTracksChange ( ) { this . hideCurrentTextTrack ( ) ; this . dispatch ( 'textTracks' , this . getTextTracks ( ) ) ; this . dispatch ( 'isTextTrackVisible' , this . isTextTrackVisible ) ; this . dispatch ( 'currentTextTrack' , this . currentTextTrack ) ; } internal @ Method ( ) async getAdapter ( ) { const adapter = ( await this . videoProvider ?. getAdapter ( ) ) ?? { } ; const canVideoProviderPlay = adapter . canPlay ; return { ... adapter , getInternalPlayer : async ( ) => this . dash , canPlay : async ( type ) => ( isString ( type ) && dashRegex . test ( type ) ) || ( canVideoProviderPlay ?. ( type ) ?? false ) , canSetPlaybackQuality : async ( ) => { try { return this . dash ?. getBitrateInfoListFor ( 'video' ) ?. length > 0 ; } catch ( e ) { this . vmError . emit ( e ) ; return false ; } } , setPlaybackQuality : async ( quality ) => { if ( ! isUndefined ( this . dash ) ) { const index = this . findLevelIndexFromQuality ( quality ) ; this . dash . updateSettings ( { streaming : { abr : { autoSwitchBitrate : { video : index === - 1 , } , } , } , } ) ; if ( index >= 0 ) this . dash . setQualityFor ( 'video' , index ) ; this . dispatch ( 'playbackQuality' , quality ) ; } } , setCurrentTextTrack : async ( trackId ) => { if ( this . shouldRenderNativeTextTracks ) { adapter . setCurrentTextTrack ( trackId ) ; } else { this . currentTextTrack = trackId ; this . dash ?. setTextTrack ( trackId ) ; this . onTextTracksChange ( ) ; } } , setTextTrackVisibility : async ( isVisible ) => { if ( this . shouldRenderNativeTextTracks ) { adapter . setTextTrackVisibility ( isVisible ) ; } else { this . isTextTrackVisible = isVisible ; this . dash ?. enableText ( isVisible ) ; this . onTextTracksChange ( ) ; } } , } ; } render ( ) { return ( < vm - video willAttach crossOrigin = { this . crossOrigin } preload = { this . preload } poster = { this . poster } controlsList = { this . controlsList } autoPiP = { this . autoPiP } disablePiP = { this . disablePiP } hasCustomTextManager = { ! this . shouldRenderNativeTextTracks } disableRemotePlayback = { this . disableRemotePlayback } mediaTitle = { this . mediaTitle } ref = { ( el ) => { this . videoProvider = el ; } } />); } }