diff --git a/packages/atlas/src/components/_video/VideoTileViewer/VideoTileViewer.tsx b/packages/atlas/src/components/_video/VideoTileViewer/VideoTileViewer.tsx index 9baecaa309..8fa1db8476 100644 --- a/packages/atlas/src/components/_video/VideoTileViewer/VideoTileViewer.tsx +++ b/packages/atlas/src/components/_video/VideoTileViewer/VideoTileViewer.tsx @@ -6,7 +6,6 @@ import { SvgActionLinkUrl, SvgIllustrativePlay } from '@/assets/icons' import { Pill } from '@/components/Pill' import { absoluteRoutes } from '@/config/routes' import { useClipboard } from '@/hooks/useClipboard' -import { useVideoPreload } from '@/hooks/useVideoPreload' import { useVideoTileSharedLogic } from '@/hooks/useVideoTileSharedLogic' import { SentryLogger } from '@/utils/logs' import { formatDurationShort } from '@/utils/time' @@ -20,24 +19,15 @@ type VideoTileViewerProps = { detailsVariant?: VideoDetailsVariant direction?: 'vertical' | 'horizontal' className?: string - prefetch?: boolean } -export const VideoTileViewer: FC = ({ - id, - onClick, - prefetch, - detailsVariant, - direction, - className, -}) => { +export const VideoTileViewer: FC = ({ id, onClick, detailsVariant, direction, className }) => { const navigate = useNavigate() const { video, loading } = useBasicVideo(id ?? '', { skip: !id, onError: (error) => SentryLogger.error('Failed to fetch video', 'VideoTile', error, { video: { id } }), }) const { copyToClipboard } = useClipboard() - useVideoPreload(prefetch ? video?.media?.resolvedUrls : undefined) const { avatarPhotoUrls, isLoadingAvatar, isLoadingThumbnail, thumbnailPhotoUrls, videoHref } = useVideoTileSharedLogic(video) diff --git a/packages/atlas/src/hooks/useGetAssetUrl.ts b/packages/atlas/src/hooks/useGetAssetUrl.ts index 01d10bb77b..83e1179391 100644 --- a/packages/atlas/src/hooks/useGetAssetUrl.ts +++ b/packages/atlas/src/hooks/useGetAssetUrl.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { atlasConfig } from '@/config' import { AssetTestOptions, logDistributorPerformance, testAssetDownload } from '@/providers/assets/assets.helpers' @@ -35,7 +35,7 @@ export const getSingleAssetUrl = async ( dataObjectType: type || undefined, resolvedUrl: distributionAssetUrl, } - const assetTestPromise = testAssetDownload(distributionAssetUrl, type, opts) + const [assetTestPromise, cleanup] = testAssetDownload(distributionAssetUrl, type, opts) const assetTestPromiseWithTimeout = withTimeout( assetTestPromise, timeout ?? atlasConfig.storage.assetResponseTimeout @@ -52,6 +52,7 @@ export const getSingleAssetUrl = async ( return distributionAssetUrl } catch (err) { + cleanup?.() if (err instanceof MediaError) { let codec = '' if (type === 'video' && !mobile) { @@ -75,7 +76,7 @@ export const getSingleAssetUrl = async ( return new Promise((res) => { const promises: Promise[] = [] for (const distributionAssetUrl of urls) { - const assetTestPromise = testAssetDownload(distributionAssetUrl, type) + const [assetTestPromise] = testAssetDownload(distributionAssetUrl, type) promises.push(assetTestPromise) } @@ -99,17 +100,32 @@ export const getSingleAssetUrl = async ( export const useGetAssetUrl = (urls: string[] | undefined | null, type: AssetType | null, opts?: AssetTestOptions) => { const [url, setUrl] = useState(undefined) const [isLoading, setIsLoading] = useState(true) + const assetPromise = useRef | null>(null) const { userBenchmarkTime } = useOperatorsContext() const id = urls?.[0]?.split('/').pop() useEffect(() => { + // nothing should be done if old promise is still pending + if (assetPromise.current) { + return + } + if (!urls || (url && urls.includes(url)) || (!url && !urls.length)) { setIsLoading(false) return } + const init = async () => { setUrl(undefined) setIsLoading(true) - const resolvedUrl = await getSingleAssetUrl(urls, id, type, userBenchmarkTime.current ?? undefined, opts) + assetPromise.current = getSingleAssetUrl( + urls, + id, + type, + type === 'video' ? 10_000 : userBenchmarkTime.current ?? undefined, + opts + ) + const resolvedUrl = await assetPromise.current + assetPromise.current = null setIsLoading(false) if (resolvedUrl) { @@ -120,10 +136,6 @@ export const useGetAssetUrl = (urls: string[] | undefined | null, type: AssetTyp } init() - - return () => { - setIsLoading(false) - } }, [id, opts, type, url, urls, userBenchmarkTime]) return { url, isLoading } diff --git a/packages/atlas/src/hooks/useVideoPreload.ts b/packages/atlas/src/hooks/useVideoPreload.ts deleted file mode 100644 index b56d563536..0000000000 --- a/packages/atlas/src/hooks/useVideoPreload.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { useGetAssetUrl } from './useGetAssetUrl' - -export const useVideoPreload = (mediaUrls?: string[]) => - !!useGetAssetUrl(mediaUrls, 'video', { resolveOnlyOnEvents: ['canplay'] }) diff --git a/packages/atlas/src/providers/assets/assets.helpers.ts b/packages/atlas/src/providers/assets/assets.helpers.ts index 181a15f74d..14948d4247 100644 --- a/packages/atlas/src/providers/assets/assets.helpers.ts +++ b/packages/atlas/src/providers/assets/assets.helpers.ts @@ -20,19 +20,24 @@ export type AssetTestOptions = { resolveOnlyOnEvents?: (keyof HTMLVideoElementEventMap)[] } -export const testAssetDownload = (url: string, type: AssetType | null, opts?: AssetTestOptions): Promise => { - return new Promise((_resolve, _reject) => { +export const testAssetDownload = ( + url: string, + type: AssetType | null, + opts?: AssetTestOptions +): [Promise, (() => void) | null] => { + let img: HTMLImageElement | null = null + let video: HTMLVideoElement | null = null + let cleanup: (() => void) | null = null + const videoEvents: (keyof HTMLVideoElementEventMap)[] = opts?.resolveOnlyOnEvents ?? [ + 'loadedmetadata', + 'loadeddata', + 'canplay', + 'progress', + ] + const assetPromise = new Promise((_resolve, _reject) => { const isImageType = type && ['thumbnail', 'avatar', 'cover'].includes(type) - let img: HTMLImageElement | null = null - let video: HTMLVideoElement | null = null - const videoEvents: (keyof HTMLVideoElementEventMap)[] = opts?.resolveOnlyOnEvents ?? [ - 'loadedmetadata', - 'loadeddata', - 'canplay', - 'progress', - ] - - const cleanup = () => { + + cleanup = () => { if (img) { img.removeEventListener('error', reject) img.removeEventListener('load', resolve) @@ -44,19 +49,23 @@ export const testAssetDownload = (url: string, type: AssetType | null, opts?: As videoEvents.forEach((event) => { video?.removeEventListener(event, resolve) }) + video.pause() + video.preload = 'none' + video.setAttribute('src', '') + video.load() video.remove() video = null } } const resolve = () => { - cleanup() + cleanup?.() _resolve(url) } const reject = (err?: unknown) => { - cleanup() + cleanup?.() _reject(err) } @@ -92,6 +101,8 @@ export const testAssetDownload = (url: string, type: AssetType | null, opts?: As reject() } }) + + return [assetPromise, cleanup] } export const logDistributorPerformance = async (assetUrl: string, eventEntry: DistributorEventEntry) => { diff --git a/packages/atlas/src/views/viewer/CuratorHomepage/CuratorHomepage.tsx b/packages/atlas/src/views/viewer/CuratorHomepage/CuratorHomepage.tsx index ea4aed8cef..21dc9aff62 100644 --- a/packages/atlas/src/views/viewer/CuratorHomepage/CuratorHomepage.tsx +++ b/packages/atlas/src/views/viewer/CuratorHomepage/CuratorHomepage.tsx @@ -28,7 +28,7 @@ export const CuratorHomepage = () => { contentProps={{ type: 'grid', grid: DEFAULT_VIDEO_GRID, - children: tiles?.map((video, idx) => ), + children: tiles?.map((video, idx) => ), }} footerProps={{ reachedEnd: !pageInfo?.hasNextPage, diff --git a/packages/atlas/src/views/viewer/HomeView.tsx b/packages/atlas/src/views/viewer/HomeView.tsx index 7df0e55765..13e9ee666c 100644 --- a/packages/atlas/src/views/viewer/HomeView.tsx +++ b/packages/atlas/src/views/viewer/HomeView.tsx @@ -27,7 +27,7 @@ export const HomeView: FC = () => { contentProps={{ type: 'grid', grid: DEFAULT_VIDEO_GRID, - children: tiles?.map((video, idx) => ), + children: tiles?.map((video, idx) => ), }} footerProps={{ reachedEnd: !hasMoreVideos, diff --git a/packages/atlas/src/views/viewer/VideoView/MoreVideos.tsx b/packages/atlas/src/views/viewer/VideoView/MoreVideos.tsx index 404e4610d2..c21e464e33 100644 --- a/packages/atlas/src/views/viewer/VideoView/MoreVideos.tsx +++ b/packages/atlas/src/views/viewer/VideoView/MoreVideos.tsx @@ -22,7 +22,6 @@ type MoreVideosProps = { categoryName?: string | null videoId?: string type: 'channel' | 'category' - shouldPrefetch?: boolean } const NUMBER_OF_VIDEOS = 6 @@ -34,7 +33,6 @@ export const MoreVideos: FC = ({ categoryName, videoId, type, - shouldPrefetch, }) => { const videoCategories = categoryId ? displayCategoriesLookup[categoryId].videoCategories : undefined const where = @@ -66,7 +64,6 @@ export const MoreVideos: FC = ({ id={video.id} detailsVariant="withChannelName" direction={lgMatch ? 'horizontal' : 'vertical'} - prefetch={idx < 5 && shouldPrefetch} /> ))} diff --git a/packages/atlas/src/views/viewer/VideoView/VideoView.tsx b/packages/atlas/src/views/viewer/VideoView/VideoView.tsx index 8e7234fff2..73e96335fe 100644 --- a/packages/atlas/src/views/viewer/VideoView/VideoView.tsx +++ b/packages/atlas/src/views/viewer/VideoView/VideoView.tsx @@ -87,7 +87,6 @@ export const VideoView: FC = () => { ) const [isInView, ref] = useIntersectionObserver() const [isCommenting, setIsCommenting] = useState(false) - const [canPrefetchNew, setCanPrefetchNew] = useState(false) const mdMatch = useMediaMatch('md') const { addVideoView } = useAddVideoView() @@ -170,9 +169,6 @@ export const VideoView: FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps const handleTimeUpdate = useCallback( throttle((time) => { - if (!canPrefetchNew && time > 5_000) { - setCanPrefetchNew(true) - } if (video?.id) { updateWatchedVideos('INTERRUPTED', video.id, time) } @@ -283,7 +279,7 @@ export const VideoView: FC = () => { )} - {!isCinematic && } + {!isCinematic && } @@ -304,7 +300,7 @@ export const VideoView: FC = () => { /> )} - + )} @@ -312,15 +308,7 @@ export const VideoView: FC = () => { ) } -const SideItems = ({ - video, - loading, - canStartPrefetch, -}: { - video: ReturnType['video'] - loading: boolean - canStartPrefetch: boolean -}) => { +const SideItems = ({ video, loading }: { video: ReturnType['video']; loading: boolean }) => { const { id } = useParams() const { openNftPutOnSale, openNftAcceptBid, openNftChangePrice, openNftPurchase, openNftSettlement, cancelNftSale } = useNftActions() @@ -359,13 +347,7 @@ const SideItems = ({ onWithdrawBid={(bid, createdAt) => id && createdAt && bid && withdrawBid(id, bid, createdAt)} /> )} - + {belongsToCategories?.map((category) => (