[Fix] revert fileRenderFloating vs Mobile change (#937)
* Bump react-draggable Old version was giving out console errors for outdated react functions * Refactor fileRenderFloating * Merge fileRenderMobile into fileRenderFloating * Fixes from review * Attempt fix failed to view live
This commit is contained in:
parent
d6cd3caa77
commit
17e3fcc27c
19 changed files with 354 additions and 530 deletions
|
@ -192,7 +192,7 @@
|
|||
"react-awesome-lightbox": "^1.7.3",
|
||||
"react-confetti": "^4.0.1",
|
||||
"react-dom": "^16.8.2",
|
||||
"react-draggable": "^3.3.0",
|
||||
"react-draggable": "^4.4.4",
|
||||
"react-google-recaptcha": "^2.0.1",
|
||||
"react-hot-loader": "^4.11.1",
|
||||
"react-modal": "^3.1.7",
|
||||
|
|
|
@ -15,7 +15,6 @@ import { openContextMenu } from 'util/context-menu';
|
|||
import useKonamiListener from 'util/enhanced-layout';
|
||||
import Yrbl from 'component/yrbl';
|
||||
import FileRenderFloating from 'component/fileRenderFloating';
|
||||
import FileRenderMobile from 'component/fileRenderMobile';
|
||||
import { withRouter } from 'react-router';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
import Nag from 'component/common/nag';
|
||||
|
@ -524,7 +523,7 @@ function App(props: Props) {
|
|||
<Router />
|
||||
<ModalRouter />
|
||||
<React.Suspense fallback={null}>{renderFiledrop && <FileDrop />}</React.Suspense>
|
||||
{isMobile ? <FileRenderMobile /> : <FileRenderFloating />}
|
||||
<FileRenderFloating />
|
||||
<React.Suspense fallback={null}>
|
||||
{isEnhancedLayout && <Yrbl className="yrbl--enhanced" />}
|
||||
|
||||
|
|
54
ui/component/fileRenderFloating/helper-functions.js
Normal file
54
ui/component/fileRenderFloating/helper-functions.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
// @flow
|
||||
import { FLOATING_PLAYER_CLASS } from './view';
|
||||
|
||||
function getRootEl() {
|
||||
return document && document.documentElement;
|
||||
}
|
||||
|
||||
export function getScreenWidth() {
|
||||
const mainEl = getRootEl();
|
||||
return mainEl ? mainEl.clientWidth : window.innerWidth;
|
||||
}
|
||||
|
||||
export function getScreenHeight() {
|
||||
const mainEl = getRootEl();
|
||||
return mainEl ? mainEl.clientHeight : window.innerHeight;
|
||||
}
|
||||
|
||||
export function getFloatingPlayerRect() {
|
||||
const elem = document.querySelector(`.${FLOATING_PLAYER_CLASS}`);
|
||||
return elem ? elem.getBoundingClientRect() : null;
|
||||
}
|
||||
|
||||
export function clampFloatingPlayerToScreen(x: number, y: number) {
|
||||
const playerRect = getFloatingPlayerRect();
|
||||
|
||||
let newX = x;
|
||||
let newY = y;
|
||||
|
||||
if (playerRect) {
|
||||
const screenW = getScreenWidth();
|
||||
const screenH = getScreenHeight();
|
||||
|
||||
if (x + playerRect.width > screenW) {
|
||||
newX = screenW - playerRect.width;
|
||||
} else if (x < 0) {
|
||||
newX = 0;
|
||||
}
|
||||
|
||||
if (y + playerRect.height > screenH) {
|
||||
newY = screenH - playerRect.height;
|
||||
} else if (y < 0) {
|
||||
newY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return { x: newX, y: newY };
|
||||
}
|
||||
|
||||
export function calculateRelativePos(x: number, y: number) {
|
||||
return {
|
||||
x: x / getScreenWidth(),
|
||||
y: y / getScreenHeight(),
|
||||
};
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectTitleForUri, selectClaimIsNsfwForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
||||
import { makeSelectFileInfoForUri, makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
|
||||
import { selectTitleForUri, makeSelectClaimWasPurchased, selectClaimForUri } from 'redux/selectors/claims';
|
||||
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
|
||||
import {
|
||||
makeSelectNextUrlForCollectionAndUrl,
|
||||
makeSelectPreviousUrlForCollectionAndUrl,
|
||||
|
@ -14,26 +14,29 @@ import {
|
|||
} from 'redux/selectors/content';
|
||||
import { selectClientSetting } from 'redux/selectors/settings';
|
||||
import { selectCostInfoForUri } from 'lbryinc';
|
||||
import { doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
||||
import { doUriInitiatePlay, doSetPlayingUri } from 'redux/actions/content';
|
||||
import { doFetchRecommendedContent } from 'redux/actions/search';
|
||||
import { doAnaltyicsPurchaseEvent } from 'redux/actions/app';
|
||||
import { withRouter } from 'react-router';
|
||||
import { getChannelIdFromClaim } from 'util/claim';
|
||||
import { selectMobilePlayerDimensions } from 'redux/selectors/app';
|
||||
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
|
||||
import { doSetMobilePlayerDimensions } from 'redux/actions/app';
|
||||
import FileRenderFloating from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { location } = props;
|
||||
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const primaryUri = selectPrimaryUri(state);
|
||||
const uri = playingUri && playingUri.uri;
|
||||
const collectionId = playingUri && playingUri.collectionId;
|
||||
const { uri, collectionId } = playingUri || {};
|
||||
|
||||
const claim = selectClaimForUri(state, uri);
|
||||
|
||||
return {
|
||||
uri,
|
||||
primaryUri,
|
||||
playingUri,
|
||||
primaryUri: selectPrimaryUri(state),
|
||||
title: selectTitleForUri(state, uri),
|
||||
fileInfo: makeSelectFileInfoForUri(uri)(state),
|
||||
mature: selectClaimIsNsfwForUri(state, uri),
|
||||
isFloating: makeSelectIsPlayerFloating(props.location)(state),
|
||||
isFloating: makeSelectIsPlayerFloating(location)(state),
|
||||
streamingUrl: makeSelectStreamingUrlForUri(uri)(state),
|
||||
floatingPlayerEnabled: selectClientSetting(state, SETTINGS.FLOATING_PLAYER),
|
||||
renderMode: makeSelectFileRenderModeForUri(uri)(state),
|
||||
|
@ -43,26 +46,17 @@ const select = (state, props) => {
|
|||
nextListUri: collectionId && makeSelectNextUrlForCollectionAndUrl(collectionId, uri)(state),
|
||||
previousListUri: collectionId && makeSelectPreviousUrlForCollectionAndUrl(collectionId, uri)(state),
|
||||
collectionId,
|
||||
isCurrentClaimLive: selectIsActiveLivestreamForUri(state, uri),
|
||||
channelClaimId: claim && getChannelIdFromClaim(claim),
|
||||
mobilePlayerDimensions: selectMobilePlayerDimensions(state),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
closeFloatingPlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
||||
doFetchRecommendedContent: (uri, mature) => dispatch(doFetchRecommendedContent(uri, mature)),
|
||||
doPlayUri: (uri, collectionId, hideFailModal) =>
|
||||
dispatch(
|
||||
doPlayUri(
|
||||
uri,
|
||||
false,
|
||||
false,
|
||||
(fileInfo) => {
|
||||
dispatch(doAnaltyicsPurchaseEvent(fileInfo));
|
||||
},
|
||||
hideFailModal
|
||||
),
|
||||
dispatch(doSetPlayingUri({ uri, collectionId }))
|
||||
),
|
||||
clearSecondarySource: (uri) => dispatch(doSetPlayingUri({ uri })),
|
||||
});
|
||||
const perform = {
|
||||
doFetchRecommendedContent,
|
||||
doUriInitiatePlay,
|
||||
doSetPlayingUri,
|
||||
doSetMobilePlayerDimensions,
|
||||
};
|
||||
|
||||
export default withRouter(connect(select, perform)(FileRenderFloating));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import classnames from 'classnames';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
|
@ -11,135 +11,98 @@ import usePersistedState from 'effects/use-persisted-state';
|
|||
import { PRIMARY_PLAYER_WRAPPER_CLASS } from 'page/file/view';
|
||||
import Draggable from 'react-draggable';
|
||||
import { onFullscreenChange } from 'util/full-screen';
|
||||
import { generateListSearchUrlParams, formatLbryUrlForWeb } from 'util/url';
|
||||
import { generateListSearchUrlParams } from 'util/url';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import debounce from 'util/debounce';
|
||||
import { useHistory } from 'react-router';
|
||||
import { isURIEqual } from 'util/lbryURI';
|
||||
import AutoplayCountdown from 'component/autoplayCountdown';
|
||||
import LivestreamIframeRender from 'component/livestreamLayout/iframe-render';
|
||||
import usePlayNext from 'effects/use-play-next';
|
||||
import { getScreenWidth, getScreenHeight, clampFloatingPlayerToScreen, calculateRelativePos } from './helper-functions';
|
||||
|
||||
// scss/init/vars.scss
|
||||
// --header-height
|
||||
const HEADER_HEIGHT = 60;
|
||||
const HEADER_HEIGHT_MOBILE = 60;
|
||||
// --header-height-mobile
|
||||
export const HEADER_HEIGHT_MOBILE = 56;
|
||||
|
||||
const IS_DESKTOP_MAC = typeof process === 'object' ? process.platform === 'darwin' : false;
|
||||
const DEBOUNCE_WINDOW_RESIZE_HANDLER_MS = 100;
|
||||
export const INLINE_PLAYER_WRAPPER_CLASS = 'inline-player__wrapper';
|
||||
|
||||
function getScreenWidth() {
|
||||
if (document && document.documentElement) {
|
||||
return document.documentElement.clientWidth;
|
||||
} else {
|
||||
return window.innerWidth;
|
||||
}
|
||||
}
|
||||
|
||||
function getScreenHeight() {
|
||||
if (document && document.documentElement) {
|
||||
return document.documentElement.clientHeight;
|
||||
} else {
|
||||
return window.innerHeight;
|
||||
}
|
||||
}
|
||||
|
||||
function getFloatingPlayerRect() {
|
||||
// TODO: use 'fileViewerRect'?
|
||||
const FLOATING_PLAYER_CLASS = 'content__viewer--floating';
|
||||
const elem = document.querySelector(`.${FLOATING_PLAYER_CLASS}`);
|
||||
if (elem) {
|
||||
return elem.getBoundingClientRect();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function clampRectToScreen(x, y, rect) {
|
||||
if (rect) {
|
||||
const ESTIMATED_SCROLL_BAR_PX = 50;
|
||||
const screenW = getScreenWidth();
|
||||
const screenH = getScreenHeight();
|
||||
|
||||
if (x + rect.width > screenW - ESTIMATED_SCROLL_BAR_PX) {
|
||||
x = screenW - rect.width - ESTIMATED_SCROLL_BAR_PX;
|
||||
}
|
||||
|
||||
if (y + rect.height > screenH) {
|
||||
y = screenH - rect.height;
|
||||
}
|
||||
}
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
function calculateRelativePos(x, y) {
|
||||
return {
|
||||
x: x / getScreenWidth(),
|
||||
y: y / getScreenHeight(),
|
||||
};
|
||||
}
|
||||
export const FLOATING_PLAYER_CLASS = 'content__viewer--floating';
|
||||
|
||||
// ****************************************************************************
|
||||
// ****************************************************************************
|
||||
|
||||
type Props = {
|
||||
isFloating: boolean,
|
||||
fileInfo: FileListItem,
|
||||
mature: boolean,
|
||||
uri: string,
|
||||
streamingUrl?: string,
|
||||
title: ?string,
|
||||
floatingPlayerEnabled: boolean,
|
||||
closeFloatingPlayer: () => void,
|
||||
clearSecondarySource: (string) => void,
|
||||
renderMode: string,
|
||||
playingUri: ?PlayingUri,
|
||||
primaryUri: ?string,
|
||||
videoTheaterMode: boolean,
|
||||
doFetchRecommendedContent: (string, boolean) => void,
|
||||
doPlayUri: (string, string, boolean) => void,
|
||||
collectionId: string,
|
||||
costInfo: any,
|
||||
claimWasPurchased: boolean,
|
||||
nextListUri: string,
|
||||
previousListUri: string,
|
||||
doFetchRecommendedContent: (uri: string) => void,
|
||||
doUriInitiatePlay: (uri: string, collectionId: ?string, isPlayable: ?boolean, isFloating: ?boolean) => void,
|
||||
doSetPlayingUri: ({ uri?: ?string }) => void,
|
||||
// mobile only
|
||||
isCurrentClaimLive?: boolean,
|
||||
channelClaimId?: any,
|
||||
mobilePlayerDimensions?: any,
|
||||
doSetMobilePlayerDimensions: ({ height?: ?number, width?: ?number }) => void,
|
||||
};
|
||||
|
||||
export default function FileRenderFloating(props: Props) {
|
||||
const {
|
||||
fileInfo,
|
||||
mature,
|
||||
uri,
|
||||
streamingUrl,
|
||||
title,
|
||||
isFloating,
|
||||
closeFloatingPlayer,
|
||||
clearSecondarySource,
|
||||
floatingPlayerEnabled,
|
||||
renderMode,
|
||||
playingUri,
|
||||
primaryUri,
|
||||
videoTheaterMode,
|
||||
doFetchRecommendedContent,
|
||||
doPlayUri,
|
||||
collectionId,
|
||||
costInfo,
|
||||
claimWasPurchased,
|
||||
nextListUri,
|
||||
previousListUri,
|
||||
doFetchRecommendedContent,
|
||||
doUriInitiatePlay,
|
||||
doSetPlayingUri,
|
||||
// mobile only
|
||||
isCurrentClaimLive,
|
||||
channelClaimId,
|
||||
mobilePlayerDimensions,
|
||||
doSetMobilePlayerDimensions,
|
||||
} = props;
|
||||
const { location, push } = useHistory();
|
||||
const hideFloatingPlayer = location.state && location.state.hideFloatingPlayer;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const {
|
||||
location: { state },
|
||||
} = useHistory();
|
||||
const hideFloatingPlayer = state && state.hideFloatingPlayer;
|
||||
|
||||
const playingUriSource = playingUri && playingUri.source;
|
||||
const isComment = playingUriSource === 'comment';
|
||||
const isMobile = useIsMobile();
|
||||
const mainFilePlaying = !isFloating && primaryUri && isURIEqual(uri, primaryUri);
|
||||
const mainFilePlaying = (!isFloating || !isMobile) && primaryUri && isURIEqual(uri, primaryUri);
|
||||
const noFloatingPlayer = !isFloating || isMobile || !floatingPlayerEnabled || hideFloatingPlayer;
|
||||
|
||||
const [fileViewerRect, setFileViewerRect] = useState();
|
||||
const [wasDragging, setWasDragging] = useState(false);
|
||||
const [doNavigate, setDoNavigate] = useState(false);
|
||||
const [playNextUrl, setPlayNextUrl] = useState(true);
|
||||
const [countdownCanceled, setCountdownCanceled] = useState(false);
|
||||
const [fileViewerRect, setFileViewerRect] = React.useState();
|
||||
const [wasDragging, setWasDragging] = React.useState(false);
|
||||
const [doNavigate, setDoNavigate] = React.useState(false);
|
||||
const [shouldPlayNext, setPlayNext] = React.useState(true);
|
||||
const [countdownCanceled, setCountdownCanceled] = React.useState(false);
|
||||
const [position, setPosition] = usePersistedState('floating-file-viewer:position', {
|
||||
x: -25,
|
||||
y: window.innerHeight - 400,
|
||||
|
@ -152,56 +115,12 @@ export default function FileRenderFloating(props: Props) {
|
|||
|
||||
const isFree = costInfo && costInfo.cost === 0;
|
||||
const canViewFile = isFree || claimWasPurchased;
|
||||
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 isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode) || isCurrentClaimLive;
|
||||
const isReadyToPlay = isPlayable && streamingUrl;
|
||||
|
||||
function restoreToRelativePosition() {
|
||||
const newX = Math.round(relativePosRef.current.x * getScreenWidth());
|
||||
const newY = Math.round(relativePosRef.current.y * getScreenHeight());
|
||||
setPosition(clampRectToScreen(newX, newY, getFloatingPlayerRect()));
|
||||
}
|
||||
|
||||
const clampToScreenOnResize = React.useCallback(
|
||||
debounce(() => {
|
||||
restoreToRelativePosition();
|
||||
}, DEBOUNCE_WINDOW_RESIZE_HANDLER_MS),
|
||||
[]
|
||||
);
|
||||
|
||||
// ???
|
||||
useEffect(() => {
|
||||
if (isFloating) {
|
||||
// When the player begins floating, remove the comment source
|
||||
// so that it doesn't try to resize again in case of going back
|
||||
// to the origin's comment section and fail to position correctly
|
||||
if (isComment && playingUri) clearSecondarySource(playingUri.uri);
|
||||
}
|
||||
}, [isFloating, isComment, clearSecondarySource, playingUri]);
|
||||
|
||||
// Initial update for relativePosRef:
|
||||
useEffect(() => {
|
||||
relativePosRef.current = calculateRelativePos(position.x, position.y);
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Ensure player is within screen when 'isFloating' changes.
|
||||
useEffect(() => {
|
||||
if (isFloating) {
|
||||
restoreToRelativePosition();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isFloating]);
|
||||
|
||||
// Listen to main-window resizing and adjust the fp position accordingly:
|
||||
useEffect(() => {
|
||||
if (isFloating) {
|
||||
window.addEventListener('resize', clampToScreenOnResize);
|
||||
return () => window.removeEventListener('resize', clampToScreenOnResize);
|
||||
}
|
||||
}, [isFloating, clampToScreenOnResize]);
|
||||
// ****************************************************************************
|
||||
// FUNCTIONS
|
||||
// ****************************************************************************
|
||||
|
||||
const handleResize = React.useCallback(() => {
|
||||
const element = mainFilePlaying
|
||||
|
@ -226,69 +145,121 @@ export default function FileRenderFloating(props: Props) {
|
|||
|
||||
// $FlowFixMe
|
||||
setFileViewerRect({ ...objectRect, windowOffset: window.pageYOffset });
|
||||
}, [mainFilePlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mobilePlayerDimensions || mobilePlayerDimensions.height !== rect.height) {
|
||||
doSetMobilePlayerDimensions({ height: rect.height, width: getScreenWidth() });
|
||||
}
|
||||
}, [doSetMobilePlayerDimensions, mainFilePlaying, mobilePlayerDimensions]);
|
||||
|
||||
const restoreToRelativePosition = React.useCallback(() => {
|
||||
const SCROLL_BAR_PX = 12; // root: --body-scrollbar-width
|
||||
const screenW = getScreenWidth() - SCROLL_BAR_PX;
|
||||
const screenH = getScreenHeight();
|
||||
|
||||
const newX = Math.round(relativePosRef.current.x * screenW);
|
||||
const newY = Math.round(relativePosRef.current.y * screenH);
|
||||
|
||||
setPosition(clampFloatingPlayerToScreen(newX, newY));
|
||||
}, [setPosition]);
|
||||
|
||||
const clampToScreenOnResize = React.useCallback(
|
||||
debounce(restoreToRelativePosition, DEBOUNCE_WINDOW_RESIZE_HANDLER_MS),
|
||||
[]
|
||||
);
|
||||
|
||||
// For playlists when pressing next/previous etc and switching players
|
||||
function resetState() {
|
||||
setCountdownCanceled(false);
|
||||
setDoNavigate(false);
|
||||
setPlayNext(true);
|
||||
}
|
||||
|
||||
// ****************************************************************************
|
||||
// EFFECTS
|
||||
// ****************************************************************************
|
||||
|
||||
usePlayNext(
|
||||
isFloating,
|
||||
collectionId,
|
||||
shouldPlayNext,
|
||||
nextListUri,
|
||||
previousListUri,
|
||||
doNavigate,
|
||||
doUriInitiatePlay,
|
||||
resetState
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (playingUri && (playingUri.primaryUri || playingUri.uri)) {
|
||||
handleResize();
|
||||
setCountdownCanceled(false);
|
||||
}
|
||||
}, [handleResize, playingUri, videoTheaterMode]);
|
||||
|
||||
useEffect(() => {
|
||||
// Listen to main-window resizing and adjust the floating player position accordingly:
|
||||
React.useEffect(() => {
|
||||
if (isFloating) {
|
||||
// Ensure player is within screen when 'isFloating' changes.
|
||||
restoreToRelativePosition();
|
||||
} else {
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
onFullscreenChange(window, 'add', handleResize);
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
return isFloating ? clampToScreenOnResize() : handleResize();
|
||||
}
|
||||
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
if (!isFloating) onFullscreenChange(window, 'add', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
onFullscreenChange(window, 'remove', handleResize);
|
||||
window.removeEventListener('resize', onWindowResize);
|
||||
if (!isFloating) onFullscreenChange(window, 'remove', handleResize);
|
||||
};
|
||||
}, [handleResize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFloating) {
|
||||
doFetchRecommendedContent(uri, mature);
|
||||
// Only listen to these and avoid infinite loops
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [clampToScreenOnResize, handleResize, isFloating]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Initial update for relativePosRef:
|
||||
relativePosRef.current = calculateRelativePos(position.x, position.y);
|
||||
|
||||
// only on mount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isFloating && isComment) {
|
||||
// When the player begins floating, remove the comment source
|
||||
// so that it doesn't try to resize again in case of going back
|
||||
// to the origin's comment section and fail to position correctly
|
||||
doSetPlayingUri({ ...playingUri, source: null });
|
||||
}
|
||||
}, [doFetchRecommendedContent, isFloating, mature, uri]);
|
||||
}, [doSetPlayingUri, isComment, isFloating, playingUri]);
|
||||
|
||||
const doPlay = React.useCallback(
|
||||
(playUri) => {
|
||||
setDoNavigate(false);
|
||||
if (!isFloating) {
|
||||
const navigateUrl = formatLbryUrlForWeb(playUri);
|
||||
push({
|
||||
pathname: navigateUrl,
|
||||
search: collectionId && generateListSearchUrlParams(collectionId),
|
||||
state: { collectionId, forceAutoplay: true, hideFloatingPlayer: true },
|
||||
});
|
||||
} else {
|
||||
doPlayUri(playUri, collectionId, true);
|
||||
React.useEffect(() => {
|
||||
if (isFloating) doFetchRecommendedContent(uri);
|
||||
}, [doFetchRecommendedContent, isFloating, uri]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isFloating && isMobile) {
|
||||
doSetMobilePlayerDimensions({ height: null, width: null });
|
||||
}
|
||||
},
|
||||
[collectionId, doPlayUri, isFloating, push]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!doNavigate) return;
|
||||
|
||||
if (playNextUrl && nextListUri) {
|
||||
doPlay(nextListUri);
|
||||
} else if (previousListUri) {
|
||||
doPlay(previousListUri);
|
||||
}
|
||||
setPlayNextUrl(true);
|
||||
}, [doNavigate, doPlay, nextListUri, playNextUrl, previousListUri]);
|
||||
}, [doSetMobilePlayerDimensions, doSetPlayingUri, isFloating, isMobile]);
|
||||
|
||||
if (
|
||||
!isPlayable ||
|
||||
!uri ||
|
||||
(isFloating && (isMobile || !floatingPlayerEnabled || hideFloatingPlayer)) ||
|
||||
(isFloating && noFloatingPlayer) ||
|
||||
(collectionId && !isFloating && ((!canViewFile && !nextListUri) || countdownCanceled))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ****************************************************************************
|
||||
// RENDER
|
||||
// ****************************************************************************
|
||||
|
||||
function handleDragStart() {
|
||||
// Not really necessary, but reset just in case 'handleStop' didn't fire.
|
||||
setWasDragging(false);
|
||||
|
@ -306,13 +277,13 @@ export default function FileRenderFloating(props: Props) {
|
|||
}
|
||||
|
||||
function handleDragStop(e, ui) {
|
||||
if (wasDragging) {
|
||||
setWasDragging(false);
|
||||
}
|
||||
if (wasDragging) setWasDragging(false);
|
||||
const { x, y } = ui;
|
||||
let newPos = { x, y };
|
||||
|
||||
let newPos = { x: ui.x, y: ui.y };
|
||||
if (newPos.x !== position.x || newPos.y !== position.y) {
|
||||
newPos = clampRectToScreen(newPos.x, newPos.y, getFloatingPlayerRect());
|
||||
newPos = clampFloatingPlayerToScreen(newPos.x, newPos.y);
|
||||
|
||||
setPosition(newPos);
|
||||
relativePosRef.current = calculateRelativePos(newPos.x, newPos.y);
|
||||
}
|
||||
|
@ -326,17 +297,18 @@ export default function FileRenderFloating(props: Props) {
|
|||
defaultPosition={position}
|
||||
position={isFloating ? position : { x: 0, y: 0 }}
|
||||
bounds="parent"
|
||||
disabled={!isFloating}
|
||||
disabled={noFloatingPlayer}
|
||||
handle=".draggable"
|
||||
cancel=".button"
|
||||
>
|
||||
<div
|
||||
className={classnames('content__viewer', {
|
||||
'content__viewer--floating': isFloating,
|
||||
[FLOATING_PLAYER_CLASS]: isFloating,
|
||||
'content__viewer--inline': !isFloating,
|
||||
'content__viewer--secondary': isComment,
|
||||
'content__viewer--theater-mode': !isFloating && videoTheaterMode && playingUri?.uri === primaryUri,
|
||||
'content__viewer--disable-click': wasDragging,
|
||||
'content__viewer--mobile': isMobile,
|
||||
})}
|
||||
style={
|
||||
!isFloating && fileViewerRect
|
||||
|
@ -344,42 +316,36 @@ export default function FileRenderFloating(props: Props) {
|
|||
width: fileViewerRect.width,
|
||||
height: fileViewerRect.height,
|
||||
left: fileViewerRect.x,
|
||||
top:
|
||||
fileViewerRect.windowOffset +
|
||||
fileViewerRect.top -
|
||||
(isMobile ? HEADER_HEIGHT_MOBILE : HEADER_HEIGHT) -
|
||||
(IS_DESKTOP_MAC ? 24 : 0),
|
||||
top: isMobile
|
||||
? HEADER_HEIGHT_MOBILE
|
||||
: fileViewerRect.windowOffset + fileViewerRect.top - HEADER_HEIGHT - (IS_DESKTOP_MAC ? 24 : 0),
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={classnames('content__wrapper', {
|
||||
'content__wrapper--floating': isFloating,
|
||||
})}
|
||||
>
|
||||
<div className={classnames('content__wrapper', { 'content__wrapper--floating': isFloating })}>
|
||||
{isFloating && (
|
||||
<Button
|
||||
title={__('Close')}
|
||||
onClick={closeFloatingPlayer}
|
||||
onClick={() => doSetPlayingUri({ uri: null })}
|
||||
icon={ICONS.REMOVE}
|
||||
button="primary"
|
||||
className="content__floating-close"
|
||||
/>
|
||||
)}
|
||||
|
||||
{isReadyToPlay ? (
|
||||
{isCurrentClaimLive && channelClaimId ? (
|
||||
<LivestreamIframeRender channelClaimId={channelClaimId} showLivestream mobileVersion />
|
||||
) : isReadyToPlay ? (
|
||||
<FileRender className="draggable" uri={uri} />
|
||||
) : (
|
||||
<>
|
||||
{collectionId && !canViewFile ? (
|
||||
) : collectionId && !canViewFile ? (
|
||||
<div className="content__loading">
|
||||
<AutoplayCountdown
|
||||
nextRecommendedUri={nextListUri}
|
||||
doNavigate={() => setDoNavigate(true)}
|
||||
doReplay={() => doPlayUri(uri, collectionId, false)}
|
||||
doReplay={() => doUriInitiatePlay(uri, collectionId, false, isFloating)}
|
||||
doPrevious={() => {
|
||||
setPlayNextUrl(false);
|
||||
setPlayNext(false);
|
||||
setDoNavigate(true);
|
||||
}}
|
||||
onCanceled={() => setCountdownCanceled(true)}
|
||||
|
@ -387,15 +353,15 @@ export default function FileRenderFloating(props: Props) {
|
|||
/>
|
||||
</div>
|
||||
) : (
|
||||
<LoadingScreen status={loadingMessage} />
|
||||
)}
|
||||
</>
|
||||
<LoadingScreen status={__('Loading')} />
|
||||
)}
|
||||
|
||||
{isFloating && (
|
||||
<div className="draggable content__info">
|
||||
<div className="claim-preview__title" title={title || uri}>
|
||||
<Button label={title || uri} navigate={navigateUrl} button="link" className="content__floating-link" />
|
||||
</div>
|
||||
|
||||
<UriIndicator link uri={uri} />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doUriInitiatePlay, doSetPlayingUri } from 'redux/actions/content';
|
||||
import { selectThumbnailForUri, selectClaimForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
||||
import { doUriInitiatePlay, doSetPlayingUri, doSetPrimaryUri } from 'redux/actions/content';
|
||||
import { selectThumbnailForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
||||
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { selectCostInfoForUri } from 'lbryinc';
|
||||
|
@ -14,16 +14,11 @@ import {
|
|||
makeSelectFileRenderModeForUri,
|
||||
} from 'redux/selectors/content';
|
||||
import FileRenderInitiator from './view';
|
||||
import { getChannelIdFromClaim } from 'util/claim';
|
||||
import { selectActiveLivestreamForChannel } from 'redux/selectors/livestream';
|
||||
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { uri } = props;
|
||||
|
||||
const claim = selectClaimForUri(state, uri);
|
||||
const claimId = claim && claim.claim_id;
|
||||
const channelClaimId = claim && getChannelIdFromClaim(claim);
|
||||
|
||||
return {
|
||||
claimThumbnail: selectThumbnailForUri(state, uri),
|
||||
fileInfo: makeSelectFileInfoForUri(uri)(state),
|
||||
|
@ -35,14 +30,14 @@ const select = (state, props) => {
|
|||
renderMode: makeSelectFileRenderModeForUri(uri)(state),
|
||||
claimWasPurchased: makeSelectClaimWasPurchased(uri)(state),
|
||||
authenticated: selectUserVerifiedEmail(state),
|
||||
activeLivestreamForChannel: channelClaimId && selectActiveLivestreamForChannel(state, channelClaimId),
|
||||
claimId,
|
||||
isCurrentClaimLive: selectIsActiveLivestreamForUri(state, uri),
|
||||
};
|
||||
};
|
||||
|
||||
const perform = {
|
||||
doUriInitiatePlay,
|
||||
doSetPlayingUri,
|
||||
doSetPrimaryUri,
|
||||
};
|
||||
|
||||
export default withRouter(connect(select, perform)(FileRenderInitiator));
|
||||
|
|
|
@ -31,10 +31,10 @@ type Props = {
|
|||
claimWasPurchased: boolean,
|
||||
authenticated: boolean,
|
||||
videoTheaterMode: boolean,
|
||||
activeLivestreamForChannel?: any,
|
||||
claimId?: string,
|
||||
isCurrentClaimLive?: boolean,
|
||||
doUriInitiatePlay: (uri: string, collectionId: ?string, isPlayable: boolean) => void,
|
||||
doSetPlayingUri: ({ uri: ?string }) => void,
|
||||
doSetPrimaryUri: (uri: ?string) => void,
|
||||
};
|
||||
|
||||
export default function FileRenderInitiator(props: Props) {
|
||||
|
@ -53,10 +53,10 @@ export default function FileRenderInitiator(props: Props) {
|
|||
claimWasPurchased,
|
||||
authenticated,
|
||||
videoTheaterMode,
|
||||
activeLivestreamForChannel,
|
||||
claimId,
|
||||
isCurrentClaimLive,
|
||||
doUriInitiatePlay,
|
||||
doSetPlayingUri,
|
||||
doSetPrimaryUri,
|
||||
} = props;
|
||||
|
||||
const containerRef = React.useRef<any>();
|
||||
|
@ -76,9 +76,8 @@ export default function FileRenderInitiator(props: Props) {
|
|||
|
||||
const isFree = costInfo && costInfo.cost === 0;
|
||||
const canViewFile = isFree || claimWasPurchased;
|
||||
const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode);
|
||||
const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode) || isCurrentClaimLive;
|
||||
const isText = RENDER_MODES.TEXT_MODES.includes(renderMode);
|
||||
const isCurrentClaimLive = activeLivestreamForChannel && claimId && activeLivestreamForChannel.claimId === claimId;
|
||||
const isMobileClaimLive = isMobile && isCurrentClaimLive;
|
||||
const foundCover = thumbnail !== FileRenderPlaceholder;
|
||||
|
||||
|
@ -87,12 +86,13 @@ export default function FileRenderInitiator(props: Props) {
|
|||
const shouldRedirect = !authenticated && !isFree;
|
||||
|
||||
React.useEffect(() => {
|
||||
// Set livestream as playing uri so it can be rendered by <FileRenderMobile />
|
||||
// Set livestream as playing uri so it can be rendered by <FileRenderFloating /> on mobile
|
||||
// instead of showing an empty cover image. Needs cover to fill the space with the player.
|
||||
if (isMobileClaimLive && foundCover) {
|
||||
doSetPlayingUri({ uri });
|
||||
doSetPrimaryUri(uri);
|
||||
}
|
||||
}, [doSetPlayingUri, foundCover, isMobileClaimLive, uri]);
|
||||
}, [doSetPlayingUri, doSetPrimaryUri, foundCover, isMobileClaimLive, uri]);
|
||||
|
||||
function doAuthRedirect() {
|
||||
history.push(`/$/${PAGES.AUTH}?redirect=${encodeURIComponent(location.pathname)}`);
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimWasPurchased, selectClaimForUri } from 'redux/selectors/claims';
|
||||
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
|
||||
import {
|
||||
makeSelectNextUrlForCollectionAndUrl,
|
||||
makeSelectPreviousUrlForCollectionAndUrl,
|
||||
} from 'redux/selectors/collections';
|
||||
import { selectPlayingUri, makeSelectFileRenderModeForUri, selectPrimaryUri } from 'redux/selectors/content';
|
||||
import { selectCostInfoForUri } from 'lbryinc';
|
||||
import { doPlayUri } from 'redux/actions/content';
|
||||
import { doSetMobilePlayerDimensions } from 'redux/actions/app';
|
||||
import { withRouter } from 'react-router';
|
||||
import { getChannelIdFromClaim } from 'util/claim';
|
||||
import { selectActiveLivestreamForChannel } from 'redux/selectors/livestream';
|
||||
import FileRenderMobile from './view';
|
||||
import { selectMobilePlayerDimensions } from 'redux/selectors/app';
|
||||
|
||||
const select = (state, props) => {
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const primaryUri = selectPrimaryUri(state);
|
||||
const uri = playingUri && playingUri.uri;
|
||||
const collectionId = playingUri && playingUri.collectionId;
|
||||
|
||||
const claim = selectClaimForUri(state, uri);
|
||||
const claimId = claim && claim.claim_id;
|
||||
const channelClaimId = claim && getChannelIdFromClaim(claim);
|
||||
|
||||
return {
|
||||
uri,
|
||||
streamingUrl: makeSelectStreamingUrlForUri(uri)(state),
|
||||
renderMode: makeSelectFileRenderModeForUri(uri)(state),
|
||||
costInfo: selectCostInfoForUri(state, uri),
|
||||
claimWasPurchased: makeSelectClaimWasPurchased(uri)(state),
|
||||
nextListUri: collectionId && makeSelectNextUrlForCollectionAndUrl(collectionId, uri)(state),
|
||||
previousListUri: collectionId && makeSelectPreviousUrlForCollectionAndUrl(collectionId, uri)(state),
|
||||
collectionId,
|
||||
activeLivestreamForChannel: channelClaimId && selectActiveLivestreamForChannel(state, channelClaimId),
|
||||
claimId,
|
||||
channelClaimId,
|
||||
mobilePlayerDimensions: selectMobilePlayerDimensions(state),
|
||||
primaryUri,
|
||||
playingUri,
|
||||
};
|
||||
};
|
||||
|
||||
const perform = {
|
||||
doPlayUri,
|
||||
doSetMobilePlayerDimensions,
|
||||
};
|
||||
|
||||
export default withRouter(connect(select, perform)(FileRenderMobile));
|
|
@ -1,194 +0,0 @@
|
|||
// @flow
|
||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { onFullscreenChange } from 'util/full-screen';
|
||||
import { generateListSearchUrlParams, formatLbryUrlForWeb } from 'util/url';
|
||||
import { useHistory } from 'react-router';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import FileRender from 'component/fileRender';
|
||||
import AutoplayCountdown from 'component/autoplayCountdown';
|
||||
import LivestreamIframeRender from 'component/livestreamLayout/iframe-render';
|
||||
|
||||
const PRIMARY_PLAYER_WRAPPER_CLASS = 'file-page__video-container';
|
||||
export const INLINE_PLAYER_WRAPPER_CLASS = 'inline-player__wrapper';
|
||||
export const HEADER_HEIGHT_MOBILE = 56;
|
||||
|
||||
// ****************************************************************************
|
||||
// ****************************************************************************
|
||||
|
||||
type Props = {
|
||||
claimId?: string,
|
||||
uri: string,
|
||||
streamingUrl?: string,
|
||||
renderMode: string,
|
||||
collectionId: string,
|
||||
costInfo: any,
|
||||
claimWasPurchased: boolean,
|
||||
nextListUri: string,
|
||||
previousListUri: string,
|
||||
activeLivestreamForChannel?: any,
|
||||
channelClaimId?: any,
|
||||
playingUri?: PlayingUri,
|
||||
primaryUri: ?string,
|
||||
mobilePlayerDimensions?: any,
|
||||
doPlayUri: (string) => void,
|
||||
doSetMobilePlayerDimensions: (height: number, width: number) => void,
|
||||
};
|
||||
|
||||
export default function FileRenderMobile(props: Props) {
|
||||
const {
|
||||
claimId,
|
||||
uri,
|
||||
streamingUrl,
|
||||
renderMode,
|
||||
collectionId,
|
||||
costInfo,
|
||||
claimWasPurchased,
|
||||
nextListUri,
|
||||
previousListUri,
|
||||
activeLivestreamForChannel,
|
||||
channelClaimId,
|
||||
playingUri,
|
||||
primaryUri,
|
||||
mobilePlayerDimensions,
|
||||
doPlayUri,
|
||||
doSetMobilePlayerDimensions,
|
||||
} = props;
|
||||
|
||||
const { push } = useHistory();
|
||||
|
||||
const [fileViewerRect, setFileViewerRect] = useState();
|
||||
const [doNavigate, setDoNavigate] = useState(false);
|
||||
const [playNextUrl, setPlayNextUrl] = useState(true);
|
||||
const [countdownCanceled, setCountdownCanceled] = useState(false);
|
||||
|
||||
const isCurrentClaimLive = activeLivestreamForChannel && activeLivestreamForChannel.claimId === claimId;
|
||||
const isFree = costInfo && costInfo.cost === 0;
|
||||
const canViewFile = isFree || claimWasPurchased;
|
||||
const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode) || activeLivestreamForChannel;
|
||||
const isReadyToPlay = isPlayable && streamingUrl;
|
||||
const isCurrentMediaPlaying = playingUri && playingUri.uri === uri;
|
||||
|
||||
const handleResize = React.useCallback(() => {
|
||||
const element = document.querySelector(`.${PRIMARY_PLAYER_WRAPPER_CLASS}`);
|
||||
|
||||
if (!element) return;
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
||||
// getBoundingClientRect returns a DomRect, not an object
|
||||
const objectRect = {
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
// $FlowFixMe
|
||||
x: rect.x,
|
||||
};
|
||||
|
||||
// $FlowFixMe
|
||||
setFileViewerRect({ ...objectRect });
|
||||
|
||||
if (doSetMobilePlayerDimensions && (!mobilePlayerDimensions || mobilePlayerDimensions.height !== rect.height)) {
|
||||
doSetMobilePlayerDimensions(rect.height, rect.width);
|
||||
}
|
||||
}, [doSetMobilePlayerDimensions, mobilePlayerDimensions]);
|
||||
|
||||
// Initial resize, will place the player correctly above the cover when starts playing
|
||||
// (remember the URI here is from playingUri). The cover then keeps on the page and kind of serves as a placeholder
|
||||
// for the player size and gives the content layered behind the player a "max scroll height"
|
||||
useEffect(() => {
|
||||
if (uri) {
|
||||
handleResize();
|
||||
setCountdownCanceled(false);
|
||||
}
|
||||
}, [handleResize, uri]);
|
||||
|
||||
useEffect(() => {
|
||||
handleResize();
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
onFullscreenChange(window, 'add', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
onFullscreenChange(window, 'remove', handleResize);
|
||||
};
|
||||
}, [handleResize]);
|
||||
|
||||
const doPlay = React.useCallback(
|
||||
(playUri) => {
|
||||
setDoNavigate(false);
|
||||
const navigateUrl = formatLbryUrlForWeb(playUri);
|
||||
push({
|
||||
pathname: navigateUrl,
|
||||
search: collectionId && generateListSearchUrlParams(collectionId),
|
||||
state: { collectionId, forceAutoplay: true, hideFloatingPlayer: true },
|
||||
});
|
||||
},
|
||||
[collectionId, push]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!doNavigate) return;
|
||||
|
||||
if (playNextUrl && nextListUri) {
|
||||
doPlay(nextListUri);
|
||||
} else if (previousListUri) {
|
||||
doPlay(previousListUri);
|
||||
}
|
||||
setPlayNextUrl(true);
|
||||
}, [doNavigate, doPlay, nextListUri, playNextUrl, previousListUri]);
|
||||
|
||||
if (
|
||||
!isCurrentMediaPlaying ||
|
||||
!isPlayable ||
|
||||
!uri ||
|
||||
countdownCanceled ||
|
||||
(!isCurrentClaimLive && primaryUri !== playingUri?.uri) || // No floating player on mobile as of now
|
||||
(collectionId && !canViewFile && !nextListUri)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="content__viewer content__viewer--inline content__viewer--mobile"
|
||||
style={
|
||||
fileViewerRect
|
||||
? {
|
||||
width: fileViewerRect.width,
|
||||
height: fileViewerRect.height,
|
||||
left: fileViewerRect.x,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<div className="content__wrapper">
|
||||
{isCurrentClaimLive && channelClaimId ? (
|
||||
<LivestreamIframeRender channelClaimId={channelClaimId} showLivestream mobileVersion />
|
||||
) : isReadyToPlay ? (
|
||||
<FileRender uri={uri} />
|
||||
) : !canViewFile ? (
|
||||
<div className="content__loading">
|
||||
<AutoplayCountdown
|
||||
nextRecommendedUri={nextListUri}
|
||||
doNavigate={() => setDoNavigate(true)}
|
||||
doReplay={() => doPlayUri(uri)}
|
||||
doPrevious={() => {
|
||||
setPlayNextUrl(false);
|
||||
setDoNavigate(true);
|
||||
}}
|
||||
onCanceled={() => setCountdownCanceled(true)}
|
||||
skipPaid
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<LoadingScreen status={__('Loading')} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -6,7 +6,8 @@ import { Global } from '@emotion/react';
|
|||
// $FlowFixMe
|
||||
import { grey } from '@mui/material/colors';
|
||||
|
||||
import { HEADER_HEIGHT_MOBILE } from 'component/fileRenderMobile/view';
|
||||
import { HEADER_HEIGHT_MOBILE } from 'component/fileRenderFloating/view';
|
||||
import { PRIMARY_PLAYER_WRAPPER_CLASS, PRIMARY_IMAGE_WRAPPER_CLASS } from 'page/file/view';
|
||||
import { SwipeableDrawer as MUIDrawer } from '@mui/material';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as React from 'react';
|
||||
|
@ -31,18 +32,29 @@ export default function SwipeableDrawer(props: Props) {
|
|||
|
||||
const [coverHeight, setCoverHeight] = React.useState();
|
||||
|
||||
const videoHeight = coverHeight || (mobilePlayerDimensions ? mobilePlayerDimensions.height : 0);
|
||||
const videoHeight = (mobilePlayerDimensions && mobilePlayerDimensions.height) || coverHeight || 0;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (open && !mobilePlayerDimensions) {
|
||||
const element = document.querySelector(`.file-page__video-container`);
|
||||
const handleResize = React.useCallback(() => {
|
||||
const element =
|
||||
document.querySelector(`.${PRIMARY_IMAGE_WRAPPER_CLASS}`) ||
|
||||
document.querySelector(`.${PRIMARY_PLAYER_WRAPPER_CLASS}`);
|
||||
|
||||
if (!element) return;
|
||||
|
||||
if (element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
setCoverHeight(rect.height);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Drawer will follow the cover image on resize, so it's always visible
|
||||
if (open && (!mobilePlayerDimensions || !mobilePlayerDimensions.height)) {
|
||||
handleResize();
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}
|
||||
}
|
||||
}, [coverHeight, mobilePlayerDimensions, open]);
|
||||
}, [handleResize, mobilePlayerDimensions, open]);
|
||||
|
||||
// Reset scroll position when opening: avoid broken position where
|
||||
// the drawer is lower than the video
|
||||
|
|
47
ui/effects/use-play-next.js
Normal file
47
ui/effects/use-play-next.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { generateListSearchUrlParams, formatLbryUrlForWeb } from 'util/url';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
// Returns web blob from the streaming url
|
||||
export default function usePlayNext(
|
||||
isFloating: boolean,
|
||||
collectionId: ?string,
|
||||
shouldPlayNext: boolean,
|
||||
nextListUri: ?string,
|
||||
previousListUri: ?string,
|
||||
doNavigate: boolean,
|
||||
doUriInitiatePlay: (uri: string, collectionId: ?string, isPlayable: ?boolean, isFloating: ?boolean) => void,
|
||||
resetState: () => void
|
||||
) {
|
||||
const { push } = useHistory();
|
||||
|
||||
const doPlay = React.useCallback(
|
||||
(playUri) => {
|
||||
if (!isFloating) {
|
||||
const navigateUrl = formatLbryUrlForWeb(playUri);
|
||||
|
||||
push({
|
||||
pathname: navigateUrl,
|
||||
search: collectionId && generateListSearchUrlParams(collectionId),
|
||||
state: { collectionId, forceAutoplay: true, hideFloatingPlayer: true },
|
||||
});
|
||||
} else {
|
||||
doUriInitiatePlay(playUri, collectionId, true, isFloating);
|
||||
}
|
||||
|
||||
resetState();
|
||||
},
|
||||
[collectionId, doUriInitiatePlay, isFloating, push, resetState]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!doNavigate) return;
|
||||
|
||||
if (shouldPlayNext) {
|
||||
if (nextListUri) doPlay(nextListUri);
|
||||
} else {
|
||||
if (previousListUri) doPlay(previousListUri);
|
||||
}
|
||||
}, [doNavigate, doPlay, nextListUri, shouldPlayNext, previousListUri]);
|
||||
}
|
|
@ -16,7 +16,6 @@ import { selectShowMatureContent, selectClientSetting } from 'redux/selectors/se
|
|||
import { makeSelectFileRenderModeForUri, makeSelectContentPositionForUri } from 'redux/selectors/content';
|
||||
import { makeSelectCommentsListTitleForUri, selectSettingsByChannelId } from 'redux/selectors/comments';
|
||||
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
|
||||
import { doSetMobilePlayerDimensions } from 'redux/actions/app';
|
||||
import { getChannelIdFromClaim } from 'util/claim';
|
||||
|
||||
import FilePage from './view';
|
||||
|
@ -53,7 +52,6 @@ const perform = {
|
|||
doSetContentHistoryItem,
|
||||
doSetPrimaryUri,
|
||||
clearPosition,
|
||||
doSetMobilePlayerDimensions,
|
||||
};
|
||||
|
||||
export default withRouter(connect(select, perform)(FilePage));
|
||||
|
|
|
@ -23,6 +23,7 @@ const CommentsList = lazyImport(() => import('component/commentsList' /* webpack
|
|||
const PostViewer = lazyImport(() => import('component/postViewer' /* webpackChunkName: "postViewer" */));
|
||||
|
||||
export const PRIMARY_PLAYER_WRAPPER_CLASS = 'file-page__video-container';
|
||||
export const PRIMARY_IMAGE_WRAPPER_CLASS = 'file-render__img-container';
|
||||
|
||||
type Props = {
|
||||
costInfo: ?{ includesData: boolean, cost: number },
|
||||
|
@ -164,7 +165,7 @@ export default function FilePage(props: Props) {
|
|||
if (renderMode === RENDER_MODES.IMAGE) {
|
||||
return (
|
||||
<>
|
||||
<div className="file-render--img-container">
|
||||
<div className={PRIMARY_IMAGE_WRAPPER_CLASS}>
|
||||
<FileRenderInitiator uri={uri} />
|
||||
<FileRenderInline uri={uri} />
|
||||
</div>
|
||||
|
|
|
@ -737,7 +737,7 @@ export function doSetIncognito(incognitoEnabled) {
|
|||
};
|
||||
}
|
||||
|
||||
export const doSetMobilePlayerDimensions = (height, width) => ({
|
||||
export const doSetMobilePlayerDimensions = ({ height, width }) => ({
|
||||
type: ACTIONS.SET_MOBILE_PLAYER_DIMENSIONS,
|
||||
data: { heightWidth: { height, width } },
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@ import Lbry from 'lbry';
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { selectCostInfoForUri, Lbryio } from 'lbryinc';
|
||||
import { selectClientSetting, selectosNotificationsEnabled, selectDaemonSettings } from 'redux/selectors/settings';
|
||||
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
|
||||
|
||||
const DOWNLOAD_POLL_INTERVAL = 1000;
|
||||
|
||||
|
@ -148,15 +149,18 @@ export function doDownloadUri(uri: string) {
|
|||
return (dispatch: Dispatch) => dispatch(doPlayUri(uri, false, true, () => dispatch(doAnalyticsView(uri))));
|
||||
}
|
||||
|
||||
export function doUriInitiatePlay(uri: string, collectionId?: string, isPlayable?: boolean) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch(doSetPrimaryUri(uri));
|
||||
export function doUriInitiatePlay(uri: string, collectionId?: string, isPlayable?: boolean, isFloating?: boolean) {
|
||||
return (dispatch: Dispatch, getState: () => any) => {
|
||||
const state = getState();
|
||||
const isLive = selectIsActiveLivestreamForUri(state, uri);
|
||||
|
||||
if (!isFloating) dispatch(doSetPrimaryUri(uri));
|
||||
|
||||
if (isPlayable) {
|
||||
dispatch(doSetPlayingUri({ uri, collectionId }));
|
||||
}
|
||||
|
||||
dispatch(doPlayUri(uri, false, true, (fileInfo) => dispatch(doAnaltyicsPurchaseEvent(fileInfo))));
|
||||
if (!isLive) dispatch(doPlayUri(uri, false, true, (fileInfo) => dispatch(doAnaltyicsPurchaseEvent(fileInfo))));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,12 @@ type State = { livestream: any };
|
|||
|
||||
const selectState = (state: State) => state.livestream || {};
|
||||
|
||||
export const selectFetchingLivestreams = (state: State) => selectState(state).fetchingById;
|
||||
export const selectViewersById = (state: State) => selectState(state).viewersById;
|
||||
export const selectActiveLivestreams = (state: State) => selectState(state).activeLivestreams;
|
||||
export const selectFetchingActiveLivestreams = (state: State) => selectState(state).fetchingActiveLivestreams;
|
||||
export const selectActiveLivestreamInitialized = (state: State) => selectState(state).activeLivestreamInitialized;
|
||||
|
||||
// select non-pending claims without sources for given channel
|
||||
export const makeSelectLivestreamsForChannelId = (channelId: string) =>
|
||||
createSelector(selectState, selectMyClaims, (livestreamState, myClaims = []) => {
|
||||
|
@ -23,9 +29,6 @@ export const makeSelectLivestreamsForChannelId = (channelId: string) =>
|
|||
.sort((a, b) => b.timestamp - a.timestamp); // newest first
|
||||
});
|
||||
|
||||
export const selectFetchingLivestreams = (state: State) => selectState(state).fetchingById;
|
||||
export const selectViewersById = (state: State) => selectState(state).viewersById;
|
||||
|
||||
export const makeSelectIsFetchingLivestreams = (channelId: string) =>
|
||||
createSelector(selectFetchingLivestreams, (fetchingLivestreams) => Boolean(fetchingLivestreams[channelId]));
|
||||
|
||||
|
@ -46,8 +49,6 @@ export const makeSelectPendingLivestreamsForChannelId = (channelId: string) =>
|
|||
);
|
||||
});
|
||||
|
||||
export const selectActiveLivestreams = (state: State) => selectState(state).activeLivestreams;
|
||||
|
||||
export const selectIsActiveLivestreamForUri = createCachedSelector(
|
||||
(state, uri) => uri,
|
||||
selectActiveLivestreams,
|
||||
|
@ -86,7 +87,3 @@ export const selectActiveLivestreamForChannel = createCachedSelector(
|
|||
return activeLivestreams[channelId] || null;
|
||||
}
|
||||
)((state, channelId) => String(channelId));
|
||||
|
||||
export const selectFetchingActiveLivestreams = (state: State) => selectState(state).fetchingActiveLivestreams;
|
||||
|
||||
export const selectActiveLivestreamInitialized = (state: State) => selectState(state).activeLivestreamInitialized;
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
margin-bottom: 0;
|
||||
height: calc(var(--floating-viewer-height) + var(--floating-viewer-info-height));
|
||||
overflow: hidden;
|
||||
left: calc(var(--spacing-l) + var(--spacing-s));
|
||||
top: 0;
|
||||
z-index: 9999;
|
||||
|
||||
border: 2px solid black;
|
||||
|
@ -53,6 +53,7 @@
|
|||
.video-js--tap-to-unmute {
|
||||
max-width: calc(var(--floating-viewer-width) - (var(--spacing-xs) * 3) - 42px);
|
||||
}
|
||||
|
||||
.content__floating-close {
|
||||
visibility: visible;
|
||||
z-index: 9999;
|
||||
|
|
|
@ -237,7 +237,7 @@
|
|||
max-height: none;
|
||||
}
|
||||
|
||||
.file-render--img-container {
|
||||
.file-render__img-container {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
|
|
@ -13921,11 +13921,12 @@ react-dom@^16.8.2:
|
|||
prop-types "^15.6.2"
|
||||
scheduler "^0.19.0"
|
||||
|
||||
react-draggable@^3.3.0:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.3.2.tgz#966ef1d90f2387af3c2d8bd3516f601ea42ca359"
|
||||
react-draggable@^4.4.4:
|
||||
version "4.4.4"
|
||||
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.4.tgz#5b26d9996be63d32d285a426f41055de87e59b2f"
|
||||
integrity sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA==
|
||||
dependencies:
|
||||
classnames "^2.2.5"
|
||||
clsx "^1.1.1"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-error-overlay@^1.0.9:
|
||||
|
|
Loading…
Reference in a new issue