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