lbry-desktop/src/ui/component/fileViewer/view.jsx

310 lines
9 KiB
React
Raw Normal View History

2018-03-26 23:32:43 +02:00
// @flow
import * as PAGES from 'constants/pages';
import React from 'react';
2018-03-26 23:32:43 +02:00
import classnames from 'classnames';
import analytics from 'analytics';
import type { Claim } from 'types/claim';
import LoadingScreen from 'component/common/loading-screen';
2018-07-10 05:32:59 +02:00
import Player from './internal/player';
import PlayButton from './internal/play-button';
2018-05-16 21:34:38 +02:00
const SPACE_BAR_KEYCODE = 32;
2018-03-26 23:32:43 +02:00
type Props = {
cancelPlay: () => void,
fileInfo: {
outpoint: string,
file_name: string,
written_bytes: number,
download_path: string,
completed: boolean,
2019-01-30 05:15:01 +01:00
blobs_completed: number,
2018-03-26 23:32:43 +02:00
},
fileInfoErrors: ?{
[string]: boolean,
},
2018-03-26 23:32:43 +02:00
metadata: ?{
nsfw: boolean,
thumbnail: string,
},
2018-05-11 16:33:33 +02:00
autoplay: boolean,
2018-03-26 23:32:43 +02:00
isLoading: boolean,
isDownloading: boolean,
playingUri: ?string,
contentType: string,
changeVolume: number => void,
volume: number,
claim: Claim,
2018-03-26 23:32:43 +02:00
uri: string,
savePosition: (string, string, number) => void,
2018-07-31 20:06:50 +02:00
position: ?number,
2018-03-26 23:32:43 +02:00
className: ?string,
obscureNsfw: boolean,
2018-05-11 16:33:33 +02:00
play: string => void,
2018-05-16 20:32:25 +02:00
searchBarFocused: boolean,
mediaType: string,
claimRewards: () => void,
nextFileToPlay: ?string,
navigate: (string, {}) => void,
2019-03-18 06:09:50 +01:00
costInfo: ?{ cost: number },
2018-03-26 23:32:43 +02:00
};
2017-04-23 11:56:50 +02:00
2018-07-10 05:32:59 +02:00
class FileViewer extends React.PureComponent<Props> {
2018-05-16 20:32:25 +02:00
constructor() {
super();
(this: any).playContent = this.playContent.bind(this);
(this: any).handleKeyDown = this.handleKeyDown.bind(this);
(this: any).logTimeToStart = this.logTimeToStart.bind(this);
(this: any).onFileFinishCb = this.onFileFinishCb.bind(this);
(this: any).onFileStartCb = undefined;
// Don't add these variables to state because we don't need to re-render when their values change
(this: any).startTime = undefined;
(this: any).playTime = undefined;
2018-05-16 20:32:25 +02:00
}
componentDidMount() {
const { fileInfo } = this.props;
if (!fileInfo) {
this.onFileStartCb = this.logTimeToStart;
}
this.handleAutoplay(this.props);
2018-05-16 20:32:25 +02:00
window.addEventListener('keydown', this.handleKeyDown);
}
componentDidUpdate(prev: Props) {
const { fileInfo } = this.props;
if (this.props.uri !== prev.uri) {
// User just directly navigated to another piece of content
if (this.startTime && !this.playTime) {
// They started playing a file but it didn't start streaming
// Fire the analytics event with the previous file
this.fireAnalyticsEvent(prev.claim);
}
this.startTime = null;
this.playTime = null;
// If this new file is already downloaded, remove the startedPlayingCallback
if (fileInfo && this.onFileStartCb) {
this.onFileStartCb = null;
} else if (!fileInfo && !this.onFileStartCb) {
this.onFileStartCb = this.logTimeToStart;
}
}
2018-05-11 16:33:33 +02:00
if (
this.props.autoplay !== prev.autoplay ||
this.props.fileInfo !== prev.fileInfo ||
this.props.isDownloading !== prev.isDownloading ||
this.props.playingUri !== prev.playingUri
2018-05-11 16:33:33 +02:00
) {
// suppress autoplay after download error
2019-01-19 19:54:06 +01:00
if (!this.props.fileInfoErrors || !(this.props.uri in this.props.fileInfoErrors)) {
this.handleAutoplay(this.props);
}
2018-05-11 16:33:33 +02:00
}
}
componentWillUnmount() {
const { claim } = this.props;
if (this.startTime && !this.playTime) {
// The user is navigating away before the file started playing, or a play time was never set
// Currently will not be set for files that don't use render-media
this.fireAnalyticsEvent(claim);
}
2018-05-11 16:33:33 +02:00
this.props.cancelPlay();
2018-05-16 20:32:25 +02:00
window.removeEventListener('keydown', this.handleKeyDown);
}
2018-05-16 20:32:25 +02:00
handleKeyDown(event: SyntheticKeyboardEvent<*>) {
const { searchBarFocused } = this.props;
2018-05-16 21:34:38 +02:00
if (!searchBarFocused && event.keyCode === SPACE_BAR_KEYCODE) {
2018-05-16 20:32:25 +02:00
event.preventDefault(); // prevent page scroll
this.playContent();
}
}
handleAutoplay = (props: Props) => {
const { autoplay, playingUri, fileInfo, costInfo, isDownloading, uri, metadata } = props;
const playable = autoplay && playingUri !== uri && metadata && !metadata.nsfw;
2018-05-11 16:33:33 +02:00
if (playable && costInfo && costInfo.cost === 0 && !fileInfo && !isDownloading) {
this.playContent();
2019-02-18 18:33:02 +01:00
} else if (playable && fileInfo && fileInfo.download_path && fileInfo.written_bytes > 0) {
this.playContent();
}
2018-05-16 20:32:25 +02:00
};
2018-03-26 23:32:43 +02:00
isMediaSame(nextProps: Props) {
return (
this.props.fileInfo &&
nextProps.fileInfo &&
this.props.fileInfo.outpoint === nextProps.fileInfo.outpoint
);
}
2018-05-16 20:32:25 +02:00
playContent() {
const { play, uri, fileInfo, isDownloading, isLoading } = this.props;
2019-03-13 06:59:07 +01:00
// @if TARGET='app'
if (fileInfo || isDownloading || isLoading) {
// User may have pressed download before clicking play
this.onFileStartCb = null;
}
if (this.onFileStartCb) {
this.startTime = Date.now();
}
2019-03-13 06:59:07 +01:00
// @endif
2018-05-16 20:32:25 +02:00
play(uri);
}
logTimeToStart() {
const { claim } = this.props;
if (this.startTime) {
this.playTime = Date.now();
this.fireAnalyticsEvent(claim, this.startTime, this.playTime);
}
}
2019-01-19 19:54:06 +01:00
fireAnalyticsEvent(claim: Claim, startTime: ?number, playTime: ?number) {
const { claimRewards } = this.props;
const { name, claim_id: claimId, txid, nout } = claim;
// ideally outpoint would exist inside of claim information
// we can use it after https://github.com/lbryio/lbry/issues/1306 is addressed
const outpoint = `${txid}:${nout}`;
let timeToStart;
if (playTime && startTime) {
timeToStart = playTime - startTime;
}
analytics.apiLogView(`${name}#${claimId}`, outpoint, claimId, timeToStart, claimRewards);
}
onFileFinishCb() {
// If a user has `autoplay` enabled, start playing the next file at the top of "related"
const { autoplay, nextFileToPlay, navigate } = this.props;
if (autoplay && nextFileToPlay) {
navigate(PAGES.SHOW, { uri: nextFileToPlay });
}
}
onFileStartCb: ?() => void;
startTime: ?number;
playTime: ?number;
2017-04-23 11:56:50 +02:00
render() {
const {
metadata,
isLoading,
isDownloading,
playingUri,
2019-03-15 17:15:31 +01:00
fileInfo = {},
contentType,
changeVolume,
volume,
claim,
uri,
savePosition,
2018-07-31 20:06:50 +02:00
position,
2018-03-26 23:32:43 +02:00
className,
obscureNsfw,
mediaType,
2017-06-06 23:19:12 +02:00
} = this.props;
2017-04-23 11:56:50 +02:00
const isPlaying = playingUri === uri;
2019-03-15 17:15:31 +01:00
// @if TARGET='app'
2019-02-18 18:33:02 +01:00
const isReadyToPlay = fileInfo && fileInfo.download_path && fileInfo.written_bytes > 0;
2019-03-15 17:15:31 +01:00
// @endif
// @if TARGET='web'
// try to play immediately on web, we don't need to call file_list since we are streaming from reflector
2019-03-18 06:09:50 +01:00
// $FlowFixMe
2019-03-15 17:15:31 +01:00
const isReadyToPlay = isPlaying;
// @endif
2017-05-15 18:34:33 +02:00
2019-03-15 17:15:31 +01:00
const shouldObscureNsfw = obscureNsfw && metadata && metadata.nsfw;
let loadStatusMessage = '';
2017-04-23 11:56:50 +02:00
2019-02-18 18:33:02 +01:00
if (fileInfo && fileInfo.completed && (!fileInfo.download_path || !fileInfo.written_bytes)) {
2017-06-06 23:19:12 +02:00
loadStatusMessage = __(
"It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds."
);
} else if (isLoading) {
loadStatusMessage = __('Requesting stream...');
2017-04-23 11:56:50 +02:00
} else if (isDownloading) {
loadStatusMessage = __('Downloading stream... not long left now!');
2017-04-23 11:56:50 +02:00
}
2018-03-26 23:32:43 +02:00
const poster = metadata && metadata.thumbnail;
const layoverClass = classnames('content__cover', { 'card__media--nsfw': shouldObscureNsfw });
2018-05-11 16:33:33 +02:00
const layoverStyle =
!shouldObscureNsfw && poster ? { backgroundImage: `url("${poster}")` } : {};
2017-04-23 11:56:50 +02:00
return (
2018-03-26 23:32:43 +02:00
<div className={classnames('video', {}, className)}>
{isPlaying && (
2019-03-25 07:18:22 +01:00
<div className="content__view">
2018-03-26 23:32:43 +02:00
{!isReadyToPlay ? (
<div className={layoverClass} style={layoverStyle}>
<LoadingScreen status={loadStatusMessage} />
</div>
2018-05-11 16:33:33 +02:00
) : (
2018-07-10 05:32:59 +02:00
<Player
2018-07-28 04:26:04 +02:00
fileName={fileInfo.file_name}
2018-03-26 23:32:43 +02:00
poster={poster}
downloadPath={fileInfo.download_path}
mediaType={mediaType}
contentType={contentType}
downloadCompleted={fileInfo.completed}
changeVolume={changeVolume}
volume={volume}
2019-01-30 05:15:01 +01:00
savePosition={newPosition =>
savePosition(claim.claim_id, `${claim.txid}:${claim.nout}`, newPosition)
}
2018-03-26 23:32:43 +02:00
claim={claim}
uri={uri}
2018-07-31 20:06:50 +02:00
position={position}
onStartCb={this.onFileStartCb}
onFinishCb={this.onFileFinishCb}
playingUri={playingUri}
2018-03-26 23:32:43 +02:00
/>
)}
</div>
)}
{!isPlaying && (
2018-05-16 20:32:25 +02:00
<div
2019-03-25 07:18:22 +01:00
role="button"
2018-05-16 20:32:25 +02:00
onClick={this.playContent}
className={layoverClass}
style={layoverStyle}
>
2018-07-10 05:32:59 +02:00
<PlayButton
2019-01-19 19:54:06 +01:00
play={(e: SyntheticInputEvent<*>) => {
2018-05-31 06:32:31 +02:00
e.stopPropagation();
this.playContent();
}}
2018-03-26 23:32:43 +02:00
fileInfo={fileInfo}
2018-01-05 20:27:58 +01:00
uri={uri}
2018-03-26 23:32:43 +02:00
isLoading={isLoading}
mediaType={mediaType}
/>
</div>
)}
</div>
2017-06-06 23:19:12 +02:00
);
2017-04-23 11:56:50 +02:00
}
}
2018-07-10 05:32:59 +02:00
export default FileViewer;