diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a1858f13..d31dbbc93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/src/renderer/component/common/form-components/form-row.jsx b/src/renderer/component/common/form-components/form-row.jsx index 1216ba816..b18bcf795 100644 --- a/src/renderer/component/common/form-components/form-row.jsx +++ b/src/renderer/component/common/form-components/form-row.jsx @@ -9,6 +9,7 @@ type Props = { padded?: boolean, verticallyCentered?: boolean, stretch?: boolean, + alignRight?: boolean, }; export class FormRow extends React.PureComponent { @@ -17,7 +18,7 @@ export class FormRow extends React.PureComponent { }; render() { - const { centered, children, padded, verticallyCentered, stretch } = this.props; + const { centered, children, padded, verticallyCentered, stretch, alignRight } = this.props; return (
{ 'form-row--padded': padded, 'form-row--vertically-centered': verticallyCentered, 'form-row--stretch': stretch, + 'form-row--right': alignRight, })} > {children} diff --git a/src/renderer/component/video/index.js b/src/renderer/component/video/index.js index 671f931be..a4063b057 100644 --- a/src/renderer/component/video/index.js +++ b/src/renderer/component/video/index.js @@ -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()), diff --git a/src/renderer/component/video/view.jsx b/src/renderer/component/video/view.jsx index 1cfda4078..c1663c5e3 100644 --- a/src/renderer/component/video/view.jsx +++ b/src/renderer/component/video/view.jsx @@ -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 { + 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 { } const poster = metadata && metadata.thumbnail; + const layoverClass = classnames('content__cover', { 'card__media--nsfw': shouldObscureNsfw }); + const layoverStyle = + !shouldObscureNsfw && poster ? { backgroundImage: `url("${poster}")` } : {}; return (
{isPlaying && (
{!isReadyToPlay ? ( - +
+ +
) : ( {
)} {!isPlaying && ( -
+
({ 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); diff --git a/src/renderer/page/file/view.jsx b/src/renderer/page/file/view.jsx index 5cbbad39b..40cc010a2 100644 --- a/src/renderer/page/file/view.jsx +++ b/src/renderer/page/file/view.jsx @@ -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 { + 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 { } } + 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 { claimIsMine, prepareEdit, navigate, + autoplay, } = this.props; // File info @@ -177,6 +195,15 @@ class FilePage extends React.Component { )}
+ + +
diff --git a/src/renderer/page/settings/index.js b/src/renderer/page/settings/index.js index e4f4ff930..d2f72da84 100644 --- a/src/renderer/page/settings/index.js +++ b/src/renderer/page/settings/index.js @@ -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 => ({ diff --git a/src/renderer/page/settings/view.jsx b/src/renderer/page/settings/view.jsx index a3997503a..f95061776 100644 --- a/src/renderer/page/settings/view.jsx +++ b/src/renderer/page/settings/view.jsx @@ -31,6 +31,7 @@ type Props = { currentTheme: string, themes: Array, automaticDarkModeEnabled: boolean, + autoplay: boolean }; type State = { @@ -53,6 +54,7 @@ class SettingsPage extends React.PureComponent { (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 { 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 { currentTheme, themes, automaticDarkModeEnabled, + autoplay } = this.props; const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0; @@ -239,6 +246,13 @@ class SettingsPage extends React.PureComponent {
{__('Content Settings')}
+