maybe ready

This commit is contained in:
Jeremy Kauffman 2020-04-16 17:43:09 -04:00 committed by Sean Yesmunt
parent 9db9363b9f
commit 6c8b0b2d68
7 changed files with 103 additions and 96 deletions

View file

@ -1,11 +1,15 @@
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeSelectClaimForUri } from 'lbry-redux'; import { makeSelectClaimForUri } from 'lbry-redux';
import { withRouter } from 'react-router';
import { makeSelectIsPlayerFloating, makeSelectNextUnplayedRecommended } from 'redux/selectors/content'; import { makeSelectIsPlayerFloating, makeSelectNextUnplayedRecommended } from 'redux/selectors/content';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetPlayingUri } from 'redux/actions/content'; import { doSetPlayingUri } from 'redux/actions/content';
import AutoplayCountdown from './view'; import AutoplayCountdown from './view';
/*
AutoplayCountdown does not fetch it's own next content to play, it relies on <RecommendedContent> being rendered. This is dumb but I'm just the guy who noticed
*/
const select = (state, props) => { const select = (state, props) => {
const nextRecommendedUri = makeSelectNextUnplayedRecommended(props.uri)(state); const nextRecommendedUri = makeSelectNextUnplayedRecommended(props.uri)(state);
return { return {
@ -20,4 +24,4 @@ const perform = dispatch => ({
setPlayingUri: uri => dispatch(doSetPlayingUri(uri)), setPlayingUri: uri => dispatch(doSetPlayingUri(uri)),
}); });
export default connect(select, perform)(AutoplayCountdown); export default withRouter(connect(select, perform)(AutoplayCountdown));

View file

@ -1,5 +1,5 @@
// @flow // @flow
import React from 'react'; import React, { useCallback } from 'react';
import Button from 'component/button'; import Button from 'component/button';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
import I18nMessage from 'component/i18nMessage'; import I18nMessage from 'component/i18nMessage';
@ -35,20 +35,22 @@ function AutoplayCountdown(props: Props) {
navigateUrl = formatLbryUrlForWeb(nextRecommendedUri); navigateUrl = formatLbryUrlForWeb(nextRecommendedUri);
} }
function doNavigate() { const doNavigate = useCallback(() => {
// FIXME: make autoplay continue in floating player
if (!isFloating) { if (!isFloating) {
// if not floating if (navigateUrl) {
setPlayingUri(null); push(navigateUrl);
push(navigateUrl); setPlayingUri(null);
}
} else { } else {
setPlayingUri(nextRecommendedUri); if (nextRecommendedUri) {
setPlayingUri(nextRecommendedUri);
}
} }
} }, [navigateUrl, nextRecommendedUri, isFloating, setPlayingUri]);
React.useEffect(() => { React.useEffect(() => {
let interval; let interval;
if (!timerCanceled) { if (!timerCanceled && nextRecommendedUri) {
interval = setInterval(() => { interval = setInterval(() => {
const newTime = timer - 1; const newTime = timer - 1;
if (newTime === 0) { if (newTime === 0) {
@ -82,10 +84,10 @@ function AutoplayCountdown(props: Props) {
<Button onClick={doNavigate} iconSize={30} title={__('Play')} className="button--icon button--play" /> <Button onClick={doNavigate} iconSize={30} title={__('Play')} className="button--icon button--play" />
</div> </div>
<div className="file-viewer__overlay-secondary autoplay-countdown__counter"> <div className="file-viewer__overlay-secondary autoplay-countdown__counter">
{__('Playing in %seconds_left% seconds', { seconds_left: timer })} {__('Playing in %seconds_left% seconds...', { seconds_left: timer })}{' '}
<Button label={__('Cancel')} button="link" onClick={() => setTimerCanceled(true)} />
</div> </div>
</div> </div>
<Button label={__('Cancel')} button="link" onClick={() => setTimerCanceled(true)} />
</div> </div>
</div> </div>
); );

View file

@ -2,7 +2,6 @@ import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeSelectFileInfoForUri, makeSelectTitleForUri } from 'lbry-redux'; import { makeSelectFileInfoForUri, makeSelectTitleForUri } from 'lbry-redux';
import { import {
makeSelectIsPlaying,
makeSelectIsPlayerFloating, makeSelectIsPlayerFloating,
selectPlayingUri, selectPlayingUri,
makeSelectFileRenderModeForUri, makeSelectFileRenderModeForUri,
@ -19,7 +18,6 @@ const select = (state, props) => {
uri, uri,
title: makeSelectTitleForUri(uri)(state), title: makeSelectTitleForUri(uri)(state),
fileInfo: makeSelectFileInfoForUri(uri)(state), fileInfo: makeSelectFileInfoForUri(uri)(state),
isPlaying: makeSelectIsPlaying(uri)(state),
isFloating: makeSelectIsPlayerFloating(props.location)(state), isFloating: makeSelectIsPlayerFloating(props.location)(state),
streamingUrl: makeSelectStreamingUrlForUriWebProxy(uri)(state), streamingUrl: makeSelectStreamingUrlForUriWebProxy(uri)(state),
floatingPlayerEnabled: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state), floatingPlayerEnabled: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state),

View file

@ -15,8 +15,6 @@ import { onFullscreenChange } from 'util/full-screen';
import useIsMobile from 'effects/use-is-mobile'; import useIsMobile from 'effects/use-is-mobile';
type Props = { type Props = {
isLoading: boolean,
isPlaying: boolean,
isFloating: boolean, isFloating: boolean,
fileInfo: FileListItem, fileInfo: FileListItem,
uri: string, uri: string,
@ -28,17 +26,7 @@ type Props = {
}; };
export default function FileRenderFloating(props: Props) { export default function FileRenderFloating(props: Props) {
const { const { fileInfo, uri, streamingUrl, title, isFloating, clearPlayingUri, floatingPlayerEnabled, renderMode } = props;
isPlaying,
fileInfo,
uri,
streamingUrl,
title,
isFloating,
clearPlayingUri,
floatingPlayerEnabled,
renderMode,
} = props;
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect'); const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect');
@ -75,7 +63,7 @@ export default function FileRenderFloating(props: Props) {
}; };
}, [setFileViewerRect, isFloating]); }, [setFileViewerRect, isFloating]);
if (!isPlayable || !isPlaying || !uri || (isFloating && (isMobile || !floatingPlayerEnabled))) { if (!isPlayable || !uri || (isFloating && (isMobile || !floatingPlayerEnabled))) {
return null; return null;
} }

View file

@ -1,9 +1,10 @@
// @flow
import React, { useContext, useEffect, useRef } from 'react'; import React, { useContext, useEffect, useRef } from 'react';
import videojs from 'video.js/dist/alt/video.core.novtt.min.js'; import videojs from 'video.js/dist/alt/video.core.novtt.min.js';
import 'video.js/dist/alt/video-js-cdn.min.css'; import 'video.js/dist/alt/video-js-cdn.min.css';
import eventTracking from 'videojs-event-tracking'; import eventTracking from 'videojs-event-tracking';
import { EmbedContext } from '../../../../page/embedWrapper/view'; import { EmbedContext } from 'page/embedWrapper/view';
import isUserTyping from '../../../../util/detect-typing'; import isUserTyping from 'util/detect-typing';
type Props = { type Props = {
source: string, source: string,
@ -11,17 +12,10 @@ type Props = {
poster: boolean, poster: boolean,
autoplay: boolean, autoplay: boolean,
onPlayerReady: () => null, onPlayerReady: () => null,
onVolumeChange: () => null,
isAudio: boolean, isAudio: boolean,
}; };
const VIDEO_JS_OPTIONS: VideoJSOptions = {
controls: true,
autoplay: true,
preload: 'auto',
playbackRates: [0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5, 1.75, 2],
responsive: true,
};
type VideoJSOptions = { type VideoJSOptions = {
controls: boolean, controls: boolean,
autoplay: boolean, autoplay: boolean,
@ -33,6 +27,14 @@ type VideoJSOptions = {
poster?: string, poster?: string,
}; };
const VIDEO_JS_OPTIONS: VideoJSOptions = {
controls: true,
autoplay: true,
preload: 'auto',
playbackRates: [0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5, 1.75, 2],
responsive: true,
};
const F11_KEYCODE = 122; const F11_KEYCODE = 122;
const SPACE_BAR_KEYCODE = 32; const SPACE_BAR_KEYCODE = 32;
const SMALL_F_KEYCODE = 70; const SMALL_F_KEYCODE = 70;
@ -57,7 +59,7 @@ properties for this component should be kept to ONLY those that if changed shoul
*/ */
export default React.memo(function VideoJs(props: Props) { export default React.memo(function VideoJs(props: Props) {
const { autoplay, source, sourceType, poster, isAudio, onPlayerReady } = props; const { autoplay, source, sourceType, poster, isAudio, onPlayerReady } = props;
const videoRef = useRef(); const containerRef = useRef();
const embedded = useContext(EmbedContext); const embedded = useContext(EmbedContext);
const videoJsOptions = { const videoJsOptions = {
...VIDEO_JS_OPTIONS, ...VIDEO_JS_OPTIONS,
@ -75,7 +77,7 @@ export default React.memo(function VideoJs(props: Props) {
videoJsOptions.muted = autoplay && embedded; videoJsOptions.muted = autoplay && embedded;
function handleKeyDown(e: KeyboardEvent) { function handleKeyDown(e: KeyboardEvent) {
const { current: videoNode } = videoRef; const videoNode = containerRef.current && containerRef.current.querySelector('video, audio');
if (!videoNode || isUserTyping()) { if (!videoNode || isUserTyping()) {
return; return;
@ -113,27 +115,33 @@ export default React.memo(function VideoJs(props: Props) {
} }
let player; let player;
useEffect(() => {
if (videoRef.current) {
console.log('videojs effect to instatiate player');
const { current: videoNode } = videoRef;
player = videojs(videoNode, videoJsOptions); // Create the video element. Note that a new videojs instantiation will happen on *every* render, so do not add props to this component!
useEffect(() => {
if (containerRef.current) {
const wrapper = document.createElement('div');
wrapper.setAttribute('data-vjs-player', true);
const el = document.createElement(isAudio ? 'audio' : 'video');
el.className = 'video-js';
wrapper.appendChild(el);
containerRef.current.appendChild(wrapper);
player = videojs(el, videoJsOptions);
onPlayerReady(player); onPlayerReady(player);
// fixes #3498 (https://github.com/lbryio/lbry-desktop/issues/3498)
// summary: on firefox the focus would stick to the fullscreen button which caused buggy behavior with spacebar
// $FlowFixMe
player.on('fullscreenchange', () => document.activeElement && document.activeElement.blur());
window.addEventListener('keydown', handleKeyDown); window.addEventListener('keydown', handleKeyDown);
return () => { return () => {
console.log('videojs effect cleanup to dispose player');
window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keydown', handleKeyDown);
player.dispose(); player.dispose();
}; };
} }
}); });
return ( return <div className="video-js-parent" ref={containerRef} />;
<div data-vjs-player>
{isAudio ? <audio ref={videoRef} className="video-js" /> : <video ref={videoRef} className="video-js" />}
</div>
);
}); });

View file

@ -1,9 +1,8 @@
// @flow // @flow
import React, { useRef, useEffect, useState, useContext, useCallback } from 'react'; import React, { useEffect, useState, useContext, useCallback } from 'react';
import { stopContextMenu } from 'util/context-menu'; import { stopContextMenu } from 'util/context-menu';
import VideoJs from './internal/videojs'; import VideoJs from './internal/videojs';
import isUserTyping from 'util/detect-typing';
import analytics from 'analytics'; import analytics from 'analytics';
import { EmbedContext } from 'page/embedWrapper/view'; import { EmbedContext } from 'page/embedWrapper/view';
import classnames from 'classnames'; import classnames from 'classnames';
@ -12,18 +11,18 @@ import AutoplayCountdown from 'component/autoplayCountdown';
import usePrevious from 'effects/use-previous'; import usePrevious from 'effects/use-previous';
import FileViewerEmbeddedEnded from 'lbrytv/component/fileViewerEmbeddedEnded'; import FileViewerEmbeddedEnded from 'lbrytv/component/fileViewerEmbeddedEnded';
import FileViewerEmbeddedTitle from 'lbrytv/component/fileViewerEmbeddedTitle'; import FileViewerEmbeddedTitle from 'lbrytv/component/fileViewerEmbeddedTitle';
import LoadingScreen from 'component/common/loading-screen';
type Props = { type Props = {
position: number, position: number,
hasFileInfo: boolean,
changeVolume: number => void, changeVolume: number => void,
savePosition: (string, number) => void,
changeMute: boolean => void, changeMute: boolean => void,
source: string, source: string,
contentType: string, contentType: string,
thumbnail: string, thumbnail: string,
hasFileInfo: boolean,
claim: Claim, claim: Claim,
muted: boolean,
volume: number,
uri: string, uri: string,
autoplaySetting: boolean, autoplaySetting: boolean,
autoplayIfEmbedded: boolean, autoplayIfEmbedded: boolean,
@ -46,6 +45,8 @@ function VideoViewer(props: Props) {
position, position,
claim, claim,
uri, uri,
muted,
volume,
autoplaySetting, autoplaySetting,
autoplayIfEmbedded, autoplayIfEmbedded,
doAnalyticsView, doAnalyticsView,
@ -58,18 +59,25 @@ function VideoViewer(props: Props) {
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false); const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false);
const [isEndededEmbed, setIsEndededEmbed] = useState(false); const [isEndededEmbed, setIsEndededEmbed] = useState(false);
const [player, setPlayer] = useState(null); const [isLoading, setIsLoading] = useState(false);
const previousUri = usePrevious(uri); const previousUri = usePrevious(uri);
const embedded = useContext(EmbedContext); const embedded = useContext(EmbedContext);
// force everything to recent when URI changes, can cause weird corner cases otherwise (e.g. navigate while autoplay is true)
useEffect(() => {
if (uri && previousUri && uri !== previousUri) {
setShowAutoplayCountdown(false);
setIsEndededEmbed(false);
setIsLoading(false);
}
}, [uri, previousUri]);
function doTrackingBuffered(e: Event, data: any) { function doTrackingBuffered(e: Event, data: any) {
analytics.videoBufferEvent(claimId, data.currentTime); analytics.videoBufferEvent(claimId, data.currentTime);
} }
function doTrackingFirstPlay(e: Event, data: any) { function doTrackingFirstPlay(e: Event, data: any) {
console.log('doTrackingFirstPlay: ' + data.secondsToLoad);
analytics.videoStartEvent(claimId, data.secondsToLoad); analytics.videoStartEvent(claimId, data.secondsToLoad);
doAnalyticsView(uri, data.secondsToLoad).then(() => { doAnalyticsView(uri, data.secondsToLoad).then(() => {
@ -85,14 +93,8 @@ function VideoViewer(props: Props) {
} }
} }
function onVolumeChange(e: Event) {
const isMuted = player.muted();
const volume = player.volume();
changeVolume(volume);
changeMute(isMuted);
}
function onPlay() { function onPlay() {
setIsLoading(false);
setIsPlaying(true); setIsPlaying(true);
setShowAutoplayCountdown(false); setShowAutoplayCountdown(false);
setIsEndededEmbed(false); setIsEndededEmbed(false);
@ -103,38 +105,50 @@ function VideoViewer(props: Props) {
} }
const onPlayerReady = useCallback(player => { const onPlayerReady = useCallback(player => {
console.log('videoViewer.onPlayerReady attach effects'); setIsLoading(!embedded); // if we are here outside of an embed, we're playing
player.on('tracking:buffered', doTrackingBuffered); player.on('tracking:buffered', doTrackingBuffered);
player.on('tracking:firstplay', doTrackingFirstPlay); player.on('tracking:firstplay', doTrackingFirstPlay);
player.on('ended', onEnded); player.on('ended', onEnded);
player.on('volumechange', onVolumeChange);
player.on('play', onPlay); player.on('play', onPlay);
player.on('pause', onPause); player.on('pause', onPause);
player.on('volumechange', () => {
// fixes #3498 (https://github.com/lbryio/lbry-desktop/issues/3498) if (player && player.volume() !== volume) {
// summary: on firefox the focus would stick to the fullscreen button which caused buggy behavior with spacebar changeVolume(player.volume());
// $FlowFixMe }
player.on('fullscreenchange', () => document.activeElement && document.activeElement.blur()); if (player && player.muted() !== muted) {
changeMute(player.muted());
}
});
if (position) { if (position) {
player.currentTime(position); player.currentTime(position);
} }
}, []);
console.log('VideoViewer render'); // FIXME: below breaks rendering?!
/* if (!embedded) {
if (muted) {
player.muted(muted);
}
if (volume) {
player.volume(volume);
}
} */
}, []);
return ( return (
<div <div
className={classnames('file-viewer', { className={classnames('file-viewer', {
'file-viewer--is-playing': isPlaying, 'file-viewer--is-playing': isPlaying,
'file-viewer--ended-embed': isEndededEmbed,
})} })}
onContextMenu={stopContextMenu} onContextMenu={stopContextMenu}
> >
{showAutoplayCountdown && <AutoplayCountdown uri={uri} />} {showAutoplayCountdown && <AutoplayCountdown uri={uri} />}
{isEndededEmbed && <FileViewerEmbeddedEnded uri={uri} />} {isEndededEmbed && <FileViewerEmbeddedEnded uri={uri} />}
{embedded && !isEndededEmbed && <FileViewerEmbeddedTitle uri={uri} />} {embedded && !isEndededEmbed && <FileViewerEmbeddedTitle uri={uri} />}
{/* change message at any time */}
{isLoading && <LoadingScreen status={__('Loading')} />}
<VideoJs <VideoJs
source={source} source={source}
isAudio={isAudio} isAudio={isAudio}

View file

@ -164,10 +164,6 @@
} }
.content__viewer--floating { .content__viewer--floating {
.file-viewer__overlay {
padding: var(--spacing-medium);
}
.file-viewer__overlay-title, .file-viewer__overlay-title,
.file-viewer__overlay-secondary { .file-viewer__overlay-secondary {
overflow: hidden; overflow: hidden;
@ -177,10 +173,6 @@
} }
} }
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
.file-viewer__overlay {
padding: var(--spacing-medium);
}
.file-viewer__overlay-title, .file-viewer__overlay-title,
.file-viewer__overlay-secondary { .file-viewer__overlay-secondary {
overflow: hidden; overflow: hidden;
@ -291,16 +283,20 @@
} }
} }
.video-js-parent {
height: 100%;
width: 100%;
}
// By default no video js play button
.vjs-big-play-button {
display: none;
}
.video-js { .video-js {
height: 100%; height: 100%;
width: 100%; width: 100%;
// Removing the play button because we have autoplay turned on
// These are classes added by video.js
.vjs-big-play-button {
display: none;
}
.vjs-modal-dialog .vjs-modal-dialog-content { .vjs-modal-dialog .vjs-modal-dialog-content {
position: relative; position: relative;
padding-top: 5rem; padding-top: 5rem;
@ -321,11 +317,8 @@
} }
} }
.vjs-paused .vjs-big-play-button, .file-viewer--ended-embed .vjs-big-play-button {
.vjs-paused.vjs-has-started .vjs-big-play-button { display: none;
@media (max-width: $breakpoint-small) {
display: block;
}
} }
.file-render { .file-render {