From 36ddc69c13d08831a17697ea37c3cf26171a8264 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Tue, 17 May 2022 13:47:35 +0800 Subject: [PATCH 1/3] videojs: refactor i18n (breaking up 'events' file) ## Issue I couldn't find where the i18n went because the "events" refactor is just as confusing as the original -- unrelated things are still lumped together in a file. Also, factoring based on events isn't useful -- it is features that drive what events are needed, not the other way around. This forces features to register events here, and do other things elsewhere? It will be more intuitive to have a one-file-per-feature structure. ## Change Use existing frameworks to encapsulate things to manageable units/features: (1) the React useEffect files (can be used isolate out React features like 'tap-to-mute' handling). (2) the videojs plugins framework. --- .../internal/plugins/videojs-i18n/plugin.js | 101 ++++++++++++++++++ .../videoViewer/internal/videojs-events.jsx | 72 +------------ .../viewers/videoViewer/internal/videojs.jsx | 41 +++---- 3 files changed, 124 insertions(+), 90 deletions(-) create mode 100644 ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js diff --git a/ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js b/ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js new file mode 100644 index 000000000..d7dc6757c --- /dev/null +++ b/ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js @@ -0,0 +1,101 @@ +/** + * Although videojs has its own i18n system, it is not trivial to pass our + * translation data to it. Instead of maintaining two sets of localization + * systems, we'll just ignore the videojs one and update the strings ourselves + * through events. + * + * We use events because that's the only way to update components with dynamic + * text like "Play/Pause" in videojs. We can't just define the set of texts at + * initialization, unless we use custom components to do that. + * + * For the case of 'MuteToggle', videojs changes the text at 'loadstart', so + * this event was chosen as the "lowest common denominator" to update the other + * static-text components. + */ + +// @flow +import videojs from 'video.js'; +import type { Player } from '../../videojs'; + +const VERSION = '1.0.0'; +const defaultOptions = {}; + +function setLabel(controlBar, childName, label) { + try { + controlBar.getChild(childName).controlText(label); + } catch (e) { + // We want to be notified, at least on dev, over any structural changes, + // so don't check for null children and let the error surface. + // @if process.env.NODE_ENV!='production' + console.error(e); + // @endif + } +} + +function resolveCtrlText(e, player) { + if (player) { + const ctrlBar = player.getChild('controlBar'); + switch (e.type) { + case 'play': + setLabel(ctrlBar, 'PlayToggle', __('Pause (space)')); + break; + case 'pause': + setLabel(ctrlBar, 'PlayToggle', __('Play (space)')); + break; + case 'volumechange': + ctrlBar + .getChild('VolumePanel') + .getChild('MuteToggle') + .controlText(player.muted() || player.volume() === 0 ? __('Unmute (m)') : __('Mute (m)')); + break; + case 'fullscreenchange': + setLabel(ctrlBar, 'FullscreenToggle', player.isFullscreen() ? __('Exit Fullscreen (f)') : __('Fullscreen (f)')); + break; + case 'loadstart': + // --- Do everything --- + setLabel(ctrlBar, 'PlaybackRateMenuButton', __('Playback Rate (<, >)')); + setLabel(ctrlBar, 'QualityButton', __('Quality')); + setLabel(ctrlBar, 'PlayNextButton', __('Play Next (SHIFT+N)')); + setLabel(ctrlBar, 'PlayPreviousButton', __('Play Previous (SHIFT+P)')); + setLabel(ctrlBar, 'TheaterModeButton', __('Toggle Theater Mode (t)')); + setLabel(ctrlBar, 'AutoplayNextButton', __('Toggle Autoplay Next')); + ctrlBar + .getChild('VolumePanel') + .getChild('MuteToggle') + .controlText(player.muted() || player.volume() === 0 ? __('Unmute (m)') : __('Mute (m)')); + + resolveCtrlText({ type: 'play' }); + resolveCtrlText({ type: 'pause' }); + resolveCtrlText({ type: 'volumechange' }); + resolveCtrlText({ type: 'fullscreenchange' }); + break; + default: + // @if process.env.NODE_ENV!='production' + throw Error('Unexpected: ' + e.type); + // @endif + } + } +} + +function onPlayerReady(player: Player, options: {}) { + const h = (e) => resolveCtrlText(e, player); + + player.on('play', h); + player.on('pause', h); + player.on('loadstart', h); + player.on('fullscreenchange', h); + player.on('volumechange', h); +} + +/** + * Odysee custom i18n plugin. + * @param options + */ +function i18n(options: {}) { + this.ready(() => onPlayerReady(this, videojs.mergeOptions(defaultOptions, options))); +} + +videojs.registerPlugin('i18n', i18n); +i18n.VERSION = VERSION; + +export default i18n; diff --git a/ui/component/viewers/videoViewer/internal/videojs-events.jsx b/ui/component/viewers/videoViewer/internal/videojs-events.jsx index eb0f04a89..17723497d 100644 --- a/ui/component/viewers/videoViewer/internal/videojs-events.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs-events.jsx @@ -10,13 +10,6 @@ const TAP = { RETRY: 'RETRY', }; -const setLabel = (controlBar, childName, label) => { - const c = controlBar.getChild(childName); - if (c) { - c.controlText(label); - } -}; - const VideoJsEvents = ({ tapToUnmuteRef, tapToRetryRef, @@ -96,63 +89,6 @@ const VideoJsEvents = ({ }); } - // Override the player's control text. We override to: - // 1. Add keyboard shortcut to the tool-tip. - // 2. Override videojs' i18n and use our own (don't want to have 2 systems). - // - // Notes: - // - For dynamic controls (e.g. play/pause), those unfortunately need to be - // updated again at their event-listener level (that's just the way videojs - // updates the text), hence the need to listen to 'play', 'pause' and 'volumechange' - // on top of just 'loadstart'. - // - videojs changes the MuteToggle text at 'loadstart', so this was chosen - // as the listener to update static texts. - - function resolveCtrlText(e) { - const player = playerRef.current; - if (player) { - const ctrlBar = player.getChild('controlBar'); - switch (e.type) { - case 'play': - setLabel(ctrlBar, 'PlayToggle', __('Pause (space)')); - break; - case 'pause': - setLabel(ctrlBar, 'PlayToggle', __('Play (space)')); - break; - case 'volumechange': - ctrlBar - .getChild('VolumePanel') - .getChild('MuteToggle') - .controlText(player.muted() || player.volume() === 0 ? __('Unmute (m)') : __('Mute (m)')); - break; - case 'fullscreenchange': - setLabel( - ctrlBar, - 'FullscreenToggle', - player.isFullscreen() ? __('Exit Fullscreen (f)') : __('Fullscreen (f)') - ); - break; - case 'loadstart': - // --- Do everything --- - setLabel(ctrlBar, 'PlaybackRateMenuButton', __('Playback Rate (<, >)')); - setLabel(ctrlBar, 'QualityButton', __('Quality')); - setLabel(ctrlBar, 'PlayNextButton', __('Play Next (SHIFT+N)')); - setLabel(ctrlBar, 'PlayPreviousButton', __('Play Previous (SHIFT+P)')); - setLabel(ctrlBar, 'TheaterModeButton', videoTheaterMode ? __('Default Mode (t)') : __('Theater Mode (t)')); - setLabel(ctrlBar, 'AutoplayNextButton', autoplaySetting ? __('Autoplay Next On') : __('Autoplay Next Off')); - - resolveCtrlText({ type: 'play' }); - resolveCtrlText({ type: 'pause' }); - resolveCtrlText({ type: 'volumechange' }); - resolveCtrlText({ type: 'fullscreenchange' }); - break; - default: - if (isDev) throw Error('Unexpected: ' + e.type); - break; - } - } - } - function onInitialPlay() { const player = playerRef.current; @@ -281,13 +217,8 @@ const VideoJsEvents = ({ function initializeEvents() { const player = playerRef.current; - // Add various event listeners to player + player.one('play', onInitialPlay); - player.on('play', resolveCtrlText); - player.on('pause', resolveCtrlText); - player.on('loadstart', resolveCtrlText); - player.on('fullscreenchange', resolveCtrlText); - player.on('volumechange', resolveCtrlText); player.on('volumechange', onVolumeChange); player.on('error', onError); // custom tracking plugin, event used for watchman data, and marking view/getting rewards @@ -316,7 +247,6 @@ const VideoJsEvents = ({ setTimeout(() => { // Do not jump ahead if user has paused the player if (player.paused()) return; - player.liveTracker.seekToLiveEdge(); }, 5 * 1000); }); diff --git a/ui/component/viewers/videoViewer/internal/videojs.jsx b/ui/component/viewers/videoViewer/internal/videojs.jsx index cd082c5a7..c8a9ea629 100644 --- a/ui/component/viewers/videoViewer/internal/videojs.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs.jsx @@ -20,6 +20,7 @@ import Chromecast from './chromecast'; import playerjs from 'player.js'; import qualityLevels from 'videojs-contrib-quality-levels'; import React, { useEffect, useRef, useState } from 'react'; +import i18n from './plugins/videojs-i18n/plugin'; import recsys from './plugins/videojs-recsys/plugin'; // import runAds from './ads'; import videojs from 'video.js'; @@ -37,12 +38,16 @@ export type Player = { claimSrcOriginal: ?{ src: string, type: string }, claimSrcVhs: ?{ src: string, type: string }, isLivestream?: boolean, - // -- original -- + // -- plugins --- + mobileUi: (any) => void, + chromecast: (any) => void, + overlay: (any) => void, + hlsQualitySelector: ?any, + i18n: (any) => void, + // -- base videojs -- controlBar: { addChild: (string, any) => void }, loadingSpinner: any, autoplay: (any) => boolean, - chromecast: (any) => void, - hlsQualitySelector: ?any, tech: (?boolean) => { vhs: ?any }, currentTime: (?number) => number, dispose: () => void, @@ -52,11 +57,9 @@ export type Player = { exitFullscreen: () => boolean, getChild: (string) => any, isFullscreen: () => boolean, - mobileUi: (any) => void, muted: (?boolean) => boolean, on: (string, (any) => void) => void, one: (string, (any) => void) => void, - overlay: (any) => void, play: () => Promise, playbackRate: (?number) => number, readyState: () => number, @@ -104,21 +107,19 @@ type Props = { const IS_IOS = platform.isIOS(); -if (!Object.keys(videojs.getPlugins()).includes('eventTracking')) { - videojs.registerPlugin('eventTracking', eventTracking); -} +const PLUGIN_MAP = { + eventTracking: eventTracking, + hlsQualitySelector: hlsQualitySelector, + qualityLevels: qualityLevels, + recsys: recsys, + i18n: i18n, +}; -if (!Object.keys(videojs.getPlugins()).includes('hlsQualitySelector')) { - videojs.registerPlugin('hlsQualitySelector', hlsQualitySelector); -} - -if (!Object.keys(videojs.getPlugins()).includes('qualityLevels')) { - videojs.registerPlugin('qualityLevels', qualityLevels); -} - -if (!Object.keys(videojs.getPlugins()).includes('recsys')) { - videojs.registerPlugin('recsys', recsys); -} +Object.entries(PLUGIN_MAP).forEach(([pluginName, plugin]) => { + if (!Object.keys(videojs.getPlugins()).includes(pluginName)) { + videojs.registerPlugin(pluginName, plugin); + } +}); // **************************************************************************** // VideoJs @@ -277,6 +278,8 @@ export default React.memo(function VideoJs(props: Props) { // Initialize mobile UI. player.mobileUi(); + player.i18n(); + if (!embedded) { window.player.bigPlayButton && window.player.bigPlayButton.hide(); } else { From 535d02807a43311bc27a0cb4e7cdc38ad6bd6150 Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Tue, 17 May 2022 14:57:14 +0800 Subject: [PATCH 2/3] Refactor "Autoplay Next" --- .../videoViewer/internal/autoplay-next.js | 33 -------- .../internal/effects/use-autoplay-next.js | 84 +++++++++++++++++++ .../internal/plugins/videojs-i18n/plugin.js | 4 +- .../videoViewer/internal/videojs-events.jsx | 19 ----- .../viewers/videoViewer/internal/videojs.jsx | 3 - ui/component/viewers/videoViewer/view.jsx | 5 +- 6 files changed, 90 insertions(+), 58 deletions(-) delete mode 100644 ui/component/viewers/videoViewer/internal/autoplay-next.js create mode 100644 ui/component/viewers/videoViewer/internal/effects/use-autoplay-next.js diff --git a/ui/component/viewers/videoViewer/internal/autoplay-next.js b/ui/component/viewers/videoViewer/internal/autoplay-next.js deleted file mode 100644 index 63d8ceb0b..000000000 --- a/ui/component/viewers/videoViewer/internal/autoplay-next.js +++ /dev/null @@ -1,33 +0,0 @@ -// @flow -import type { Player } from './videojs'; -import videojs from 'video.js'; - -class AutoplayNextButton extends videojs.getComponent('Button') { - constructor(player, options = {}, autoplayNext) { - super(player, options, autoplayNext); - const title = autoplayNext ? 'Autoplay Next On' : 'Autoplay Next Off'; - - this.controlText(title); - this.setAttribute('aria-label', title); - this.addClass('vjs-button--autoplay-next'); - this.setAttribute('aria-checked', autoplayNext); - } -} - -export function addAutoplayNextButton(player: Player, toggleAutoplayNext: () => void, autoplayNext: boolean) { - const controlBar = player.getChild('controlBar'); - - const autoplayButton = new AutoplayNextButton( - player, - { - name: 'AutoplayNextButton', - text: 'Autoplay Next', - clickHandler: () => { - toggleAutoplayNext(); - }, - }, - autoplayNext - ); - - controlBar.addChild(autoplayButton); -} diff --git a/ui/component/viewers/videoViewer/internal/effects/use-autoplay-next.js b/ui/component/viewers/videoViewer/internal/effects/use-autoplay-next.js new file mode 100644 index 000000000..4f11bda45 --- /dev/null +++ b/ui/component/viewers/videoViewer/internal/effects/use-autoplay-next.js @@ -0,0 +1,84 @@ +/** + * Videojs "Autoplay Next" button. + * + * --- How to use --- + * Apply `useAutoplayNext` in your React component. It registers an effect that + * listens to the given Redux state, and returns a callback for you to mount the + * custom videojs component. + * + * --- Notes --- + * Usually, custom videojs components can just listen to videojs events, query + * states from `player` (e.g. player.paused()) and update accordingly. But since + * the state comes from Redux, there will be a need to listen and pass the info + * to videojs somehow. + * + * Instead of going through an 'effect->css->videojs' trip, we'll just listen to + * the Redux state through a normal effect to update the component. + * + * This file aims to encapsulate both the React and Videojs side of things + * through a single `useAutoplayNext` call. + */ + +// @flow +import React from 'react'; +import videojs from 'video.js'; +import type { Player } from '../videojs'; + +// **************************************************************************** +// AutoplayNextButton +// **************************************************************************** + +class AutoplayNextButton extends videojs.getComponent('Button') { + constructor(player, options = {}, autoplayNext) { + super(player, options, autoplayNext); + const title = __(autoplayNext ? 'Autoplay Next On' : 'Autoplay Next Off'); + + this.controlText(title); + this.addClass('vjs-button--autoplay-next'); + this.setAttribute('aria-label', title); + this.setAttribute('aria-checked', autoplayNext); + } +} + +function addAutoplayNextButton(player: Player, toggleAutoplayNext: () => void, autoplayNext: boolean) { + const controlBar = player.getChild('controlBar'); + + const autoplayButton = new AutoplayNextButton( + player, + { + name: 'AutoplayNextButton', + text: 'Autoplay Next', + clickHandler: () => { + toggleAutoplayNext(); + }, + }, + autoplayNext + ); + + controlBar.addChild(autoplayButton); +} + +// **************************************************************************** +// useAutoplayNext +// **************************************************************************** + +export default function useAutoplayNext(playerRef: any, autoplayNext: boolean) { + React.useEffect(() => { + const player = playerRef.current; + if (player) { + const touchOverlay = player.getChild('TouchOverlay'); + const controlBar = player.getChild('controlBar') || touchOverlay.getChild('controlBar'); + const autoplayButton = controlBar.getChild('AutoplayNextButton'); + + if (autoplayButton) { + const title = autoplayNext ? __('Autoplay Next On') : __('Autoplay Next Off'); + + autoplayButton.controlText(title); + autoplayButton.setAttribute('aria-label', title); + autoplayButton.setAttribute('aria-checked', autoplayNext); + } + } + }, [autoplayNext]); + + return addAutoplayNextButton; +} diff --git a/ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js b/ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js index d7dc6757c..2847a91e2 100644 --- a/ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js +++ b/ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js @@ -11,6 +11,9 @@ * For the case of 'MuteToggle', videojs changes the text at 'loadstart', so * this event was chosen as the "lowest common denominator" to update the other * static-text components. + * + * --- Notes --- + * (1) 'AutoplayNextButton' handles i18n (among other things) on its own. */ // @flow @@ -58,7 +61,6 @@ function resolveCtrlText(e, player) { setLabel(ctrlBar, 'PlayNextButton', __('Play Next (SHIFT+N)')); setLabel(ctrlBar, 'PlayPreviousButton', __('Play Previous (SHIFT+P)')); setLabel(ctrlBar, 'TheaterModeButton', __('Toggle Theater Mode (t)')); - setLabel(ctrlBar, 'AutoplayNextButton', __('Toggle Autoplay Next')); ctrlBar .getChild('VolumePanel') .getChild('MuteToggle') diff --git a/ui/component/viewers/videoViewer/internal/videojs-events.jsx b/ui/component/viewers/videoViewer/internal/videojs-events.jsx index 17723497d..fc72f8a84 100644 --- a/ui/component/viewers/videoViewer/internal/videojs-events.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs-events.jsx @@ -16,7 +16,6 @@ const VideoJsEvents = ({ setReload, videoTheaterMode, playerRef, - autoplaySetting, replay, claimId, userId, @@ -33,7 +32,6 @@ const VideoJsEvents = ({ setReload: any, // react hook videoTheaterMode: any, // dispatch function playerRef: any, // DOM element - autoplaySetting: boolean, replay: boolean, claimId: ?string, userId: ?number, @@ -191,23 +189,6 @@ const VideoJsEvents = ({ } } - useEffect(() => { - const player = playerRef.current; - if (player) { - const touchOverlay = player.getChild('TouchOverlay'); - const controlBar = player.getChild('controlBar') || touchOverlay.getChild('controlBar'); - const autoplayButton = controlBar.getChild('AutoplayNextButton'); - - if (autoplayButton) { - const title = autoplaySetting ? __('Autoplay Next On') : __('Autoplay Next Off'); - - autoplayButton.controlText(title); - autoplayButton.setAttribute('aria-label', title); - autoplayButton.setAttribute('aria-checked', autoplaySetting); - } - } - }, [autoplaySetting]); - useEffect(() => { const player = playerRef.current; if (replay && player) { diff --git a/ui/component/viewers/videoViewer/internal/videojs.jsx b/ui/component/viewers/videoViewer/internal/videojs.jsx index c8a9ea629..ca6337609 100644 --- a/ui/component/viewers/videoViewer/internal/videojs.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs.jsx @@ -74,7 +74,6 @@ type Props = { adUrl: ?string, allowPreRoll: ?boolean, autoplay: boolean, - autoplaySetting: boolean, claimId: ?string, title: ?string, channelName: ?string, @@ -133,7 +132,6 @@ export default React.memo(function VideoJs(props: Props) { // adUrl, // TODO: this ad functionality isn't used, can be pulled out // allowPreRoll, autoplay, - autoplaySetting, claimId, title, channelName, @@ -202,7 +200,6 @@ export default React.memo(function VideoJs(props: Props) { setReload, videoTheaterMode, playerRef, - autoplaySetting, replay, claimValues, userId, diff --git a/ui/component/viewers/videoViewer/view.jsx b/ui/component/viewers/videoViewer/view.jsx index 2b9a82fb8..bfc94513b 100644 --- a/ui/component/viewers/videoViewer/view.jsx +++ b/ui/component/viewers/videoViewer/view.jsx @@ -15,8 +15,8 @@ import AutoplayCountdown from 'component/autoplayCountdown'; import usePrevious from 'effects/use-previous'; import FileViewerEmbeddedEnded from 'web/component/fileViewerEmbeddedEnded'; import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle'; +import useAutoplayNext from './internal/effects/use-autoplay-next'; import { addTheaterModeButton } from './internal/theater-mode'; -import { addAutoplayNextButton } from './internal/autoplay-next'; import { addPlayNextButton } from './internal/play-next'; import { addPlayPreviousButton } from './internal/play-previous'; import { useGetAds } from 'effects/use-get-ads'; @@ -154,6 +154,8 @@ function VideoViewer(props: Props) { const isFirstRender = React.useRef(true); const playerRef = React.useRef(null); + const addAutoplayNextButton = useAutoplayNext(playerRef, autoplayNext); + React.useEffect(() => { if (isPlaying) { doSetContentHistoryItem(claim.permanent_url); @@ -503,7 +505,6 @@ function VideoViewer(props: Props) { startMuted={autoplayIfEmbedded} toggleVideoTheaterMode={toggleVideoTheaterMode} autoplay={!embedded || autoplayIfEmbedded} - autoplaySetting={localAutoplayNext} claimId={claimId} title={claim && ((claim.value && claim.value.title) || claim.name)} channelName={channelName} From 5e944499f3265f3e3b86cf9ef26b58b10b81a67e Mon Sep 17 00:00:00 2001 From: infinite-persistence Date: Tue, 17 May 2022 15:10:28 +0800 Subject: [PATCH 3/3] Refactor "Theather Mode" --- .../internal/effects/use-theater-mode.js | 70 +++++++++++++++++++ .../internal/plugins/videojs-i18n/plugin.js | 4 +- .../videoViewer/internal/theater-mode.js | 25 ------- .../videoViewer/internal/videojs-events.jsx | 13 ---- .../viewers/videoViewer/internal/videojs.jsx | 3 - ui/component/viewers/videoViewer/view.jsx | 4 +- 6 files changed, 74 insertions(+), 45 deletions(-) create mode 100644 ui/component/viewers/videoViewer/internal/effects/use-theater-mode.js delete mode 100644 ui/component/viewers/videoViewer/internal/theater-mode.js diff --git a/ui/component/viewers/videoViewer/internal/effects/use-theater-mode.js b/ui/component/viewers/videoViewer/internal/effects/use-theater-mode.js new file mode 100644 index 000000000..d9fafcfbe --- /dev/null +++ b/ui/component/viewers/videoViewer/internal/effects/use-theater-mode.js @@ -0,0 +1,70 @@ +/** + * Videojs "Theater Mode" button. + * + * --- How to use --- + * Apply `useTheaterMode` in your React component. It registers an effect that + * listens to the given Redux state, and returns a callback for you to mount the + * custom videojs component. + * + * --- Notes --- + * Usually, custom videojs components can just listen to videojs events, query + * states from `player` (e.g. player.paused()) and update accordingly. But since + * the state comes from Redux, there will be a need to listen and pass the info + * to videojs somehow. + * + * Instead of going through an 'effect->css->videojs' trip, we'll just listen to + * the Redux state through a normal effect to update the component. + * + * This file aims to encapsulate both the React and Videojs side of things + * through a single `useAutoplayNext` call. + */ + +// @flow +import React from 'react'; +import videojs from 'video.js'; +import type { Player } from '../videojs'; + +// **************************************************************************** +// TheaterModeButton +// **************************************************************************** + +class TheaterModeButton extends videojs.getComponent('Button') { + constructor(player, options = {}) { + super(player, options); + this.addClass('vjs-button--theater-mode'); + this.controlText(__('Theater Mode (t)')); + } +} + +function addTheaterModeButton(player: Player, toggleVideoTheaterMode: () => void) { + const controlBar = player.getChild('controlBar'); + + const theaterMode = new TheaterModeButton(player, { + name: 'TheaterModeButton', + text: 'Theater mode', + clickHandler: () => { + toggleVideoTheaterMode(); + }, + }); + + controlBar.addChild(theaterMode); +} + +// **************************************************************************** +// useAutoplayNext +// **************************************************************************** + +export default function useTheaterMode(playerRef: any, videoTheaterMode: boolean) { + React.useEffect(() => { + const player = playerRef.current; + if (player) { + const controlBar = player.getChild('controlBar'); + const theaterButton = controlBar.getChild('TheaterModeButton'); + if (theaterButton) { + theaterButton.controlText(videoTheaterMode ? __('Default Mode (t)') : __('Theater Mode (t)')); + } + } + }, [videoTheaterMode]); + + return addTheaterModeButton; +} diff --git a/ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js b/ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js index 2847a91e2..2ca795992 100644 --- a/ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js +++ b/ui/component/viewers/videoViewer/internal/plugins/videojs-i18n/plugin.js @@ -13,7 +13,8 @@ * static-text components. * * --- Notes --- - * (1) 'AutoplayNextButton' handles i18n (among other things) on its own. + * (1) 'AutoplayNextButton' and 'TheaterModeButton' handles i18n (among other + * things) on its own. */ // @flow @@ -60,7 +61,6 @@ function resolveCtrlText(e, player) { setLabel(ctrlBar, 'QualityButton', __('Quality')); setLabel(ctrlBar, 'PlayNextButton', __('Play Next (SHIFT+N)')); setLabel(ctrlBar, 'PlayPreviousButton', __('Play Previous (SHIFT+P)')); - setLabel(ctrlBar, 'TheaterModeButton', __('Toggle Theater Mode (t)')); ctrlBar .getChild('VolumePanel') .getChild('MuteToggle') diff --git a/ui/component/viewers/videoViewer/internal/theater-mode.js b/ui/component/viewers/videoViewer/internal/theater-mode.js deleted file mode 100644 index 8eeaa9201..000000000 --- a/ui/component/viewers/videoViewer/internal/theater-mode.js +++ /dev/null @@ -1,25 +0,0 @@ -// @flow -import type { Player } from './videojs'; -import videojs from 'video.js'; - -class TheaterModeButton extends videojs.getComponent('Button') { - constructor(player, options = {}) { - super(player, options); - this.addClass('vjs-button--theater-mode'); - this.controlText('Theater Mode'); - } -} - -export function addTheaterModeButton(player: Player, toggleVideoTheaterMode: () => void) { - const controlBar = player.getChild('controlBar'); - - const theaterMode = new TheaterModeButton(player, { - name: 'TheaterModeButton', - text: 'Theater mode', - clickHandler: () => { - toggleVideoTheaterMode(); - }, - }); - - controlBar.addChild(theaterMode); -} diff --git a/ui/component/viewers/videoViewer/internal/videojs-events.jsx b/ui/component/viewers/videoViewer/internal/videojs-events.jsx index fc72f8a84..9c7d13e35 100644 --- a/ui/component/viewers/videoViewer/internal/videojs-events.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs-events.jsx @@ -14,7 +14,6 @@ const VideoJsEvents = ({ tapToUnmuteRef, tapToRetryRef, setReload, - videoTheaterMode, playerRef, replay, claimId, @@ -30,7 +29,6 @@ const VideoJsEvents = ({ tapToUnmuteRef: any, // DOM element tapToRetryRef: any, // DOM element setReload: any, // react hook - videoTheaterMode: any, // dispatch function playerRef: any, // DOM element replay: boolean, claimId: ?string, @@ -128,17 +126,6 @@ const VideoJsEvents = ({ // } // }, [adUrl]); - useEffect(() => { - const player = playerRef.current; - if (player) { - const controlBar = player.getChild('controlBar'); - const theaterButton = controlBar.getChild('TheaterModeButton'); - if (theaterButton) { - theaterButton.controlText(videoTheaterMode ? __('Default Mode (t)') : __('Theater Mode (t)')); - } - } - }, [videoTheaterMode]); - // when user clicks 'Unmute' button, turn audio on and hide unmute button function unmuteAndHideHint() { const player = playerRef.current; diff --git a/ui/component/viewers/videoViewer/internal/videojs.jsx b/ui/component/viewers/videoViewer/internal/videojs.jsx index ca6337609..9a5ce1e03 100644 --- a/ui/component/viewers/videoViewer/internal/videojs.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs.jsx @@ -87,7 +87,6 @@ type Props = { sourceType: string, startMuted: boolean, userId: ?number, - videoTheaterMode: boolean, defaultQuality: ?string, onPlayerReady: (Player, any) => void, playNext: () => void, @@ -145,7 +144,6 @@ export default React.memo(function VideoJs(props: Props) { sourceType, startMuted, userId, - videoTheaterMode, defaultQuality, onPlayerReady, playNext, @@ -198,7 +196,6 @@ export default React.memo(function VideoJs(props: Props) { tapToUnmuteRef, tapToRetryRef, setReload, - videoTheaterMode, playerRef, replay, claimValues, diff --git a/ui/component/viewers/videoViewer/view.jsx b/ui/component/viewers/videoViewer/view.jsx index bfc94513b..425f13c32 100644 --- a/ui/component/viewers/videoViewer/view.jsx +++ b/ui/component/viewers/videoViewer/view.jsx @@ -16,7 +16,7 @@ import usePrevious from 'effects/use-previous'; import FileViewerEmbeddedEnded from 'web/component/fileViewerEmbeddedEnded'; import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle'; import useAutoplayNext from './internal/effects/use-autoplay-next'; -import { addTheaterModeButton } from './internal/theater-mode'; +import useTheaterMode from './internal/effects/use-theater-mode'; import { addPlayNextButton } from './internal/play-next'; import { addPlayPreviousButton } from './internal/play-previous'; import { useGetAds } from 'effects/use-get-ads'; @@ -155,6 +155,7 @@ function VideoViewer(props: Props) { const playerRef = React.useRef(null); const addAutoplayNextButton = useAutoplayNext(playerRef, autoplayNext); + const addTheaterModeButton = useTheaterMode(playerRef, videoTheaterMode); React.useEffect(() => { if (isPlaying) { @@ -513,7 +514,6 @@ function VideoViewer(props: Props) { internalFeatureEnabled={internalFeature} shareTelemetry={shareTelemetry} replay={replay} - videoTheaterMode={videoTheaterMode} playNext={doPlayNext} playPrevious={doPlayPrevious} embedded={embedded}