basic autoplay timer working
This commit is contained in:
parent
28f2a9da0a
commit
d312a8e8b6
7 changed files with 146 additions and 31 deletions
ui
component
scss/component
22
ui/component/autoplayCountdown/index.js
Normal file
22
ui/component/autoplayCountdown/index.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import * as SETTINGS from 'constants/settings';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||||
|
import { makeSelectNextUnplayedRecommended } from 'redux/selectors/content';
|
||||||
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
|
import RecommendedVideos from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => {
|
||||||
|
const nextRecommendedUri = makeSelectNextUnplayedRecommended(props.uri)(state);
|
||||||
|
return {
|
||||||
|
nextRecommendedUri,
|
||||||
|
nextRecommendedClaim: makeSelectClaimForUri(nextRecommendedUri)(state),
|
||||||
|
autoplay: makeSelectClientSetting(SETTINGS.AUTOPLAY)(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = (dispatch, ownProps) => ({});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(RecommendedVideos);
|
66
ui/component/autoplayCountdown/view.jsx
Normal file
66
ui/component/autoplayCountdown/view.jsx
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import UriIndicator from 'component/uriIndicator';
|
||||||
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
history: { push: string => void },
|
||||||
|
nextRecommendedClaim: ?StreamClaim,
|
||||||
|
nextRecommendedUri: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function AutoplayCountdown(props: Props) {
|
||||||
|
const {
|
||||||
|
nextRecommendedUri,
|
||||||
|
nextRecommendedClaim,
|
||||||
|
history: { push },
|
||||||
|
} = props;
|
||||||
|
const nextTitle = nextRecommendedClaim && nextRecommendedClaim.value && nextRecommendedClaim.value.title;
|
||||||
|
const [timer, setTimer] = React.useState(5);
|
||||||
|
const [timerCanceled, setTimerCanceled] = React.useState(false);
|
||||||
|
|
||||||
|
let navigateUrl;
|
||||||
|
if (nextTitle) {
|
||||||
|
navigateUrl = formatLbryUrlForWeb(nextRecommendedUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
let interval;
|
||||||
|
if (!timerCanceled) {
|
||||||
|
interval = setInterval(() => {
|
||||||
|
const newTime = timer - 1;
|
||||||
|
if (newTime === 0) {
|
||||||
|
push(navigateUrl);
|
||||||
|
} else {
|
||||||
|
setTimer(timer - 1);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [timer, navigateUrl, push, timerCanceled]);
|
||||||
|
|
||||||
|
if (timerCanceled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="video-overlay__wrapper">
|
||||||
|
<div className="video-overlay__subtitle">Up Next</div>
|
||||||
|
<div className="video-overlay__title">{nextTitle}</div>
|
||||||
|
<UriIndicator link uri={nextRecommendedUri} />
|
||||||
|
|
||||||
|
<div className="video-overlay__actions">
|
||||||
|
<div className="video-overlay__subtitle">Playing in {timer} seconds</div>
|
||||||
|
<div className="section__actions--centered">
|
||||||
|
<Button label={__('Cancel')} button="secondary" onClick={() => setTimerCanceled(true)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(AutoplayCountdown);
|
|
@ -8,13 +8,13 @@ import {
|
||||||
makeSelectDownloadPathForUri,
|
makeSelectDownloadPathForUri,
|
||||||
makeSelectFileNameForUri,
|
makeSelectFileNameForUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { THEME, AUTOPLAY } from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { makeSelectNextUnplayedRecommended, makeSelectIsText } from 'redux/selectors/content';
|
import { makeSelectIsText } from 'redux/selectors/content';
|
||||||
import FileRender from './view';
|
import FileRender from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
currentTheme: makeSelectClientSetting(THEME)(state),
|
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
mediaType: makeSelectMediaTypeForUri(props.uri)(state),
|
mediaType: makeSelectMediaTypeForUri(props.uri)(state),
|
||||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||||
|
@ -22,8 +22,6 @@ const select = (state, props) => ({
|
||||||
downloadPath: makeSelectDownloadPathForUri(props.uri)(state),
|
downloadPath: makeSelectDownloadPathForUri(props.uri)(state),
|
||||||
fileName: makeSelectFileNameForUri(props.uri)(state),
|
fileName: makeSelectFileNameForUri(props.uri)(state),
|
||||||
streamingUrl: makeSelectStreamingUrlForUri(props.uri)(state),
|
streamingUrl: makeSelectStreamingUrlForUri(props.uri)(state),
|
||||||
autoplay: makeSelectClientSetting(AUTOPLAY)(state),
|
|
||||||
nextUnplayed: makeSelectNextUnplayedRecommended(props.uri)(state),
|
|
||||||
isText: makeSelectIsText(props.uri)(state),
|
isText: makeSelectIsText(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import ImageViewer from 'component/viewers/imageViewer';
|
||||||
import AppViewer from 'component/viewers/appViewer';
|
import AppViewer from 'component/viewers/appViewer';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
import AutoplayCountdown from 'component/autoplayCountdown';
|
||||||
// @if TARGET='web'
|
// @if TARGET='web'
|
||||||
import { generateStreamUrl } from 'util/lbrytv';
|
import { generateStreamUrl } from 'util/lbrytv';
|
||||||
// @endif
|
// @endif
|
||||||
|
@ -36,16 +36,20 @@ type Props = {
|
||||||
currentTheme: string,
|
currentTheme: string,
|
||||||
downloadPath: string,
|
downloadPath: string,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
autoplay: boolean,
|
|
||||||
nextFileToPlay: string,
|
|
||||||
nextUnplayed: string,
|
|
||||||
history: { push: string => void },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileRender extends React.PureComponent<Props> {
|
type State = {
|
||||||
|
showAutoplayCountdown: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileRender extends React.PureComponent<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
showAutoplayCountdown: false,
|
||||||
|
};
|
||||||
|
|
||||||
(this: any).escapeListener = this.escapeListener.bind(this);
|
(this: any).escapeListener = this.escapeListener.bind(this);
|
||||||
(this: any).onEndedCb = this.onEndedCb.bind(this);
|
(this: any).onEndedCb = this.onEndedCb.bind(this);
|
||||||
}
|
}
|
||||||
|
@ -73,10 +77,7 @@ class FileRender extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
onEndedCb() {
|
onEndedCb() {
|
||||||
const { autoplay, nextUnplayed, history } = this.props;
|
this.setState({ showAutoplayCountdown: true });
|
||||||
if (autoplay && nextUnplayed) {
|
|
||||||
history.push(formatLbryUrlForWeb(nextUnplayed));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderViewer() {
|
renderViewer() {
|
||||||
|
@ -188,10 +189,12 @@ class FileRender extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isText } = this.props;
|
const { isText, uri } = this.props;
|
||||||
|
const { showAutoplayCountdown } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames('file-render', { 'file-render--document': isText })}>
|
<div className={classnames('file-render', { 'file-render--document': isText })}>
|
||||||
|
{showAutoplayCountdown && <AutoplayCountdown uri={uri} />}
|
||||||
<Suspense fallback={<div />}>{this.renderViewer()}</Suspense>
|
<Suspense fallback={<div />}>{this.renderViewer()}</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -38,7 +38,6 @@ type Props = {
|
||||||
changeVolume: number => void,
|
changeVolume: number => void,
|
||||||
savePosition: (string, number) => void,
|
savePosition: (string, number) => void,
|
||||||
changeMute: boolean => void,
|
changeMute: boolean => void,
|
||||||
setPlayingUri: (string | null) => void,
|
|
||||||
source: string,
|
source: string,
|
||||||
contentType: string,
|
contentType: string,
|
||||||
thumbnail: string,
|
thumbnail: string,
|
||||||
|
@ -48,18 +47,7 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function VideoViewer(props: Props) {
|
function VideoViewer(props: Props) {
|
||||||
const {
|
const { contentType, source, onEndedCB, changeVolume, changeMute, volume, muted, thumbnail, claim } = props;
|
||||||
contentType,
|
|
||||||
source,
|
|
||||||
setPlayingUri,
|
|
||||||
onEndedCB,
|
|
||||||
changeVolume,
|
|
||||||
changeMute,
|
|
||||||
volume,
|
|
||||||
muted,
|
|
||||||
thumbnail,
|
|
||||||
claim,
|
|
||||||
} = props;
|
|
||||||
const claimId = claim && claim.claim_id;
|
const claimId = claim && claim.claim_id;
|
||||||
const videoRef = useRef();
|
const videoRef = useRef();
|
||||||
const isAudio = contentType.includes('audio');
|
const isAudio = contentType.includes('audio');
|
||||||
|
@ -85,13 +73,13 @@ function VideoViewer(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function doEnded() {
|
function doEnded() {
|
||||||
// clear position
|
|
||||||
setPlayingUri(null);
|
|
||||||
onEndedCB();
|
onEndedCB();
|
||||||
}
|
}
|
||||||
|
|
||||||
function doPause(e: Event) {
|
function doPause(e: Event) {
|
||||||
// store position e.target.currentTime
|
// store position e.target.currentTime
|
||||||
}
|
}
|
||||||
|
|
||||||
function doVolume(e: Event) {
|
function doVolume(e: Event) {
|
||||||
// $FlowFixMe volume is missing in EventTarget
|
// $FlowFixMe volume is missing in EventTarget
|
||||||
changeVolume(e.target.volume);
|
changeVolume(e.target.volume);
|
||||||
|
|
|
@ -153,3 +153,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video-overlay__wrapper {
|
||||||
|
position: absolute;
|
||||||
|
left: auto;
|
||||||
|
right: auto;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.9);
|
||||||
|
z-index: 999;
|
||||||
|
color: var(--color-white);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.button--uri-indicator {
|
||||||
|
color: var(--color-gray-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-overlay__title {
|
||||||
|
@extend .section__title;
|
||||||
|
margin-top: var(--spacing-medium);
|
||||||
|
margin-bottom: var(--spacing-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-overlay__subtitle {
|
||||||
|
color: var(--color-gray-3);
|
||||||
|
margin: var(--spacing-medium) 0;
|
||||||
|
}
|
||||||
|
.video-overlay__actions {
|
||||||
|
margin-top: var(--spacing-large);
|
||||||
|
}
|
||||||
|
|
|
@ -92,6 +92,11 @@
|
||||||
margin-top: var(--spacing-medium);
|
margin-top: var(--spacing-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section__actions--centered {
|
||||||
|
@extend .section__actions;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
.section__actions {
|
.section__actions {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
Loading…
Add table
Reference in a new issue