[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-awesome-lightbox": "^1.7.3",
|
||||||
"react-confetti": "^4.0.1",
|
"react-confetti": "^4.0.1",
|
||||||
"react-dom": "^16.8.2",
|
"react-dom": "^16.8.2",
|
||||||
"react-draggable": "^3.3.0",
|
"react-draggable": "^4.4.4",
|
||||||
"react-google-recaptcha": "^2.0.1",
|
"react-google-recaptcha": "^2.0.1",
|
||||||
"react-hot-loader": "^4.11.1",
|
"react-hot-loader": "^4.11.1",
|
||||||
"react-modal": "^3.1.7",
|
"react-modal": "^3.1.7",
|
||||||
|
|
|
@ -15,7 +15,6 @@ import { openContextMenu } from 'util/context-menu';
|
||||||
import useKonamiListener from 'util/enhanced-layout';
|
import useKonamiListener from 'util/enhanced-layout';
|
||||||
import Yrbl from 'component/yrbl';
|
import Yrbl from 'component/yrbl';
|
||||||
import FileRenderFloating from 'component/fileRenderFloating';
|
import FileRenderFloating from 'component/fileRenderFloating';
|
||||||
import FileRenderMobile from 'component/fileRenderMobile';
|
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import usePrevious from 'effects/use-previous';
|
import usePrevious from 'effects/use-previous';
|
||||||
import Nag from 'component/common/nag';
|
import Nag from 'component/common/nag';
|
||||||
|
@ -524,7 +523,7 @@ function App(props: Props) {
|
||||||
<Router />
|
<Router />
|
||||||
<ModalRouter />
|
<ModalRouter />
|
||||||
<React.Suspense fallback={null}>{renderFiledrop && <FileDrop />}</React.Suspense>
|
<React.Suspense fallback={null}>{renderFiledrop && <FileDrop />}</React.Suspense>
|
||||||
{isMobile ? <FileRenderMobile /> : <FileRenderFloating />}
|
<FileRenderFloating />
|
||||||
<React.Suspense fallback={null}>
|
<React.Suspense fallback={null}>
|
||||||
{isEnhancedLayout && <Yrbl className="yrbl--enhanced" />}
|
{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 { connect } from 'react-redux';
|
||||||
import { selectTitleForUri, selectClaimIsNsfwForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
import { selectTitleForUri, makeSelectClaimWasPurchased, selectClaimForUri } from 'redux/selectors/claims';
|
||||||
import { makeSelectFileInfoForUri, makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
|
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
|
||||||
import {
|
import {
|
||||||
makeSelectNextUrlForCollectionAndUrl,
|
makeSelectNextUrlForCollectionAndUrl,
|
||||||
makeSelectPreviousUrlForCollectionAndUrl,
|
makeSelectPreviousUrlForCollectionAndUrl,
|
||||||
|
@ -14,26 +14,29 @@ import {
|
||||||
} from 'redux/selectors/content';
|
} from 'redux/selectors/content';
|
||||||
import { selectClientSetting } from 'redux/selectors/settings';
|
import { selectClientSetting } from 'redux/selectors/settings';
|
||||||
import { selectCostInfoForUri } from 'lbryinc';
|
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 { doFetchRecommendedContent } from 'redux/actions/search';
|
||||||
import { doAnaltyicsPurchaseEvent } from 'redux/actions/app';
|
|
||||||
import { withRouter } from 'react-router';
|
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';
|
import FileRenderFloating from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
const { location } = props;
|
||||||
|
|
||||||
const playingUri = selectPlayingUri(state);
|
const playingUri = selectPlayingUri(state);
|
||||||
const primaryUri = selectPrimaryUri(state);
|
const { uri, collectionId } = playingUri || {};
|
||||||
const uri = playingUri && playingUri.uri;
|
|
||||||
const collectionId = playingUri && playingUri.collectionId;
|
const claim = selectClaimForUri(state, uri);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uri,
|
uri,
|
||||||
primaryUri,
|
|
||||||
playingUri,
|
playingUri,
|
||||||
|
primaryUri: selectPrimaryUri(state),
|
||||||
title: selectTitleForUri(state, uri),
|
title: selectTitleForUri(state, uri),
|
||||||
fileInfo: makeSelectFileInfoForUri(uri)(state),
|
isFloating: makeSelectIsPlayerFloating(location)(state),
|
||||||
mature: selectClaimIsNsfwForUri(state, uri),
|
|
||||||
isFloating: makeSelectIsPlayerFloating(props.location)(state),
|
|
||||||
streamingUrl: makeSelectStreamingUrlForUri(uri)(state),
|
streamingUrl: makeSelectStreamingUrlForUri(uri)(state),
|
||||||
floatingPlayerEnabled: selectClientSetting(state, SETTINGS.FLOATING_PLAYER),
|
floatingPlayerEnabled: selectClientSetting(state, SETTINGS.FLOATING_PLAYER),
|
||||||
renderMode: makeSelectFileRenderModeForUri(uri)(state),
|
renderMode: makeSelectFileRenderModeForUri(uri)(state),
|
||||||
|
@ -43,26 +46,17 @@ const select = (state, props) => {
|
||||||
nextListUri: collectionId && makeSelectNextUrlForCollectionAndUrl(collectionId, uri)(state),
|
nextListUri: collectionId && makeSelectNextUrlForCollectionAndUrl(collectionId, uri)(state),
|
||||||
previousListUri: collectionId && makeSelectPreviousUrlForCollectionAndUrl(collectionId, uri)(state),
|
previousListUri: collectionId && makeSelectPreviousUrlForCollectionAndUrl(collectionId, uri)(state),
|
||||||
collectionId,
|
collectionId,
|
||||||
|
isCurrentClaimLive: selectIsActiveLivestreamForUri(state, uri),
|
||||||
|
channelClaimId: claim && getChannelIdFromClaim(claim),
|
||||||
|
mobilePlayerDimensions: selectMobilePlayerDimensions(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
const perform = {
|
||||||
closeFloatingPlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
doFetchRecommendedContent,
|
||||||
doFetchRecommendedContent: (uri, mature) => dispatch(doFetchRecommendedContent(uri, mature)),
|
doUriInitiatePlay,
|
||||||
doPlayUri: (uri, collectionId, hideFailModal) =>
|
doSetPlayingUri,
|
||||||
dispatch(
|
doSetMobilePlayerDimensions,
|
||||||
doPlayUri(
|
};
|
||||||
uri,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
(fileInfo) => {
|
|
||||||
dispatch(doAnaltyicsPurchaseEvent(fileInfo));
|
|
||||||
},
|
|
||||||
hideFailModal
|
|
||||||
),
|
|
||||||
dispatch(doSetPlayingUri({ uri, collectionId }))
|
|
||||||
),
|
|
||||||
clearSecondarySource: (uri) => dispatch(doSetPlayingUri({ uri })),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default withRouter(connect(select, perform)(FileRenderFloating));
|
export default withRouter(connect(select, perform)(FileRenderFloating));
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
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 Button from 'component/button';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import LoadingScreen from 'component/common/loading-screen';
|
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 { PRIMARY_PLAYER_WRAPPER_CLASS } from 'page/file/view';
|
||||||
import Draggable from 'react-draggable';
|
import Draggable from 'react-draggable';
|
||||||
import { onFullscreenChange } from 'util/full-screen';
|
import { onFullscreenChange } from 'util/full-screen';
|
||||||
import { generateListSearchUrlParams, formatLbryUrlForWeb } from 'util/url';
|
import { generateListSearchUrlParams } from 'util/url';
|
||||||
import { useIsMobile } from 'effects/use-screensize';
|
import { useIsMobile } from 'effects/use-screensize';
|
||||||
import debounce from 'util/debounce';
|
import debounce from 'util/debounce';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { isURIEqual } from 'util/lbryURI';
|
import { isURIEqual } from 'util/lbryURI';
|
||||||
import AutoplayCountdown from 'component/autoplayCountdown';
|
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
|
// scss/init/vars.scss
|
||||||
// --header-height
|
// --header-height
|
||||||
const HEADER_HEIGHT = 60;
|
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 IS_DESKTOP_MAC = typeof process === 'object' ? process.platform === 'darwin' : false;
|
||||||
const DEBOUNCE_WINDOW_RESIZE_HANDLER_MS = 100;
|
const DEBOUNCE_WINDOW_RESIZE_HANDLER_MS = 100;
|
||||||
export const INLINE_PLAYER_WRAPPER_CLASS = 'inline-player__wrapper';
|
export const INLINE_PLAYER_WRAPPER_CLASS = 'inline-player__wrapper';
|
||||||
|
export const FLOATING_PLAYER_CLASS = 'content__viewer--floating';
|
||||||
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(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isFloating: boolean,
|
isFloating: boolean,
|
||||||
fileInfo: FileListItem,
|
|
||||||
mature: boolean,
|
|
||||||
uri: string,
|
uri: string,
|
||||||
streamingUrl?: string,
|
streamingUrl?: string,
|
||||||
title: ?string,
|
title: ?string,
|
||||||
floatingPlayerEnabled: boolean,
|
floatingPlayerEnabled: boolean,
|
||||||
closeFloatingPlayer: () => void,
|
|
||||||
clearSecondarySource: (string) => void,
|
|
||||||
renderMode: string,
|
renderMode: string,
|
||||||
playingUri: ?PlayingUri,
|
playingUri: ?PlayingUri,
|
||||||
primaryUri: ?string,
|
primaryUri: ?string,
|
||||||
videoTheaterMode: boolean,
|
videoTheaterMode: boolean,
|
||||||
doFetchRecommendedContent: (string, boolean) => void,
|
|
||||||
doPlayUri: (string, string, boolean) => void,
|
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
costInfo: any,
|
costInfo: any,
|
||||||
claimWasPurchased: boolean,
|
claimWasPurchased: boolean,
|
||||||
nextListUri: string,
|
nextListUri: string,
|
||||||
previousListUri: 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) {
|
export default function FileRenderFloating(props: Props) {
|
||||||
const {
|
const {
|
||||||
fileInfo,
|
|
||||||
mature,
|
|
||||||
uri,
|
uri,
|
||||||
streamingUrl,
|
streamingUrl,
|
||||||
title,
|
title,
|
||||||
isFloating,
|
isFloating,
|
||||||
closeFloatingPlayer,
|
|
||||||
clearSecondarySource,
|
|
||||||
floatingPlayerEnabled,
|
floatingPlayerEnabled,
|
||||||
renderMode,
|
renderMode,
|
||||||
playingUri,
|
playingUri,
|
||||||
primaryUri,
|
primaryUri,
|
||||||
videoTheaterMode,
|
videoTheaterMode,
|
||||||
doFetchRecommendedContent,
|
|
||||||
doPlayUri,
|
|
||||||
collectionId,
|
collectionId,
|
||||||
costInfo,
|
costInfo,
|
||||||
claimWasPurchased,
|
claimWasPurchased,
|
||||||
nextListUri,
|
nextListUri,
|
||||||
previousListUri,
|
previousListUri,
|
||||||
|
doFetchRecommendedContent,
|
||||||
|
doUriInitiatePlay,
|
||||||
|
doSetPlayingUri,
|
||||||
|
// mobile only
|
||||||
|
isCurrentClaimLive,
|
||||||
|
channelClaimId,
|
||||||
|
mobilePlayerDimensions,
|
||||||
|
doSetMobilePlayerDimensions,
|
||||||
} = props;
|
} = 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 playingUriSource = playingUri && playingUri.source;
|
||||||
const isComment = playingUriSource === 'comment';
|
const isComment = playingUriSource === 'comment';
|
||||||
const isMobile = useIsMobile();
|
const mainFilePlaying = (!isFloating || !isMobile) && primaryUri && isURIEqual(uri, primaryUri);
|
||||||
const mainFilePlaying = !isFloating && primaryUri && isURIEqual(uri, primaryUri);
|
const noFloatingPlayer = !isFloating || isMobile || !floatingPlayerEnabled || hideFloatingPlayer;
|
||||||
|
|
||||||
const [fileViewerRect, setFileViewerRect] = useState();
|
const [fileViewerRect, setFileViewerRect] = React.useState();
|
||||||
const [wasDragging, setWasDragging] = useState(false);
|
const [wasDragging, setWasDragging] = React.useState(false);
|
||||||
const [doNavigate, setDoNavigate] = useState(false);
|
const [doNavigate, setDoNavigate] = React.useState(false);
|
||||||
const [playNextUrl, setPlayNextUrl] = useState(true);
|
const [shouldPlayNext, setPlayNext] = React.useState(true);
|
||||||
const [countdownCanceled, setCountdownCanceled] = useState(false);
|
const [countdownCanceled, setCountdownCanceled] = React.useState(false);
|
||||||
const [position, setPosition] = usePersistedState('floating-file-viewer:position', {
|
const [position, setPosition] = usePersistedState('floating-file-viewer:position', {
|
||||||
x: -25,
|
x: -25,
|
||||||
y: window.innerHeight - 400,
|
y: window.innerHeight - 400,
|
||||||
|
@ -152,56 +115,12 @@ export default function FileRenderFloating(props: Props) {
|
||||||
|
|
||||||
const isFree = costInfo && costInfo.cost === 0;
|
const isFree = costInfo && costInfo.cost === 0;
|
||||||
const canViewFile = isFree || claimWasPurchased;
|
const canViewFile = isFree || claimWasPurchased;
|
||||||
const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode);
|
const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode) || isCurrentClaimLive;
|
||||||
const isReadyToPlay = isPlayable && (streamingUrl || (fileInfo && fileInfo.completed));
|
const isReadyToPlay = isPlayable && streamingUrl;
|
||||||
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');
|
|
||||||
|
|
||||||
function restoreToRelativePosition() {
|
// ****************************************************************************
|
||||||
const newX = Math.round(relativePosRef.current.x * getScreenWidth());
|
// FUNCTIONS
|
||||||
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]);
|
|
||||||
|
|
||||||
const handleResize = React.useCallback(() => {
|
const handleResize = React.useCallback(() => {
|
||||||
const element = mainFilePlaying
|
const element = mainFilePlaying
|
||||||
|
@ -226,69 +145,121 @@ export default function FileRenderFloating(props: Props) {
|
||||||
|
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
setFileViewerRect({ ...objectRect, windowOffset: window.pageYOffset });
|
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)) {
|
if (playingUri && (playingUri.primaryUri || playingUri.uri)) {
|
||||||
handleResize();
|
handleResize();
|
||||||
setCountdownCanceled(false);
|
|
||||||
}
|
}
|
||||||
}, [handleResize, playingUri, videoTheaterMode]);
|
}, [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();
|
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 () => {
|
return () => {
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', onWindowResize);
|
||||||
onFullscreenChange(window, 'remove', handleResize);
|
if (!isFloating) onFullscreenChange(window, 'remove', handleResize);
|
||||||
};
|
};
|
||||||
}, [handleResize]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Only listen to these and avoid infinite loops
|
||||||
if (isFloating) {
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
doFetchRecommendedContent(uri, mature);
|
}, [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(
|
React.useEffect(() => {
|
||||||
(playUri) => {
|
if (isFloating) doFetchRecommendedContent(uri);
|
||||||
setDoNavigate(false);
|
}, [doFetchRecommendedContent, isFloating, uri]);
|
||||||
if (!isFloating) {
|
|
||||||
const navigateUrl = formatLbryUrlForWeb(playUri);
|
React.useEffect(() => {
|
||||||
push({
|
if (isFloating && isMobile) {
|
||||||
pathname: navigateUrl,
|
doSetMobilePlayerDimensions({ height: null, width: null });
|
||||||
search: collectionId && generateListSearchUrlParams(collectionId),
|
|
||||||
state: { collectionId, forceAutoplay: true, hideFloatingPlayer: true },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
doPlayUri(playUri, collectionId, true);
|
|
||||||
}
|
}
|
||||||
},
|
}, [doSetMobilePlayerDimensions, doSetPlayingUri, isFloating, isMobile]);
|
||||||
[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]);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isPlayable ||
|
!isPlayable ||
|
||||||
!uri ||
|
!uri ||
|
||||||
(isFloating && (isMobile || !floatingPlayerEnabled || hideFloatingPlayer)) ||
|
(isFloating && noFloatingPlayer) ||
|
||||||
(collectionId && !isFloating && ((!canViewFile && !nextListUri) || countdownCanceled))
|
(collectionId && !isFloating && ((!canViewFile && !nextListUri) || countdownCanceled))
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ****************************************************************************
|
||||||
|
// RENDER
|
||||||
|
// ****************************************************************************
|
||||||
|
|
||||||
function handleDragStart() {
|
function handleDragStart() {
|
||||||
// Not really necessary, but reset just in case 'handleStop' didn't fire.
|
// Not really necessary, but reset just in case 'handleStop' didn't fire.
|
||||||
setWasDragging(false);
|
setWasDragging(false);
|
||||||
|
@ -306,13 +277,13 @@ export default function FileRenderFloating(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragStop(e, ui) {
|
function handleDragStop(e, ui) {
|
||||||
if (wasDragging) {
|
if (wasDragging) setWasDragging(false);
|
||||||
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) {
|
if (newPos.x !== position.x || newPos.y !== position.y) {
|
||||||
newPos = clampRectToScreen(newPos.x, newPos.y, getFloatingPlayerRect());
|
newPos = clampFloatingPlayerToScreen(newPos.x, newPos.y);
|
||||||
|
|
||||||
setPosition(newPos);
|
setPosition(newPos);
|
||||||
relativePosRef.current = calculateRelativePos(newPos.x, newPos.y);
|
relativePosRef.current = calculateRelativePos(newPos.x, newPos.y);
|
||||||
}
|
}
|
||||||
|
@ -326,17 +297,18 @@ export default function FileRenderFloating(props: Props) {
|
||||||
defaultPosition={position}
|
defaultPosition={position}
|
||||||
position={isFloating ? position : { x: 0, y: 0 }}
|
position={isFloating ? position : { x: 0, y: 0 }}
|
||||||
bounds="parent"
|
bounds="parent"
|
||||||
disabled={!isFloating}
|
disabled={noFloatingPlayer}
|
||||||
handle=".draggable"
|
handle=".draggable"
|
||||||
cancel=".button"
|
cancel=".button"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={classnames('content__viewer', {
|
className={classnames('content__viewer', {
|
||||||
'content__viewer--floating': isFloating,
|
[FLOATING_PLAYER_CLASS]: isFloating,
|
||||||
'content__viewer--inline': !isFloating,
|
'content__viewer--inline': !isFloating,
|
||||||
'content__viewer--secondary': isComment,
|
'content__viewer--secondary': isComment,
|
||||||
'content__viewer--theater-mode': !isFloating && videoTheaterMode && playingUri?.uri === primaryUri,
|
'content__viewer--theater-mode': !isFloating && videoTheaterMode && playingUri?.uri === primaryUri,
|
||||||
'content__viewer--disable-click': wasDragging,
|
'content__viewer--disable-click': wasDragging,
|
||||||
|
'content__viewer--mobile': isMobile,
|
||||||
})}
|
})}
|
||||||
style={
|
style={
|
||||||
!isFloating && fileViewerRect
|
!isFloating && fileViewerRect
|
||||||
|
@ -344,42 +316,36 @@ export default function FileRenderFloating(props: Props) {
|
||||||
width: fileViewerRect.width,
|
width: fileViewerRect.width,
|
||||||
height: fileViewerRect.height,
|
height: fileViewerRect.height,
|
||||||
left: fileViewerRect.x,
|
left: fileViewerRect.x,
|
||||||
top:
|
top: isMobile
|
||||||
fileViewerRect.windowOffset +
|
? HEADER_HEIGHT_MOBILE
|
||||||
fileViewerRect.top -
|
: fileViewerRect.windowOffset + fileViewerRect.top - HEADER_HEIGHT - (IS_DESKTOP_MAC ? 24 : 0),
|
||||||
(isMobile ? HEADER_HEIGHT_MOBILE : HEADER_HEIGHT) -
|
|
||||||
(IS_DESKTOP_MAC ? 24 : 0),
|
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div className={classnames('content__wrapper', { 'content__wrapper--floating': isFloating })}>
|
||||||
className={classnames('content__wrapper', {
|
|
||||||
'content__wrapper--floating': isFloating,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{isFloating && (
|
{isFloating && (
|
||||||
<Button
|
<Button
|
||||||
title={__('Close')}
|
title={__('Close')}
|
||||||
onClick={closeFloatingPlayer}
|
onClick={() => doSetPlayingUri({ uri: null })}
|
||||||
icon={ICONS.REMOVE}
|
icon={ICONS.REMOVE}
|
||||||
button="primary"
|
button="primary"
|
||||||
className="content__floating-close"
|
className="content__floating-close"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isReadyToPlay ? (
|
{isCurrentClaimLive && channelClaimId ? (
|
||||||
|
<LivestreamIframeRender channelClaimId={channelClaimId} showLivestream mobileVersion />
|
||||||
|
) : isReadyToPlay ? (
|
||||||
<FileRender className="draggable" uri={uri} />
|
<FileRender className="draggable" uri={uri} />
|
||||||
) : (
|
) : collectionId && !canViewFile ? (
|
||||||
<>
|
|
||||||
{collectionId && !canViewFile ? (
|
|
||||||
<div className="content__loading">
|
<div className="content__loading">
|
||||||
<AutoplayCountdown
|
<AutoplayCountdown
|
||||||
nextRecommendedUri={nextListUri}
|
nextRecommendedUri={nextListUri}
|
||||||
doNavigate={() => setDoNavigate(true)}
|
doNavigate={() => setDoNavigate(true)}
|
||||||
doReplay={() => doPlayUri(uri, collectionId, false)}
|
doReplay={() => doUriInitiatePlay(uri, collectionId, false, isFloating)}
|
||||||
doPrevious={() => {
|
doPrevious={() => {
|
||||||
setPlayNextUrl(false);
|
setPlayNext(false);
|
||||||
setDoNavigate(true);
|
setDoNavigate(true);
|
||||||
}}
|
}}
|
||||||
onCanceled={() => setCountdownCanceled(true)}
|
onCanceled={() => setCountdownCanceled(true)}
|
||||||
|
@ -387,15 +353,15 @@ export default function FileRenderFloating(props: Props) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<LoadingScreen status={loadingMessage} />
|
<LoadingScreen status={__('Loading')} />
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isFloating && (
|
{isFloating && (
|
||||||
<div className="draggable content__info">
|
<div className="draggable content__info">
|
||||||
<div className="claim-preview__title" title={title || uri}>
|
<div className="claim-preview__title" title={title || uri}>
|
||||||
<Button label={title || uri} navigate={navigateUrl} button="link" className="content__floating-link" />
|
<Button label={title || uri} navigate={navigateUrl} button="link" className="content__floating-link" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UriIndicator link uri={uri} />
|
<UriIndicator link uri={uri} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doUriInitiatePlay, doSetPlayingUri } from 'redux/actions/content';
|
import { doUriInitiatePlay, doSetPlayingUri, doSetPrimaryUri } from 'redux/actions/content';
|
||||||
import { selectThumbnailForUri, selectClaimForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
import { selectThumbnailForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
||||||
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { selectCostInfoForUri } from 'lbryinc';
|
import { selectCostInfoForUri } from 'lbryinc';
|
||||||
|
@ -14,16 +14,11 @@ import {
|
||||||
makeSelectFileRenderModeForUri,
|
makeSelectFileRenderModeForUri,
|
||||||
} from 'redux/selectors/content';
|
} from 'redux/selectors/content';
|
||||||
import FileRenderInitiator from './view';
|
import FileRenderInitiator from './view';
|
||||||
import { getChannelIdFromClaim } from 'util/claim';
|
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
|
||||||
import { selectActiveLivestreamForChannel } from 'redux/selectors/livestream';
|
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const { uri } = props;
|
const { uri } = props;
|
||||||
|
|
||||||
const claim = selectClaimForUri(state, uri);
|
|
||||||
const claimId = claim && claim.claim_id;
|
|
||||||
const channelClaimId = claim && getChannelIdFromClaim(claim);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimThumbnail: selectThumbnailForUri(state, uri),
|
claimThumbnail: selectThumbnailForUri(state, uri),
|
||||||
fileInfo: makeSelectFileInfoForUri(uri)(state),
|
fileInfo: makeSelectFileInfoForUri(uri)(state),
|
||||||
|
@ -35,14 +30,14 @@ const select = (state, props) => {
|
||||||
renderMode: makeSelectFileRenderModeForUri(uri)(state),
|
renderMode: makeSelectFileRenderModeForUri(uri)(state),
|
||||||
claimWasPurchased: makeSelectClaimWasPurchased(uri)(state),
|
claimWasPurchased: makeSelectClaimWasPurchased(uri)(state),
|
||||||
authenticated: selectUserVerifiedEmail(state),
|
authenticated: selectUserVerifiedEmail(state),
|
||||||
activeLivestreamForChannel: channelClaimId && selectActiveLivestreamForChannel(state, channelClaimId),
|
isCurrentClaimLive: selectIsActiveLivestreamForUri(state, uri),
|
||||||
claimId,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const perform = {
|
const perform = {
|
||||||
doUriInitiatePlay,
|
doUriInitiatePlay,
|
||||||
doSetPlayingUri,
|
doSetPlayingUri,
|
||||||
|
doSetPrimaryUri,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRouter(connect(select, perform)(FileRenderInitiator));
|
export default withRouter(connect(select, perform)(FileRenderInitiator));
|
||||||
|
|
|
@ -31,10 +31,10 @@ type Props = {
|
||||||
claimWasPurchased: boolean,
|
claimWasPurchased: boolean,
|
||||||
authenticated: boolean,
|
authenticated: boolean,
|
||||||
videoTheaterMode: boolean,
|
videoTheaterMode: boolean,
|
||||||
activeLivestreamForChannel?: any,
|
isCurrentClaimLive?: boolean,
|
||||||
claimId?: string,
|
|
||||||
doUriInitiatePlay: (uri: string, collectionId: ?string, isPlayable: boolean) => void,
|
doUriInitiatePlay: (uri: string, collectionId: ?string, isPlayable: boolean) => void,
|
||||||
doSetPlayingUri: ({ uri: ?string }) => void,
|
doSetPlayingUri: ({ uri: ?string }) => void,
|
||||||
|
doSetPrimaryUri: (uri: ?string) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FileRenderInitiator(props: Props) {
|
export default function FileRenderInitiator(props: Props) {
|
||||||
|
@ -53,10 +53,10 @@ export default function FileRenderInitiator(props: Props) {
|
||||||
claimWasPurchased,
|
claimWasPurchased,
|
||||||
authenticated,
|
authenticated,
|
||||||
videoTheaterMode,
|
videoTheaterMode,
|
||||||
activeLivestreamForChannel,
|
isCurrentClaimLive,
|
||||||
claimId,
|
|
||||||
doUriInitiatePlay,
|
doUriInitiatePlay,
|
||||||
doSetPlayingUri,
|
doSetPlayingUri,
|
||||||
|
doSetPrimaryUri,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const containerRef = React.useRef<any>();
|
const containerRef = React.useRef<any>();
|
||||||
|
@ -76,9 +76,8 @@ export default function FileRenderInitiator(props: Props) {
|
||||||
|
|
||||||
const isFree = costInfo && costInfo.cost === 0;
|
const isFree = costInfo && costInfo.cost === 0;
|
||||||
const canViewFile = isFree || claimWasPurchased;
|
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 isText = RENDER_MODES.TEXT_MODES.includes(renderMode);
|
||||||
const isCurrentClaimLive = activeLivestreamForChannel && claimId && activeLivestreamForChannel.claimId === claimId;
|
|
||||||
const isMobileClaimLive = isMobile && isCurrentClaimLive;
|
const isMobileClaimLive = isMobile && isCurrentClaimLive;
|
||||||
const foundCover = thumbnail !== FileRenderPlaceholder;
|
const foundCover = thumbnail !== FileRenderPlaceholder;
|
||||||
|
|
||||||
|
@ -87,12 +86,13 @@ export default function FileRenderInitiator(props: Props) {
|
||||||
const shouldRedirect = !authenticated && !isFree;
|
const shouldRedirect = !authenticated && !isFree;
|
||||||
|
|
||||||
React.useEffect(() => {
|
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.
|
// instead of showing an empty cover image. Needs cover to fill the space with the player.
|
||||||
if (isMobileClaimLive && foundCover) {
|
if (isMobileClaimLive && foundCover) {
|
||||||
doSetPlayingUri({ uri });
|
doSetPlayingUri({ uri });
|
||||||
|
doSetPrimaryUri(uri);
|
||||||
}
|
}
|
||||||
}, [doSetPlayingUri, foundCover, isMobileClaimLive, uri]);
|
}, [doSetPlayingUri, doSetPrimaryUri, foundCover, isMobileClaimLive, uri]);
|
||||||
|
|
||||||
function doAuthRedirect() {
|
function doAuthRedirect() {
|
||||||
history.push(`/$/${PAGES.AUTH}?redirect=${encodeURIComponent(location.pathname)}`);
|
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
|
// $FlowFixMe
|
||||||
import { grey } from '@mui/material/colors';
|
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 { SwipeableDrawer as MUIDrawer } from '@mui/material';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
@ -31,18 +32,29 @@ export default function SwipeableDrawer(props: Props) {
|
||||||
|
|
||||||
const [coverHeight, setCoverHeight] = React.useState();
|
const [coverHeight, setCoverHeight] = React.useState();
|
||||||
|
|
||||||
const videoHeight = coverHeight || (mobilePlayerDimensions ? mobilePlayerDimensions.height : 0);
|
const videoHeight = (mobilePlayerDimensions && mobilePlayerDimensions.height) || coverHeight || 0;
|
||||||
|
|
||||||
React.useEffect(() => {
|
const handleResize = React.useCallback(() => {
|
||||||
if (open && !mobilePlayerDimensions) {
|
const element =
|
||||||
const element = document.querySelector(`.file-page__video-container`);
|
document.querySelector(`.${PRIMARY_IMAGE_WRAPPER_CLASS}`) ||
|
||||||
|
document.querySelector(`.${PRIMARY_PLAYER_WRAPPER_CLASS}`);
|
||||||
|
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
if (element) {
|
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
setCoverHeight(rect.height);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}, [handleResize, mobilePlayerDimensions, open]);
|
||||||
}, [coverHeight, mobilePlayerDimensions, open]);
|
|
||||||
|
|
||||||
// Reset scroll position when opening: avoid broken position where
|
// Reset scroll position when opening: avoid broken position where
|
||||||
// the drawer is lower than the video
|
// 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 { makeSelectFileRenderModeForUri, makeSelectContentPositionForUri } from 'redux/selectors/content';
|
||||||
import { makeSelectCommentsListTitleForUri, selectSettingsByChannelId } from 'redux/selectors/comments';
|
import { makeSelectCommentsListTitleForUri, selectSettingsByChannelId } from 'redux/selectors/comments';
|
||||||
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
|
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
|
||||||
import { doSetMobilePlayerDimensions } from 'redux/actions/app';
|
|
||||||
import { getChannelIdFromClaim } from 'util/claim';
|
import { getChannelIdFromClaim } from 'util/claim';
|
||||||
|
|
||||||
import FilePage from './view';
|
import FilePage from './view';
|
||||||
|
@ -53,7 +52,6 @@ const perform = {
|
||||||
doSetContentHistoryItem,
|
doSetContentHistoryItem,
|
||||||
doSetPrimaryUri,
|
doSetPrimaryUri,
|
||||||
clearPosition,
|
clearPosition,
|
||||||
doSetMobilePlayerDimensions,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRouter(connect(select, perform)(FilePage));
|
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" */));
|
const PostViewer = lazyImport(() => import('component/postViewer' /* webpackChunkName: "postViewer" */));
|
||||||
|
|
||||||
export const PRIMARY_PLAYER_WRAPPER_CLASS = 'file-page__video-container';
|
export const PRIMARY_PLAYER_WRAPPER_CLASS = 'file-page__video-container';
|
||||||
|
export const PRIMARY_IMAGE_WRAPPER_CLASS = 'file-render__img-container';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
costInfo: ?{ includesData: boolean, cost: number },
|
costInfo: ?{ includesData: boolean, cost: number },
|
||||||
|
@ -164,7 +165,7 @@ export default function FilePage(props: Props) {
|
||||||
if (renderMode === RENDER_MODES.IMAGE) {
|
if (renderMode === RENDER_MODES.IMAGE) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="file-render--img-container">
|
<div className={PRIMARY_IMAGE_WRAPPER_CLASS}>
|
||||||
<FileRenderInitiator uri={uri} />
|
<FileRenderInitiator uri={uri} />
|
||||||
<FileRenderInline uri={uri} />
|
<FileRenderInline uri={uri} />
|
||||||
</div>
|
</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,
|
type: ACTIONS.SET_MOBILE_PLAYER_DIMENSIONS,
|
||||||
data: { heightWidth: { height, width } },
|
data: { heightWidth: { height, width } },
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,6 +19,7 @@ import Lbry from 'lbry';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { selectCostInfoForUri, Lbryio } from 'lbryinc';
|
import { selectCostInfoForUri, Lbryio } from 'lbryinc';
|
||||||
import { selectClientSetting, selectosNotificationsEnabled, selectDaemonSettings } from 'redux/selectors/settings';
|
import { selectClientSetting, selectosNotificationsEnabled, selectDaemonSettings } from 'redux/selectors/settings';
|
||||||
|
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
|
||||||
|
|
||||||
const DOWNLOAD_POLL_INTERVAL = 1000;
|
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))));
|
return (dispatch: Dispatch) => dispatch(doPlayUri(uri, false, true, () => dispatch(doAnalyticsView(uri))));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doUriInitiatePlay(uri: string, collectionId?: string, isPlayable?: boolean) {
|
export function doUriInitiatePlay(uri: string, collectionId?: string, isPlayable?: boolean, isFloating?: boolean) {
|
||||||
return (dispatch: Dispatch) => {
|
return (dispatch: Dispatch, getState: () => any) => {
|
||||||
dispatch(doSetPrimaryUri(uri));
|
const state = getState();
|
||||||
|
const isLive = selectIsActiveLivestreamForUri(state, uri);
|
||||||
|
|
||||||
|
if (!isFloating) dispatch(doSetPrimaryUri(uri));
|
||||||
|
|
||||||
if (isPlayable) {
|
if (isPlayable) {
|
||||||
dispatch(doSetPlayingUri({ uri, collectionId }));
|
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 || {};
|
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
|
// select non-pending claims without sources for given channel
|
||||||
export const makeSelectLivestreamsForChannelId = (channelId: string) =>
|
export const makeSelectLivestreamsForChannelId = (channelId: string) =>
|
||||||
createSelector(selectState, selectMyClaims, (livestreamState, myClaims = []) => {
|
createSelector(selectState, selectMyClaims, (livestreamState, myClaims = []) => {
|
||||||
|
@ -23,9 +29,6 @@ export const makeSelectLivestreamsForChannelId = (channelId: string) =>
|
||||||
.sort((a, b) => b.timestamp - a.timestamp); // newest first
|
.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) =>
|
export const makeSelectIsFetchingLivestreams = (channelId: string) =>
|
||||||
createSelector(selectFetchingLivestreams, (fetchingLivestreams) => Boolean(fetchingLivestreams[channelId]));
|
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(
|
export const selectIsActiveLivestreamForUri = createCachedSelector(
|
||||||
(state, uri) => uri,
|
(state, uri) => uri,
|
||||||
selectActiveLivestreams,
|
selectActiveLivestreams,
|
||||||
|
@ -86,7 +87,3 @@ export const selectActiveLivestreamForChannel = createCachedSelector(
|
||||||
return activeLivestreams[channelId] || null;
|
return activeLivestreams[channelId] || null;
|
||||||
}
|
}
|
||||||
)((state, channelId) => String(channelId));
|
)((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;
|
margin-bottom: 0;
|
||||||
height: calc(var(--floating-viewer-height) + var(--floating-viewer-info-height));
|
height: calc(var(--floating-viewer-height) + var(--floating-viewer-info-height));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
left: calc(var(--spacing-l) + var(--spacing-s));
|
top: 0;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
|
|
||||||
border: 2px solid black;
|
border: 2px solid black;
|
||||||
|
@ -53,6 +53,7 @@
|
||||||
.video-js--tap-to-unmute {
|
.video-js--tap-to-unmute {
|
||||||
max-width: calc(var(--floating-viewer-width) - (var(--spacing-xs) * 3) - 42px);
|
max-width: calc(var(--floating-viewer-width) - (var(--spacing-xs) * 3) - 42px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content__floating-close {
|
.content__floating-close {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
|
|
|
@ -237,7 +237,7 @@
|
||||||
max-height: none;
|
max-height: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-render--img-container {
|
.file-render__img-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 16 / 9;
|
aspect-ratio: 16 / 9;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13921,11 +13921,12 @@ react-dom@^16.8.2:
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
scheduler "^0.19.0"
|
scheduler "^0.19.0"
|
||||||
|
|
||||||
react-draggable@^3.3.0:
|
react-draggable@^4.4.4:
|
||||||
version "3.3.2"
|
version "4.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-3.3.2.tgz#966ef1d90f2387af3c2d8bd3516f601ea42ca359"
|
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.4.tgz#5b26d9996be63d32d285a426f41055de87e59b2f"
|
||||||
|
integrity sha512-6e0WdcNLwpBx/YIDpoyd2Xb04PB0elrDrulKUgdrIlwuYvxh5Ok9M+F8cljm8kPXXs43PmMzek9RrB1b7mLMqA==
|
||||||
dependencies:
|
dependencies:
|
||||||
classnames "^2.2.5"
|
clsx "^1.1.1"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
react-error-overlay@^1.0.9:
|
react-error-overlay@^1.0.9:
|
||||||
|
|
Loading…
Reference in a new issue