UNPKG

bitmovin-player-ui

Version:
263 lines (222 loc) 8.65 kB
import { Container, ContainerConfig } from '../Container'; import { SmallCenteredPlaybackToggleButton } from '../buttons/SmallCenteredPlaybackToggleButton'; import { PlayerAPI } from 'bitmovin-player'; import { UIInstanceManager } from '../../UIManager'; import { EventDispatcher, NoArgs, Event as EDEvent } from '../../EventDispatcher'; import { Timeout } from '../../utils/Timeout'; import { HTMLElementWithComponent } from '../../DOM'; import { Label, LabelConfig } from '../labels/Label'; import { i18n } from '../../localization/i18n'; export interface TouchControlOverlayConfig extends ContainerConfig { /** * Specify whether the player should be set to enter fullscreen by clicking on the playback toggle button * when initiating the initial playback. * Default: false. */ enterFullscreenOnInitialPlayback?: boolean; /** * Specifies whether the first touch event received by the {@link UIContainer} should be prevented or not. * * Default: true */ acceptsTouchWithUiHidden?: boolean; /** * Specifies how many seconds are seeked incase user seeks through double-tapping * Default: 10sec */ seekTime?: number; /** * The second tap of a double tap has to be in a specific range of the first tap * This specifies how many pixels off the second tap is allowed to be from the first tap * in order to trigger the seek events * * Default: 15px */ seekDoubleTapMargin?: number; /** * Time in milliseconds within which two consecutive taps are considered a double tap. * Default: 200ms */ seekDoubleTapTimeout?: number; } interface ClickPosition { x: number; y: number; } /** * Overlays the player and detects touch input */ export class TouchControlOverlay extends Container<TouchControlOverlayConfig> { private readonly SEEK_FORWARD_CLASS = 'seek-forward'; private readonly SEEK_BACKWARD_CLASS = 'seek-backward'; private touchControlEvents = { onSingleClick: new EventDispatcher<TouchControlOverlay, NoArgs>(), onDoubleClick: new EventDispatcher<TouchControlOverlay, NoArgs>(), onSeekBackward: new EventDispatcher<TouchControlOverlay, NoArgs>(), onSeekForward: new EventDispatcher<TouchControlOverlay, NoArgs>(), }; private playbackToggleButton: SmallCenteredPlaybackToggleButton; private seekForwardLabel: Label<LabelConfig>; private seekBackwardLabel: Label<LabelConfig>; // true if the last tap on the overlay was less than 500msec ago private couldBeDoubleTapping: Boolean; private doubleTapTimeout: Timeout; private latestTapPosition: ClickPosition; constructor(config: TouchControlOverlayConfig = {}) { super(config); this.playbackToggleButton = new SmallCenteredPlaybackToggleButton({ enterFullscreenOnInitialPlayback: Boolean(config.enterFullscreenOnInitialPlayback), }); this.seekForwardLabel = new Label({ text: '', for: this.getConfig().id, cssClass: 'seek-forward-label', hidden: true, }); this.seekBackwardLabel = new Label({ text: '', for: this.getConfig().id, cssClass: 'seek-backward-label', hidden: true, }); this.config = this.mergeConfig( config, { cssClass: 'ui-touch-control-overlay', acceptsTouchWithUiHidden: true, seekTime: 10, seekDoubleTapMargin: 15, seekDoubleTapTimeout: 200, components: [this.seekBackwardLabel, this.playbackToggleButton, this.seekForwardLabel], }, this.config, ); } configure(player: PlayerAPI, uimanager: UIInstanceManager): void { super.configure(player, uimanager); let playerSeekTime = 0; let startSeekTime = 0; this.doubleTapTimeout = new Timeout(this.config.seekDoubleTapTimeout, () => { this.couldBeDoubleTapping = false; startSeekTime = 0; setTimeout(() => this.hideSeekAnimationElements(), 150); }); let isBufferingOverlayVisible = false; let areControlsVisible = false; const showPlaybackToggleButton = () => { this.playbackToggleButton.show(); }; const hidePlaybackToggleButton = () => { this.playbackToggleButton.hide(); }; uimanager.onBufferingShow.subscribe(() => { isBufferingOverlayVisible = true; hidePlaybackToggleButton(); }); uimanager.onBufferingHide.subscribe(() => { isBufferingOverlayVisible = false; if (areControlsVisible) { showPlaybackToggleButton(); } }); uimanager.onControlsHide.subscribe(() => { areControlsVisible = false; hidePlaybackToggleButton(); }); uimanager.onControlsShow.subscribe(() => { areControlsVisible = true; if (!isBufferingOverlayVisible) { showPlaybackToggleButton(); } }); this.touchControlEvents.onSeekBackward.subscribe(() => { playerSeekTime -= this.config.seekTime; player.seek(playerSeekTime); this.seekBackwardLabel.setText( Math.abs(Math.round(playerSeekTime - startSeekTime)) + ' ' + i18n.performLocalization(i18n.getLocalizer('settings.time.seconds')), ); this.seekBackwardLabel.show(); this.getDomElement().addClass(this.prefixCss(this.SEEK_BACKWARD_CLASS)); this.seekForwardLabel.hide(); this.getDomElement().removeClass(this.prefixCss(this.SEEK_FORWARD_CLASS)); }); this.touchControlEvents.onSeekForward.subscribe(() => { playerSeekTime += this.config.seekTime; player.seek(playerSeekTime); this.seekForwardLabel.setText( Math.abs(Math.round(playerSeekTime - startSeekTime)) + ' ' + i18n.performLocalization(i18n.getLocalizer('settings.time.seconds')), ); this.seekForwardLabel.show(); this.getDomElement().addClass(this.prefixCss(this.SEEK_FORWARD_CLASS)); this.seekBackwardLabel.hide(); this.getDomElement().removeClass(this.prefixCss(this.SEEK_BACKWARD_CLASS)); }); this.touchControlEvents.onSingleClick.subscribe((_, e) => { uimanager.getUI().toggleUiShown(); playerSeekTime = player.getCurrentTime(); startSeekTime = playerSeekTime; const eventTarget = (e as Event).target as HTMLElementWithComponent; const rect = eventTarget.getBoundingClientRect(); const eventTapX = (<MouseEvent>e).clientX - rect.left; const eventTapY = (<MouseEvent>e).clientY - rect.top; this.latestTapPosition = { x: eventTapX, y: eventTapY }; }); this.touchControlEvents.onDoubleClick.subscribe((_, e) => { uimanager.getUI().hideUi(); const event = e as Event; const eventTarget = event.target as HTMLElementWithComponent; if (!eventTarget || !(eventTarget.component instanceof TouchControlOverlay)) { return; } const width = eventTarget.clientWidth; const tapMargin = width * 0.4; const rect = eventTarget.getBoundingClientRect(); const eventTapX = (<MouseEvent>e).clientX - rect.left; const eventTapY = (<MouseEvent>e).clientY - rect.top; const doubleTapMargin = this.config.seekDoubleTapMargin; if ( Math.abs(this.latestTapPosition.x - eventTapX) <= doubleTapMargin && Math.abs(this.latestTapPosition.y - eventTapY) <= doubleTapMargin ) if (eventTapX < tapMargin) { this.touchControlEvents.onSeekBackward.dispatch(this); } else if (eventTapX > width - tapMargin) { this.touchControlEvents.onSeekForward.dispatch(this); } this.latestTapPosition = { x: eventTapX, y: eventTapY }; }); this.getDomElement().on('click', e => { if ((e.target as HTMLElementWithComponent).component instanceof TouchControlOverlay) { clickEventDispatcher(e); } }); const clickEventDispatcher = (e: Event): void => { if (this.couldBeDoubleTapping) { this.onDoubleClickEvent(e); } else { this.onSingleClickEvent(e); } this.couldBeDoubleTapping = true; this.doubleTapTimeout.start(); }; } private hideSeekAnimationElements(): void { this.getDomElement().removeClass(this.prefixCss(this.SEEK_FORWARD_CLASS)); this.getDomElement().removeClass(this.prefixCss(this.SEEK_BACKWARD_CLASS)); this.seekForwardLabel.hide(); this.seekBackwardLabel.hide(); } protected onDoubleClickEvent(e: Event) { this.touchControlEvents.onDoubleClick.dispatch(this, e); } protected onSingleClickEvent(e: Event) { this.touchControlEvents.onSingleClick.dispatch(this, e); } get onClick(): EDEvent<TouchControlOverlay, NoArgs> { return this.touchControlEvents.onSingleClick.getEvent(); } }