feat: autoplay next file in related list #2235
9 changed files with 83 additions and 58 deletions
|
@ -51,6 +51,7 @@
|
|||
"flowtype/space-after-type-colon": [2, "always", { "allowLineBreak": true }],
|
||||
"no-restricted-syntax": 0,
|
||||
"no-empty": 0,
|
||||
"react/prefer-stateless-function": 0
|
||||
"react/prefer-stateless-function": 0,
|
||||
"react/sort-comp": 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
"hast-util-sanitize": "^1.1.2",
|
||||
"keytar": "^4.2.1",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#a22f8284110f957f3f645d42abc457ab8fb3fa8a",
|
||||
"lbry-redux": "lbryio/lbry-redux#2ff9f70a3d765946a1c83c8e7eee7d81c96c1345",
|
||||
"lbryinc": "lbryio/lbryinc#83c275da7a44f346ce9e796d06f30126f02b4c63",
|
||||
"localforage": "^1.7.1",
|
||||
"mammoth": "^1.4.6",
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as settings from 'constants/settings';
|
|||
import { doChangeVolume } from 'redux/actions/app';
|
||||
import { selectVolume } from 'redux/selectors/app';
|
||||
import { doPlayUri, doSetPlayingUri, savePosition } from 'redux/actions/content';
|
||||
import { doNavigate } from 'redux/actions/navigation';
|
||||
import { doClaimEligiblePurchaseRewards } from 'lbryinc';
|
||||
import {
|
||||
makeSelectMetadataForUri,
|
||||
|
@ -13,6 +14,7 @@ import {
|
|||
makeSelectLoadingForUri,
|
||||
makeSelectDownloadingForUri,
|
||||
selectSearchBarFocused,
|
||||
makeSelectFirstRecommendedFileForUri,
|
||||
} from 'lbry-redux';
|
||||
import { makeSelectClientSetting, selectShowNsfw } from 'redux/selectors/settings';
|
||||
import { selectPlayingUri, makeSelectContentPositionForUri } from 'redux/selectors/content';
|
||||
|
@ -34,6 +36,7 @@ const select = (state, props) => ({
|
|||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
||||
searchBarFocused: selectSearchBarFocused(state),
|
||||
fileInfoErrors: selectFileInfoErrors(state),
|
||||
nextFileToPlay: makeSelectFirstRecommendedFileForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
@ -43,6 +46,7 @@ const perform = dispatch => ({
|
|||
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||
savePosition: (claimId, outpoint, position) =>
|
||||
dispatch(savePosition(claimId, outpoint, position)),
|
||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -46,14 +46,15 @@ class MediaPlayer extends React.PureComponent {
|
|||
volume,
|
||||
position,
|
||||
claim,
|
||||
startedPlayingCb,
|
||||
onStartCb,
|
||||
onFinishCb,
|
||||
} = this.props;
|
||||
|
||||
const loadedMetadata = () => {
|
||||
this.setState({ hasMetadata: true, startedPlaying: true });
|
||||
|
||||
if (startedPlayingCb) {
|
||||
startedPlayingCb();
|
||||
if (onStartCb) {
|
||||
onStartCb();
|
||||
}
|
||||
this.media.children[0].play();
|
||||
};
|
||||
|
@ -105,6 +106,11 @@ class MediaPlayer extends React.PureComponent {
|
|||
mediaElement.addEventListener('loadedmetadata', loadedMetadata.bind(this), {
|
||||
once: true,
|
||||
});
|
||||
mediaElement.addEventListener('ended', () => {
|
||||
if (onFinishCb) {
|
||||
onFinishCb();
|
||||
}
|
||||
});
|
||||
mediaElement.addEventListener('webkitfullscreenchange', win32FullScreenChange.bind(this));
|
||||
mediaElement.addEventListener('volumechange', () => {
|
||||
changeVolume(mediaElement.volume);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import analytics from 'analytics';
|
||||
|
@ -42,7 +43,9 @@ type Props = {
|
|||
searchBarFocused: boolean,
|
||||
mediaType: string,
|
||||
claimRewards: () => void,
|
||||
costInfo: ?{ cost: number },
|
||||
nextFileToPlay: ?string,
|
||||
navigate: (string, {}) => void,
|
||||
costInfo: ?{ cost: number }, // eslint-disable-line react/no-unused-prop-types
|
||||
};
|
||||
|
||||
class FileViewer extends React.PureComponent<Props> {
|
||||
|
@ -51,7 +54,8 @@ class FileViewer extends React.PureComponent<Props> {
|
|||
(this: any).playContent = this.playContent.bind(this);
|
||||
(this: any).handleKeyDown = this.handleKeyDown.bind(this);
|
||||
(this: any).logTimeToStart = this.logTimeToStart.bind(this);
|
||||
(this: any).startedPlayingCb = undefined;
|
||||
(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;
|
||||
|
@ -61,7 +65,7 @@ class FileViewer extends React.PureComponent<Props> {
|
|||
componentDidMount() {
|
||||
const { fileInfo } = this.props;
|
||||
if (!fileInfo) {
|
||||
this.startedPlayingCb = this.logTimeToStart;
|
||||
this.onFileStartCb = this.logTimeToStart;
|
||||
}
|
||||
|
||||
this.handleAutoplay(this.props);
|
||||
|
@ -83,10 +87,10 @@ class FileViewer extends React.PureComponent<Props> {
|
|||
this.playTime = null;
|
||||
|
||||
// If this new file is already downloaded, remove the startedPlayingCallback
|
||||
if (fileInfo && this.startedPlayingCb) {
|
||||
this.startedPlayingCb = null;
|
||||
} else if (!fileInfo && !this.startedPlayingCb) {
|
||||
this.startedPlayingCb = this.logTimeToStart;
|
||||
if (fileInfo && this.onFileStartCb) {
|
||||
this.onFileStartCb = null;
|
||||
} else if (!fileInfo && !this.onFileStartCb) {
|
||||
this.onFileStartCb = this.logTimeToStart;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,10 +153,10 @@ class FileViewer extends React.PureComponent<Props> {
|
|||
|
||||
if (fileInfo || isDownloading || isLoading) {
|
||||
// User may have pressed download before clicking play
|
||||
this.startedPlayingCb = null;
|
||||
this.onFileStartCb = null;
|
||||
}
|
||||
|
||||
if (this.startedPlayingCb) {
|
||||
if (this.onFileStartCb) {
|
||||
this.startTime = Date.now();
|
||||
}
|
||||
|
||||
|
@ -184,7 +188,15 @@ class FileViewer extends React.PureComponent<Props> {
|
|||
analytics.apiLogView(`${name}#${claimId}`, outpoint, claimId, timeToStart, claimRewards);
|
||||
}
|
||||
|
||||
startedPlayingCb: ?() => void;
|
||||
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;
|
||||
|
||||
|
@ -251,7 +263,8 @@ class FileViewer extends React.PureComponent<Props> {
|
|||
claim={claim}
|
||||
uri={uri}
|
||||
position={position}
|
||||
startedPlayingCb={this.startedPlayingCb}
|
||||
onStartCb={this.onFileStartCb}
|
||||
onFinishCb={this.onFileFinishCb}
|
||||
playingUri={playingUri}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -4,7 +4,6 @@ import type { FileInfo } from 'types/file_info';
|
|||
import * as MODALS from 'constants/modal_types';
|
||||
import * as icons from 'constants/icons';
|
||||
import * as React from 'react';
|
||||
import * as settings from 'constants/settings';
|
||||
import { buildURI, normalizeURI } from 'lbry-redux';
|
||||
import FileViewer from 'component/fileViewer';
|
||||
import Thumbnail from 'component/common/thumbnail';
|
||||
|
@ -41,7 +40,6 @@ type Props = {
|
|||
prepareEdit: ({}, string) => void,
|
||||
navigate: (string, ?{}) => void,
|
||||
openModal: (id: string, { uri: string }) => void,
|
||||
setClientSetting: (string, string | boolean | number) => void,
|
||||
markSubscriptionRead: (string, string) => void,
|
||||
};
|
||||
|
||||
|
@ -59,12 +57,6 @@ class FilePage extends React.Component<Props> {
|
|||
'application',
|
||||
];
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
(this: any).onAutoplayChange = this.onAutoplayChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { uri, fileInfo, fetchFileInfo, fetchCostInfo, setViewed, isSubscribed } = this.props;
|
||||
|
||||
|
@ -98,10 +90,6 @@ class FilePage extends React.Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
onAutoplayChange(event: SyntheticInputEvent<*>) {
|
||||
this.props.setClientSetting(settings.AUTOPLAY, event.target.checked);
|
||||
}
|
||||
|
||||
removeFromSubscriptionNotifications() {
|
||||
// Always try to remove
|
||||
// If it doesn't exist, nothing will happen
|
||||
|
|
|
@ -287,26 +287,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
</header>
|
||||
|
||||
<div className="card__content">
|
||||
<FormRow>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="autoplay"
|
||||
onChange={this.onAutoplayChange}
|
||||
checked={autoplay}
|
||||
postfix={__('Autoplay media files')}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<FormRow>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="auto_download"
|
||||
onChange={this.onAutoDownloadChange}
|
||||
checked={autoDownload}
|
||||
postfix={__('Automatically download new content from your subscriptions')}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<FormRow>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
|
@ -420,6 +400,39 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section className="card card--section">
|
||||
<header className="card__header">
|
||||
<h2 className="card__title">{__('Experimental Settings')}</h2>
|
||||
</header>
|
||||
|
||||
<div className="card__content">
|
||||
<FormRow>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="auto_download"
|
||||
onChange={this.onAutoDownloadChange}
|
||||
checked={autoDownload}
|
||||
postfix={__('Automatically download new content from my subscriptions')}
|
||||
helper={__(
|
||||
"The latest file from each of your subscriptions will be downloaded for quick access as soon as it's published."
|
||||
)}
|
||||
/>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="autoplay"
|
||||
onChange={this.onAutoplayChange}
|
||||
checked={autoplay}
|
||||
postfix={__('Autoplay media files')}
|
||||
helper={__(
|
||||
'Autoplay video and audio files when navigating to a file, as well as the next related item when a file finishes playing.'
|
||||
)}
|
||||
/>
|
||||
</FormRow>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="card card--section">
|
||||
<header className="card__header">
|
||||
<h2 className="card__title">{__('Application Cache')}</h2>
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
makeSelectFileInfoForUri,
|
||||
selectFileInfosByOutpoint,
|
||||
selectDownloadingByOutpoint,
|
||||
selectTotalDownloadProgress,
|
||||
selectBalance,
|
||||
makeSelectChannelForClaimUri,
|
||||
parseURI,
|
||||
|
@ -27,7 +26,6 @@ import {
|
|||
} from 'lbry-redux';
|
||||
import { makeSelectClientSetting, selectosNotificationsEnabled } from 'redux/selectors/settings';
|
||||
import setBadge from 'util/set-badge';
|
||||
import setProgressBar from 'util/set-progress-bar';
|
||||
import analytics from 'analytics';
|
||||
|
||||
const DOWNLOAD_POLL_INTERVAL = 250;
|
||||
|
@ -66,8 +64,10 @@ export function doUpdateLoadStatus(uri: string, outpoint: string) {
|
|||
const badgeNumber = selectBadgeNumber(state);
|
||||
setBadge(badgeNumber === 0 ? '' : `${badgeNumber}`);
|
||||
|
||||
const totalProgress = selectTotalDownloadProgress(state);
|
||||
setProgressBar(totalProgress);
|
||||
// Disabling this for now because it's confusing for new users that don't realize files are actually being downloaded
|
||||
// This should move inside of the app
|
||||
// const totalProgress = selectTotalDownloadProgress(state);
|
||||
// setProgressBar(totalProgress);
|
||||
|
||||
const channelUri = makeSelectChannelForClaimUri(uri, true)(state);
|
||||
const { claimName: channelName } = parseURI(channelUri);
|
||||
|
@ -120,8 +120,8 @@ export function doUpdateLoadStatus(uri: string, outpoint: string) {
|
|||
},
|
||||
});
|
||||
|
||||
const totalProgress = selectTotalDownloadProgress(getState());
|
||||
setProgressBar(totalProgress);
|
||||
// const totalProgress = selectTotalDownloadProgress(getState());
|
||||
// setProgressBar(totalProgress);
|
||||
setNextStatusUpdate();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -5723,17 +5723,17 @@ lazy-val@^1.0.3:
|
|||
tar-stream "^1.6.2"
|
||||
zstd-codec "^0.1.1"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#84b7d396934d57a37802aadbef71db91230a9404:
|
||||
lbry-redux@lbryio/lbry-redux#2ff9f70a3d765946a1c83c8e7eee7d81c96c1345:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/84b7d396934d57a37802aadbef71db91230a9404"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/2ff9f70a3d765946a1c83c8e7eee7d81c96c1345"
|
||||
dependencies:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#a22f8284110f957f3f645d42abc457ab8fb3fa8a:
|
||||
lbry-redux@lbryio/lbry-redux#84b7d396934d57a37802aadbef71db91230a9404:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/a22f8284110f957f3f645d42abc457ab8fb3fa8a"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/84b7d396934d57a37802aadbef71db91230a9404"
|
||||
dependencies:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
|
Loading…
Reference in a new issue