mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
perf(app): defer video embed metadata probes until hover (#1893)
This commit is contained in:
1 parent
3fe6217809
commit
d8bad50eec
1 file changed
+33
-9
@@ -24,7 +24,7 @@ import {useMediaFavorite} from '@app/features/messaging/hooks/useMediaFavorite';
|
|||||||
import {useNearViewport} from '@app/features/messaging/hooks/useNearViewport';
|
import {useNearViewport} from '@app/features/messaging/hooks/useNearViewport';
|
||||||
import {createDownloadHandler} from '@app/features/messaging/utils/FileDownloadUtils';
|
import {createDownloadHandler} from '@app/features/messaging/utils/FileDownloadUtils';
|
||||||
import * as ImageCacheUtils from '@app/features/messaging/utils/ImageCacheUtils';
|
import * as ImageCacheUtils from '@app/features/messaging/utils/ImageCacheUtils';
|
||||||
import {buildMediaProxyURL} from '@app/features/messaging/utils/MediaProxyUtils';
|
import {buildMediaProxyURL, mediaDevicePixelRatio} from '@app/features/messaging/utils/MediaProxyUtils';
|
||||||
import {attachmentsToViewerItems, findViewerItemIndex} from '@app/features/messaging/utils/MediaViewerItemUtils';
|
import {attachmentsToViewerItems, findViewerItemIndex} from '@app/features/messaging/utils/MediaViewerItemUtils';
|
||||||
import {decodeThumbHashDataURL} from '@app/features/messaging/utils/ThumbHashUtils';
|
import {decodeThumbHashDataURL} from '@app/features/messaging/utils/ThumbHashUtils';
|
||||||
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
|
import {remFromPx} from '@app/features/theme/layout/RemFromPx';
|
||||||
@@ -33,6 +33,7 @@ import * as ContextMenuCommands from '@app/features/ui/commands/ContextMenuComma
|
|||||||
import * as MediaViewerCommands from '@app/features/ui/commands/MediaViewerCommands';
|
import * as MediaViewerCommands from '@app/features/ui/commands/MediaViewerCommands';
|
||||||
import MobileLayout from '@app/features/ui/state/MobileLayout';
|
import MobileLayout from '@app/features/ui/state/MobileLayout';
|
||||||
import {
|
import {
|
||||||
|
detachMediaElementSource,
|
||||||
VideoPlayer,
|
VideoPlayer,
|
||||||
type VideoPlayerMetadata,
|
type VideoPlayerMetadata,
|
||||||
} from '@app/features/voice/components/media_player/components/VideoPlayer';
|
} from '@app/features/voice/components/media_player/components/VideoPlayer';
|
||||||
@@ -45,7 +46,7 @@ import {PlayIcon, SpeakerHighIcon, SpeakerXIcon} from '@phosphor-icons/react';
|
|||||||
import {AnimatePresence, motion} from 'framer-motion';
|
import {AnimatePresence, motion} from 'framer-motion';
|
||||||
import {observer} from 'mobx-react-lite';
|
import {observer} from 'mobx-react-lite';
|
||||||
import type {FC} from 'react';
|
import type {FC} from 'react';
|
||||||
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
import {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
|
||||||
|
|
||||||
const OPEN_VIDEO_DESCRIPTOR = msg({
|
const OPEN_VIDEO_DESCRIPTOR = msg({
|
||||||
message: 'Open video',
|
message: 'Open video',
|
||||||
@@ -137,7 +138,8 @@ const MobileVideoOverlay: FC<{
|
|||||||
exit={{opacity: 0}}
|
exit={{opacity: 0}}
|
||||||
transition={{duration: Accessibility.useReducedMotion ? 0 : 0.2}}
|
transition={{duration: Accessibility.useReducedMotion ? 0 : 0.2}}
|
||||||
src={thumbHashURL}
|
src={thumbHashURL}
|
||||||
alt={title ? i18n._(THUMBNAIL_FOR_DESCRIPTOR, {title}) : i18n._(VIDEO_THUMBNAIL_DESCRIPTOR)}
|
alt=""
|
||||||
|
aria-hidden={true}
|
||||||
className={styles.thumbHashPlaceholder}
|
className={styles.thumbHashPlaceholder}
|
||||||
data-flx="channel.embeds.media.embed-video.mobile-video-overlay.thumbnail-placeholder"
|
data-flx="channel.embeds.media.embed-video.mobile-video-overlay.thumbnail-placeholder"
|
||||||
/>
|
/>
|
||||||
@@ -213,7 +215,12 @@ const EmbedVideo: FC<EmbedVideoProps> = observer(
|
|||||||
const messageViewContext = useMaybeMessageViewContext();
|
const messageViewContext = useMaybeMessageViewContext();
|
||||||
const effectiveSrc = buildMediaProxyURL(src);
|
const effectiveSrc = buildMediaProxyURL(src);
|
||||||
const isBlob = src.startsWith('blob:');
|
const isBlob = src.startsWith('blob:');
|
||||||
const posterSrc = isBlob ? null : buildMediaProxyURL(src, {format: 'webp'});
|
const declaredLayout = resolveVideoLayout(getEffectiveVideoLayoutDimensions({width, height}, null), {
|
||||||
|
maxWidth,
|
||||||
|
maxHeight,
|
||||||
|
});
|
||||||
|
const posterRequestWidth = Math.max(1, Math.round(declaredLayout.renderDimensions.width * mediaDevicePixelRatio()));
|
||||||
|
const posterSrc = isBlob ? null : buildMediaProxyURL(src, {format: 'webp', width: posterRequestWidth});
|
||||||
const {ref: visibilityRef, isNearViewport} = useNearViewport<HTMLDivElement>({
|
const {ref: visibilityRef, isNearViewport} = useNearViewport<HTMLDivElement>({
|
||||||
rememberKey: posterSrc ?? effectiveSrc,
|
rememberKey: posterSrc ?? effectiveSrc,
|
||||||
});
|
});
|
||||||
@@ -231,8 +238,16 @@ const EmbedVideo: FC<EmbedVideoProps> = observer(
|
|||||||
const [isPlayingInline, setIsPlayingInline] = useState(false);
|
const [isPlayingInline, setIsPlayingInline] = useState(false);
|
||||||
const [decodedVideoDimensions, setDecodedVideoDimensions] = useState<DecodedVideoDimensions | null>(null);
|
const [decodedVideoDimensions, setDecodedVideoDimensions] = useState<DecodedVideoDimensions | null>(null);
|
||||||
const [posterNaturalDimensions, setPosterNaturalDimensions] = useState<DecodedVideoDimensions | null>(null);
|
const [posterNaturalDimensions, setPosterNaturalDimensions] = useState<DecodedVideoDimensions | null>(null);
|
||||||
|
const [metadataProbeArmed, setMetadataProbeArmed] = useState(false);
|
||||||
const inlineVideoRef = useRef<HTMLVideoElement>(null);
|
const inlineVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
useInAppMediaSoundCapture(inlineVideoRef);
|
useInAppMediaSoundCapture(inlineVideoRef);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!isPlayingInline) return;
|
||||||
|
const inlineVideo = inlineVideoRef.current;
|
||||||
|
return () => {
|
||||||
|
detachMediaElementSource(inlineVideo);
|
||||||
|
};
|
||||||
|
}, [isPlayingInline]);
|
||||||
const {shouldBlur, gateReason, canReveal, reveal: revealSensitiveMedia} = useMatureMedia(nsfw, channelId);
|
const {shouldBlur, gateReason, canReveal, reveal: revealSensitiveMedia} = useMatureMedia(nsfw, channelId);
|
||||||
const shouldLoadMedia = isNearViewport && !shouldBlur;
|
const shouldLoadMedia = isNearViewport && !shouldBlur;
|
||||||
const defaultName =
|
const defaultName =
|
||||||
@@ -313,6 +328,8 @@ const EmbedVideo: FC<EmbedVideoProps> = observer(
|
|||||||
? !hasDifferentAspectRatio(decodedDimensionsForSrc, posterDimensionsForSrc, 0.05)
|
? !hasDifferentAspectRatio(decodedDimensionsForSrc, posterDimensionsForSrc, 0.05)
|
||||||
: true;
|
: true;
|
||||||
const effectivePosterSrc = posterMatchesVideo ? posterSrc : null;
|
const effectivePosterSrc = posterMatchesVideo ? posterSrc : null;
|
||||||
|
const posterKnownLoaded = posterLoaded && posterMatchesVideo;
|
||||||
|
const presentedPosterSrc = shouldLoadMedia || posterKnownLoaded ? effectivePosterSrc : null;
|
||||||
const updateDecodedVideoDimensions = useCallback(
|
const updateDecodedVideoDimensions = useCallback(
|
||||||
(nextWidth: number, nextHeight: number) => {
|
(nextWidth: number, nextHeight: number) => {
|
||||||
const nextDimensions = normalizeVideoDimensions({width: nextWidth, height: nextHeight});
|
const nextDimensions = normalizeVideoDimensions({width: nextWidth, height: nextHeight});
|
||||||
@@ -481,6 +498,9 @@ const EmbedVideo: FC<EmbedVideoProps> = observer(
|
|||||||
const handleInitialPlay = useCallback(() => {
|
const handleInitialPlay = useCallback(() => {
|
||||||
setHasPlayed(true);
|
setHasPlayed(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
const handleMediaPointerEnter = useCallback(() => {
|
||||||
|
setMetadataProbeArmed(true);
|
||||||
|
}, []);
|
||||||
const containerStyles: React.CSSProperties = isMobile
|
const containerStyles: React.CSSProperties = isMobile
|
||||||
? {
|
? {
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
@@ -514,6 +534,7 @@ const EmbedVideo: FC<EmbedVideoProps> = observer(
|
|||||||
<img
|
<img
|
||||||
src={thumbHashUrl}
|
src={thumbHashUrl}
|
||||||
alt=""
|
alt=""
|
||||||
|
aria-hidden={true}
|
||||||
className={styles.blurThumbnail}
|
className={styles.blurThumbnail}
|
||||||
style={{filter: 'blur(40px)'}}
|
style={{filter: 'blur(40px)'}}
|
||||||
data-flx="channel.embeds.media.embed-video.blur-thumbnail"
|
data-flx="channel.embeds.media.embed-video.blur-thumbnail"
|
||||||
@@ -537,7 +558,7 @@ const EmbedVideo: FC<EmbedVideoProps> = observer(
|
|||||||
{disableDelete: !!isPreview || snapshotIndex !== undefined},
|
{disableDelete: !!isPreview || snapshotIndex !== undefined},
|
||||||
);
|
);
|
||||||
const metadataProbe =
|
const metadataProbe =
|
||||||
shouldLoadMedia && !fillContainer && !isBlob && !hasPlayed && !isPlayingInline ? (
|
metadataProbeArmed && shouldLoadMedia && !fillContainer && !isBlob && !hasPlayed && !isPlayingInline ? (
|
||||||
<video
|
<video
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className={styles.metadataProbe}
|
className={styles.metadataProbe}
|
||||||
@@ -567,6 +588,7 @@ const EmbedVideo: FC<EmbedVideoProps> = observer(
|
|||||||
showDeleteButton={showDeleteButton}
|
showDeleteButton={showDeleteButton}
|
||||||
onDeleteClick={handleDeleteClick}
|
onDeleteClick={handleDeleteClick}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
|
onMouseEnter={handleMediaPointerEnter}
|
||||||
renderedWidth={dimensions.width}
|
renderedWidth={dimensions.width}
|
||||||
renderedHeight={dimensions.height}
|
renderedHeight={dimensions.height}
|
||||||
data-flx="channel.embeds.media.embed-video.media-container.context-menu"
|
data-flx="channel.embeds.media.embed-video.media-container.context-menu"
|
||||||
@@ -579,6 +601,7 @@ const EmbedVideo: FC<EmbedVideoProps> = observer(
|
|||||||
ref={inlineVideoRef}
|
ref={inlineVideoRef}
|
||||||
className={styles.inlineVideo}
|
className={styles.inlineVideo}
|
||||||
src={effectiveSrc}
|
src={effectiveSrc}
|
||||||
|
poster={presentedPosterSrc ?? undefined}
|
||||||
autoPlay
|
autoPlay
|
||||||
playsInline
|
playsInline
|
||||||
muted={VideoVolume.isMuted}
|
muted={VideoVolume.isMuted}
|
||||||
@@ -613,9 +636,9 @@ const EmbedVideo: FC<EmbedVideoProps> = observer(
|
|||||||
) : (
|
) : (
|
||||||
<MobileVideoOverlay
|
<MobileVideoOverlay
|
||||||
thumbHashURL={thumbHashUrl}
|
thumbHashURL={thumbHashUrl}
|
||||||
posterSrc={shouldLoadMedia ? effectivePosterSrc : null}
|
posterSrc={presentedPosterSrc}
|
||||||
posterLoaded={shouldLoadMedia && posterLoaded && posterMatchesVideo}
|
posterLoaded={posterKnownLoaded}
|
||||||
posterCachedOnMount={shouldLoadMedia && posterCachedOnMount && posterMatchesVideo}
|
posterCachedOnMount={posterCachedOnMount && posterMatchesVideo}
|
||||||
onTap={handleMobileTap}
|
onTap={handleMobileTap}
|
||||||
onPlayInline={handlePlayInline}
|
onPlayInline={handlePlayInline}
|
||||||
title={title}
|
title={title}
|
||||||
@@ -641,6 +664,7 @@ const EmbedVideo: FC<EmbedVideoProps> = observer(
|
|||||||
showDeleteButton={showDeleteButton}
|
showDeleteButton={showDeleteButton}
|
||||||
onDeleteClick={handleDeleteClick}
|
onDeleteClick={handleDeleteClick}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
|
onMouseEnter={handleMediaPointerEnter}
|
||||||
renderedWidth={dimensions.width}
|
renderedWidth={dimensions.width}
|
||||||
renderedHeight={dimensions.height}
|
renderedHeight={dimensions.height}
|
||||||
data-flx="channel.embeds.media.embed-video.media-container.context-menu--2"
|
data-flx="channel.embeds.media.embed-video.media-container.context-menu--2"
|
||||||
@@ -649,7 +673,7 @@ const EmbedVideo: FC<EmbedVideoProps> = observer(
|
|||||||
<div className={styles.videoPlayerWrapper} data-flx="channel.embeds.media.embed-video.video-player-wrapper">
|
<div className={styles.videoPlayerWrapper} data-flx="channel.embeds.media.embed-video.video-player-wrapper">
|
||||||
<VideoPlayer
|
<VideoPlayer
|
||||||
src={effectiveSrc}
|
src={effectiveSrc}
|
||||||
poster={shouldLoadMedia ? (effectivePosterSrc ?? undefined) : undefined}
|
poster={presentedPosterSrc ?? undefined}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
duration={duration}
|
duration={duration}
|
||||||
width={dimensions.width}
|
width={dimensions.width}
|
||||||
|
|||||||
Reference in new issue
Block a user