import { Disposable , PlayerOptions , Plugin } from './types' ; import { processOptions } from './options' ; import { $ , addClass , getEl , Rect , EventEmitter , clamp , isString , dispose , removeNode , addDisposable , patchProps , internalUtils , } from './utils' ; import { Control , ControlItem } from './parts/control' ; import { Loading } from './parts/loading' ; import { ContextMenu , ContextMenuItem } from './parts/contextmenu' ; import { Toast } from './parts/toast' ; import { Fullscreen } from './features/fullscreen' ; import { WebFullscreen } from './features/web-fullscreen' ; import { setCssVariables , setVideoAttrs , transferEvent , tryOpenEdge , setVideoVolumeFromLocal , registerNamedMap , setVideoSources , saveVideoVolume , } from './helper' ; import { EVENT } from './constants' ; import { Shortcut } from './features/shortcut' ; import { SettingItem } from './parts/control/items/setting' ; import * as components from './components' ; import { I18n , Icon } from './features' ; import { Poster } from './parts/poster' ; import { Touch } from './features/touch' ; export class Player extends EventEmitter implements Disposable { container : HTMLElement | null ; el : HTMLElement ; opts : Required < PlayerOptions > ; mounted = false ; private prevVolume = 1 ; private readonly settingNamedMap : Record < string , SettingItem > = Object . create ( null ) ; private readonly contextmenuNamedMap : Record < string , ContextMenuItem > = Object . create ( null ) ; private readonly controlNamedMap : Record < string , ControlItem > = Object . create ( null ) ; readonly __settingItems : SettingItem [ ] ; readonly video : HTMLVideoElement ; readonly rect : Rect ; readonly fullscreen : Fullscreen ; readonly webFullscreen : WebFullscreen ; readonly shortcut : Shortcut ; readonly touch : Touch ; readonly control : Control ; readonly loading : Loading ; readonly poster : Poster ; readonly contextmenu : ContextMenu ; readonly toast : Toast ; private readonly plugins : Set < Plugin > = new Set ( ) ; constructor ( opts ? ) { super ( ) ; this . opts = processOptions ( opts ) ; tryOpenEdge ( this ) ; I18n . setCurrentLang ( this . opts . i18n ) ; this . container = getEl ( this . opts . container ) ; this . el = $ ( '.nplayer' , { tabindex : '0' } , undefined , '' ) ; if ( this . opts . video ) { this . video = this . opts . video ; addClass ( this . video , 'video' ) ; } else { this . video = $ ( 'video.video' ) ; } if ( this . opts . src ) this . opts . videoProps . src = this . opts . src ; setCssVariables ( this . el , this . opts ) ; setVideoAttrs ( this . video , this . opts . videoProps ) ; setVideoSources ( this . video , this . opts . videoSources ) ; setVideoVolumeFromLocal ( this . video ) ; transferEvent ( this ) ; this . el . appendChild ( this . video ) ; registerNamedMap ( this ) ; this . rect = addDisposable ( this , new Rect ( this . el , this ) ) ; this . fullscreen = addDisposable ( this , new Fullscreen ( this ) ) ; this . webFullscreen = addDisposable ( this , new WebFullscreen ( this ) ) ; this . shortcut = addDisposable ( this , new Shortcut ( this , this . opts . shortcut ) ) ; this . touch = addDisposable ( this , new Touch ( this ) ) ; this . toast = addDisposable ( this , new Toast ( this . el ) ) ; this . loading = addDisposable ( this , new Loading ( this . el , this ) ) ; this . poster = addDisposable ( this , new Poster ( this . el , this ) ) ; if ( this . opts . plugins ) { this . opts . plugins . forEach ( ( plugin ) => this . use ( plugin ) ) ; } this . contextmenu = addDisposable ( this , new ContextMenu ( this . el , this , this . opts . contextMenus . map ( ( item ) => { if ( isString ( item ) ) return this . contextmenuNamedMap [ item ] ; return item ; } ) . filter ( Boolean ) ) ) ; this . __settingItems = this . opts . settings . map ( ( item ) => { if ( isString ( item ) ) return this . settingNamedMap [ item ] ; return item ; } ) . filter ( Boolean ) ; this . control = addDisposable ( this , new Control ( this . el , this ) ) ; addDisposable ( this , this . on ( EVENT . CANPLAY , ( ) => { const time = this . opts . autoSeekTime || 0.3 ; if ( this . currentTime < time ) { this . currentTime = time ; see https if ( this . currentTime < time ) { this . video . load ( ) ; this . video . pause ( ) ; } } this . opts . autoSeekTime = 0 ; } ) ) ; if ( ! this . opts . isTouch ) { this . enableClickPause ( ) ; } this . emit ( EVENT . AFTER_INIT ) ; } get currentTime ( ) : number { return this . video . currentTime ; } set currentTime ( v ) { if ( ! this . duration ) return ; const diff = v - this . video . currentTime ; if ( ! diff ) return ; this . video . currentTime = clamp ( v , 0 , this . duration ) ; } get loaded ( ) : boolean { return this . video . readyState >= 3 ; } get duration ( ) : number { return this . video . duration || 0 ; } get buffered ( ) : TimeRanges { return this . video . buffered ; } get volume ( ) : number { return this . video . volume ; } set volume ( v ) { this . video . volume = clamp ( v ) ; if ( this . muted && v > 0 ) this . muted = false ; saveVideoVolume ( this . video . volume ) ; } get muted ( ) : boolean { return this . video . muted || this . volume === 0 ; } set muted ( v ) { this . video . muted = v ; } get playbackRate ( ) : number { return this . video . playbackRate ; } set playbackRate ( v ) { this . video . playbackRate = v ; } get ended ( ) : boolean { return this . video . ended ; } get paused ( ) : boolean { return this . video . paused ; } get playing ( ) : boolean { return ! this . paused && ! this . ended ; } get loop ( ) : boolean { return this . video . loop ; } set loop ( v ) { this . video . loop = v ; } enableClickPause ( ) { this . video . addEventListener ( 'click' , this . toggle ) ; } disableClickPause ( ) { this . video . removeEventListener ( 'click' , this . toggle ) ; } use ( plugin ) { if ( ! plugin || this . plugins . has ( plugin ) ) return this ; if ( plugin . dispose ) { addDisposable ( this , plugin as Disposable ) ; } plugin . apply ( this ) ; return this ; } mount ( el ? ) { if ( this . mounted ) { el = getEl ( el ) as any ; if ( el && el !== this . container ) { if ( this . container ) this . container . removeChild ( this . el ) ; this . container = el as HTMLElement ; this . container . appendChild ( this . el ) ; this . emit ( EVENT . UPDATE_SIZE ) ; } return ; } if ( el ) this . container = getEl ( el ) || this . container ; if ( ! this . container ) return ; this . container . appendChild ( this . el ) ; this . emit ( EVENT . MOUNTED ) ; this . mounted = true ; } incVolume ( v = this . opts . volumeStep ) { this . volume += v ; } decVolume ( v = this . opts . volumeStep ) { this . volume -= v ; } forward ( v = this . opts . seekStep ) { this . currentTime += v ; } rewind ( v = this . opts . seekStep ) { this . currentTime -= v ; } play ( ) { return this . video . play ( ) ; } pause ( ) { this . video . pause ( ) ; } seek ( seconds ) { this . video . currentTime = clamp ( seconds , 0 , this . duration ) ; } toggle = ( ) => { if ( this . paused ) { this . play ( ) ; } else { this . pause ( ) ; } } ; toggleVolume ( ) { if ( this . muted ) { this . volume = this . prevVolume || 1 ; this . muted = false ; } else { this . prevVolume = this . volume ; this . volume = 0 ; } } eachBuffer ( fn ) { for ( let l = this . buffered . length , i = l - 1 ; i >= 0 ; i -- ) { if ( fn ( this . buffered . start ( i ) , this . buffered . end ( i ) ) ) { break ; } } } registerSettingItem ( item , id ? ) { this . settingNamedMap [ id || item . id ! ] = item ; } registerContextMenuItem ( item , id ? ) { this . contextmenuNamedMap [ id || item . id ! ] = item ; } registerControlItem ( item , id ? ) { this . controlNamedMap [ id || item . id ! ] = item ; } getSettingItem ( id ) { return this . settingNamedMap [ id ] ; } getContextMenuItem ( id ) { return this . contextmenuNamedMap [ id ] ; } getControlItem ( id ) { return this . controlNamedMap [ id ] as ControlItem ; } updateOptions ( opts ) { if ( opts . videoProps !== this . opts . videoProps ) patchProps ( this . video , this . opts . videoProps , opts . videoProps ) ; if ( opts . src && opts . src !== this . opts . src ) this . video . src = opts . src ; if ( opts . videoSources !== this . opts . videoSources ) setVideoSources ( this . video , opts . videoSources ) ; this . opts = { ... this . opts , ... opts } ; setCssVariables ( this . el , this . opts ) ; if ( this . opts . shortcut ) { this . shortcut . enable ( ) ; } else { this . shortcut . disable ( ) ; } this . emit ( EVENT . UPDATE_OPTIONS , this . opts ) ; } updateControlItems ( items , index ? ) { this . control . updateItems ( items , index ) ; } dispose ( ) { if ( ! this . el ) return ; this . emit ( EVENT . BEFORE_DISPOSE ) ; dispose ( this ) ; const plugins = this . opts . plugins ; if ( plugins ) plugins . forEach ( ( p ) => p . dispose && p . dispose ( ) ) ; this . removeAllListeners ( ) ; removeNode ( this . el ) ; this . el = null ! ; this . container = null ; } static __utils = internalUtils ; static EVENT = EVENT ; static I18n = I18n ; static Icon = Icon ; static components = components ; static Player = Player ; Player ! : typeof Player ; EVENT ! : typeof EVENT ; } Player . prototype . Player = Player ; Player . prototype . EVENT = EVENT ;