UNPKG

bitmovin-player-ui

Version:
178 lines (153 loc) 7.4 kB
import { PlaybackToggleButton, PlaybackToggleButtonConfig } from './PlaybackToggleButton'; import { DOM } from '../../DOM'; import { UIInstanceManager } from '../../UIManager'; import { PlayerAPI, PlayerEventBase, WarningEvent } from 'bitmovin-player'; import { i18n } from '../../localization/i18n'; /** * A button that overlays the video and toggles between playback and pause. * * @category Buttons */ export class HugePlaybackToggleButton extends PlaybackToggleButton { constructor(config: PlaybackToggleButtonConfig = {}) { super(config); this.config = this.mergeConfig( config, { cssClass: 'ui-hugeplaybacktogglebutton', text: i18n.getLocalizer('playPause'), role: 'button', }, this.config, ); } configure(player: PlayerAPI, uimanager: UIInstanceManager): void { // Update button state through API events super.configure(player, uimanager, false); // Set enterFullscreenOnInitialPlayback if set in the uimanager config if (typeof uimanager.getConfig().enterFullscreenOnInitialPlayback === 'boolean') { this.config.enterFullscreenOnInitialPlayback = uimanager.getConfig().enterFullscreenOnInitialPlayback; } const togglePlayback = () => { if (player.isPlaying() || this.isPlayInitiated) { player.pause('ui'); } else { player.play('ui'); } }; const toggleFullscreen = () => { if (player.getViewMode() === player.exports.ViewMode.Fullscreen) { player.setViewMode(player.exports.ViewMode.Inline); } else { player.setViewMode(player.exports.ViewMode.Fullscreen); } }; let firstPlay = true; let clickTime = 0; let doubleClickTime = 0; /* * YouTube-style toggle button handling * * The goal is to prevent a short pause or playback interval between a click, that toggles playback, and a * double click, that toggles fullscreen. In this naive approach, the first click would e.g. start playback, * the second click would be detected as double click and toggle to fullscreen, and as second normal click stop * playback, which results is a short playback interval with max length of the double click detection * period (usually 500ms). * * To solve this issue, we defer handling of the first click for 200ms, which is almost unnoticeable to the user, * and just toggle playback if no second click (double click) has been registered during this period. If a double * click is registered, we just toggle the fullscreen. In the first 200ms, undesired playback changes thus cannot * happen. If a double click is registered within 500ms, we undo the playback change and switch fullscreen mode. * In the end, this method basically introduces a 200ms observing interval in which playback changes are prevented * if a double click happens. */ this.onClick.subscribe(() => { // Directly start playback on first click of the button. // This is a required workaround for mobile browsers where video playback needs to be triggered directly // by the user. A deferred playback start through the timeout below is not considered as user action and // therefore ignored by mobile browsers. if (firstPlay) { // Try to start playback. Then we wait for Play and only when it arrives, we disable the firstPlay flag. // If we disable the flag here, onClick was triggered programmatically instead of by a user interaction, and // playback is blocked (e.g. on mobile devices due to the programmatic play() call), we loose the chance to // ever start playback through a user interaction again with this button. togglePlayback(); if (this.config.enterFullscreenOnInitialPlayback) { player.setViewMode(player.exports.ViewMode.Fullscreen); } return; } const now = Date.now(); if (now - clickTime < 200) { // We have a double click inside the 200ms interval, just toggle fullscreen mode toggleFullscreen(); doubleClickTime = now; return; } else if (now - clickTime < 500) { // We have a double click inside the 500ms interval, undo playback toggle and toggle fullscreen mode toggleFullscreen(); togglePlayback(); doubleClickTime = now; return; } clickTime = now; setTimeout(() => { if (Date.now() - doubleClickTime > 200) { // No double click detected, so we toggle playback and wait what happens next togglePlayback(); } }, 200); }); player.on(player.exports.PlayerEvent.Play, () => { // Playback has really started, we can disable the flag to switch to normal toggle button handling firstPlay = false; }); player.on(player.exports.PlayerEvent.Warning, (event: WarningEvent) => { if (event.code === player.exports.WarningCode.PLAYBACK_COULD_NOT_BE_STARTED) { // if playback could not be started, reset the first play flag as we need the user interaction to start firstPlay = true; } }); const suppressPlayButtonTransitionAnimation = () => { // Disable the current animation this.setTransitionAnimationsEnabled(false); // Enable the transition animations for the next state change this.onToggle.subscribeOnce(() => { this.setTransitionAnimationsEnabled(true); }); }; // Hide the play button animation when the UI is loaded (it should only be animated on state changes) suppressPlayButtonTransitionAnimation(); const isAutoplayEnabled = player.getConfig().playback && Boolean(player.getConfig().playback.autoplay); // We only know if an autoplay attempt is upcoming if the player is not yet ready. It the player is already ready, // the attempt might be upcoming or might have already happened, but we don't have to handle that because we can // simply rely on isPlaying and the play state events. const isAutoplayUpcoming = !player.getSource() && isAutoplayEnabled; // Hide the play button when the player is already playing or autoplay is upcoming if (player.isPlaying() || isAutoplayUpcoming) { // Hide the play button (switch to playing state) this.on(); // Disable the animation of the playing state switch suppressPlayButtonTransitionAnimation(); // Show the play button without an animation if a play attempt is blocked player.on(player.exports.PlayerEvent.Warning, (event: WarningEvent) => { if (event.code === player.exports.WarningCode.PLAYBACK_COULD_NOT_BE_STARTED) { suppressPlayButtonTransitionAnimation(); } }); } } /** * Enables or disables the play state transition animations of the play button image. Can be used to suppress * animations. * @param {boolean} enabled true to enable the animations (default), false to disable them */ protected setTransitionAnimationsEnabled(enabled: boolean): void { const noTransitionAnimationsClass = this.prefixCss('no-transition-animations'); if (enabled) { this.getDomElement().removeClass(noTransitionAnimationsClass); } else if (!this.getDomElement().hasClass(noTransitionAnimationsClass)) { this.getDomElement().addClass(noTransitionAnimationsClass); } } }