import { EVENT } from 'src/ts/constants' ; import { Player } from 'src/ts/player' ; import { $ , addClass , addDisposable , addDisposableListener , clamp , Component , Drag , Rect , throttle , } from 'src/ts/utils' ; import { ControlItem } from '..' ; import { Thumbnail } from './thumbnail' ; export class Progress extends Component implements ControlItem { readonly id = 'progress' ; private playedBar ! : HTMLElement ; private bufBar ! : HTMLElement ; private bars ! : HTMLElement ; private dot ! : HTMLElement ; private rect ! : Rect ; private thumbnail ! : Thumbnail ; private player ! : Player ; private dragging = false ; init ( player ) { this . player = player ; addClass ( this . el , 'progress' ) ; this . bars = this . el . appendChild ( $ ( '.progress_bars' ) ) ; this . bufBar = this . bars . appendChild ( $ ( '.progress_buf' ) ) ; this . playedBar = this . bars . appendChild ( $ ( '.progress_played' ) ) ; this . dot = this . el . appendChild ( $ ( '.progress_dot' ) ) ; this . dot . appendChild ( player . opts . progressDot || $ ( '.progress_dot_inner' ) ) ; this . rect = addDisposable ( this , new Rect ( this . bars , player ) ) ; this . thumbnail = addDisposable ( this , new Thumbnail ( this . el , player . opts . thumbnail ) ) ; addDisposable ( this , new Drag ( this . el , this . onDragStart , this . onDragging , this . onDragEnd ) ) ; addDisposable ( this , player . on ( EVENT . TIME_UPDATE , this . updatePlayedBar ) ) ; addDisposable ( this , player . on ( EVENT . PROGRESS , this . updateBufBar ) ) ; addDisposable ( this , player . on ( EVENT . UPDATE_OPTIONS , ( ) => this . thumbnail . updateOptions ( player . opts . thumbnail ) ) ) ; addDisposable ( this , player . on ( EVENT . UPDATE_SIZE , ( ) => { if ( ! player . playing ) this . resetPlayedBar ( ) ; } ) ) ; addDisposable ( this , player . on ( EVENT . CONTROL_ITEM_UPDATE , ( ) => { this . rect . update ( ) ; this . resetPlayedBar ( ) ; } ) ) ; addDisposableListener ( this , this . el , 'mousemove' , throttle ( ( ev ) => this . updateThumbnail ( ev . clientX ) ) , true ) ; if ( player . opts . isTouch ) { addDisposableListener ( this , this . el , 'touchstart' , ( ev ) => ev . preventDefault ( ) ) ; } } private resetPlayedBar ( ) { this . setPlayedBarLength ( this . player . currentTime / this . player . duration ) ; } private setPlayedBarLength ( percentage ) { this . playedBar . style . transform = `scaleX(${ clamp ( percentage ) })` ; this . dot . style . transform = `translate(${ clamp ( percentage * this . rect . width , 0 , this . rect . width ) }px, -50%)` ; } private setBufBarLength ( percentage ) { this . bufBar . style . transform = `scaleX(${ clamp ( percentage ) })` ; } private onDragStart = ( ev ) => { this . dragging = true ; this . rect . update ( ) ; this . onDragging ( ev ) ; } ; private onDragging = ( ev ) => { const x = ev . clientX - this . rect . x ; this . setPlayedBarLength ( x / this . rect . width ) ; this . updateThumbnail ( ev . clientX ) ; } ; private onDragEnd = ( ev ) => { this . dragging = false ; this . player . seek ( this . getCurrentTime ( ev . clientX ) ) ; } ; private updateThumbnail ( x ) { this . rect . update ( ) ; this . thumbnail . update ( this . getCurrentTime ( x ) , x - this . rect . x , this . rect . width ) ; } private updateBufBar = ( ) : void => { const bufLen = this . player . buffered . length ; if ( ! bufLen ) return this . setBufBarLength ( 0 ) ; const curTime = this . player . currentTime ; let percentage = 0 ; this . player . eachBuffer ( ( start , end ) => { if ( start <= curTime && end >= curTime ) { percentage = end / this . player . duration ; return true ; } } ) ; this . setBufBarLength ( percentage ) ; } ; private updatePlayedBar = ( ) : void => { if ( this . dragging ) return ; this . setPlayedBarLength ( this . player . currentTime / this . player . duration ) ; } ; private getCurrentTime ( x ) { return clamp ( ( ( x - this . rect . x ) / this . rect . width ) ) * this . player . duration ; } } export const progressControlItem = ( ) => new Progress ( ) ;