diff --git a/lbrytv/component/fileViewerEmbeddedEnded/index.js b/lbrytv/component/fileViewerEmbeddedEnded/index.js new file mode 100644 index 000000000..159805381 --- /dev/null +++ b/lbrytv/component/fileViewerEmbeddedEnded/index.js @@ -0,0 +1,7 @@ +import { connect } from 'react-redux'; +import fileViewerEmbeddedEnded from './view'; +import { selectUserVerifiedEmail } from 'lbryinc'; + +export default connect(state => ({ + isAuthenticated: selectUserVerifiedEmail(state), +}))(fileViewerEmbeddedEnded); diff --git a/lbrytv/component/fileViewerEmbeddedEnded/view.jsx b/lbrytv/component/fileViewerEmbeddedEnded/view.jsx new file mode 100644 index 000000000..ec190deff --- /dev/null +++ b/lbrytv/component/fileViewerEmbeddedEnded/view.jsx @@ -0,0 +1,55 @@ +// @flow +import React from 'react'; +import Button from 'component/button'; +import { formatLbryUrlForWeb } from 'util/url'; +import { withRouter } from 'react-router'; +import { URL } from 'config'; +import * as ICONS from 'constants/icons'; + +type Props = { + uri: string, + isAuthenticated: boolean, +}; + +function fileViewerEmbeddedEnded(props: Props) { + const { uri, isAuthenticated } = props; + + const prompts = isAuthenticated + ? { + discuss_auth: 'Continue the discussion on lbry.tv', + tip_auth: 'Always tip your creators', + } + : { + bigtech_unauth: 'Together, we can take back control from big tech', + discuss_unauth: 'Continue the discussion on lbry.tv', + find_unauth: 'Find more great content on lbry.tv', + a_b_unauth: "We test a lot of messages here. Wouldn't it be funny if the one telling you that did the best?", + earn_unauth: 'Join lbry.tv and earn to watch.', + blockchain_unauth: "Now if anyone asks, you can say you've used a blockchain.", + }; + + const promptKeys = Object.keys(prompts); + const promptKey = promptKeys[Math.floor(Math.random() * promptKeys.length)]; + const prompt = prompts[promptKey]; + const lbrytvLink = `${URL}${formatLbryUrlForWeb(uri)}?src=${promptKey}`; + + return ( +
+
+
+
{prompt}
+
+
+
+ ); +} + +export default withRouter(fileViewerEmbeddedEnded); diff --git a/lbrytv/component/fileViewerEmbeddedTitle/index.js b/lbrytv/component/fileViewerEmbeddedTitle/index.js new file mode 100644 index 000000000..513d9dd95 --- /dev/null +++ b/lbrytv/component/fileViewerEmbeddedTitle/index.js @@ -0,0 +1,9 @@ +import { connect } from 'react-redux'; +import fileViewerEmbeddedTitle from './view'; +import { makeSelectTitleForUri } from 'lbry-redux'; + +export default connect((state, props) => { + return { + title: makeSelectTitleForUri(props.uri)(state), + }; +})(fileViewerEmbeddedTitle); diff --git a/lbrytv/component/fileViewerEmbeddedTitle/view.jsx b/lbrytv/component/fileViewerEmbeddedTitle/view.jsx new file mode 100644 index 000000000..db65ba6f5 --- /dev/null +++ b/lbrytv/component/fileViewerEmbeddedTitle/view.jsx @@ -0,0 +1,27 @@ +// @flow +import React from 'react'; +import Button from 'component/button'; +import { formatLbryUrlForWeb } from 'util/url'; +import { withRouter } from 'react-router'; +import { URL } from 'config'; +import * as ICONS from 'constants/icons'; + +type Props = { + uri: string, + title: ?string, +}; + +function fileViewerEmbeddedTitle(props: Props) { + const { uri, title } = props; + + const lbrytvLink = `${URL}${formatLbryUrlForWeb(uri)}?src=embed`; + + return ( +
+
+ ); +} + +export default withRouter(fileViewerEmbeddedTitle); diff --git a/package.json b/package.json index 3e7978aeb..880eb4a55 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,8 @@ "electron-notarize": "^0.1.1", "electron-updater": "^4.2.4", "express": "^4.17.1", - "if-env": "^1.0.4" + "if-env": "^1.0.4", + "videojs-logo": "^2.0.0" }, "devDependencies": { "@babel/core": "^7.0.0", diff --git a/ui/component/app/view.jsx b/ui/component/app/view.jsx index 10b5e17b7..a1d5fef0c 100644 --- a/ui/component/app/view.jsx +++ b/ui/component/app/view.jsx @@ -284,7 +284,7 @@ function App(props: Props) { - + {isEnhancedLayout && } {/* @if TARGET='app' */} diff --git a/ui/component/autoplayCountdown/index.js b/ui/component/autoplayCountdown/index.js index e965abcc3..8683ad57e 100644 --- a/ui/component/autoplayCountdown/index.js +++ b/ui/component/autoplayCountdown/index.js @@ -1,16 +1,17 @@ import * as SETTINGS from 'constants/settings'; import { connect } from 'react-redux'; import { makeSelectClaimForUri } from 'lbry-redux'; -import { makeSelectNextUnplayedRecommended } from 'redux/selectors/content'; +import { makeSelectIsPlayerFloating, makeSelectNextUnplayedRecommended } from 'redux/selectors/content'; import { makeSelectClientSetting } from 'redux/selectors/settings'; import { doSetPlayingUri } from 'redux/actions/content'; -import RecommendedVideos from './view'; +import AutoplayCountdown from './view'; const select = (state, props) => { const nextRecommendedUri = makeSelectNextUnplayedRecommended(props.uri)(state); return { nextRecommendedUri, nextRecommendedClaim: makeSelectClaimForUri(nextRecommendedUri)(state), + isFloating: makeSelectIsPlayerFloating(props.location)(state), autoplay: makeSelectClientSetting(SETTINGS.AUTOPLAY)(state), }; }; @@ -19,7 +20,4 @@ const perform = dispatch => ({ setPlayingUri: uri => dispatch(doSetPlayingUri(uri)), }); -export default connect( - select, - perform -)(RecommendedVideos); +export default connect(select, perform)(AutoplayCountdown); diff --git a/ui/component/autoplayCountdown/view.jsx b/ui/component/autoplayCountdown/view.jsx index 83121d136..2813807cd 100644 --- a/ui/component/autoplayCountdown/view.jsx +++ b/ui/component/autoplayCountdown/view.jsx @@ -10,6 +10,7 @@ type Props = { history: { push: string => void }, nextRecommendedClaim: ?StreamClaim, nextRecommendedUri: string, + isFloating: boolean, setPlayingUri: (string | null) => void, }; @@ -18,10 +19,15 @@ function AutoplayCountdown(props: Props) { nextRecommendedUri, nextRecommendedClaim, setPlayingUri, + isFloating, history: { push }, } = props; const nextTitle = nextRecommendedClaim && nextRecommendedClaim.value && nextRecommendedClaim.value.title; - const [timer, setTimer] = React.useState(5); + + /* this value is coupled with CSS timing variables on .autoplay-countdown__timer */ + const countdownTime = 1000; + + const [timer, setTimer] = React.useState(countdownTime); const [timerCanceled, setTimerCanceled] = React.useState(false); let navigateUrl; @@ -29,15 +35,24 @@ function AutoplayCountdown(props: Props) { navigateUrl = formatLbryUrlForWeb(nextRecommendedUri); } + function doNavigate() { + // FIXME: make autoplay continue in floating player + if (!isFloating) { + // if not floating + setPlayingUri(null); + push(navigateUrl); + } else { + setPlayingUri(nextRecommendedUri); + } + } + React.useEffect(() => { let interval; if (!timerCanceled) { interval = setInterval(() => { const newTime = timer - 1; if (newTime === 0) { - // Set the playingUri to null so the app doesn't try to make a floating window with the video that just finished - setPlayingUri(null); - push(navigateUrl); + doNavigate(); } else { setTimer(timer - 1); } @@ -46,28 +61,31 @@ function AutoplayCountdown(props: Props) { return () => { clearInterval(interval); }; - }, [timer, navigateUrl, push, timerCanceled, setPlayingUri, nextRecommendedUri]); + }, [timer, doNavigate, navigateUrl, push, timerCanceled, setPlayingUri, nextRecommendedUri]); - if (timerCanceled) { + if (timerCanceled || !nextRecommendedUri) { return null; } return ( -
-
- }}> - Up Next by %channel% - -
-
{nextTitle}
+
+
+
+ }}> + Up Next by %channel% + +
-
-
- {__('Playing in %seconds_left% seconds', { seconds_left: timer })} -
-
-
+
+ {__('Playing in %seconds_left% seconds', { seconds_left: timer })} +
+
); diff --git a/ui/component/fileRender/index.js b/ui/component/fileRender/index.js index fd3cae8dd..48c6fead0 100644 --- a/ui/component/fileRender/index.js +++ b/ui/component/fileRender/index.js @@ -12,7 +12,6 @@ import { makeSelectFileExtensionForUri, makeSelectStreamingUrlForUriWebProxy, } from 'redux/selectors/content'; -import { doSetPlayingUri } from 'redux/actions/content'; import FileRender from './view'; const select = (state, props) => { @@ -30,8 +29,4 @@ const select = (state, props) => { }; }; -const perform = dispatch => ({ - setPlayingUri: uri => dispatch(doSetPlayingUri(uri)), -}); - -export default connect(select, perform)(FileRender); +export default connect(select)(FileRender); diff --git a/ui/component/fileRender/view.jsx b/ui/component/fileRender/view.jsx index fcd1a0eda..8f047a0b6 100644 --- a/ui/component/fileRender/view.jsx +++ b/ui/component/fileRender/view.jsx @@ -1,5 +1,4 @@ // @flow -import { URL } from 'config'; import { remote } from 'electron'; import React, { Suspense } from 'react'; import classnames from 'classnames'; @@ -7,10 +6,7 @@ import * as RENDER_MODES from 'constants/file_render_modes'; import VideoViewer from 'component/viewers/videoViewer'; import ImageViewer from 'component/viewers/imageViewer'; import AppViewer from 'component/viewers/appViewer'; -import Button from 'component/button'; import { withRouter } from 'react-router-dom'; -import AutoplayCountdown from 'component/autoplayCountdown'; -import { formatLbryUrlForWeb } from 'util/url'; import fs from 'fs'; import DocumentViewer from 'component/viewers/documentViewer'; @@ -34,32 +30,15 @@ type Props = { downloadPath: string, fileExtension: string, autoplay: boolean, - setPlayingUri: (string | null) => void, - currentlyFloating: boolean, renderMode: string, thumbnail: string, - onStartedCallback: () => void, -}; - -type State = { - showAutoplayCountdown: boolean, - showEmbededMessage: boolean, }; class FileRender extends React.PureComponent { constructor(props: Props) { super(props); - this.state = { - showAutoplayCountdown: false, - showEmbededMessage: false, - }; - (this: any).escapeListener = this.escapeListener.bind(this); - (this: any).onEndedAutoplay = this.onEndedAutoplay.bind(this); - (this: any).onEndedEmbedded = this.onEndedEmbedded.bind(this); - (this: any).getOnEndedCb = this.getOnEndedCb.bind(this); - (this: any).onStartedCb = this.onStartedCb.bind(this); } componentDidMount() { @@ -84,39 +63,6 @@ class FileRender extends React.PureComponent { remote.getCurrentWindow().setFullScreen(false); } - onStartedCb() { - const { onStartedCallback } = this.props; - - if (onStartedCallback) { - onStartedCallback(); - } - } - - getOnEndedCb() { - const { setPlayingUri, currentlyFloating, embedded } = this.props; - - if (embedded) { - return this.onEndedEmbedded; - } - - if (!currentlyFloating) { - return this.onEndedAutoplay; - } - - return () => setPlayingUri(null); - } - - onEndedAutoplay() { - const { autoplay } = this.props; - if (autoplay) { - this.setState({ showAutoplayCountdown: true }); - } - } - - onEndedEmbedded() { - this.setState({ showEmbededMessage: true }); - } - renderViewer() { const { currentTheme, contentType, downloadPath, fileExtension, streamingUrl, uri, renderMode } = this.props; const source = streamingUrl; @@ -124,15 +70,7 @@ class FileRender extends React.PureComponent { switch (renderMode) { case RENDER_MODES.AUDIO: case RENDER_MODES.VIDEO: - return ( - - ); + return ; case RENDER_MODES.IMAGE: return ; case RENDER_MODES.HTML: @@ -177,30 +115,15 @@ class FileRender extends React.PureComponent { } render() { - const { uri, currentlyFloating, embedded, renderMode } = this.props; - const { showAutoplayCountdown, showEmbededMessage } = this.state; - const lbrytvLink = `${URL}${formatLbryUrlForWeb(uri)}?src=embed`; + const { embedded, renderMode } = this.props; return (
- {embedded && showEmbededMessage && ( -
-
{__('See more on lbry.tv')}
- -
-
-
-
-
- )} - {!currentlyFloating && showAutoplayCountdown && } }>{this.renderViewer()}
); diff --git a/ui/component/fileRenderFloating/index.js b/ui/component/fileRenderFloating/index.js index 748957fad..1580d8373 100644 --- a/ui/component/fileRenderFloating/index.js +++ b/ui/component/fileRenderFloating/index.js @@ -1,9 +1,9 @@ import * as SETTINGS from 'constants/settings'; import { connect } from 'react-redux'; import { makeSelectFileInfoForUri, makeSelectTitleForUri } from 'lbry-redux'; -import { doClaimEligiblePurchaseRewards } from 'lbryinc'; import { makeSelectIsPlaying, + makeSelectIsPlayerFloating, selectPlayingUri, makeSelectFileRenderModeForUri, makeSelectStreamingUrlForUriWebProxy, @@ -11,7 +11,6 @@ import { import { makeSelectClientSetting } from 'redux/selectors/settings'; import { doSetPlayingUri } from 'redux/actions/content'; import { withRouter } from 'react-router'; -import { doAnalyticsView } from 'redux/actions/app'; import FileRenderFloating from './view'; const select = (state, props) => { @@ -21,6 +20,7 @@ const select = (state, props) => { title: makeSelectTitleForUri(uri)(state), fileInfo: makeSelectFileInfoForUri(uri)(state), isPlaying: makeSelectIsPlaying(uri)(state), + isFloating: makeSelectIsPlayerFloating(props.location)(state), streamingUrl: makeSelectStreamingUrlForUriWebProxy(uri)(state), floatingPlayerEnabled: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state), renderMode: makeSelectFileRenderModeForUri(uri)(state), @@ -29,8 +29,6 @@ const select = (state, props) => { const perform = dispatch => ({ clearPlayingUri: () => dispatch(doSetPlayingUri(null)), - triggerAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)), - claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()), }); export default withRouter(connect(select, perform)(FileRenderFloating)); diff --git a/ui/component/fileRenderFloating/view.jsx b/ui/component/fileRenderFloating/view.jsx index 261ecd9ca..2591581e5 100644 --- a/ui/component/fileRenderFloating/view.jsx +++ b/ui/component/fileRenderFloating/view.jsx @@ -1,14 +1,13 @@ // @flow import * as ICONS from 'constants/icons'; import * as RENDER_MODES from 'constants/file_render_modes'; -import React, { useState, useEffect } from 'react'; +import React, { useEffect } from 'react'; import Button from 'component/button'; import classnames from 'classnames'; import LoadingScreen from 'component/common/loading-screen'; import FileRender from 'component/fileRender'; import UriIndicator from 'component/uriIndicator'; import usePersistedState from 'effects/use-persisted-state'; -import usePrevious from 'effects/use-previous'; import { FILE_WRAPPER_CLASS } from 'page/file/view'; import Draggable from 'react-draggable'; import Tooltip from 'component/common/tooltip'; @@ -18,50 +17,42 @@ import useIsMobile from 'effects/use-is-mobile'; type Props = { isLoading: boolean, isPlaying: boolean, + isFloating: boolean, fileInfo: FileListItem, uri: string, streamingUrl?: string, - floatingPlayer: boolean, - pageUri: ?string, title: ?string, floatingPlayerEnabled: boolean, clearPlayingUri: () => void, - triggerAnalyticsView: (string, number) => Promise, renderMode: string, - claimRewards: () => void, }; -export default function FloatingViewer(props: Props) { +export default function FileRenderFloating(props: Props) { const { isPlaying, fileInfo, uri, streamingUrl, - pageUri, title, + isFloating, clearPlayingUri, floatingPlayerEnabled, - triggerAnalyticsView, - claimRewards, renderMode, } = props; + const isMobile = useIsMobile(); - const [playTime, setPlayTime] = useState(); const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect'); const [position, setPosition] = usePersistedState('floating-file-viewer:position', { x: -25, y: window.innerHeight - 400, }); - const inline = pageUri === uri; + const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode); const isReadyToPlay = isPlayable && (streamingUrl || (fileInfo && fileInfo.completed)); const loadingMessage = fileInfo && fileInfo.blobs_completed >= 1 && (!fileInfo.download_path || !fileInfo.written_bytes) ? __("It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.") : __('Loading'); - const previousUri = usePrevious(uri); - const isNewView = uri && previousUri !== uri && isPlaying; - const [hasRecordedView, setHasRecordedView] = useState(false); useEffect(() => { function handleResize() { @@ -82,26 +73,9 @@ export default function FloatingViewer(props: Props) { window.removeEventListener('resize', handleResize); onFullscreenChange(window, 'remove', handleResize); }; - }, [setFileViewerRect, inline]); + }, [setFileViewerRect, isFloating]); - useEffect(() => { - if (isNewView) { - setPlayTime(Date.now()); - } - }, [isNewView, uri]); - - useEffect(() => { - if (playTime && isReadyToPlay && !hasRecordedView) { - const timeToStart = Date.now() - playTime; - triggerAnalyticsView(uri, timeToStart).then(() => { - claimRewards(); - setHasRecordedView(false); // This is a terrible variable name, rename this - setPlayTime(null); - }); - } - }, [setPlayTime, triggerAnalyticsView, isReadyToPlay, hasRecordedView, playTime, uri, claimRewards]); - - if (!isPlayable || !isPlaying || !uri || (!inline && (isMobile || !floatingPlayerEnabled))) { + if (!isPlayable || !isPlaying || !uri || (isFloating && (isMobile || !floatingPlayerEnabled))) { return null; } @@ -119,29 +93,29 @@ export default function FloatingViewer(props: Props) {
- {!inline && ( + {isFloating && (
{uri}
@@ -155,12 +129,8 @@ export default function FloatingViewer(props: Props) {
)} - {isReadyToPlay ? ( - - ) : ( - - )} - {!inline && ( + {isReadyToPlay ? : } + {isFloating && (
{title || uri} diff --git a/ui/component/fileRenderInitiator/view.jsx b/ui/component/fileRenderInitiator/view.jsx index 2d5ef9ae6..88d1c71d1 100644 --- a/ui/component/fileRenderInitiator/view.jsx +++ b/ui/component/fileRenderInitiator/view.jsx @@ -1,6 +1,6 @@ // @flow // This component is entirely for triggering the start of a file view -// The actual viewer for a file exists in TextViewer and FloatingViewer +// The actual viewer for a file exists in TextViewer and FileRenderFloating // They can't exist in one component because we need to handle/listen for the start of a new file view // while a file is currently being viewed import React, { useEffect, useCallback } from 'react'; @@ -89,7 +89,7 @@ export default function FileRenderInitiator(props: Props) { if (isFree && ((autoplay && !videoOnPage && isPlayable) || RENDER_MODES.AUTO_RENDER_MODES.includes(renderMode))) { viewFile(); } - }, [autoplay, viewFile, isFree, renderMode]); + }, [autoplay, viewFile, isFree, renderMode, isPlayable]); /* once content is playing, let the appropriate take care of it... diff --git a/ui/component/fileRenderInline/view.jsx b/ui/component/fileRenderInline/view.jsx index ca11075a1..417a704b0 100644 --- a/ui/component/fileRenderInline/view.jsx +++ b/ui/component/fileRenderInline/view.jsx @@ -35,6 +35,9 @@ export default function FileRenderInline(props: Props) { }, [isPlaying, setPlayTime, uri]); useEffect(() => { + /* + note: code can currently double fire with videoViewer logic if a video is rendered by FileRenderInline (currently this never happens) + */ if (playTime && isReadyToPlay) { const timeToStart = Date.now() - playTime; diff --git a/ui/component/viewers/appViewer/view.jsx b/ui/component/viewers/appViewer/view.jsx index ceebbd253..f4358d26e 100644 --- a/ui/component/viewers/appViewer/view.jsx +++ b/ui/component/viewers/appViewer/view.jsx @@ -48,7 +48,7 @@ function AppViewer(props: Props) { ); // return ( - //
+ //
// {!appUrl && ( // { const errorMessage = __("Sorry, looks like we can't load the document."); return ( -
+
{loading && !error &&
} {error && } {isReady && this.renderDocument()} diff --git a/ui/component/viewers/docxViewer.jsx b/ui/component/viewers/docxViewer.jsx index c8a69590b..b3e9fba16 100644 --- a/ui/component/viewers/docxViewer.jsx +++ b/ui/component/viewers/docxViewer.jsx @@ -58,7 +58,7 @@ class DocxViewer extends React.PureComponent { const errorMessage = __("Sorry, looks like we can't load the document."); return ( -
+
{loading && } {error && } {content &&
} diff --git a/ui/component/viewers/htmlViewer.jsx b/ui/component/viewers/htmlViewer.jsx index d5e11e165..77f93a355 100644 --- a/ui/component/viewers/htmlViewer.jsx +++ b/ui/component/viewers/htmlViewer.jsx @@ -35,10 +35,7 @@ class HtmlViewer extends React.PureComponent { const { source } = this.props; const { loading } = this.state; return ( -
+
{loading &&
} {/* @if TARGET='app' */}