diff --git a/ui/component/viewers/videoViewer/index.js b/ui/component/viewers/videoViewer/index.js index df6e352c7..0af23d565 100644 --- a/ui/component/viewers/videoViewer/index.js +++ b/ui/component/viewers/videoViewer/index.js @@ -6,6 +6,7 @@ import { SETTINGS, COLLECTIONS_CONSTS, makeSelectNextUrlForCollectionAndUrl, + makeSelectPreviousUrlForCollectionAndUrl, } from 'lbry-redux'; import { doChangeVolume, doChangeMute, doAnalyticsView, doAnalyticsBuffer } from 'redux/actions/app'; import { selectVolume, selectMute } from 'redux/selectors/app'; @@ -33,8 +34,10 @@ const select = (state, props) => { const collectionId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID) || (playingUri && playingUri.collectionId); let playNextUri; + let playPreviousUri; if (collectionId) { playNextUri = makeSelectNextUrlForCollectionAndUrl(collectionId, props.uri)(state); + playPreviousUri = makeSelectPreviousUrlForCollectionAndUrl(collectionId, props.uri)(state); } return { @@ -55,6 +58,7 @@ const select = (state, props) => { isFloating: makeSelectIsPlayerFloating(props.location)(state), collectionId, playNextUri, + playPreviousUri, }; }; diff --git a/ui/component/viewers/videoViewer/internal/videojs.jsx b/ui/component/viewers/videoViewer/internal/videojs.jsx index d1fff243e..fcb361a81 100644 --- a/ui/component/viewers/videoViewer/internal/videojs.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs.jsx @@ -49,7 +49,7 @@ type Props = { source: string, sourceType: string, poster: ?string, - onPlayerReady: (Player) => void, + onPlayerReady: (Player, any) => void, isAudio: boolean, startMuted: boolean, autoplay: boolean, @@ -61,6 +61,7 @@ type Props = { shareTelemetry: boolean, replay: boolean, videoTheaterMode: boolean, + setStartPlayPrevious: (boolean) => void, setStartPlayNext: (boolean) => void, }; @@ -106,6 +107,7 @@ const SMALL_J_KEYCODE = 74; const SMALL_K_KEYCODE = 75; const SMALL_L_KEYCODE = 76; +const P_KEYCODE = 80; const N_KEYCODE = 78; const ZERO_KEYCODE = 48; @@ -215,6 +217,7 @@ export default React.memo(function VideoJs(props: Props) { shareTelemetry, replay, videoTheaterMode, + setStartPlayPrevious, setStartPlayNext, } = props; @@ -404,6 +407,7 @@ export default React.memo(function VideoJs(props: Props) { if (e.altKey || e.ctrlKey || e.metaKey || !e.shiftKey) return; if (e.keyCode === PERIOD_KEYCODE) changePlaybackSpeed(true); if (e.keyCode === COMMA_KEYCODE) changePlaybackSpeed(false); + if (e.keyCode === P_KEYCODE) setStartPlayPrevious(true); if (e.keyCode === N_KEYCODE) setStartPlayNext(true); } @@ -619,7 +623,8 @@ export default React.memo(function VideoJs(props: Props) { player.children_[0].setAttribute('playsinline', ''); // I think this is a callback function - onPlayerReady(player); + const videoNode = containerRef.current && containerRef.current.querySelector('video, audio'); + onPlayerReady(player, videoNode); }); // pre-roll ads diff --git a/ui/component/viewers/videoViewer/view.jsx b/ui/component/viewers/videoViewer/view.jsx index 711874f04..d34b93746 100644 --- a/ui/component/viewers/videoViewer/view.jsx +++ b/ui/component/viewers/videoViewer/view.jsx @@ -17,6 +17,7 @@ import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle'; import LoadingScreen from 'component/common/loading-screen'; import { addTheaterModeButton } from './internal/theater-mode'; import { addPlayNextButton } from './internal/play-next'; +import { addPlayPreviousButton } from './internal/play-previous'; import { useGetAds } from 'effects/use-get-ads'; import Button from 'component/button'; import I18nMessage from 'component/i18nMessage'; @@ -54,6 +55,7 @@ type Props = { doSetPlayingUri: (string, string) => void, doPlayUri: (string) => void, playNextUri: string, + playPreviousUri: string, authenticated: boolean, userId: number, homepageData?: { [string]: HomepageCat }, @@ -94,6 +96,7 @@ function VideoViewer(props: Props) { doSetPlayingUri, doPlayUri, playNextUri, + playPreviousUri, homepageData, authenticated, userId, @@ -126,6 +129,21 @@ function VideoViewer(props: Props) { const [isLoading, setIsLoading] = useState(false); const [replay, setReplay] = useState(false); const [startPlayNext, setStartPlayNext] = useState(false); + const [startPlayPrevious, setStartPlayPrevious] = useState(false); + const [videoNode, setVideoNode] = useState(false); + + const getNavigateUrl = React.useCallback((playUri: string) => { + let navigateUrl; + if (playUri) { + navigateUrl = formatLbryUrlForWeb(playUri); + if (collectionId) { + const collectionParams = new URLSearchParams(); + collectionParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, collectionId); + navigateUrl = navigateUrl + `?` + collectionParams.toString(); + } + } + return navigateUrl; + }, [collectionId]); // force everything to recent when URI changes, can cause weird corner cases otherwise (e.g. navigate while autoplay is true) useEffect(() => { @@ -144,18 +162,9 @@ function VideoViewer(props: Props) { }; }, [embedded, videoPlaybackRate]); - let navigateUrl; - if (playNextUri) { - navigateUrl = formatLbryUrlForWeb(playNextUri); - if (collectionId) { - const collectionParams = new URLSearchParams(); - collectionParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, collectionId); - navigateUrl = navigateUrl + `?` + collectionParams.toString(); - } - } - useEffect(() => { if (startPlayNext) { + const navigateUrl = getNavigateUrl(playNextUri); if (!isFloating && navigateUrl) { push(navigateUrl); } @@ -165,7 +174,26 @@ function VideoViewer(props: Props) { } setStartPlayNext(false); } - }, [isFloating, navigateUrl, push, doSetPlayingUri, playNextUri, doPlayUri, startPlayNext, collectionId]); + if (videoNode) { + const currentTime = videoNode.currentTime; + + if (startPlayPrevious) { + if (currentTime > 5) { + videoNode.currentTime = 0; + } else { + const navigateUrl = getNavigateUrl(playPreviousUri); + if (!isFloating && navigateUrl) { + push(navigateUrl); + } + if (playPreviousUri) { + doSetPlayingUri(playPreviousUri, collectionId); + doPlayUri(playPreviousUri); + } + } + setStartPlayPrevious(false); + } + } + }, [isFloating, push, doSetPlayingUri, playNextUri, doPlayUri, startPlayNext, collectionId, getNavigateUrl, videoNode, startPlayPrevious, playPreviousUri]); function doTrackingBuffered(e: Event, data: any) { fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => { @@ -245,13 +273,17 @@ function VideoViewer(props: Props) { playerReadyDependencyList.push(desktopPlayStartTime); } - const onPlayerReady = useCallback((player: Player) => { + const onPlayerReady = useCallback((player: Player, videoNode: any) => { if (!embedded) { + setVideoNode(videoNode); player.muted(muted); player.volume(volume); player.playbackRate(videoPlaybackRate); addTheaterModeButton(player, toggleVideoTheaterMode); - if (collectionId) addPlayNextButton(player, () => setStartPlayNext(true)); + if (collectionId) { + addPlayNextButton(player, () => setStartPlayNext(true)); + addPlayPreviousButton(player, () => setStartPlayPrevious(true)); + } } const shouldPlay = !embedded || autoplayIfEmbedded; @@ -382,6 +414,7 @@ function VideoViewer(props: Props) { replay={replay} videoTheaterMode={videoTheaterMode} setStartPlayNext={setStartPlayNext} + setStartPlayPrevious={setStartPlayPrevious} /> )}