lbry-desktop/ui/component/fileRenderInitiator/view.jsx
infinite-persistence df2a717e8d Change poster-fetch implementation
## Ticket
1526: strange thumbnail size requested on mobile layout (pc only?)

## General Problem
It was trying to fetch based on the exact size of the video container, which would satisfy Core Vitals (in an overkill way), but would bring several issues:
- server-side caching would not work since everyone's window size is different in a responsive layout design.
- the additional 200ms wait for container size to settle down is not good (hardcoded wait time).
- the code did not account for device-pixel-ratio, so it's quite a futile effort.

Aside:  In the past, we used to take the same image url as the tiles, so the video poster would appear immediately from due to browser cache, but the quality is bad because the tile requested a much smaller size.

The embed wrapper was not going through the CDN either as a null `containerRef` was passed in.

## Change
Removed the container-size check and just request for 1280x720. Reasons for this size:
- On average, that would be the ballpark of the final calculated value anyway for the average screen (+DPR consideration).
- That seems to be the current suggested thumbnail size in most recommendations.
- Our YT Sync is grabbing a much smaller size anyway.
2022-06-16 15:18:15 -04:00

210 lines
7.2 KiB
JavaScript

// @flow
// This component is entirely for triggering the start of a file view
// The actual viewer for a file exists in TextViewer and FileRenderFloating
// They can't exist in one component because we need to handle/listen for the start of a new file view
// while a file is currently being viewed
import { useIsMobile } from 'effects/use-screensize';
import React from 'react';
import classnames from 'classnames';
import * as PAGES from 'constants/pages';
import * as RENDER_MODES from 'constants/file_render_modes';
import Button from 'component/button';
import Nag from 'component/common/nag';
import * as COLLECTIONS_CONSTS from 'constants/collections';
import { LivestreamContext } from 'page/livestream/view';
import { formatLbryUrlForWeb } from 'util/url';
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
import useFetchLiveStatus from 'effects/use-fetch-live';
import useGetPoster from 'effects/use-get-poster';
type Props = {
channelClaimId: ?string,
isPlaying: boolean,
fileInfo: FileListItem,
uri: string,
history: { push: (string) => void },
location: { search: ?string, pathname: string, href: string, state: { forceAutoplay: boolean } },
obscurePreview: boolean,
insufficientCredits: boolean,
claimThumbnail?: string,
autoplay: boolean,
costInfo: any,
inline: boolean,
renderMode: string,
claimWasPurchased: boolean,
authenticated: boolean,
videoTheaterMode: boolean,
isCurrentClaimLive?: boolean,
isLivestreamClaim: boolean,
customAction?: any,
embedded?: boolean,
parentCommentId?: string,
isMarkdownPost?: boolean,
doUriInitiatePlay: (playingOptions: PlayingUri, isPlayable: boolean) => void,
doFetchChannelLiveStatus: (string) => void,
};
export default function FileRenderInitiator(props: Props) {
const {
channelClaimId,
isPlaying,
fileInfo,
uri,
obscurePreview,
insufficientCredits,
history,
location,
claimThumbnail,
autoplay,
renderMode,
costInfo,
claimWasPurchased,
authenticated,
videoTheaterMode,
isCurrentClaimLive,
isLivestreamClaim,
customAction,
embedded,
parentCommentId,
isMarkdownPost,
doUriInitiatePlay,
doFetchChannelLiveStatus,
} = props;
const theaterMode = renderMode === 'video' || renderMode === 'audio' ? videoTheaterMode : false;
const { livestreamPage, layountRendered } = React.useContext(LivestreamContext) || {};
const isMobile = useIsMobile();
const { search, href, state: locationState, pathname } = location;
const urlParams = search && new URLSearchParams(search);
const collectionId = urlParams && urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID);
// check if there is a time or autoplay parameter, if so force autoplay
const urlTimeParam = href && href.indexOf('t=') > -1;
const forceAutoplayParam = locationState && locationState.forceAutoplay;
const shouldAutoplay = !embedded && (forceAutoplayParam || urlTimeParam || autoplay);
const isFree = costInfo && costInfo.cost === 0;
const canViewFile = isLivestreamClaim
? (layountRendered || isMobile) && isCurrentClaimLive
: isFree || claimWasPurchased;
const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode) || isCurrentClaimLive;
const isText = RENDER_MODES.TEXT_MODES.includes(renderMode);
const renderUnsupported = RENDER_MODES.UNSUPPORTED_IN_THIS_APP.includes(renderMode);
const disabled =
(isLivestreamClaim && !isCurrentClaimLive) ||
renderUnsupported ||
(!fileInfo && insufficientCredits && !claimWasPurchased);
const shouldRedirect = !authenticated && !isFree;
function doAuthRedirect() {
history.push(`/$/${PAGES.AUTH}?redirect=${encodeURIComponent(pathname)}`);
}
// in case of a livestream outside of the livestream page component, like embed
useFetchLiveStatus(isLivestreamClaim && !livestreamPage ? channelClaimId : undefined, doFetchChannelLiveStatus);
const thumbnail = useGetPoster(claimThumbnail);
function handleClick() {
if (embedded && !isPlayable) {
const formattedUrl = formatLbryUrlForWeb(uri);
history.push(formattedUrl);
} else {
viewFile();
}
}
// Wrap this in useCallback because we need to use it to the view effect
// If we don't a new instance will be created for every render and react will think the dependencies have changed, which will add/remove the listener for every render
const viewFile = React.useCallback(() => {
const playingOptions = { uri, collectionId, pathname, source: undefined, commentId: undefined };
if (parentCommentId) {
playingOptions.source = 'comment';
playingOptions.commentId = parentCommentId;
} else if (isMarkdownPost) {
playingOptions.source = 'markdown';
}
doUriInitiatePlay(playingOptions, isPlayable);
}, [collectionId, doUriInitiatePlay, isMarkdownPost, isPlayable, parentCommentId, pathname, uri]);
React.useEffect(() => {
// avoid selecting 'video' anymore -> can cause conflicts with Ad popup videos
const videoOnPage = document.querySelector('.vjs-tech');
if (
(canViewFile || forceAutoplayParam) &&
((shouldAutoplay && (!videoOnPage || forceAutoplayParam) && isPlayable) ||
(!embedded && RENDER_MODES.AUTO_RENDER_MODES.includes(renderMode)))
) {
viewFile();
}
}, [canViewFile, embedded, forceAutoplayParam, isPlayable, renderMode, shouldAutoplay, viewFile]);
/*
once content is playing, let the appropriate <FileRender> take care of it...
but for playables, always render so area can be used to fill with floating player
*/
if (isPlaying && !isPlayable && canViewFile && !collectionId) {
return null;
}
return (
<div
onClick={disabled ? undefined : shouldRedirect ? doAuthRedirect : handleClick}
style={thumbnail && !obscurePreview ? { backgroundImage: `url("${thumbnail}")` } : {}}
className={
embedded
? 'embed__inline-button'
: classnames('content__cover', {
'content__cover--disabled': disabled,
'content__cover--theater-mode': theaterMode && !isMobile,
'content__cover--text': isText,
'card__media--nsfw': obscurePreview,
})
}
>
{embedded && <FileViewerEmbeddedTitle uri={uri} isInApp />}
{renderUnsupported ? (
<Nag
type="helpful"
inline
message={__('This content requires LBRY Desktop to display.')}
actionText={__('Get the App')}
href="https://lbry.com/get"
/>
) : (
!claimWasPurchased &&
insufficientCredits && (
<Nag
type="helpful"
inline
message={__('You need more Credits to purchase this.')}
actionText={__('Open Rewards')}
onClick={() => history.push(`/$/${PAGES.REWARDS}`)}
/>
)
)}
{(!disabled || (embedded && isLivestreamClaim)) && (
<Button
requiresAuth={shouldRedirect}
onClick={handleClick}
iconSize={30}
title={isPlayable ? __('Play') : __('View')}
className={classnames('button--icon', {
'button--play': isPlayable,
'button--view': !isPlayable,
})}
/>
)}
{customAction}
</div>
);
}