Autoplay #1453
14 changed files with 108 additions and 11 deletions
|
@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|||
* New dark mode ([#1269](https://github.com/lbryio/lbry-app/pull/1269))
|
||||
* Pre-fill publish URL after clicking "Put something here" link ([#1303](https://github.com/lbryio/lbry-app/pull/1303))
|
||||
* Add Danger JS to automate code reviews ([#1289](https://github.com/lbryio/lbry-app/pull/1289))
|
||||
* Autoplay downloaded and free media ([#584](https://github.com/lbryio/lbry-app/pull/1453))
|
||||
|
||||
### Changed
|
||||
* Add flair to snackbar ([#1313](https://github.com/lbryio/lbry-app/pull/1313))
|
||||
|
|
|
@ -9,6 +9,7 @@ type Props = {
|
|||
padded?: boolean,
|
||||
verticallyCentered?: boolean,
|
||||
stretch?: boolean,
|
||||
alignRight?: boolean,
|
||||
};
|
||||
|
||||
export class FormRow extends React.PureComponent<Props> {
|
||||
|
@ -17,7 +18,7 @@ export class FormRow extends React.PureComponent<Props> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { centered, children, padded, verticallyCentered, stretch } = this.props;
|
||||
const { centered, children, padded, verticallyCentered, stretch, alignRight } = this.props;
|
||||
return (
|
||||
<div
|
||||
className={classnames('form-row', {
|
||||
|
@ -25,6 +26,7 @@ export class FormRow extends React.PureComponent<Props> {
|
|||
'form-row--padded': padded,
|
||||
'form-row--vertically-centered': verticallyCentered,
|
||||
'form-row--stretch': stretch,
|
||||
'form-row--right': alignRight,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { connect } from 'react-redux';
|
||||
import * as settings from 'constants/settings';
|
||||
import { doChangeVolume } from 'redux/actions/app';
|
||||
import { selectVolume } from 'redux/selectors/app';
|
||||
import { doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
||||
import { doPlayUri, doSetPlayingUri, doLoadVideo } from 'redux/actions/content';
|
||||
import { doPlay, doPause, savePosition } from 'redux/actions/media';
|
||||
import {
|
||||
makeSelectMetadataForUri,
|
||||
|
@ -12,7 +13,10 @@ import {
|
|||
makeSelectLoadingForUri,
|
||||
makeSelectDownloadingForUri,
|
||||
} from 'lbry-redux';
|
||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||
import {
|
||||
makeSelectClientSetting,
|
||||
selectShowNsfw
|
||||
} from 'redux/selectors/settings';
|
||||
import { selectMediaPaused, makeSelectMediaPositionForUri } from 'redux/selectors/media';
|
||||
import { selectPlayingUri } from 'redux/selectors/content';
|
||||
import Video from './view';
|
||||
|
@ -30,10 +34,12 @@ const select = (state, props) => ({
|
|||
volume: selectVolume(state),
|
||||
mediaPaused: selectMediaPaused(state),
|
||||
mediaPosition: makeSelectMediaPositionForUri(props.uri)(state),
|
||||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state)
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
play: uri => dispatch(doPlayUri(uri)),
|
||||
load: uri => dispatch(doLoadVideo(uri)),
|
||||
cancelPlay: () => dispatch(doSetPlayingUri(null)),
|
||||
changeVolume: volume => dispatch(doChangeVolume(volume)),
|
||||
doPlay: () => dispatch(doPlay()),
|
||||
|
|
|
@ -19,6 +19,7 @@ type Props = {
|
|||
nsfw: boolean,
|
||||
thumbnail: string,
|
||||
},
|
||||
autoplay: boolean,
|
||||
isLoading: boolean,
|
||||
isDownloading: boolean,
|
||||
playingUri: ?string,
|
||||
|
@ -38,10 +39,38 @@ type Props = {
|
|||
};
|
||||
|
||||
class Video extends React.PureComponent<Props> {
|
||||
componentDidMount() {
|
||||
this.handleAutoplay(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
if (
|
||||
this.props.autoplay !== nextProps.autoplay ||
|
||||
this.props.fileInfo !== nextProps.fileInfo ||
|
||||
this.props.isDownloading !== nextProps.isDownloading ||
|
||||
this.props.playingUri !== nextProps.playingUri
|
||||
) {
|
||||
this.handleAutoplay(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.cancelPlay();
|
||||
}
|
||||
|
||||
handleAutoplay(props: Props) {
|
||||
const { autoplay, playingUri, fileInfo, costInfo, isDownloading, uri, load, play, metadata } = props;
|
||||
|
||||
const playable = autoplay && playingUri !== uri && metadata && !metadata.nsfw;
|
||||
|
||||
if (playable && costInfo && costInfo.cost === 0 && !fileInfo && !isDownloading) {
|
||||
load(uri);
|
||||
play(uri);
|
||||
} else if (playable && fileInfo && fileInfo.blobs_completed > 0) {
|
||||
play(uri);
|
||||
}
|
||||
}
|
||||
|
||||
isMediaSame(nextProps: Props) {
|
||||
return (
|
||||
this.props.fileInfo &&
|
||||
|
@ -90,13 +119,18 @@ class Video extends React.PureComponent<Props> {
|
|||
}
|
||||
|
||||
const poster = metadata && metadata.thumbnail;
|
||||
const layoverClass = classnames('content__cover', { 'card__media--nsfw': shouldObscureNsfw });
|
||||
const layoverStyle =
|
||||
!shouldObscureNsfw && poster ? { backgroundImage: `url("${poster}")` } : {};
|
||||
|
||||
return (
|
||||
<div className={classnames('video', {}, className)}>
|
||||
{isPlaying && (
|
||||
<div className="content__view">
|
||||
{!isReadyToPlay ? (
|
||||
<LoadingScreen status={loadStatusMessage} />
|
||||
<div className={layoverClass} style={layoverStyle}>
|
||||
<LoadingScreen status={loadStatusMessage} />
|
||||
</div>
|
||||
) : (
|
||||
<VideoPlayer
|
||||
filename={fileInfo.file_name}
|
||||
|
@ -119,10 +153,7 @@ class Video extends React.PureComponent<Props> {
|
|||
</div>
|
||||
)}
|
||||
{!isPlaying && (
|
||||
<div
|
||||
className={classnames('content__cover', { 'card__media--nsfw': shouldObscureNsfw })}
|
||||
style={!shouldObscureNsfw && poster ? { backgroundImage: `url("${poster}")` } : {}}
|
||||
>
|
||||
<div className={layoverClass} style={layoverStyle}>
|
||||
<VideoPlayButton
|
||||
play={play}
|
||||
fileInfo={fileInfo}
|
||||
|
|
|
@ -24,3 +24,4 @@ export const PHONE = 'Phone';
|
|||
export const CHECK = 'CheckCircle';
|
||||
export const HEART = 'Heart';
|
||||
export const UNLOCK = 'Unlock';
|
||||
export const CHECK_SIMPLE = 'Check';
|
||||
|
|
|
@ -12,3 +12,4 @@ export const INSTANT_PURCHASE_MAX = 'instantPurchaseMax';
|
|||
export const THEME = 'theme';
|
||||
export const THEMES = 'themes';
|
||||
export const AUTOMATIC_DARK_MODE_ENABLED = 'automaticDarkModeEnabled';
|
||||
export const AUTOPLAY = 'autoplay';
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import * as settings from 'constants/settings';
|
||||
import { doNavigate } from 'redux/actions/navigation';
|
||||
import { selectRewardContentClaimIds, selectPlayingUri } from 'redux/selectors/content';
|
||||
import { doCheckSubscription } from 'redux/actions/subscriptions';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import {
|
||||
doFetchFileInfo,
|
||||
doFetchCostInfoForUri,
|
||||
|
@ -13,7 +15,7 @@ import {
|
|||
makeSelectMetadataForUri,
|
||||
doNotify,
|
||||
} from 'lbry-redux';
|
||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||
import { selectShowNsfw, makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import { selectMediaPaused } from 'redux/selectors/media';
|
||||
import { doPrepareEdit } from 'redux/actions/publish';
|
||||
|
@ -31,6 +33,7 @@ const select = (state, props) => ({
|
|||
playingUri: selectPlayingUri(state),
|
||||
isPaused: selectMediaPaused(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state)
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
@ -40,6 +43,7 @@ const perform = dispatch => ({
|
|||
checkSubscription: subscription => dispatch(doCheckSubscription(subscription)),
|
||||
openModal: (modal, props) => dispatch(doNotify(modal, props)),
|
||||
prepareEdit: (publishData, uri) => dispatch(doPrepareEdit(publishData, uri)),
|
||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FilePage);
|
||||
|
|
|
@ -7,6 +7,7 @@ import FilePrice from 'component/filePrice';
|
|||
import FileDetails from 'component/fileDetails';
|
||||
import FileActions from 'component/fileActions';
|
||||
import UriIndicator from 'component/uriIndicator';
|
||||
import { FormField, FormRow } from 'component/common/form';
|
||||
import Icon from 'component/common/icon';
|
||||
import DateTime from 'component/dateTime';
|
||||
import * as icons from 'constants/icons';
|
||||
|
@ -14,6 +15,7 @@ import Button from 'component/button';
|
|||
import SubscribeButton from 'component/subscribeButton';
|
||||
import Page from 'component/page';
|
||||
import player from 'render-media';
|
||||
import * as settings from 'constants/settings';
|
||||
|
||||
type Props = {
|
||||
claim: {
|
||||
|
@ -39,15 +41,25 @@ type Props = {
|
|||
playingUri: ?string,
|
||||
isPaused: boolean,
|
||||
claimIsMine: boolean,
|
||||
autoplay: boolean,
|
||||
costInfo: ?{},
|
||||
navigate: (string, ?{}) => void,
|
||||
openModal: ({ id: string }, { uri: string }) => void,
|
||||
fetchFileInfo: string => void,
|
||||
fetchCostInfo: string => void,
|
||||
prepareEdit: ({}) => void,
|
||||
setClientSetting: (string, boolean | string) => void,
|
||||
checkSubscription: ({ channelName: string, uri: string }) => void,
|
||||
subscriptions: Array<{}>,
|
||||
};
|
||||
|
||||
class FilePage extends React.Component<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
(this: any).onAutoplayChange = this.onAutoplayChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { uri, fileInfo, fetchFileInfo, costInfo, fetchCostInfo } = this.props;
|
||||
|
||||
|
@ -69,8 +81,13 @@ class FilePage extends React.Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
onAutoplayChange(event: SyntheticInputEvent<*>) {
|
||||
this.props.setClientSetting(settings.AUTOPLAY, event.target.checked);
|
||||
}
|
||||
|
||||
checkSubscription = (props: Props) => {
|
||||
if (
|
||||
props.claim.value.publisherSignature &&
|
||||
props.subscriptions
|
||||
.map(subscription => subscription.channelName)
|
||||
.indexOf(props.claim.channel_name) !== -1
|
||||
|
@ -102,6 +119,7 @@ class FilePage extends React.Component<Props> {
|
|||
claimIsMine,
|
||||
prepareEdit,
|
||||
navigate,
|
||||
autoplay,
|
||||
} = this.props;
|
||||
|
||||
// File info
|
||||
|
@ -177,6 +195,15 @@ class FilePage extends React.Component<Props> {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<FormRow alignRight>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="autoplay"
|
||||
onChange={this.onAutoplayChange}
|
||||
checked={autoplay}
|
||||
postfix={__('Autoplay')}
|
||||
/>
|
||||
</FormRow>
|
||||
</div>
|
||||
|
||||
<div className="card__content">
|
||||
|
|
|
@ -26,6 +26,7 @@ const select = state => ({
|
|||
language: selectCurrentLanguage(state),
|
||||
languages: selectLanguages(state),
|
||||
automaticDarkModeEnabled: makeSelectClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED)(state),
|
||||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state)
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
|
|
@ -31,6 +31,7 @@ type Props = {
|
|||
currentTheme: string,
|
||||
themes: Array<string>,
|
||||
automaticDarkModeEnabled: boolean,
|
||||
autoplay: boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
@ -53,6 +54,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
(this: any).onShareDataChange = this.onShareDataChange.bind(this);
|
||||
(this: any).onThemeChange = this.onThemeChange.bind(this);
|
||||
(this: any).onAutomaticDarkModeChange = this.onAutomaticDarkModeChange.bind(this);
|
||||
(this: any).onAutoplayChange = this.onAutoplayChange.bind(this);
|
||||
(this: any).clearCache = this.clearCache.bind(this);
|
||||
// (this: any).onLanguageChange = this.onLanguageChange.bind(this)
|
||||
}
|
||||
|
@ -95,6 +97,10 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
this.props.setClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED, value);
|
||||
}
|
||||
|
||||
onAutoplayChange(event: SyntheticInputEvent<*>) {
|
||||
this.props.setClientSetting(settings.AUTOPLAY, event.target.checked);
|
||||
}
|
||||
|
||||
onInstantPurchaseEnabledChange(enabled: boolean) {
|
||||
this.props.setClientSetting(settings.INSTANT_PURCHASE_ENABLED, enabled);
|
||||
}
|
||||
|
@ -138,6 +144,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
currentTheme,
|
||||
themes,
|
||||
automaticDarkModeEnabled,
|
||||
autoplay
|
||||
} = this.props;
|
||||
|
||||
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
||||
|
@ -239,6 +246,13 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
</section>
|
||||
<section className="card card--section">
|
||||
<div className="card__title">{__('Content Settings')}</div>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="autoplay"
|
||||
onChange={this.onAutoplayChange}
|
||||
checked={autoplay}
|
||||
postfix={__('Autoplay media files')}
|
||||
/>
|
||||
<FormField
|
||||
type="checkbox"
|
||||
name="show_unavailable"
|
||||
|
|
|
@ -24,6 +24,7 @@ const defaultState = {
|
|||
theme: getLocalStorageSetting(SETTINGS.THEME, 'light'),
|
||||
themes: getLocalStorageSetting(SETTINGS.THEMES, []),
|
||||
automaticDarkModeEnabled: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false),
|
||||
autoplay: getLocalStorageSetting(SETTINGS.AUTOPLAY, false)
|
||||
},
|
||||
isNight: false,
|
||||
languages: {},
|
||||
|
|
|
@ -168,12 +168,12 @@
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: $spacing-width 0;
|
||||
padding: $spacing-width 0 0;
|
||||
}
|
||||
|
||||
.card__channel-info--large {
|
||||
padding-top: 0;
|
||||
padding-bottom: $spacing-width;
|
||||
padding-bottom: $spacing-vertical * 2/3;
|
||||
}
|
||||
|
||||
.card__content {
|
||||
|
|
|
@ -59,7 +59,11 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 20px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content__loading-text {
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
&.form-row--right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.form-field.form-field--stretch {
|
||||
width: 100%;
|
||||
|
||||
|
|
Loading…
Reference in a new issue