lbry-desktop/ui/component/viewers/videoViewer/view.jsx

188 lines
5.7 KiB
React
Raw Normal View History

2019-08-02 08:28:14 +02:00
// @flow
2020-04-16 23:43:09 +02:00
import React, { useEffect, useState, useContext, useCallback } from 'react';
2019-08-02 08:28:14 +02:00
import { stopContextMenu } from 'util/context-menu';
2020-04-28 21:33:30 +02:00
import type { Player } from './internal/videojs';
2020-04-16 01:21:17 +02:00
import VideoJs from './internal/videojs';
2020-01-22 18:19:49 +01:00
import analytics from 'analytics';
2020-01-31 19:25:48 +01:00
import { EmbedContext } from 'page/embedWrapper/view';
2020-04-14 01:48:11 +02:00
import classnames from 'classnames';
2020-03-27 17:49:41 +01:00
import { FORCE_CONTENT_TYPE_PLAYER } from 'constants/claim';
2020-04-14 01:48:11 +02:00
import AutoplayCountdown from 'component/autoplayCountdown';
import usePrevious from 'effects/use-previous';
import FileViewerEmbeddedEnded from 'lbrytv/component/fileViewerEmbeddedEnded';
import FileViewerEmbeddedTitle from 'lbrytv/component/fileViewerEmbeddedTitle';
2020-04-16 23:43:09 +02:00
import LoadingScreen from 'component/common/loading-screen';
2019-08-02 08:28:14 +02:00
2020-04-28 21:33:30 +02:00
const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
2019-08-02 08:28:14 +02:00
type Props = {
2019-09-06 02:26:03 +02:00
position: number,
changeVolume: number => void,
changeMute: boolean => void,
2019-08-02 08:28:14 +02:00
source: string,
contentType: string,
thumbnail: string,
2020-01-22 18:19:49 +01:00
claim: Claim,
2020-04-16 23:43:09 +02:00
muted: boolean,
volume: number,
2020-04-14 01:48:11 +02:00
uri: string,
autoplaySetting: boolean,
autoplayIfEmbedded: boolean,
doAnalyticsView: (string, number) => Promise<any>,
claimRewards: () => void,
2019-08-02 08:28:14 +02:00
};
2020-04-16 01:21:17 +02:00
/*
codesandbox of idealized/clean videojs and react 16+
https://codesandbox.io/s/71z2lm4ko6
*/
2019-08-02 08:28:14 +02:00
function VideoViewer(props: Props) {
const {
contentType,
source,
changeVolume,
changeMute,
thumbnail,
2020-03-19 21:25:37 +01:00
position,
claim,
2020-04-14 01:48:11 +02:00
uri,
2020-04-16 23:43:09 +02:00
muted,
volume,
2020-04-14 01:48:11 +02:00
autoplaySetting,
autoplayIfEmbedded,
doAnalyticsView,
claimRewards,
} = props;
2020-01-22 18:19:49 +01:00
const claimId = claim && claim.claim_id;
const isAudio = contentType.includes('audio');
2020-03-27 17:49:41 +01:00
const forcePlayer = FORCE_CONTENT_TYPE_PLAYER.includes(contentType);
2020-04-14 01:48:11 +02:00
const [isPlaying, setIsPlaying] = useState(false);
const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false);
const [isEndededEmbed, setIsEndededEmbed] = useState(false);
2020-04-26 22:32:39 +02:00
/* isLoading was designed to show loading screen on first play press, rather than completely black screen, but
breaks because some browsers (e.g. Firefox) block autoplay but leave the player.play Promise pending */
2020-04-16 23:43:09 +02:00
const [isLoading, setIsLoading] = useState(false);
2020-04-16 01:21:17 +02:00
const previousUri = usePrevious(uri);
const embedded = useContext(EmbedContext);
2019-08-02 08:28:14 +02:00
2020-04-16 23:43:09 +02:00
// 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]);
2020-04-16 01:21:17 +02:00
function doTrackingBuffered(e: Event, data: any) {
analytics.videoBufferEvent(claimId, data.currentTime);
}
2019-08-13 07:35:13 +02:00
2020-04-16 01:21:17 +02:00
function doTrackingFirstPlay(e: Event, data: any) {
analytics.videoStartEvent(claimId, data.secondsToLoad);
2020-04-14 01:48:11 +02:00
2020-04-16 01:21:17 +02:00
doAnalyticsView(uri, data.secondsToLoad).then(() => {
claimRewards();
});
}
2020-04-14 01:48:11 +02:00
2020-04-16 01:21:17 +02:00
function onEnded() {
if (embedded) {
setIsEndededEmbed(true);
} else if (autoplaySetting) {
setShowAutoplayCountdown(true);
2020-01-22 18:19:49 +01:00
}
2020-04-16 01:21:17 +02:00
}
2020-04-16 01:21:17 +02:00
function onPlay() {
2020-04-16 23:43:09 +02:00
setIsLoading(false);
2020-04-16 01:21:17 +02:00
setIsPlaying(true);
setShowAutoplayCountdown(false);
setIsEndededEmbed(false);
}
2020-04-16 01:21:17 +02:00
function onPause() {
setIsPlaying(false);
}
2020-04-14 01:48:11 +02:00
2020-04-28 21:33:30 +02:00
const onPlayerReady = useCallback((player: Player) => {
2020-04-27 21:23:07 +02:00
if (!embedded) {
player.muted(muted);
player.volume(volume);
}
2020-04-26 22:32:39 +02:00
const shouldPlay = !embedded || autoplayIfEmbedded;
// https://blog.videojs.com/autoplay-best-practices-with-video-js/#Programmatic-Autoplay-and-Success-Failure-Detection
2020-04-26 22:32:39 +02:00
if (shouldPlay) {
2020-04-28 19:25:13 +02:00
const playPromise = player.play();
const timeoutPromise = new Promise((resolve, reject) => {
2020-04-28 21:33:30 +02:00
setTimeout(() => reject(PLAY_TIMEOUT_ERROR), 1000);
2020-04-28 19:25:13 +02:00
});
Promise.race([playPromise, timeoutPromise]).catch(error => {
2020-04-28 21:33:30 +02:00
if (error === PLAY_TIMEOUT_ERROR) {
2020-04-28 19:25:13 +02:00
// The player promise hung
// This is probably in firefox
2020-04-28 21:33:30 +02:00
// The second attempt usually works
2020-04-28 19:25:13 +02:00
player.play();
} else {
2020-04-28 21:33:30 +02:00
// Autoplay was actually blocked by the browser
2020-04-28 19:25:13 +02:00
// Reset everything so the user sees the thumbnail/play button and can start it on their own
setIsLoading(false);
setIsPlaying(false);
}
});
}
2020-04-26 22:32:39 +02:00
setIsLoading(shouldPlay); // if we are here outside of an embed, we're playing
2020-04-16 23:43:09 +02:00
player.on('tracking:buffered', doTrackingBuffered);
2020-04-27 21:23:07 +02:00
player.on('tracking:firstplay', doTrackingFirstPlay);
2020-04-16 18:10:47 +02:00
player.on('ended', onEnded);
player.on('play', onPlay);
player.on('pause', onPause);
2020-04-16 23:43:09 +02:00
player.on('volumechange', () => {
if (player && player.volume() !== volume) {
changeVolume(player.volume());
}
if (player && player.muted() !== muted) {
changeMute(player.muted());
}
});
2020-04-16 18:10:47 +02:00
if (position) {
player.currentTime(position);
}
2020-04-16 23:43:09 +02:00
}, []);
2020-03-19 21:25:37 +01:00
2019-08-02 08:28:14 +02:00
return (
2020-04-14 01:48:11 +02:00
<div
className={classnames('file-viewer', {
'file-viewer--is-playing': isPlaying,
2020-04-16 23:43:09 +02:00
'file-viewer--ended-embed': isEndededEmbed,
2020-04-14 01:48:11 +02:00
})}
onContextMenu={stopContextMenu}
>
{showAutoplayCountdown && <AutoplayCountdown uri={uri} />}
{isEndededEmbed && <FileViewerEmbeddedEnded uri={uri} />}
{embedded && !isEndededEmbed && <FileViewerEmbeddedTitle uri={uri} />}
2020-04-26 22:32:39 +02:00
{/* disable this loading behavior because it breaks when player.play() promise hangs */}
2020-04-28 19:25:13 +02:00
{isLoading && <LoadingScreen status={__('Loading')} />}
2020-04-16 01:21:17 +02:00
<VideoJs
source={source}
isAudio={isAudio}
poster={isAudio || (embedded && !autoplayIfEmbedded) ? thumbnail : null}
sourceType={forcePlayer ? 'video/mp4' : contentType}
2020-04-16 18:10:47 +02:00
onPlayerReady={onPlayerReady}
startMuted={autoplayIfEmbedded}
2020-04-16 01:21:17 +02:00
/>
2019-08-02 08:28:14 +02:00
</div>
);
}
export default VideoViewer;