Add Play Next Button
This commit is contained in:
parent
ec6f9c8a7f
commit
b26255bf53
3 changed files with 71 additions and 3 deletions
|
@ -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));
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue