Autoplay #1453

Merged
daovist merged 4 commits from autoplay into master 2018-05-11 20:16:26 +02:00
14 changed files with 108 additions and 11 deletions

View file

@ -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)) * 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)) * 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)) * 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 ### Changed
* Add flair to snackbar ([#1313](https://github.com/lbryio/lbry-app/pull/1313)) * Add flair to snackbar ([#1313](https://github.com/lbryio/lbry-app/pull/1313))

View file

@ -9,6 +9,7 @@ type Props = {
padded?: boolean, padded?: boolean,
verticallyCentered?: boolean, verticallyCentered?: boolean,
stretch?: boolean, stretch?: boolean,
alignRight?: boolean,
}; };
export class FormRow extends React.PureComponent<Props> { export class FormRow extends React.PureComponent<Props> {
@ -17,7 +18,7 @@ export class FormRow extends React.PureComponent<Props> {
}; };
render() { render() {
const { centered, children, padded, verticallyCentered, stretch } = this.props; const { centered, children, padded, verticallyCentered, stretch, alignRight } = this.props;
return ( return (
<div <div
className={classnames('form-row', { className={classnames('form-row', {
@ -25,6 +26,7 @@ export class FormRow extends React.PureComponent<Props> {
'form-row--padded': padded, 'form-row--padded': padded,
'form-row--vertically-centered': verticallyCentered, 'form-row--vertically-centered': verticallyCentered,
'form-row--stretch': stretch, 'form-row--stretch': stretch,
'form-row--right': alignRight,
})} })}
> >
{children} {children}

View file

@ -1,7 +1,8 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
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 } from 'redux/actions/content'; import { doPlayUri, doSetPlayingUri, doLoadVideo } from 'redux/actions/content';
import { doPlay, doPause, savePosition } from 'redux/actions/media'; import { doPlay, doPause, savePosition } from 'redux/actions/media';
import { import {
makeSelectMetadataForUri, makeSelectMetadataForUri,
@ -12,7 +13,10 @@ import {
makeSelectLoadingForUri, makeSelectLoadingForUri,
makeSelectDownloadingForUri, makeSelectDownloadingForUri,
} from 'lbry-redux'; } from 'lbry-redux';
import { selectShowNsfw } from 'redux/selectors/settings'; import {
makeSelectClientSetting,
selectShowNsfw
} from 'redux/selectors/settings';
import { selectMediaPaused, makeSelectMediaPositionForUri } from 'redux/selectors/media'; import { selectMediaPaused, makeSelectMediaPositionForUri } from 'redux/selectors/media';
import { selectPlayingUri } from 'redux/selectors/content'; import { selectPlayingUri } from 'redux/selectors/content';
import Video from './view'; import Video from './view';
@ -30,10 +34,12 @@ const select = (state, props) => ({
volume: selectVolume(state), volume: selectVolume(state),
mediaPaused: selectMediaPaused(state), mediaPaused: selectMediaPaused(state),
mediaPosition: makeSelectMediaPositionForUri(props.uri)(state), mediaPosition: makeSelectMediaPositionForUri(props.uri)(state),
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state)
}); });
const perform = dispatch => ({ const perform = dispatch => ({
play: uri => dispatch(doPlayUri(uri)), play: uri => dispatch(doPlayUri(uri)),
load: uri => dispatch(doLoadVideo(uri)),
cancelPlay: () => dispatch(doSetPlayingUri(null)), cancelPlay: () => dispatch(doSetPlayingUri(null)),
changeVolume: volume => dispatch(doChangeVolume(volume)), changeVolume: volume => dispatch(doChangeVolume(volume)),
doPlay: () => dispatch(doPlay()), doPlay: () => dispatch(doPlay()),

View file

@ -19,6 +19,7 @@ type Props = {
nsfw: boolean, nsfw: boolean,
thumbnail: string, thumbnail: string,
}, },
autoplay: boolean,
isLoading: boolean, isLoading: boolean,
isDownloading: boolean, isDownloading: boolean,
playingUri: ?string, playingUri: ?string,
@ -38,10 +39,38 @@ type Props = {
}; };
class Video extends React.PureComponent<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() { componentWillUnmount() {
this.props.cancelPlay(); 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) { isMediaSame(nextProps: Props) {
return ( return (
this.props.fileInfo && this.props.fileInfo &&
@ -90,13 +119,18 @@ class Video extends React.PureComponent<Props> {
} }
const poster = metadata && metadata.thumbnail; const poster = metadata && metadata.thumbnail;
const layoverClass = classnames('content__cover', { 'card__media--nsfw': shouldObscureNsfw });
const layoverStyle =
!shouldObscureNsfw && poster ? { backgroundImage: `url("${poster}")` } : {};
return ( return (
<div className={classnames('video', {}, className)}> <div className={classnames('video', {}, className)}>
{isPlaying && ( {isPlaying && (
<div className="content__view"> <div className="content__view">
{!isReadyToPlay ? ( {!isReadyToPlay ? (
<LoadingScreen status={loadStatusMessage} /> <div className={layoverClass} style={layoverStyle}>
<LoadingScreen status={loadStatusMessage} />
</div>
) : ( ) : (
<VideoPlayer <VideoPlayer
filename={fileInfo.file_name} filename={fileInfo.file_name}
@ -119,10 +153,7 @@ class Video extends React.PureComponent<Props> {
</div> </div>
)} )}
{!isPlaying && ( {!isPlaying && (
<div <div className={layoverClass} style={layoverStyle}>
className={classnames('content__cover', { 'card__media--nsfw': shouldObscureNsfw })}
style={!shouldObscureNsfw && poster ? { backgroundImage: `url("${poster}")` } : {}}
>
<VideoPlayButton <VideoPlayButton
play={play} play={play}
fileInfo={fileInfo} fileInfo={fileInfo}

View file

@ -24,3 +24,4 @@ export const PHONE = 'Phone';
export const CHECK = 'CheckCircle'; export const CHECK = 'CheckCircle';
export const HEART = 'Heart'; export const HEART = 'Heart';
export const UNLOCK = 'Unlock'; export const UNLOCK = 'Unlock';
export const CHECK_SIMPLE = 'Check';

View file

@ -12,3 +12,4 @@ export const INSTANT_PURCHASE_MAX = 'instantPurchaseMax';
export const THEME = 'theme'; export const THEME = 'theme';
export const THEMES = 'themes'; export const THEMES = 'themes';
export const AUTOMATIC_DARK_MODE_ENABLED = 'automaticDarkModeEnabled'; export const AUTOMATIC_DARK_MODE_ENABLED = 'automaticDarkModeEnabled';
export const AUTOPLAY = 'autoplay';

View file

@ -1,7 +1,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import * as settings from 'constants/settings';
import { doNavigate } from 'redux/actions/navigation'; import { doNavigate } from 'redux/actions/navigation';
import { selectRewardContentClaimIds, selectPlayingUri } from 'redux/selectors/content'; import { selectRewardContentClaimIds, selectPlayingUri } from 'redux/selectors/content';
import { doCheckSubscription } from 'redux/actions/subscriptions'; import { doCheckSubscription } from 'redux/actions/subscriptions';
import { doSetClientSetting } from 'redux/actions/settings';
import { import {
doFetchFileInfo, doFetchFileInfo,
doFetchCostInfoForUri, doFetchCostInfoForUri,
@ -13,7 +15,7 @@ import {
makeSelectMetadataForUri, makeSelectMetadataForUri,
doNotify, doNotify,
} from 'lbry-redux'; } from 'lbry-redux';
import { selectShowNsfw } from 'redux/selectors/settings'; import { selectShowNsfw, makeSelectClientSetting } from 'redux/selectors/settings';
import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { selectMediaPaused } from 'redux/selectors/media'; import { selectMediaPaused } from 'redux/selectors/media';
import { doPrepareEdit } from 'redux/actions/publish'; import { doPrepareEdit } from 'redux/actions/publish';
@ -31,6 +33,7 @@ const select = (state, props) => ({
playingUri: selectPlayingUri(state), playingUri: selectPlayingUri(state),
isPaused: selectMediaPaused(state), isPaused: selectMediaPaused(state),
claimIsMine: makeSelectClaimIsMine(props.uri)(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state),
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state)
}); });
const perform = dispatch => ({ const perform = dispatch => ({
@ -40,6 +43,7 @@ const perform = dispatch => ({
checkSubscription: subscription => dispatch(doCheckSubscription(subscription)), checkSubscription: subscription => dispatch(doCheckSubscription(subscription)),
openModal: (modal, props) => dispatch(doNotify(modal, props)), openModal: (modal, props) => dispatch(doNotify(modal, props)),
prepareEdit: (publishData, uri) => dispatch(doPrepareEdit(publishData, uri)), prepareEdit: (publishData, uri) => dispatch(doPrepareEdit(publishData, uri)),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
}); });
export default connect(select, perform)(FilePage); export default connect(select, perform)(FilePage);

View file

@ -7,6 +7,7 @@ import FilePrice from 'component/filePrice';
import FileDetails from 'component/fileDetails'; import FileDetails from 'component/fileDetails';
import FileActions from 'component/fileActions'; import FileActions from 'component/fileActions';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
import { FormField, FormRow } from 'component/common/form';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import DateTime from 'component/dateTime'; import DateTime from 'component/dateTime';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
@ -14,6 +15,7 @@ import Button from 'component/button';
import SubscribeButton from 'component/subscribeButton'; import SubscribeButton from 'component/subscribeButton';
import Page from 'component/page'; import Page from 'component/page';
import player from 'render-media'; import player from 'render-media';
import * as settings from 'constants/settings';
type Props = { type Props = {
claim: { claim: {
@ -39,15 +41,25 @@ type Props = {
playingUri: ?string, playingUri: ?string,
isPaused: boolean, isPaused: boolean,
claimIsMine: boolean, claimIsMine: boolean,
autoplay: boolean,
costInfo: ?{}, costInfo: ?{},
navigate: (string, ?{}) => void, navigate: (string, ?{}) => void,
openModal: ({ id: string }, { uri: string }) => void, openModal: ({ id: string }, { uri: string }) => void,
fetchFileInfo: string => void, fetchFileInfo: string => void,
fetchCostInfo: string => void, fetchCostInfo: string => void,
prepareEdit: ({}) => void, prepareEdit: ({}) => void,
setClientSetting: (string, boolean | string) => void,
checkSubscription: ({ channelName: string, uri: string }) => void,
subscriptions: Array<{}>,
}; };
class FilePage extends React.Component<Props> { class FilePage extends React.Component<Props> {
constructor(props: Props) {
super(props);
(this: any).onAutoplayChange = this.onAutoplayChange.bind(this);
}
componentDidMount() { componentDidMount() {
const { uri, fileInfo, fetchFileInfo, costInfo, fetchCostInfo } = this.props; 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) => { checkSubscription = (props: Props) => {
if ( if (
props.claim.value.publisherSignature &&
props.subscriptions props.subscriptions
.map(subscription => subscription.channelName) .map(subscription => subscription.channelName)
.indexOf(props.claim.channel_name) !== -1 .indexOf(props.claim.channel_name) !== -1
@ -102,6 +119,7 @@ class FilePage extends React.Component<Props> {
claimIsMine, claimIsMine,
prepareEdit, prepareEdit,
navigate, navigate,
autoplay,
} = this.props; } = this.props;
// File info // File info
@ -177,6 +195,15 @@ class FilePage extends React.Component<Props> {
)} )}
</div> </div>
</div> </div>
<FormRow alignRight>
<FormField
type="checkbox"
name="autoplay"
onChange={this.onAutoplayChange}
checked={autoplay}
postfix={__('Autoplay')}
/>
</FormRow>
</div> </div>
<div className="card__content"> <div className="card__content">

View file

@ -26,6 +26,7 @@ const select = state => ({
language: selectCurrentLanguage(state), language: selectCurrentLanguage(state),
languages: selectLanguages(state), languages: selectLanguages(state),
automaticDarkModeEnabled: makeSelectClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED)(state), automaticDarkModeEnabled: makeSelectClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED)(state),
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state)
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -31,6 +31,7 @@ type Props = {
currentTheme: string, currentTheme: string,
themes: Array<string>, themes: Array<string>,
automaticDarkModeEnabled: boolean, automaticDarkModeEnabled: boolean,
autoplay: boolean
}; };
type State = { type State = {
@ -53,6 +54,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
(this: any).onShareDataChange = this.onShareDataChange.bind(this); (this: any).onShareDataChange = this.onShareDataChange.bind(this);
(this: any).onThemeChange = this.onThemeChange.bind(this); (this: any).onThemeChange = this.onThemeChange.bind(this);
(this: any).onAutomaticDarkModeChange = this.onAutomaticDarkModeChange.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).clearCache = this.clearCache.bind(this);
// (this: any).onLanguageChange = this.onLanguageChange.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); this.props.setClientSetting(settings.AUTOMATIC_DARK_MODE_ENABLED, value);
} }
onAutoplayChange(event: SyntheticInputEvent<*>) {
this.props.setClientSetting(settings.AUTOPLAY, event.target.checked);
}
onInstantPurchaseEnabledChange(enabled: boolean) { onInstantPurchaseEnabledChange(enabled: boolean) {
this.props.setClientSetting(settings.INSTANT_PURCHASE_ENABLED, enabled); this.props.setClientSetting(settings.INSTANT_PURCHASE_ENABLED, enabled);
} }
@ -138,6 +144,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
currentTheme, currentTheme,
themes, themes,
automaticDarkModeEnabled, automaticDarkModeEnabled,
autoplay
} = this.props; } = this.props;
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0; const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
@ -239,6 +246,13 @@ class SettingsPage extends React.PureComponent<Props, State> {
</section> </section>
<section className="card card--section"> <section className="card card--section">
<div className="card__title">{__('Content Settings')}</div> <div className="card__title">{__('Content Settings')}</div>
<FormField
type="checkbox"
name="autoplay"
onChange={this.onAutoplayChange}
checked={autoplay}
postfix={__('Autoplay media files')}
/>
<FormField <FormField
type="checkbox" type="checkbox"
name="show_unavailable" name="show_unavailable"

View file

@ -24,6 +24,7 @@ const defaultState = {
theme: getLocalStorageSetting(SETTINGS.THEME, 'light'), theme: getLocalStorageSetting(SETTINGS.THEME, 'light'),
themes: getLocalStorageSetting(SETTINGS.THEMES, []), themes: getLocalStorageSetting(SETTINGS.THEMES, []),
automaticDarkModeEnabled: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false), automaticDarkModeEnabled: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false),
autoplay: getLocalStorageSetting(SETTINGS.AUTOPLAY, false)
}, },
isNight: false, isNight: false,
languages: {}, languages: {},

View file

@ -168,12 +168,12 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: $spacing-width 0; padding: $spacing-width 0 0;
} }
.card__channel-info--large { .card__channel-info--large {
padding-top: 0; padding-top: 0;
padding-bottom: $spacing-width; padding-bottom: $spacing-vertical * 2/3;
} }
.card__content { .card__content {

View file

@ -59,7 +59,11 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center;
padding: 0 20px; padding: 0 20px;
background-color: rgba(0, 0, 0, 0.5);
height: 100%;
width: 100%;
} }
.content__loading-text { .content__loading-text {

View file

@ -23,6 +23,10 @@
flex: 1; flex: 1;
} }
&.form-row--right {
justify-content: flex-end;
}
.form-field.form-field--stretch { .form-field.form-field--stretch {
width: 100%; width: 100%;