Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bring in player refactor with seek ahead/back #7460

Merged
merged 2 commits into from
Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions static/app-strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2275,5 +2275,6 @@
"Use https": "Use https",
"Custom Servers": "Custom Servers",
"Add A Server": "Add A Server",
"Autoplay Next is off.": "Autoplay Next is off.",
"--end--": "--end--"
}
12 changes: 3 additions & 9 deletions ui/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ type Analytics = {
readyState: number,
}
) => Promise<any>,
adsFetchedEvent: () => void,
adsReceivedEvent: (any) => void,
adsErrorEvent: (any) => void,
emailProvidedEvent: () => void,
emailVerifiedEvent: () => void,
rewardEligibleEvent: () => void,
Expand Down Expand Up @@ -168,10 +165,7 @@ async function sendWatchmanData(body) {
});

return response;
} catch (err) {
console.log('ERROR FROM WATCHMAN BACKEND');
console.log(err);
}
} catch (err) {}
}

const analytics: Analytics = {
Expand Down Expand Up @@ -213,7 +207,7 @@ const analytics: Analytics = {
startWatchmanIntervalIfNotRunning();
}
},
videoStartEvent: (claimId, duration, poweredBy, passedUserId, canonicalUrl, passedPlayer, videoBitrate) => {
videoStartEvent: (claimId, timeToStartVideo, poweredBy, passedUserId, canonicalUrl, passedPlayer, videoBitrate) => {
// populate values for watchman when video starts
userId = passedUserId;
claimUrl = canonicalUrl;
Expand All @@ -224,7 +218,7 @@ const analytics: Analytics = {
bitrateAsBitsPerSecond = videoBitrate;

// sendPromMetric('time_to_start', duration);
sendMatomoEvent('Media', 'TimeToStart', claimId, duration);
sendMatomoEvent('Media', 'TimeToStart', claimId, timeToStartVideo);
},
error: (message) => {
return new Promise((resolve) => {
Expand Down
46 changes: 46 additions & 0 deletions ui/component/viewers/videoViewer/internal/lbry-volume-bar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// ****************************************************************************
// LbryVolumeBarClass
// ****************************************************************************

import videojs from 'video.js';

const isDev = process.env.NODE_ENV !== 'production';

const VIDEOJS_CONTROL_BAR_CLASS = 'ControlBar';
const VIDEOJS_VOLUME_PANEL_CLASS = 'VolumePanel';
const VIDEOJS_VOLUME_CONTROL_CLASS = 'VolumeControl';
const VIDEOJS_VOLUME_BAR_CLASS = 'VolumeBar';

class LbryVolumeBarClass extends videojs.getComponent(VIDEOJS_VOLUME_BAR_CLASS) {
constructor(player, options = {}) {
super(player, options);
}

static replaceExisting(player) {
try {
const volumeControl = player
.getChild(VIDEOJS_CONTROL_BAR_CLASS)
.getChild(VIDEOJS_VOLUME_PANEL_CLASS)
.getChild(VIDEOJS_VOLUME_CONTROL_CLASS);
const volumeBar = volumeControl.getChild(VIDEOJS_VOLUME_BAR_CLASS);
volumeControl.removeChild(volumeBar);
volumeControl.addChild(new LbryVolumeBarClass(player));
} catch (error) {
// In case it slips in 'Production', the original volume bar will be used and the site should still be working
// (just not exactly the way we want).
if (isDev) throw Error('\n\nvideojs.jsx: Volume Panel hierarchy changed?\n\n' + error);
}
}

handleMouseDown(event) {
super.handleMouseDown(event);
event.stopPropagation();
}

handleMouseMove(event) {
super.handleMouseMove(event);
event.stopPropagation();
}
}

export default LbryVolumeBarClass;
236 changes: 236 additions & 0 deletions ui/component/viewers/videoViewer/internal/videojs-events.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// @flow
import { useEffect } from 'react';

const isDev = process.env.NODE_ENV !== 'production';

const TAP = {
NONE: 'NONE',
UNMUTE: 'UNMUTE',
RETRY: 'RETRY',
};

const setLabel = (controlBar, childName, label) => {
const c = controlBar.getChild(childName);
if (c) {
c.controlText(label);
}
};

// $FlowFixMe
const VideoJsEvents = ({
tapToUnmuteRef,
tapToRetryRef,
setReload,
videoTheaterMode,
playerRef,
autoplaySetting,
replay,
}: {
tapToUnmuteRef: any, // DOM element
tapToRetryRef: any, // DOM element
setReload: any, // react hook
videoTheaterMode: any, // dispatch function
playerRef: any, // DOM element
autoplaySetting: boolean,
replay: boolean,
}) => {
// 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;
if (player && (player.muted() || player.volume() === 0)) {
// The css starts as "hidden". We make it visible here without
// re-rendering the whole thing.
showTapButton(TAP.UNMUTE);
} else {
showTapButton(TAP.NONE);
}
}

function onVolumeChange() {
const player = playerRef.current;
if (player && !player.muted()) {
showTapButton(TAP.NONE);
}
}

function onError() {
const player = playerRef.current;
showTapButton(TAP.RETRY);

// reattach initial play listener in case we recover from error successfully
// $FlowFixMe
player.one('play', onInitialPlay);

if (player && player.loadingSpinner) {
player.loadingSpinner.hide();
}
}

// const onEnded = React.useCallback(() => {
// if (!adUrl) {
// showTapButton(TAP.NONE);
// }
// }, [adUrl]);

useEffect(() => {
const player = playerRef.current;
if (player) {
const controlBar = player.getChild('controlBar');
controlBar
.getChild('TheaterModeButton')
.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;
if (player) {
player.muted(false);
if (player.volume() === 0) {
player.volume(1.0);
}
}
showTapButton(TAP.NONE);
}

function retryVideoAfterFailure() {
const player = playerRef.current;
if (player) {
setReload(Date.now());
showTapButton(TAP.NONE);
}
}

function showTapButton(tapButton) {
const setButtonVisibility = (theRef, newState) => {
// Use the DOM to control the state of the button to prevent re-renders.
if (theRef.current) {
const curState = theRef.current.style.visibility === 'visible';
if (newState !== curState) {
theRef.current.style.visibility = newState ? 'visible' : 'hidden';
}
}
};

switch (tapButton) {
case TAP.NONE:
setButtonVisibility(tapToUnmuteRef, false);
setButtonVisibility(tapToRetryRef, false);
break;
case TAP.UNMUTE:
setButtonVisibility(tapToUnmuteRef, true);
setButtonVisibility(tapToRetryRef, false);
break;
case TAP.RETRY:
setButtonVisibility(tapToUnmuteRef, false);
setButtonVisibility(tapToRetryRef, true);
break;
default:
if (isDev) throw new Error('showTapButton: unexpected ref');
break;
}
}

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) {
player.play();
}
}, [replay]);

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);
// player.on('ended', onEnded);
}

return {
retryVideoAfterFailure,
unmuteAndHideHint,
initializeEvents,
};
};

export default VideoJsEvents;
Loading