Add Play Next Button

This commit is contained in:
saltrafael 2021-08-25 09:08:30 -03:00 committed by zeppi
parent ec6f9c8a7f
commit b26255bf53
3 changed files with 71 additions and 3 deletions

View file

@ -1,9 +1,20 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeSelectClaimForUri, makeSelectFileInfoForUri, makeSelectThumbnailForUri, SETTINGS } from 'lbry-redux'; import {
makeSelectClaimForUri,
makeSelectFileInfoForUri,
makeSelectThumbnailForUri,
SETTINGS,
COLLECTIONS_CONSTS,
makeSelectNextUrlForCollectionAndUrl,
} from 'lbry-redux';
import { doChangeVolume, doChangeMute, doAnalyticsView, doAnalyticsBuffer } from 'redux/actions/app'; import { doChangeVolume, doChangeMute, doAnalyticsView, doAnalyticsBuffer } from 'redux/actions/app';
import { selectVolume, selectMute } from 'redux/selectors/app'; import { selectVolume, selectMute } from 'redux/selectors/app';
import { savePosition, clearPosition } from 'redux/actions/content'; import { savePosition, clearPosition, doSetPlayingUri, doPlayUri } from 'redux/actions/content';
import { makeSelectContentPositionForUri } from 'redux/selectors/content'; import {
makeSelectContentPositionForUri,
selectPlayingUri,
makeSelectIsPlayerFloating,
} from 'redux/selectors/content';
import VideoViewer from './view'; import VideoViewer from './view';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards'; import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
@ -18,6 +29,13 @@ const select = (state, props) => {
// TODO: eventually this should be received from DB and not local state (https://github.com/lbryio/lbry-desktop/issues/6796) // TODO: eventually this should be received from DB and not local state (https://github.com/lbryio/lbry-desktop/issues/6796)
const position = urlParams.get('t') !== null ? urlParams.get('t') : makeSelectContentPositionForUri(props.uri)(state); const position = urlParams.get('t') !== null ? urlParams.get('t') : makeSelectContentPositionForUri(props.uri)(state);
const userId = selectUser(state) && selectUser(state).id; const userId = selectUser(state) && selectUser(state).id;
const playingUri = selectPlayingUri(state);
const collectionId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID) || (playingUri && playingUri.collectionId);
let playNextUri;
if (collectionId) {
playNextUri = makeSelectNextUrlForCollectionAndUrl(collectionId, props.uri)(state);
}
return { return {
autoplayIfEmbedded: Boolean(autoplay), autoplayIfEmbedded: Boolean(autoplay),
@ -34,6 +52,9 @@ const select = (state, props) => {
userId: userId, userId: userId,
shareTelemetry: IS_WEB || selectDaemonSettings(state).share_usage_data, shareTelemetry: IS_WEB || selectDaemonSettings(state).share_usage_data,
videoTheaterMode: makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state), videoTheaterMode: makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state),
isFloating: makeSelectIsPlayerFloating(props.location)(state),
collectionId,
playNextUri,
}; };
}; };
@ -47,6 +68,8 @@ const perform = (dispatch) => ({
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()), claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
toggleVideoTheaterMode: () => dispatch(toggleVideoTheaterMode()), toggleVideoTheaterMode: () => dispatch(toggleVideoTheaterMode()),
setVideoPlaybackRate: (rate) => dispatch(doSetClientSetting(SETTINGS.VIDEO_PLAYBACK_RATE, rate)), setVideoPlaybackRate: (rate) => dispatch(doSetClientSetting(SETTINGS.VIDEO_PLAYBACK_RATE, rate)),
doSetPlayingUri: (uri, collectionId) => dispatch(doSetPlayingUri({ uri, collectionId })),
doPlayUri: (uri) => dispatch(doPlayUri(uri)),
}); });
export default withRouter(connect(select, perform)(VideoViewer)); export default withRouter(connect(select, perform)(VideoViewer));

