Reuse videojs instance between video reload, return mobile UI plugin for iOS (#1512)
* add mobile plugin back on ios * further touchups and fix ios * finish mobile functionality * dont show big play button on mobile * remove logs * proof of concept * dont go full screen on rotate * add back functionality * replace dispose event with navigate away * bugfix * turn off show if you liked button and nag only on homepage * add back old functionality * ending event not working * test here * working but needs cleanup * more player touchups * bugfix * add settings button on mobile * more touchups * more cleanups * touchup loading functionality * fix hover thumbnails * touchup and eslint fix * fix repopulation bug * change recsys event name * bugfix events * change the way buttons are removed and added * finish chapters button * refactor to use videojs methods * refactor to fix autoplay next * ux touchups * seems to be behaving properly * control bar behaving how it should * fix control bar on ios * working on flow and eslint errors * bugfix and flow fixes * bring back nudge * fix playlist button bug * remove chapter markers properly * show big play button * bugfix recsys closed event * fix analytics bug * fix embeds * bugfix * possible bugfix for kp * bugfix playlist buttons * fix issue with mobile ui plugin * fix firefox autoplay issue * fix bug for play on floating player closed * bugfix volume control for ios * instantiate new player if switching between claim types * fix flow and lint errors * fix control bar not showing up when switching sources * dispose old player if recreating * bugfix save position * reset recsys data between videos * fix audio upload posters * clear claimSrcVhs on reload * bugfix errant image previews showing up * reset player value of having already switched quality * fix watch position not being used * bugfix switching between sources not perserving position * fix save position bug * fix playlist buttons * bugfix * code cleanup and add back 5 second feature
This commit is contained in:
parent
85cb741feb
commit
87c94e3c1c
17 changed files with 388 additions and 2263 deletions
|
@ -57,6 +57,7 @@ export default function NagLocaleSwitch(props: Props) {
|
||||||
const [switchOption, setSwitchOption] = React.useState(optionToSwitch);
|
const [switchOption, setSwitchOption] = React.useState(optionToSwitch);
|
||||||
|
|
||||||
if (localeSwitchDismissed || !optionToSwitch) return null;
|
if (localeSwitchDismissed || !optionToSwitch) return null;
|
||||||
|
if (!onFrontPage) return null;
|
||||||
|
|
||||||
let message = __(
|
let message = __(
|
||||||
// If no homepage, only suggest language switch
|
// If no homepage, only suggest language switch
|
||||||
|
|
|
@ -98,6 +98,9 @@ function overrideHoverTooltip(player: any, tsData: TimestampData, duration: numb
|
||||||
.getChild('mouseTimeDisplay')
|
.getChild('mouseTimeDisplay')
|
||||||
.getChild('timeTooltip');
|
.getChild('timeTooltip');
|
||||||
|
|
||||||
|
// sometimes old 'right' rule is persisted and messes up styling
|
||||||
|
timeTooltip.el().style.removeProperty('right');
|
||||||
|
|
||||||
timeTooltip.update = function (seekBarRect, seekBarPoint, time) {
|
timeTooltip.update = function (seekBarRect, seekBarPoint, time) {
|
||||||
const values = Object.values(tsData);
|
const values = Object.values(tsData);
|
||||||
// $FlowIssue: mixed
|
// $FlowIssue: mixed
|
||||||
|
@ -153,6 +156,19 @@ function load(player: any, timestampData: TimestampData, duration: number) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteHoverInformation(player) {
|
||||||
|
try {
|
||||||
|
const timeTooltip = player
|
||||||
|
.getChild('controlBar')
|
||||||
|
.getChild('progressControl')
|
||||||
|
.getChild('seekBar')
|
||||||
|
.getChild('mouseTimeDisplay')
|
||||||
|
.getChild('timeTooltip');
|
||||||
|
|
||||||
|
delete timeTooltip.update;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
export function parseAndLoad(player: any, claim: StreamClaim) {
|
export function parseAndLoad(player: any, claim: StreamClaim) {
|
||||||
console.assert(claim, 'null claim');
|
console.assert(claim, 'null claim');
|
||||||
|
|
||||||
|
@ -166,6 +182,8 @@ export function parseAndLoad(player: any, claim: StreamClaim) {
|
||||||
if (tsData && duration) {
|
if (tsData && duration) {
|
||||||
load(player, tsData, duration);
|
load(player, tsData, duration);
|
||||||
overrideHoverTooltip(player, tsData, duration);
|
overrideHoverTooltip(player, tsData, duration);
|
||||||
|
} else {
|
||||||
|
deleteHoverInformation(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ class AutoplayNextButton extends videojs.getComponent('Button') {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addAutoplayNextButton(player: Player, toggleAutoplayNext: () => void, autoplayNext: boolean) {
|
function addAutoplayNextButton(player: Player, toggleAutoplayNext: () => void, autoplayNext: boolean) {
|
||||||
const controlBar = player.getChild('controlBar');
|
const controlBar = player.controlBar;
|
||||||
|
|
||||||
const autoplayButton = new AutoplayNextButton(
|
const autoplayButton = new AutoplayNextButton(
|
||||||
player,
|
player,
|
||||||
|
@ -55,8 +55,10 @@ function addAutoplayNextButton(player: Player, toggleAutoplayNext: () => void, a
|
||||||
autoplayNext
|
autoplayNext
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (controlBar) {
|
||||||
controlBar.addChild(autoplayButton);
|
controlBar.addChild(autoplayButton);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
// useAutoplayNext
|
// useAutoplayNext
|
||||||
|
|
|
@ -47,8 +47,12 @@ function addTheaterModeButton(player: Player, toggleVideoTheaterMode: () => void
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (controlBar) {
|
||||||
|
const existingTheatreModeButton = controlBar.getChild('TheaterModeButton');
|
||||||
|
if (existingTheatreModeButton) controlBar.removeChild('TheaterModeButton');
|
||||||
controlBar.addChild(theaterMode);
|
controlBar.addChild(theaterMode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
// useAutoplayNext
|
// useAutoplayNext
|
||||||
|
@ -58,7 +62,7 @@ export default function useTheaterMode(playerRef: any, videoTheaterMode: boolean
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const player = playerRef.current;
|
const player = playerRef.current;
|
||||||
if (player) {
|
if (player) {
|
||||||
const controlBar = player.getChild('controlBar');
|
const controlBar = player.controlBar;
|
||||||
const theaterButton = controlBar.getChild('TheaterModeButton');
|
const theaterButton = controlBar.getChild('TheaterModeButton');
|
||||||
if (theaterButton) {
|
if (theaterButton) {
|
||||||
theaterButton.controlText(videoTheaterMode ? __('Default Mode (t)') : __('Theater Mode (t)'));
|
theaterButton.controlText(videoTheaterMode ? __('Default Mode (t)') : __('Theater Mode (t)'));
|
||||||
|
|
|
@ -11,7 +11,7 @@ class PlayNextButton extends videojs.getComponent('Button') {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addPlayNextButton(player: Player, playNextURI: () => void) {
|
export function addPlayNextButton(player: Player, playNextURI: () => void) {
|
||||||
const controlBar = player.getChild('controlBar');
|
const controlBar = player.controlBar;
|
||||||
|
|
||||||
const playNext = new PlayNextButton(player, {
|
const playNext = new PlayNextButton(player, {
|
||||||
name: 'PlayNextButton',
|
name: 'PlayNextButton',
|
||||||
|
@ -21,5 +21,7 @@ export function addPlayNextButton(player: Player, playNextURI: () => void) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (controlBar) {
|
||||||
controlBar.addChild(playNext, {}, 1);
|
controlBar.addChild(playNext, {}, 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ class PlayPreviousButton extends videojs.getComponent('Button') {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addPlayPreviousButton(player: Player, playPreviousURI: () => void) {
|
export function addPlayPreviousButton(player: Player, playPreviousURI: () => void) {
|
||||||
const controlBar = player.getChild('controlBar');
|
const controlBar = player.controlBar;
|
||||||
|
|
||||||
const playPrevious = new PlayPreviousButton(player, {
|
const playPrevious = new PlayPreviousButton(player, {
|
||||||
name: 'PlayPreviousButton',
|
name: 'PlayPreviousButton',
|
||||||
|
@ -21,5 +21,7 @@ export function addPlayPreviousButton(player: Player, playPreviousURI: () => voi
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (controlBar) {
|
||||||
controlBar.addChild(playPrevious, {}, 0);
|
controlBar.addChild(playPrevious, {}, 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -277,9 +277,24 @@ class HlsQualitySelectorPlugin {
|
||||||
|
|
||||||
swapSrcTo(mode = QUALITY_OPTIONS.ORIGINAL) {
|
swapSrcTo(mode = QUALITY_OPTIONS.ORIGINAL) {
|
||||||
const currentTime = this.player.currentTime();
|
const currentTime = this.player.currentTime();
|
||||||
|
const isAlreadyPlaying = !this.player.paused();
|
||||||
this.player.src(mode === 'vhs' ? this.player.claimSrcVhs : this.player.claimSrcOriginal);
|
this.player.src(mode === 'vhs' ? this.player.claimSrcVhs : this.player.claimSrcOriginal);
|
||||||
this.player.load();
|
|
||||||
|
// run this when new source is loaded
|
||||||
|
this.player.one('loadstart', () => {
|
||||||
|
// fixes a bug where when reusing vjs instance the player doesn't play
|
||||||
|
// when it should and the control bar is hidden when changing quality
|
||||||
this.player.currentTime(currentTime);
|
this.player.currentTime(currentTime);
|
||||||
|
if (isAlreadyPlaying) {
|
||||||
|
this.player.play();
|
||||||
|
} else {
|
||||||
|
// show control bar
|
||||||
|
this.player.addClass('vjs-has-started');
|
||||||
|
this.player.addClass('vjs-playing');
|
||||||
|
this.player.addClass('vjs-paused');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.player.load();
|
||||||
|
|
||||||
console.assert(mode === 'vhs' || mode === QUALITY_OPTIONS.ORIGINAL, 'Unexpected input');
|
console.assert(mode === 'vhs' || mode === QUALITY_OPTIONS.ORIGINAL, 'Unexpected input');
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,12 +106,14 @@ const onPlayerReady = (player, options) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (player.options.enterOnRotate) {
|
||||||
if (videojs.browser.IS_IOS) {
|
if (videojs.browser.IS_IOS) {
|
||||||
window.addEventListener('orientationchange', rotationHandler);
|
window.addEventListener('orientationchange', rotationHandler);
|
||||||
} else {
|
} else {
|
||||||
// addEventListener('orientationchange') is not a user interaction on Android
|
// addEventListener('orientationchange') is not a user interaction on Android
|
||||||
screen.orientation.onchange = rotationHandler;
|
screen.orientation.onchange = rotationHandler;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
player.on('ended', (_) => {
|
player.on('ended', (_) => {
|
||||||
if (locked === true) {
|
if (locked === true) {
|
||||||
|
@ -149,8 +151,7 @@ const onPlayerReady = (player, options) => {
|
||||||
* Never shows if the endscreen plugin is present
|
* Never shows if the endscreen plugin is present
|
||||||
*/
|
*/
|
||||||
function mobileUi(options) {
|
function mobileUi(options) {
|
||||||
// if (videojs.browser.IS_ANDROID || videojs.browser.IS_IOS) {
|
if (videojs.browser.IS_ANDROID || videojs.browser.IS_IOS) {
|
||||||
if (videojs.browser.IS_ANDROID) {
|
|
||||||
this.ready(() => onPlayerReady(this, videojs.mergeOptions(defaults, options)));
|
this.ready(() => onPlayerReady(this, videojs.mergeOptions(defaults, options)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ class RecsysPlugin extends Component {
|
||||||
player.on('seeked', (event) => this.onSeeked(event));
|
player.on('seeked', (event) => this.onSeeked(event));
|
||||||
|
|
||||||
// Event trigger to send recsys event
|
// Event trigger to send recsys event
|
||||||
player.on('dispose', (event) => this.onDispose(event));
|
player.on('playerClosed', (event) => this.onDispose(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
onPlay(event) {
|
onPlay(event) {
|
||||||
|
|
|
@ -113,8 +113,8 @@ const VideoJsEvents = ({
|
||||||
const player = playerRef.current;
|
const player = playerRef.current;
|
||||||
updateMediaSession();
|
updateMediaSession();
|
||||||
|
|
||||||
const bigPlayButton = document.querySelector('.vjs-big-play-button');
|
// $FlowIssue
|
||||||
if (bigPlayButton) bigPlayButton.style.setProperty('display', 'none');
|
player.bigPlayButton?.hide();
|
||||||
|
|
||||||
if (player && (player.muted() || player.volume() === 0)) {
|
if (player && (player.muted() || player.volume() === 0)) {
|
||||||
// The css starts as "hidden". We make it visible here without
|
// The css starts as "hidden". We make it visible here without
|
||||||
|
@ -222,6 +222,13 @@ const VideoJsEvents = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeControlBar() {
|
||||||
|
setTimeout(function () {
|
||||||
|
window.player.controlBar.el().classList.remove('vjs-transitioning-video');
|
||||||
|
window.player.controlBar.show();
|
||||||
|
}, 1000 * 2); // wait 2 seconds to hide control bar
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const player = playerRef.current;
|
const player = playerRef.current;
|
||||||
if (replay && player) {
|
if (replay && player) {
|
||||||
|
@ -240,17 +247,23 @@ const VideoJsEvents = ({
|
||||||
// used for tracking buffering for watchman
|
// used for tracking buffering for watchman
|
||||||
player.on('tracking:buffered', doTrackingBuffered);
|
player.on('tracking:buffered', doTrackingBuffered);
|
||||||
|
|
||||||
// hide forcing control bar show
|
player.on('loadstart', function () {
|
||||||
player.on('canplaythrough', function () {
|
if (embedded) {
|
||||||
setTimeout(function () {
|
// $FlowIssue
|
||||||
// $FlowFixMe
|
player.bigPlayButton?.show();
|
||||||
const vjsControlBar = document.querySelector('.vjs-control-bar');
|
}
|
||||||
if (vjsControlBar) vjsControlBar.style.removeProperty('opacity');
|
|
||||||
}, 1000 * 3); // wait 3 seconds to hit control bar
|
|
||||||
});
|
});
|
||||||
player.on('playing', function () {
|
|
||||||
// $FlowFixMe
|
player.on('playing', removeControlBar);
|
||||||
document.querySelector('.vjs-big-play-button').style.setProperty('display', 'none', 'important');
|
player.on('playerClosed', () => {
|
||||||
|
player.off('play', onInitialPlay);
|
||||||
|
player.off('volumechange', onVolumeChange);
|
||||||
|
player.off('error', onError);
|
||||||
|
// custom tracking plugin, event used for watchman data, and marking view/getting rewards
|
||||||
|
player.off('tracking:firstplay', doTrackingFirstPlay);
|
||||||
|
// used for tracking buffering for watchman
|
||||||
|
player.off('tracking:buffered', doTrackingBuffered);
|
||||||
|
player.off('playing', removeControlBar);
|
||||||
});
|
});
|
||||||
// player.on('ended', onEnded);
|
// player.on('ended', onEnded);
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ const VideoJsFunctions = ({ isAudio }: { isAudio: boolean }) => {
|
||||||
// This seems like a poor way to generate the DOM for video.js
|
// This seems like a poor way to generate the DOM for video.js
|
||||||
const wrapper = document.createElement('div');
|
const wrapper = document.createElement('div');
|
||||||
wrapper.setAttribute('data-vjs-player', 'true');
|
wrapper.setAttribute('data-vjs-player', 'true');
|
||||||
const el = document.createElement(isAudio ? 'audio' : 'video');
|
const el = document.createElement('video');
|
||||||
el.className = 'video-js vjs-big-play-centered ';
|
el.className = 'video-js vjs-big-play-centered ';
|
||||||
wrapper.appendChild(el);
|
wrapper.appendChild(el);
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,6 @@ import { useIsMobile } from 'effects/use-screensize';
|
||||||
import { platform } from 'util/platform';
|
import { platform } from 'util/platform';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
|
|
||||||
const canAutoplay = require('./plugins/canAutoplay');
|
|
||||||
|
|
||||||
require('@silvermine/videojs-chromecast')(videojs);
|
require('@silvermine/videojs-chromecast')(videojs);
|
||||||
require('@silvermine/videojs-airplay')(videojs);
|
require('@silvermine/videojs-airplay')(videojs);
|
||||||
|
|
||||||
|
@ -46,7 +44,11 @@ export type Player = {
|
||||||
hlsQualitySelector: ?any,
|
hlsQualitySelector: ?any,
|
||||||
i18n: (any) => void,
|
i18n: (any) => void,
|
||||||
// -- base videojs --
|
// -- base videojs --
|
||||||
controlBar: { addChild: (string, any) => void },
|
controlBar: {
|
||||||
|
addChild: (string | any, ?any, ?number) => void,
|
||||||
|
getChild: (string) => void,
|
||||||
|
removeChild: (string) => void,
|
||||||
|
},
|
||||||
loadingSpinner: any,
|
loadingSpinner: any,
|
||||||
autoplay: (any) => boolean,
|
autoplay: (any) => boolean,
|
||||||
tech: (?boolean) => { vhs: ?any },
|
tech: (?boolean) => { vhs: ?any },
|
||||||
|
@ -60,6 +62,7 @@ export type Player = {
|
||||||
isFullscreen: () => boolean,
|
isFullscreen: () => boolean,
|
||||||
muted: (?boolean) => boolean,
|
muted: (?boolean) => boolean,
|
||||||
on: (string, (any) => void) => void,
|
on: (string, (any) => void) => void,
|
||||||
|
off: (string, (any) => void) => void,
|
||||||
one: (string, (any) => void) => void,
|
one: (string, (any) => void) => void,
|
||||||
play: () => Promise<any>,
|
play: () => Promise<any>,
|
||||||
playbackRate: (?number) => number,
|
playbackRate: (?number) => number,
|
||||||
|
@ -103,7 +106,6 @@ type Props = {
|
||||||
activeLivestreamForChannel: any,
|
activeLivestreamForChannel: any,
|
||||||
doToast: ({ message: string, linkText: string, linkTarget: string }) => void,
|
doToast: ({ message: string, linkText: string, linkTarget: string }) => void,
|
||||||
};
|
};
|
||||||
const VIDEOJS_CONTROL_BAR_CLASS = 'ControlBar';
|
|
||||||
const VIDEOJS_VOLUME_PANEL_CLASS = 'VolumePanel';
|
const VIDEOJS_VOLUME_PANEL_CLASS = 'VolumePanel';
|
||||||
|
|
||||||
const IS_IOS = platform.isIOS();
|
const IS_IOS = platform.isIOS();
|
||||||
|
@ -242,7 +244,6 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
liveTolerance: 10,
|
liveTolerance: 10,
|
||||||
},
|
},
|
||||||
inactivityTimeout: 2000,
|
inactivityTimeout: 2000,
|
||||||
autoplay: autoplay,
|
|
||||||
muted: startMuted,
|
muted: startMuted,
|
||||||
poster: poster, // thumb looks bad in app, and if autoplay, flashing poster is annoying
|
poster: poster, // thumb looks bad in app, and if autoplay, flashing poster is annoying
|
||||||
plugins: { eventTracking: true, overlay: OVERLAY.OVERLAY_DATA },
|
plugins: { eventTracking: true, overlay: OVERLAY.OVERLAY_DATA },
|
||||||
|
@ -263,11 +264,12 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
suppressNotSupportedError: true,
|
suppressNotSupportedError: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: would be nice to pull this out into functions file
|
||||||
// Initialize video.js
|
// Initialize video.js
|
||||||
function initializeVideoPlayer(el, canAutoplayVideo) {
|
function initializeVideoPlayer(domElement) {
|
||||||
if (!el) return;
|
if (!domElement) return;
|
||||||
|
|
||||||
const vjs = videojs(el, videoJsOptions, async () => {
|
const vjs = videojs(domElement, videoJsOptions, async () => {
|
||||||
const player = playerRef.current;
|
const player = playerRef.current;
|
||||||
const adapter = new playerjs.VideoJSAdapter(player);
|
const adapter = new playerjs.VideoJSAdapter(player);
|
||||||
|
|
||||||
|
@ -276,8 +278,6 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
|
|
||||||
// runAds(internalFeatureEnabled, allowPreRoll, player, embedded);
|
// runAds(internalFeatureEnabled, allowPreRoll, player, embedded);
|
||||||
|
|
||||||
initializeEvents();
|
|
||||||
|
|
||||||
// Replace volume bar with custom LBRY volume bar
|
// Replace volume bar with custom LBRY volume bar
|
||||||
LbryVolumeBarClass.replaceExisting(player);
|
LbryVolumeBarClass.replaceExisting(player);
|
||||||
|
|
||||||
|
@ -285,17 +285,17 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
player.reloadSourceOnError({ errorInterval: 10 });
|
player.reloadSourceOnError({ errorInterval: 10 });
|
||||||
|
|
||||||
// Initialize mobile UI.
|
// Initialize mobile UI.
|
||||||
player.mobileUi();
|
player.mobileUi({
|
||||||
|
fullscreen: {
|
||||||
|
enterOnRotate: false,
|
||||||
|
},
|
||||||
|
touchControls: {
|
||||||
|
seekSeconds: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
player.i18n();
|
player.i18n();
|
||||||
|
|
||||||
if (!embedded) {
|
|
||||||
window.player.bigPlayButton && window.player.bigPlayButton.hide();
|
|
||||||
} else {
|
|
||||||
const bigPlayButton = document.querySelector('.vjs-big-play-button');
|
|
||||||
if (bigPlayButton) bigPlayButton.style.setProperty('display', 'block', 'important');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add quality selector to player
|
// Add quality selector to player
|
||||||
if (showQualitySelector) {
|
if (showQualitySelector) {
|
||||||
player.hlsQualitySelector({
|
player.hlsQualitySelector({
|
||||||
|
@ -320,30 +320,11 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
// set playsinline for mobile
|
// set playsinline for mobile
|
||||||
player.children_[0].setAttribute('playsinline', '');
|
player.children_[0].setAttribute('playsinline', '');
|
||||||
|
|
||||||
if (canAutoplayVideo === true) {
|
// immediately show control bar while video is loading
|
||||||
// show waiting spinner as video is loading
|
player.userActive(true);
|
||||||
player.addClass('vjs-waiting');
|
|
||||||
// document.querySelector('.vjs-big-play-button').style.setProperty('display', 'none', 'important');
|
|
||||||
} else {
|
|
||||||
// $FlowFixMe
|
|
||||||
document.querySelector('.vjs-big-play-button').style.setProperty('display', 'block', 'important');
|
|
||||||
}
|
|
||||||
|
|
||||||
// I think this is a callback function
|
|
||||||
const videoNode = containerRef.current && containerRef.current.querySelector('video, audio');
|
|
||||||
|
|
||||||
onPlayerReady(player, videoNode);
|
|
||||||
adapter.ready();
|
adapter.ready();
|
||||||
|
|
||||||
// sometimes video doesnt start properly, this addresses the edge case
|
|
||||||
if (autoplay) {
|
|
||||||
const videoDiv = window.player.children_[0];
|
|
||||||
if (videoDiv) {
|
|
||||||
videoDiv.click();
|
|
||||||
}
|
|
||||||
window.player.userActive(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Chromecast.initialize(player);
|
Chromecast.initialize(player);
|
||||||
player.airPlay();
|
player.airPlay();
|
||||||
});
|
});
|
||||||
|
@ -366,26 +347,72 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
// This lifecycle hook is only called once (on mount), or when `isAudio` or `source` changes.
|
// This lifecycle hook is only called once (on mount), or when `isAudio` or `source` changes.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async function () {
|
(async function () {
|
||||||
let canAutoplayVideo = await canAutoplay.video({ timeout: 2000, inline: true });
|
let vjsPlayer;
|
||||||
canAutoplayVideo = canAutoplayVideo.result === true;
|
const vjsParent = document.querySelector('.video-js-parent');
|
||||||
|
|
||||||
|
let canUseOldPlayer = window.oldSavedDiv && vjsParent;
|
||||||
|
const isLivestream = isLivestreamClaim && userClaimId;
|
||||||
|
// make an additional check and reinstantiate if switching between player types
|
||||||
|
// switching between types on iOS causes issues and this is a faster solution
|
||||||
|
if (vjsParent && window.player) {
|
||||||
|
const oldVideoType = window.player.isLivestream ? 'livestream' : 'video';
|
||||||
|
const switchFromLivestreamToVideo = oldVideoType === 'livestream' && !isLivestream;
|
||||||
|
const switchFromVideoToLivestream = oldVideoType === 'video' && isLivestream;
|
||||||
|
if (switchFromLivestreamToVideo || switchFromVideoToLivestream) {
|
||||||
|
canUseOldPlayer = false;
|
||||||
|
window.player.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize videojs if it hasn't been done yet
|
||||||
|
if (!canUseOldPlayer) {
|
||||||
const vjsElement = createVideoPlayerDOM(containerRef.current);
|
const vjsElement = createVideoPlayerDOM(containerRef.current);
|
||||||
const vjsPlayer = initializeVideoPlayer(vjsElement, canAutoplayVideo);
|
vjsPlayer = initializeVideoPlayer(vjsElement);
|
||||||
if (!vjsPlayer) {
|
if (!vjsPlayer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add reference to player to global scope
|
// Add reference to player to global scope
|
||||||
window.player = vjsPlayer;
|
window.player = vjsPlayer;
|
||||||
|
} else {
|
||||||
|
vjsPlayer = window.player;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add recsys plugin
|
||||||
|
if (shareTelemetry) {
|
||||||
|
vjsPlayer.recsys.options_ = {
|
||||||
|
videoId: claimId,
|
||||||
|
userId: userId,
|
||||||
|
embedded: embedded,
|
||||||
|
};
|
||||||
|
|
||||||
|
vjsPlayer.recsys.lastTimeUpdate = null;
|
||||||
|
vjsPlayer.recsys.currentTimeUpdate = null;
|
||||||
|
vjsPlayer.recsys.inPause = false;
|
||||||
|
vjsPlayer.recsys.watchedDuration = { total: 0, lastTimestamp: -1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!embedded) {
|
||||||
|
vjsPlayer.bigPlayButton && window.player.bigPlayButton.hide();
|
||||||
|
} else {
|
||||||
|
// $FlowIssue
|
||||||
|
vjsPlayer.bigPlayButton?.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// I think this is a callback function
|
||||||
|
const videoNode = containerRef.current && containerRef.current.querySelector('video, audio');
|
||||||
|
|
||||||
|
// add theatre and autoplay next button and initiate player events
|
||||||
|
onPlayerReady(vjsPlayer, videoNode);
|
||||||
|
|
||||||
// Set reference in component state
|
// Set reference in component state
|
||||||
playerRef.current = vjsPlayer;
|
playerRef.current = vjsPlayer;
|
||||||
|
|
||||||
|
initializeEvents();
|
||||||
|
|
||||||
// volume control div, used for changing volume when scrolled over
|
// volume control div, used for changing volume when scrolled over
|
||||||
volumePanelRef.current = playerRef.current
|
// $FlowIssue
|
||||||
.getChild(VIDEOJS_CONTROL_BAR_CLASS)
|
volumePanelRef.current = playerRef.current?.controlBar?.getChild(VIDEOJS_VOLUME_PANEL_CLASS)?.el();
|
||||||
.getChild(VIDEOJS_VOLUME_PANEL_CLASS)
|
|
||||||
.el();
|
|
||||||
|
|
||||||
const keyDownHandler = createKeyDownShortcutsHandler(playerRef, containerRef);
|
const keyDownHandler = createKeyDownShortcutsHandler(playerRef, containerRef);
|
||||||
const videoScrollHandler = createVideoScrollShortcutsHandler(playerRef, containerRef);
|
const videoScrollHandler = createVideoScrollShortcutsHandler(playerRef, containerRef);
|
||||||
|
@ -399,12 +426,15 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
videoScrollHandlerRef.current = videoScrollHandler;
|
videoScrollHandlerRef.current = videoScrollHandler;
|
||||||
volumePanelScrollHandlerRef.current = volumePanelHandler;
|
volumePanelScrollHandlerRef.current = volumePanelHandler;
|
||||||
|
|
||||||
const controlBar = document.querySelector('.vjs-control-bar');
|
// $FlowIssue
|
||||||
if (controlBar) {
|
vjsPlayer.controlBar?.show();
|
||||||
controlBar.style.setProperty('opacity', '1', 'important');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isLivestreamClaim && userClaimId) {
|
vjsPlayer.poster(poster);
|
||||||
|
|
||||||
|
let contentUrl;
|
||||||
|
// TODO: pull this function into videojs-functions
|
||||||
|
// determine which source to use and load it
|
||||||
|
if (isLivestream) {
|
||||||
vjsPlayer.isLivestream = true;
|
vjsPlayer.isLivestream = true;
|
||||||
vjsPlayer.addClass('livestreamPlayer');
|
vjsPlayer.addClass('livestreamPlayer');
|
||||||
vjsPlayer.src({ type: 'application/x-mpegURL', src: livestreamVideoUrl });
|
vjsPlayer.src({ type: 'application/x-mpegURL', src: livestreamVideoUrl });
|
||||||
|
@ -421,23 +451,81 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
vjsPlayer.claimSrcVhs = { type: 'application/x-mpegURL', src: response.url };
|
vjsPlayer.claimSrcVhs = { type: 'application/x-mpegURL', src: response.url };
|
||||||
vjsPlayer.src(vjsPlayer.claimSrcVhs);
|
vjsPlayer.src(vjsPlayer.claimSrcVhs);
|
||||||
|
|
||||||
const trimmedPath = response.url.substring(0, response.url.lastIndexOf('/'));
|
contentUrl = response.url;
|
||||||
const thumbnailPath = trimmedPath + '/stream_sprite.vtt';
|
|
||||||
|
|
||||||
// disable thumbnails on mobile for now
|
|
||||||
if (!IS_MOBILE) {
|
|
||||||
vjsPlayer.vttThumbnails({
|
|
||||||
src: thumbnailPath,
|
|
||||||
showTimestamp: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
vjsPlayer.src(vjsPlayer.claimSrcOriginal);
|
vjsPlayer.src(vjsPlayer.claimSrcOriginal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bugfix thumbnails showing up if new video doesn't have them
|
||||||
|
if (typeof vjsPlayer.vttThumbnails.detach === 'function') {
|
||||||
|
vjsPlayer.vttThumbnails.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize hover thumbnails
|
||||||
|
if (contentUrl) {
|
||||||
|
const trimmedPath = contentUrl.substring(0, contentUrl.lastIndexOf('/'));
|
||||||
|
const thumbnailPath = trimmedPath + '/stream_sprite.vtt';
|
||||||
|
|
||||||
|
// progress bar hover thumbnails
|
||||||
|
if (!IS_MOBILE) {
|
||||||
|
// if src is a function, it's already been initialized
|
||||||
|
if (typeof vjsPlayer.vttThumbnails.src === 'function') {
|
||||||
|
vjsPlayer.vttThumbnails.src(thumbnailPath);
|
||||||
|
} else {
|
||||||
|
// otherwise, initialize plugin
|
||||||
|
vjsPlayer.vttThumbnails({
|
||||||
|
src: thumbnailPath,
|
||||||
|
showTimestamp: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vjsPlayer.load();
|
vjsPlayer.load();
|
||||||
|
|
||||||
|
if (canUseOldPlayer) {
|
||||||
|
// $FlowIssue
|
||||||
|
document.querySelector('.video-js-parent')?.append(window.oldSavedDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow tap to unmute if no perms on iOS
|
||||||
|
if (autoplay && !embedded) {
|
||||||
|
const promise = vjsPlayer.play();
|
||||||
|
|
||||||
|
window.player.userActive(true);
|
||||||
|
|
||||||
|
if (promise !== undefined) {
|
||||||
|
promise
|
||||||
|
.then((_) => {
|
||||||
|
// $FlowIssue
|
||||||
|
vjsPlayer?.controlBar.el().classList.add('vjs-transitioning-video');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const noPermissionError = typeof error === 'object' && error.name && error.name === 'NotAllowedError';
|
||||||
|
|
||||||
|
if (noPermissionError) {
|
||||||
|
if (IS_IOS) {
|
||||||
|
// autoplay not allowed, mute video, play and show 'tap to unmute' button
|
||||||
|
// $FlowIssue
|
||||||
|
vjsPlayer?.muted(true);
|
||||||
|
// $FlowIssue
|
||||||
|
vjsPlayer?.play();
|
||||||
|
// $FlowIssue
|
||||||
|
document.querySelector('.video-js--tap-to-unmute')?.style.setProperty('visibility', 'visible');
|
||||||
|
// $FlowIssue
|
||||||
|
document
|
||||||
|
.querySelector('.video-js--tap-to-unmute')
|
||||||
|
?.style.setProperty('display', 'inline', 'important');
|
||||||
|
} else {
|
||||||
|
// $FlowIssue
|
||||||
|
vjsPlayer?.bigPlayButton?.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fix invisible vidcrunch overlay on IOS << TODO: does not belong here. Move to ads.jsx (#739)
|
// fix invisible vidcrunch overlay on IOS << TODO: does not belong here. Move to ads.jsx (#739)
|
||||||
if (IS_IOS) {
|
if (IS_IOS) {
|
||||||
// ads video player
|
// ads video player
|
||||||
|
@ -477,14 +565,45 @@ export default React.memo<Props>(function VideoJs(props: Props) {
|
||||||
volumePanelRef.current.removeEventListener('wheel', volumePanelScrollHandlerRef.current);
|
volumePanelRef.current.removeEventListener('wheel', volumePanelScrollHandlerRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chapterMarkers = document.getElementsByClassName('vjs-chapter-marker');
|
||||||
|
while (chapterMarkers.length > 0) {
|
||||||
|
// $FlowIssue
|
||||||
|
chapterMarkers[0].parentNode?.removeChild(chapterMarkers[0]);
|
||||||
|
}
|
||||||
|
|
||||||
const player = playerRef.current;
|
const player = playerRef.current;
|
||||||
if (player) {
|
if (player) {
|
||||||
try {
|
try {
|
||||||
window.cast.framework.CastContext.getInstance().getCurrentSession().endSession(false);
|
window.cast.framework.CastContext.getInstance().getCurrentSession().endSession(false);
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
player.dispose();
|
window.player.switchedFromDefaultQuality = false;
|
||||||
window.player = undefined;
|
|
||||||
|
window.player.userActive(false);
|
||||||
|
window.player.pause();
|
||||||
|
|
||||||
|
if (IS_IOS) {
|
||||||
|
// $FlowIssue
|
||||||
|
window.player.controlBar?.playToggle?.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// $FlowIssue
|
||||||
|
window.player?.controlBar?.getChild('ChaptersButton')?.hide();
|
||||||
|
|
||||||
|
// this solves an issue with portrait videos
|
||||||
|
// $FlowIssue
|
||||||
|
const videoDiv = window.player?.tech_?.el(); // video element
|
||||||
|
if (videoDiv) videoDiv.style.top = '0px';
|
||||||
|
|
||||||
|
window.player.controlBar.el().classList.add('vjs-transitioning-video');
|
||||||
|
|
||||||
|
window.oldSavedDiv = window.player.el();
|
||||||
|
|
||||||
|
window.player.trigger('playerClosed');
|
||||||
|
|
||||||
|
window.player.currentTime(0);
|
||||||
|
|
||||||
|
window.player.claimSrcVhs = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [isAudio, source, reload, userClaimId, isLivestreamClaim]);
|
}, [isAudio, source, reload, userClaimId, isLivestreamClaim]);
|
||||||
|
|
|
@ -29,12 +29,15 @@ import debounce from 'util/debounce';
|
||||||
import { formatLbryUrlForWeb, generateListSearchUrlParams } from 'util/url';
|
import { formatLbryUrlForWeb, generateListSearchUrlParams } from 'util/url';
|
||||||
import useInterval from 'effects/use-interval';
|
import useInterval from 'effects/use-interval';
|
||||||
import { lastBandwidthSelector } from './internal/plugins/videojs-http-streaming--override/playlist-selectors';
|
import { lastBandwidthSelector } from './internal/plugins/videojs-http-streaming--override/playlist-selectors';
|
||||||
|
import { platform } from 'util/platform';
|
||||||
import RecSys from 'recsys';
|
import RecSys from 'recsys';
|
||||||
|
|
||||||
// const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
|
// const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
|
||||||
// const PLAY_TIMEOUT_LIMIT = 2000;
|
// const PLAY_TIMEOUT_LIMIT = 2000;
|
||||||
const PLAY_POSITION_SAVE_INTERVAL_MS = 15000;
|
const PLAY_POSITION_SAVE_INTERVAL_MS = 15000;
|
||||||
|
|
||||||
|
const IS_IOS = platform.isIOS();
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
position: number,
|
position: number,
|
||||||
changeVolume: (number) => void,
|
changeVolume: (number) => void,
|
||||||
|
@ -161,6 +164,7 @@ function VideoViewer(props: Props) {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
|
// save the updated watch time
|
||||||
doSetContentHistoryItem(claim.permanent_url);
|
doSetContentHistoryItem(claim.permanent_url);
|
||||||
}
|
}
|
||||||
}, [isPlaying]);
|
}, [isPlaying]);
|
||||||
|
@ -209,6 +213,7 @@ function VideoViewer(props: Props) {
|
||||||
|
|
||||||
const doPlay = useCallback(
|
const doPlay = useCallback(
|
||||||
(playUri) => {
|
(playUri) => {
|
||||||
|
if (!playUri) return;
|
||||||
setDoNavigate(false);
|
setDoNavigate(false);
|
||||||
if (!isFloating) {
|
if (!isFloating) {
|
||||||
const navigateUrl = formatLbryUrlForWeb(playUri);
|
const navigateUrl = formatLbryUrlForWeb(playUri);
|
||||||
|
@ -227,27 +232,23 @@ function VideoViewer(props: Props) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!doNavigate) return;
|
if (!doNavigate) return;
|
||||||
|
|
||||||
if (playNextUrl) {
|
const shouldPlayNextUrl = playNextUrl && nextRecommendedUri && permanentUrl !== nextRecommendedUri;
|
||||||
if (permanentUrl !== nextRecommendedUri) {
|
const shouldPlayPreviousUrl = !playNextUrl && previousListUri && permanentUrl !== previousListUri;
|
||||||
if (nextRecommendedUri) {
|
|
||||||
|
// play next video if someone hits Next button
|
||||||
|
if (shouldPlayNextUrl) {
|
||||||
doPlay(nextRecommendedUri);
|
doPlay(nextRecommendedUri);
|
||||||
}
|
// rewind if video is over 5 seconds and they hit the back button
|
||||||
|
} else if (videoNode && videoNode.currentTime > 5) {
|
||||||
|
videoNode.currentTime = 0;
|
||||||
|
// move to previous video when they hit back button if behind 5 seconds
|
||||||
|
} else if (shouldPlayPreviousUrl) {
|
||||||
|
doPlay(previousListUri);
|
||||||
} else {
|
} else {
|
||||||
setReplay(true);
|
setReplay(true);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (videoNode) {
|
|
||||||
const currentTime = videoNode.currentTime;
|
|
||||||
|
|
||||||
if (currentTime <= 5) {
|
|
||||||
if (previousListUri && permanentUrl !== previousListUri) doPlay(previousListUri);
|
|
||||||
} else {
|
|
||||||
videoNode.currentTime = 0;
|
|
||||||
}
|
|
||||||
setDoNavigate(false);
|
setDoNavigate(false);
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!ended) setDoNavigate(false);
|
|
||||||
setEnded(false);
|
setEnded(false);
|
||||||
setPlayNextUrl(true);
|
setPlayNextUrl(true);
|
||||||
}, [
|
}, [
|
||||||
|
@ -262,6 +263,7 @@ function VideoViewer(props: Props) {
|
||||||
videoNode,
|
videoNode,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// functionality to run on video end
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!ended) return;
|
if (!ended) return;
|
||||||
|
|
||||||
|
@ -274,13 +276,21 @@ function VideoViewer(props: Props) {
|
||||||
|
|
||||||
if (embedded) {
|
if (embedded) {
|
||||||
setIsEndedEmbed(true);
|
setIsEndedEmbed(true);
|
||||||
|
// show autoplay countdown div if not playlist
|
||||||
} else if (!collectionId && autoplayNext) {
|
} else if (!collectionId && autoplayNext) {
|
||||||
setShowAutoplayCountdown(true);
|
setShowAutoplayCountdown(true);
|
||||||
|
// if a playlist, navigate to next item
|
||||||
} else if (collectionId) {
|
} else if (collectionId) {
|
||||||
setDoNavigate(true);
|
setDoNavigate(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearPosition(uri);
|
clearPosition(uri);
|
||||||
|
|
||||||
|
if (IS_IOS && !autoplayNext) {
|
||||||
|
// show play button on ios if video is paused with no autoplay on
|
||||||
|
// $FlowFixMe
|
||||||
|
document.querySelector('.vjs-touch-overlay')?.classList.add('show-play-toggle'); // eslint-disable-line no-unused-expressions
|
||||||
|
}
|
||||||
}, [adUrl, autoplayNext, clearPosition, collectionId, embedded, ended, setAdUrl, uri]);
|
}, [adUrl, autoplayNext, clearPosition, collectionId, embedded, ended, setAdUrl, uri]);
|
||||||
|
|
||||||
// MORE ON PLAY STUFF
|
// MORE ON PLAY STUFF
|
||||||
|
@ -300,7 +310,7 @@ function VideoViewer(props: Props) {
|
||||||
analytics.videoIsPlaying(false, player);
|
analytics.videoIsPlaying(false, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDispose(event, player) {
|
function onPlayerClosed(event, player) {
|
||||||
handlePosition(player);
|
handlePosition(player);
|
||||||
analytics.videoIsPlaying(false, player);
|
analytics.videoIsPlaying(false, player);
|
||||||
}
|
}
|
||||||
|
@ -328,6 +338,7 @@ function VideoViewer(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPlayerReady = useCallback((player: Player, videoNode: any) => {
|
const onPlayerReady = useCallback((player: Player, videoNode: any) => {
|
||||||
|
// add buttons and initialize some settings for the player
|
||||||
if (!embedded) {
|
if (!embedded) {
|
||||||
setVideoNode(videoNode);
|
setVideoNode(videoNode);
|
||||||
player.muted(muted);
|
player.muted(muted);
|
||||||
|
@ -335,6 +346,21 @@ function VideoViewer(props: Props) {
|
||||||
player.playbackRate(videoPlaybackRate);
|
player.playbackRate(videoPlaybackRate);
|
||||||
if (!isMarkdownOrComment) {
|
if (!isMarkdownOrComment) {
|
||||||
addTheaterModeButton(player, toggleVideoTheaterMode);
|
addTheaterModeButton(player, toggleVideoTheaterMode);
|
||||||
|
// if part of a playlist
|
||||||
|
|
||||||
|
// remove old play next/previous buttons if they exist
|
||||||
|
const controlBar = player.controlBar;
|
||||||
|
if (controlBar) {
|
||||||
|
const existingPlayNextButton = controlBar.getChild('PlayNextButton');
|
||||||
|
if (existingPlayNextButton) controlBar.removeChild('PlayNextButton');
|
||||||
|
|
||||||
|
const existingPlayPreviousButton = controlBar.getChild('PlayPreviousButton');
|
||||||
|
if (existingPlayPreviousButton) controlBar.removeChild('PlayPreviousButton');
|
||||||
|
|
||||||
|
const existingAutoplayButton = controlBar.getChild('AutoplayNextButton');
|
||||||
|
if (existingAutoplayButton) controlBar.removeChild('AutoplayNextButton');
|
||||||
|
}
|
||||||
|
|
||||||
if (collectionId) {
|
if (collectionId) {
|
||||||
addPlayNextButton(player, doPlayNext);
|
addPlayNextButton(player, doPlayNext);
|
||||||
addPlayPreviousButton(player, doPlayPrevious);
|
addPlayPreviousButton(player, doPlayPrevious);
|
||||||
|
@ -349,74 +375,36 @@ function VideoViewer(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// currently not being used, but leaving for time being
|
|
||||||
// const shouldPlay = !embedded || autoplayIfEmbedded;
|
|
||||||
// // https://blog.videojs.com/autoplay-best-practices-with-video-js/#Programmatic-Autoplay-and-Success-Failure-Detection
|
|
||||||
// if (shouldPlay) {
|
|
||||||
// const playPromise = player.play();
|
|
||||||
//
|
|
||||||
// const timeoutPromise = new Promise((resolve, reject) =>
|
|
||||||
// setTimeout(() => reject(PLAY_TIMEOUT_ERROR), PLAY_TIMEOUT_LIMIT)
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// // if user hasn't interacted with document, mute video and play it
|
|
||||||
// Promise.race([playPromise, timeoutPromise]).catch((error) => {
|
|
||||||
// console.log(error);
|
|
||||||
// console.log(playPromise);
|
|
||||||
//
|
|
||||||
// const noPermissionError = typeof error === 'object' && error.name && error.name === 'NotAllowedError';
|
|
||||||
// const isATimeoutError = error === PLAY_TIMEOUT_ERROR;
|
|
||||||
//
|
|
||||||
// if (noPermissionError) {
|
|
||||||
// // if (player.paused()) {
|
|
||||||
// // document.querySelector('.vjs-big-play-button').style.setProperty('display', 'block', 'important');
|
|
||||||
// // }
|
|
||||||
//
|
|
||||||
// centerPlayButton();
|
|
||||||
//
|
|
||||||
// // to turn muted autoplay on
|
|
||||||
// // if (player.autoplay() && !player.muted()) {
|
|
||||||
// // player.muted(true);
|
|
||||||
// // player.play();
|
|
||||||
// // }
|
|
||||||
// }
|
|
||||||
// setIsPlaying(false);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// PR: #5535
|
// PR: #5535
|
||||||
// Move the restoration to a later `loadedmetadata` phase to counter the
|
// Move the restoration to a later `loadedmetadata` phase to counter the
|
||||||
// delay from the header-fetch. This is a temp change until the next
|
// delay from the header-fetch. This is a temp change until the next
|
||||||
// re-factoring.
|
// re-factoring.
|
||||||
player.on('loadedmetadata', () => restorePlaybackRate(player));
|
const restorePlaybackRateEvent = () => restorePlaybackRate(player);
|
||||||
|
|
||||||
// Override the "auto" algorithm to post-process the result
|
// Override the "auto" algorithm to post-process the result
|
||||||
player.on('loadedmetadata', () => {
|
const overrideAutoAlgorithm = () => {
|
||||||
const vhs = player.tech(true).vhs;
|
const vhs = player.tech(true).vhs;
|
||||||
if (vhs) {
|
if (vhs) {
|
||||||
// https://github.com/videojs/http-streaming/issues/749#issuecomment-606972884
|
// https://github.com/videojs/http-streaming/issues/749#issuecomment-606972884
|
||||||
vhs.selectPlaylist = lastBandwidthSelector;
|
vhs.selectPlaylist = lastBandwidthSelector;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
player.on('ended', () => setEnded(true));
|
const onPauseEvent = (event) => onPause(event, player);
|
||||||
player.on('play', onPlay);
|
const onPlayerClosedEvent = (event) => onPlayerClosed(event, player);
|
||||||
player.on('pause', (event) => onPause(event, player));
|
const onVolumeChange = () => {
|
||||||
player.on('dispose', (event) => onDispose(event, player));
|
if (player) {
|
||||||
|
updateVolumeState(player.volume(), player.muted());
|
||||||
player.on('error', () => {
|
}
|
||||||
|
};
|
||||||
|
const onPlayerEnded = () => setEnded(true);
|
||||||
|
const onError = () => {
|
||||||
const error = player.error();
|
const error = player.error();
|
||||||
if (error) {
|
if (error) {
|
||||||
analytics.sentryError('Video.js error', error);
|
analytics.sentryError('Video.js error', error);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
player.on('volumechange', () => {
|
const onRateChange = () => {
|
||||||
if (player) {
|
|
||||||
updateVolumeState(player.volume(), player.muted());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
player.on('ratechange', () => {
|
|
||||||
const HAVE_NOTHING = 0; // https://docs.videojs.com/player#readyState
|
const HAVE_NOTHING = 0; // https://docs.videojs.com/player#readyState
|
||||||
if (player && player.readyState() !== HAVE_NOTHING) {
|
if (player && player.readyState() !== HAVE_NOTHING) {
|
||||||
// The playbackRate occasionally resets to 1, typically when loading a fresh video or when 'src' changes.
|
// The playbackRate occasionally resets to 1, typically when loading a fresh video or when 'src' changes.
|
||||||
|
@ -425,11 +413,43 @@ function VideoViewer(props: Props) {
|
||||||
// [ ] Ideally, the controlBar should be hidden to prevent users from changing the rate while loading.
|
// [ ] Ideally, the controlBar should be hidden to prevent users from changing the rate while loading.
|
||||||
setVideoPlaybackRate(player.playbackRate());
|
setVideoPlaybackRate(player.playbackRate());
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const moveToPosition = () => {
|
||||||
|
// update current time based on previous position
|
||||||
if (position && !isLivestreamClaim) {
|
if (position && !isLivestreamClaim) {
|
||||||
player.currentTime(position);
|
player.currentTime(position);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// load events onto player
|
||||||
|
player.on('play', onPlay);
|
||||||
|
player.on('pause', onPauseEvent);
|
||||||
|
player.on('playerClosed', onPlayerClosedEvent);
|
||||||
|
player.on('ended', onPlayerEnded);
|
||||||
|
player.on('error', onError);
|
||||||
|
player.on('volumechange', onVolumeChange);
|
||||||
|
player.on('ratechange', onRateChange);
|
||||||
|
player.on('loadedmetadata', overrideAutoAlgorithm);
|
||||||
|
player.on('loadedmetadata', restorePlaybackRateEvent);
|
||||||
|
player.one('loadedmetadata', moveToPosition);
|
||||||
|
|
||||||
|
const cancelOldEvents = () => {
|
||||||
|
player.off('play', onPlay);
|
||||||
|
player.off('pause', onPauseEvent);
|
||||||
|
player.off('playerClosed', onPlayerClosedEvent);
|
||||||
|
player.off('ended', onPlayerEnded);
|
||||||
|
player.off('error', onError);
|
||||||
|
player.off('volumechange', onVolumeChange);
|
||||||
|
player.off('ratechange', onRateChange);
|
||||||
|
player.off('loadedmetadata', overrideAutoAlgorithm);
|
||||||
|
player.off('loadedmetadata', restorePlaybackRateEvent);
|
||||||
|
player.off('playerClosed', cancelOldEvents);
|
||||||
|
player.off('loadedmetadata', moveToPosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
// turn off old events to prevent duplicate runs
|
||||||
|
player.on('playerClosed', cancelOldEvents);
|
||||||
|
|
||||||
Chapters.parseAndLoad(player, claim);
|
Chapters.parseAndLoad(player, claim);
|
||||||
|
|
||||||
|
|
|
@ -331,7 +331,7 @@ $control-bar-popup-font-size: 0.8rem;
|
||||||
|
|
||||||
// TODO: make sure there's no bad side effects of this
|
// TODO: make sure there's no bad side effects of this
|
||||||
button.vjs-big-play-button {
|
button.vjs-big-play-button {
|
||||||
display: none !important;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-big-play-centered {
|
.vjs-big-play-centered {
|
||||||
|
@ -465,3 +465,16 @@ button.vjs-big-play-button {
|
||||||
background-color: var(--color-error);
|
background-color: var(--color-error);
|
||||||
width: 2px;
|
width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't show Tap To Unmute button on mobile miniplayer
|
||||||
|
.content__viewer--floating.content__viewer--mobile {
|
||||||
|
.video-js--tap-to-unmute {
|
||||||
|
visibility: hidden !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vjs-transitioning-video {
|
||||||
|
opacity: 1 !important;
|
||||||
|
display: flex !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
|
@ -7374,6 +7374,7 @@ flatten@^1.0.2:
|
||||||
flow-bin@^0.97.0:
|
flow-bin@^0.97.0:
|
||||||
version "0.97.0"
|
version "0.97.0"
|
||||||
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.97.0.tgz#036ffcfc27503367a9d906ec9d843a0aa6f6bb83"
|
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.97.0.tgz#036ffcfc27503367a9d906ec9d843a0aa6f6bb83"
|
||||||
|
integrity sha512-jXjD05gkatLuC4+e28frH1hZoRwr1iASP6oJr61Q64+kR4kmzaS+AdFBhYgoYS5kpoe4UzwDebWK8ETQFNh00w==
|
||||||
|
|
||||||
flow-typed@^2.3.0:
|
flow-typed@^2.3.0:
|
||||||
version "2.6.2"
|
version "2.6.2"
|
||||||
|
|
Loading…
Reference in a new issue