import { Component , Element , h , Prop , State , Watch } from '@stencil/core' ; import { Disposal } from '../../../../utils/Disposal' ; import { listen } from '../../../../utils/dom' ; import { formatTime } from '../../../../utils/formatters' ; import { isUndefined } from '../../../../utils/unit' ; import { findPlayer } from '../../../core/player/findPlayer' ; import { createDispatcher , Dispatcher , } from '../../../core/player/PlayerDispatcher' ; import { PlayerProps } from '../../../core/player/PlayerProps' ; import { withComponentRegistry } from '../../../core/player/withComponentRegistry' ; import { withPlayerContext } from '../../../core/player/withPlayerContext' ; /**
 * A control that displays the progression of playback and the amount buffered on a horizontal
 * timeline. The timeline is a slider (`input[type="range"]`) that can be used to change the
 * current playback time.
 *
 * If the player is buffering, the scrubber will display an animated candystripe in the porition
 * of the timeline that has not buffered.
 *
 * ## Visual
 *
 * <img
 *   src="https://raw.githubusercontent.com/vime-js/vime/master/packages/core/src/components/ui/controls/scrubber-control/scrubber-control.png"
 *   alt="Vime scrubber control component"
 * />
 */ @ Component ( { tag : 'vm-scrubber-control' , styleUrl : 'scrubber-control.css' , shadow : true , } ) export class ScrubberControl { private slider ! : HTMLVmSliderElement ; private tooltip ! : HTMLVmTooltipElement ; private dispatch ! : Dispatcher ; private keyboardDisposal = new Disposal ( ) ; @ Element ( ) host ! : HTMLVmScrubberControlElement ; @ State ( ) timestamp = '' ; @ State ( ) endTime = 0 ; /**
     * Whether the timestamp in the tooltip should show the hours unit, even if the time is less than
     * 1 hour (eg: `20:35` -> `00:20:35`).
     */ @ Prop ( ) alwaysShowHours = false ; /**
     * Whether the tooltip should not be displayed.
     */ @ Prop ( ) hideTooltip = false ; internal @ Prop ( ) currentTime : PlayerProps [ 'currentTime' ] = 0 ; internal @ Prop ( ) duration : PlayerProps [ 'duration' ] = - 1 ; /**
     * Prevents seeking forward/backward by using the Left/Right arrow keys.
     */ @ Prop ( ) noKeyboard = false ; @ Watch ( 'noKeyboard' ) async onNoKeyboardChange ( ) { this . keyboardDisposal . empty ( ) ; if ( this . noKeyboard ) return ; const player = await findPlayer ( this ) ; if ( isUndefined ( player ) ) return ; const onKeyDown = ( event ) => { if ( event . key !== 'ArrowLeft' && event . key !== 'ArrowRight' ) return ; event . preventDefault ( ) ; const isLeftArrow = event . key === 'ArrowLeft' ; const seekTo = isLeftArrow ? Math . max ( 0 , this . currentTime - 5 ) : Math . min ( this . duration , this . currentTime + 5 ) ; this . dispatch ( 'currentTime' , seekTo ) ; } ; this . keyboardDisposal . add ( listen ( player , 'keydown' , onKeyDown ) ) ; } @ Watch ( 'duration' ) onDurationChange ( ) { this . endTime = Math . max ( 0 , this . duration ) ; } internal @ Prop ( ) buffering : PlayerProps [ 'buffering' ] = false ; internal @ Prop ( ) buffered : PlayerProps [ 'buffered' ] = 0 ; internal @ Prop ( ) i18n : PlayerProps [ 'i18n' ] = { } ; constructor ( ) { withComponentRegistry ( this ) ; withPlayerContext ( this , [ 'i18n' , 'currentTime' , 'duration' , 'buffering' , 'buffered' , ] ) ; } connectedCallback ( ) { this . dispatch = createDispatcher ( this ) ; this . timestamp = formatTime ( this . currentTime , this . alwaysShowHours ) ; this . onNoKeyboardChange ( ) ; } disconnectedCallback ( ) { this . keyboardDisposal . empty ( ) ; } private setTooltipPosition ( value ) { const tooltipRect = this . tooltip . shadowRoot ?. querySelector ( '.tooltip' ) ?. getBoundingClientRect ( ) as DOMRect ; const bounds = this . slider . getBoundingClientRect ( ) ; const thumbWidth = parseFloat ( window . getComputedStyle ( this . slider ) . getPropertyValue ( '--vm-slider-thumb-width' ) ) ; const leftLimit = tooltipRect . width / 2 - thumbWidth / 2 ; const rightLimit = bounds . width - tooltipRect . width / 2 - thumbWidth / 2 ; const xPos = Math . max ( leftLimit , Math . min ( value , rightLimit ) ) ; ( this . tooltip as any ) . style = `--vm-tooltip-left: ${ xPos }px` ; } private onSeek ( event ) { this . dispatch ( 'currentTime' , event . detail ) ; } private onSeeking ( event ) { if ( this . duration < 0 || this . tooltip . hidden ) return ; if ( event . type === 'mouseleave' ) { this . getSliderInput ( ) . blur ( ) ; this . tooltip . active = false ; return ; } const rect = this . host . getBoundingClientRect ( ) ; const percent = Math . max ( 0 , Math . min ( 100 , ( 100 / rect . width ) * ( event . pageX - rect . left ) ) ) ; this . timestamp = formatTime ( ( this . duration / 100 ) * percent , this . alwaysShowHours ) ; this . setTooltipPosition ( ( percent / 100 ) * rect . width ) ; if ( ! this . tooltip . active ) { this . getSliderInput ( ) . focus ( ) ; this . tooltip . active = true ; } } private getSliderInput ( ) { return this . slider . shadowRoot ?. querySelector ( 'input' ) as HTMLInputElement ; } render ( ) { const sliderValueText = this . i18n . scrubberLabel . replace ( /{currentTime}/ , formatTime ( this . currentTime ) ) . replace ( /{duration}/ , formatTime ( this . endTime ) ) ; return ( < div class = "scrubber" onMouseEnter = { this . onSeeking . bind ( this ) } onMouseLeave = { this . onSeeking . bind ( this ) } onMouseMove = { this . onSeeking . bind ( this ) } onTouchMove = { ( ) => { this . getSliderInput ( ) . focus ( ) ; } } onTouchEnd = { ( ) => { this . getSliderInput ( ) . blur ( ) ; } } > < vm - slider step = { 0.01 } max = { this . endTime } value = { this . currentTime } label = { this . i18n . scrubber } valueText = { sliderValueText } onVmValueChange = { this . onSeek . bind ( this ) } ref = { ( el ) => { this . slider = el ; } } /> < progress class = { { loading : this . buffering , } } min = { 0 } max = { this . endTime } value = { this . buffered } aria - label = { this . i18n . buffered } aria - valuemin = "0" aria - valuemax = { this . endTime } aria - valuenow = { this . buffered } aria - valuetext = { `${ ( this . endTime > 0 ? this . buffered / this . endTime : 0 ) . toFixed ( 0 ) }%` } > % buffered < /progress> < vm - tooltip hidden = { this . hideTooltip } ref = { ( el ) => { this . tooltip = el ; } } > { this . timestamp } < /vm-tooltip> < /div>); } }