View file

@ -61,6 +61,7 @@ type Props = {
shareTelemetry: boolean, shareTelemetry: boolean,
replay: boolean, replay: boolean,
videoTheaterMode: boolean, videoTheaterMode: boolean,
setStartPlayNext: (boolean) => void,
}; };
// type VideoJSOptions = { // type VideoJSOptions = {
@ -105,6 +106,8 @@ const SMALL_J_KEYCODE = 74;
const SMALL_K_KEYCODE = 75; const SMALL_K_KEYCODE = 75;
const SMALL_L_KEYCODE = 76; const SMALL_L_KEYCODE = 76;
const N_KEYCODE = 78;
const ZERO_KEYCODE = 48; const ZERO_KEYCODE = 48;
const ONE_KEYCODE = 49; const ONE_KEYCODE = 49;
const TWO_KEYCODE = 50; const TWO_KEYCODE = 50;
@ -212,6 +215,7 @@ export default React.memo<Props>(function VideoJs(props: Props) {
shareTelemetry, shareTelemetry,
replay, replay,
videoTheaterMode, videoTheaterMode,
setStartPlayNext,
} = props; } = props;
const [reload, setReload] = useState('initial'); const [reload, setReload] = useState('initial');
@ -399,6 +403,7 @@ export default React.memo<Props>(function VideoJs(props: Props) {
if (e.altKey || e.ctrlKey || e.metaKey || !e.shiftKey) return; if (e.altKey || e.ctrlKey || e.metaKey || !e.shiftKey) return;
if (e.keyCode === PERIOD_KEYCODE) changePlaybackSpeed(true); if (e.keyCode === PERIOD_KEYCODE) changePlaybackSpeed(true);
if (e.keyCode === COMMA_KEYCODE) changePlaybackSpeed(false); if (e.keyCode === COMMA_KEYCODE) changePlaybackSpeed(false);
if (e.keyCode === N_KEYCODE) setStartPlayNext(true);
} }
function handleSingleKeyActions(e: KeyboardEvent) { function handleSingleKeyActions(e: KeyboardEvent) {

View file

@ -16,12 +16,15 @@ import FileViewerEmbeddedEnded from 'web/component/fileViewerEmbeddedEnded';
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle'; import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
import LoadingScreen from 'component/common/loading-screen'; import LoadingScreen from 'component/common/loading-screen';
import { addTheaterModeButton } from './internal/theater-mode'; import { addTheaterModeButton } from './internal/theater-mode';
import { addPlayNextButton } from './internal/play-next';
import { useGetAds } from 'effects/use-get-ads'; import { useGetAds } from 'effects/use-get-ads';
import Button from 'component/button'; import Button from 'component/button';
import I18nMessage from 'component/i18nMessage'; import I18nMessage from 'component/i18nMessage';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { getAllIds } from 'util/buildHomepage'; import { getAllIds } from 'util/buildHomepage';
import type { HomepageCat } from 'util/buildHomepage'; import type { HomepageCat } from 'util/buildHomepage';
import { formatLbryUrlForWeb } from 'util/url';
import { COLLECTIONS_CONSTS } from 'lbry-redux';
const PLAY_TIMEOUT_ERROR = 'play_timeout_error'; const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
const PLAY_TIMEOUT_LIMIT = 2000; const PLAY_TIMEOUT_LIMIT = 2000;
@ -48,11 +51,16 @@ type Props = {
clearPosition: (string) => void, clearPosition: (string) => void,
toggleVideoTheaterMode: () => void, toggleVideoTheaterMode: () => void,
setVideoPlaybackRate: (number) => void, setVideoPlaybackRate: (number) => void,
doSetPlayingUri: (string, string) => void,
doPlayUri: (string) => void,
playNextUri: string,
authenticated: boolean, authenticated: boolean,
userId: number, userId: number,
homepageData?: { [string]: HomepageCat }, homepageData?: { [string]: HomepageCat },
shareTelemetry: boolean, shareTelemetry: boolean,
videoTheaterMode: boolean, videoTheaterMode: boolean,
collectionId: string,
isFloating: boolean,
}; };
/* /*
@ -83,11 +91,16 @@ function VideoViewer(props: Props) {
desktopPlayStartTime, desktopPlayStartTime,
toggleVideoTheaterMode, toggleVideoTheaterMode,
setVideoPlaybackRate, setVideoPlaybackRate,
doSetPlayingUri,
doPlayUri,
playNextUri,
homepageData, homepageData,
authenticated, authenticated,
userId, userId,
shareTelemetry, shareTelemetry,
videoTheaterMode, videoTheaterMode,
collectionId,
isFloating,
} = props; } = props;
const adApprovedChannelIds = homepageData ? getAllIds(homepageData) : []; const adApprovedChannelIds = homepageData ? getAllIds(homepageData) : [];
@ -97,6 +110,7 @@ function VideoViewer(props: Props) {
const forcePlayer = FORCE_CONTENT_TYPE_PLAYER.includes(contentType); const forcePlayer = FORCE_CONTENT_TYPE_PLAYER.includes(contentType);
const { const {
location: { pathname }, location: { pathname },
push,
} = useHistory(); } = useHistory();
const [isPlaying, setIsPlaying] = useState(false); const [isPlaying, setIsPlaying] = useState(false);
const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false); const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false);
@ -111,6 +125,7 @@ function VideoViewer(props: Props) {
breaks because some browsers (e.g. Firefox) block autoplay but leave the player.play Promise pending */ breaks because some browsers (e.g. Firefox) block autoplay but leave the player.play Promise pending */
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [replay, setReplay] = useState(false); const [replay, setReplay] = useState(false);
const [startPlayNext, setStartPlayNext] = useState(false);
// force everything to recent when URI changes, can cause weird corner cases otherwise (e.g. navigate while autoplay is true) // force everything to recent when URI changes, can cause weird corner cases otherwise (e.g. navigate while autoplay is true)
useEffect(() => { useEffect(() => {
@ -129,6 +144,29 @@ function VideoViewer(props: Props) {
}; };
}, [embedded, videoPlaybackRate]); }, [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) {
if (!isFloating && navigateUrl) {
push(navigateUrl);
}
if (playNextUri) {
doSetPlayingUri(playNextUri, collectionId);
doPlayUri(playNextUri);
}
setStartPlayNext(false);
}
}, [isFloating, navigateUrl, push, doSetPlayingUri, playNextUri, doPlayUri, startPlayNext, collectionId]);
function doTrackingBuffered(e: Event, data: any) { function doTrackingBuffered(e: Event, data: any) {
fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => { fetch(source, { method: 'HEAD', cache: 'no-store' }).then((response) => {
data.playerPoweredBy = response.headers.get('x-powered-by'); data.playerPoweredBy = response.headers.get('x-powered-by');
@ -213,6 +251,7 @@ function VideoViewer(props: Props) {
player.volume(volume); player.volume(volume);
player.playbackRate(videoPlaybackRate); player.playbackRate(videoPlaybackRate);
addTheaterModeButton(player, toggleVideoTheaterMode); addTheaterModeButton(player, toggleVideoTheaterMode);
if (collectionId) addPlayNextButton(player, () => setStartPlayNext(true));
} }
const shouldPlay = !embedded || autoplayIfEmbedded; const shouldPlay = !embedded || autoplayIfEmbedded;
@ -342,6 +381,7 @@ function VideoViewer(props: Props) {
shareTelemetry={shareTelemetry} shareTelemetry={shareTelemetry}
replay={replay} replay={replay}
videoTheaterMode={videoTheaterMode} videoTheaterMode={videoTheaterMode}
setStartPlayNext={setStartPlayNext}
/> />
)} )}
</div> </div>