UNPKG

playable

Version:

Video player based on HTML5Video

439 lines (365 loc) 11.8 kB
import { TextLabel } from '../../../../constants'; import View from '../../core/view'; import { IView } from '../../core/types'; import { ITooltipReference, ITooltipService } from '../../core/tooltip/types'; import getProgressTimeTooltipPosition from './utils/getProgressTimeTooltipPosition'; import { progressTemplate, progressTimeIndicatorTemplate } from './templates'; import htmlToElement from '../../core/htmlToElement'; import getElementByHook from '../../core/getElementByHook'; import toggleElementClass from '../../core/toggleElementClass'; import { IProgressViewStyles, IProgressViewCallbacks, IProgressViewConfig, IProgressDragEvents, } from './types'; import { ITextMap } from '../../../text-map/types'; import progressViewTheme from './progress.theme'; import styles from './progress.scss'; const DATA_PLAYED = 'data-playable-played-percent'; const getPercentBasedOnXPosition = ( event: MouseEvent, element: HTMLElement, ) => { const boundingRect = element.getBoundingClientRect(); const positionX = event.clientX; if (positionX < boundingRect.left) { return 0; } if (positionX > boundingRect.left + boundingRect.width) { return 100; } return ((event.clientX - boundingRect.left) / boundingRect.width) * 100; }; const getSupportedDragEventNames = (): IProgressDragEvents => { if ('onpointerdown' in window) { return { mouseDown: 'pointerdown', mouseMove: 'pointermove', mouseOut: 'pointerout', mouseUp: 'pointerup', }; } if ('ontouchstart' in window) { return { mouseDown: 'touchstart', mouseMove: 'touchmove', mouseOut: 'mouseout', mouseUp: 'touchend', }; } return { mouseDown: 'mousedown', mouseMove: 'mousemove', mouseOut: 'mouseout', mouseUp: 'mouseup', }; }; class ProgressView extends View<IProgressViewStyles> implements IView<IProgressViewStyles> { private _callbacks: IProgressViewCallbacks; private _textMap: ITextMap; private _tooltipService: ITooltipService; private _syncButtonTooltipReference: ITooltipReference; private _isDragging: boolean; private _currentPlayedPercent: number; private _dragEvents: IProgressDragEvents; private _$rootElement: HTMLElement; private _$hitbox: HTMLElement; private _$played: HTMLElement; private _$buffered: HTMLElement; private _$seekTo: HTMLElement; private _$timeIndicators: HTMLElement; private _$seekButton: HTMLElement; private _$syncButton: HTMLElement; constructor(config: IProgressViewConfig) { const { callbacks, textMap, tooltipService, theme } = config; super(theme); this._callbacks = callbacks; this._textMap = textMap; this._tooltipService = tooltipService; this._dragEvents = getSupportedDragEventNames(); this._initDOM(); this._bindCallbacks(); this._bindEvents(); this._setPlayedDOMAttributes(0); this._setBufferedDOMAttributes(0); this.setUsualMode(); } private _initDOM() { this._$rootElement = htmlToElement( progressTemplate({ styles: this.styleNames, themeStyles: this.themeStyles, }), ); this._$played = getElementByHook(this._$rootElement, 'progress-played'); this._$buffered = getElementByHook(this._$rootElement, 'progress-buffered'); this._$seekTo = getElementByHook(this._$rootElement, 'progress-seek-to'); this._$timeIndicators = getElementByHook( this._$rootElement, 'progress-time-indicators', ); this._$seekButton = getElementByHook( this._$rootElement, 'progress-seek-button', ); this._$syncButton = getElementByHook( this._$rootElement, 'progress-sync-button', ); this._syncButtonTooltipReference = this._tooltipService.createReference( this._$syncButton, { text: this._textMap.get(TextLabel.LIVE_SYNC_TOOLTIP), }, ); this._$hitbox = getElementByHook(this._$rootElement, 'progress-hitbox'); } private _bindCallbacks() { this._setPlayedByDrag = this._setPlayedByDrag.bind(this); this._startDragOnMouseDown = this._startDragOnMouseDown.bind(this); this._stopDragOnMouseUp = this._stopDragOnMouseUp.bind(this); this._startSeekToByMouse = this._startSeekToByMouse.bind(this); this._stopSeekToByMouse = this._stopSeekToByMouse.bind(this); this._syncWithLive = this._syncWithLive.bind(this); } private _bindEvents() { this._$seekButton.addEventListener( this._dragEvents.mouseDown, this._startDragOnMouseDown, ); this._$seekButton.addEventListener( this._dragEvents.mouseMove, this._startSeekToByMouse, ); this._$seekButton.addEventListener( this._dragEvents.mouseOut, this._stopSeekToByMouse, ); this._$hitbox.addEventListener( this._dragEvents.mouseDown, this._startDragOnMouseDown, ); this._$hitbox.addEventListener( this._dragEvents.mouseMove, this._startSeekToByMouse, ); this._$hitbox.addEventListener( this._dragEvents.mouseOut, this._stopSeekToByMouse, ); window.addEventListener(this._dragEvents.mouseMove, this._setPlayedByDrag); window.addEventListener(this._dragEvents.mouseUp, this._stopDragOnMouseUp); this._$syncButton.addEventListener('click', this._syncWithLive); this._$syncButton.addEventListener( 'mouseenter', this._callbacks.onSyncWithLiveMouseEnter, ); this._$syncButton.addEventListener( 'mouseleave', this._callbacks.onSyncWithLiveMouseLeave, ); } private _unbindEvents() { this._$seekButton.removeEventListener( this._dragEvents.mouseDown, this._startDragOnMouseDown, ); this._$seekButton.removeEventListener( this._dragEvents.mouseMove, this._startSeekToByMouse, ); this._$seekButton.removeEventListener( this._dragEvents.mouseOut, this._stopSeekToByMouse, ); this._$hitbox.removeEventListener( this._dragEvents.mouseDown, this._startDragOnMouseDown, ); this._$hitbox.removeEventListener( this._dragEvents.mouseMove, this._startSeekToByMouse, ); this._$hitbox.removeEventListener( this._dragEvents.mouseOut, this._stopSeekToByMouse, ); window.removeEventListener( this._dragEvents.mouseMove, this._setPlayedByDrag, ); window.removeEventListener( this._dragEvents.mouseUp, this._stopDragOnMouseUp, ); this._$syncButton.removeEventListener('click', this._syncWithLive); this._$syncButton.removeEventListener( 'mouseenter', this._callbacks.onSyncWithLiveMouseEnter, ); this._$syncButton.removeEventListener( 'mouseleave', this._callbacks.onSyncWithLiveMouseLeave, ); } private _startDragOnMouseDown(event: MouseEvent) { if (event.button > 1) { return; } const percent = getPercentBasedOnXPosition(event, this._$hitbox); this._setPlayedDOMAttributes(percent); this._callbacks.onChangePlayedPercent(percent); this._startDrag(); } private _stopDragOnMouseUp(event: MouseEvent) { if (event.button > 1) { return; } this._stopDrag(); } private _startSeekToByMouse(event: MouseEvent) { const percent = getPercentBasedOnXPosition(event, this._$hitbox); this._setSeekToDOMAttributes(percent); this._callbacks.onSeekToByMouseStart(percent); } private _stopSeekToByMouse() { this._setSeekToDOMAttributes(0); this._callbacks.onSeekToByMouseEnd(); } private _setPlayedByDrag(event: MouseEvent) { if (this._isDragging) { const percent = getPercentBasedOnXPosition(event, this._$hitbox); this._setPlayedDOMAttributes(percent); this._callbacks.onChangePlayedPercent(percent); } } private _startDrag() { this._isDragging = true; this._callbacks.onDragStart(); this._$rootElement.classList.add(this.styleNames.isDragging); } private _stopDrag() { if (this._isDragging) { this._isDragging = false; this._callbacks.onDragEnd(); this._$rootElement.classList.remove(this.styleNames.isDragging); } } private _setSeekToDOMAttributes(percent: number) { this._$seekTo.setAttribute('style', `width:${percent}%;`); } private _setPlayedDOMAttributes(percent: number) { this._$rootElement.setAttribute( 'aria-valuetext', this._textMap.get(TextLabel.PROGRESS_CONTROL_VALUE, { percent }), ); this._$rootElement.setAttribute('aria-valuenow', percent.toFixed(2)); this._$rootElement.setAttribute(DATA_PLAYED, percent.toFixed(2)); this._setPlayedDOMPosition(percent); } private _setPlayedDOMPosition(percent: number) { const scaleValue = percent / 100; const translateValue = this._$rootElement.getBoundingClientRect().width * scaleValue; this._$played.style.transform = `scaleX(${scaleValue.toFixed(3)})`; this._$seekButton.style.transform = `translateX(${translateValue.toFixed( 3, )}px)`; } private _setBufferedDOMAttributes(percent: number) { this._$buffered.setAttribute('style', `width:${percent}%;`); } private _syncWithLive() { this._callbacks.onSyncWithLiveClick(); } updateOnResize() { this._setPlayedDOMPosition(this._currentPlayedPercent); } showSyncWithLive() { this._$syncButton.classList.remove(this.styleNames.hidden); } hideSyncWithLive() { this._$syncButton.classList.add(this.styleNames.hidden); } setLiveSyncState(isSync: boolean) { toggleElementClass(this._$syncButton, this.styleNames.liveSync, isSync); toggleElementClass(this._$seekButton, this.styleNames.liveSync, isSync); if (isSync) { this._syncButtonTooltipReference.disable(); this._$played.setAttribute('style', `width:100%;`); } else { this._syncButtonTooltipReference.enable(); } } showProgressTimeTooltip(element: HTMLElement, percent: number) { this._tooltipService.show({ element, position: tooltipContainer => getProgressTimeTooltipPosition( percent, this._$hitbox, tooltipContainer, ), }); } hideProgressTimeTooltip() { this._tooltipService.hide(); } setLiveMode() { this._$rootElement.classList.add(this.styleNames.inLive); this.showSyncWithLive(); } setUsualMode() { this._$rootElement.classList.remove(this.styleNames.inLive); this.hideSyncWithLive(); } setPlayed(percent: number) { this._currentPlayedPercent = percent; this._setPlayedDOMAttributes(percent); } setBuffered(percent: number) { this._setBufferedDOMAttributes(percent); } addTimeIndicator(percent: number) { this._$timeIndicators.appendChild( htmlToElement( progressTimeIndicatorTemplate({ percent, styles: this.styleNames, }), ), ); } clearTimeIndicators() { this._$timeIndicators.innerHTML = ''; } hide() { this._$rootElement.classList.add(this.styleNames.hidden); } show() { this._$rootElement.classList.remove(this.styleNames.hidden); } getElement() { return this._$rootElement; } destroy() { this._unbindEvents(); this._syncButtonTooltipReference.destroy(); if (this._$rootElement.parentNode) { this._$rootElement.parentNode.removeChild(this._$rootElement); } this._$rootElement = null; this._$buffered = null; this._$hitbox = null; this._$played = null; this._$seekTo = null; this._$seekButton = null; this._$syncButton = null; this._$timeIndicators = null; } } ProgressView.setTheme(progressViewTheme); ProgressView.extendStyleNames(styles); export default ProgressView;