From fd2b7a344c7bccbf0d022bb1b99d2e95211effcb Mon Sep 17 00:00:00 2001 From: Dag Stuan Date: Sun, 31 Aug 2025 15:50:33 +0200 Subject: [PATCH] fix(web): wait for image to load before playing memories. (#19757) --- .../memory-page/memory-photo-viewer.svelte | 12 +++++---- .../memory-page/memory-viewer.svelte | 26 +++++++++++++++---- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/web/src/lib/components/memory-page/memory-photo-viewer.svelte b/web/src/lib/components/memory-page/memory-photo-viewer.svelte index b0b1dc98a6..89242eb966 100644 --- a/web/src/lib/components/memory-page/memory-photo-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-photo-viewer.svelte @@ -10,26 +10,28 @@ interface Props { asset: TimelineAsset; + onImageLoad: () => void; } - const { asset }: Props = $props(); + const { asset, onImageLoad }: Props = $props(); let assetFileUrl: string = $state(''); let imageLoaded: boolean = $state(false); let loader = $state(); - const onload = () => { + const onLoadCallback = () => { imageLoaded = true; assetFileUrl = imageLoaderUrl; + onImageLoad(); }; onMount(() => { if (loader?.complete) { - onload(); + onLoadCallback(); } - loader?.addEventListener('load', onload); + loader?.addEventListener('load', onLoadCallback); return () => { - loader?.removeEventListener('load', onload); + loader?.removeEventListener('load', onLoadCallback); }; }); diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index f724c4e811..b7e0dae17c 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -84,6 +84,7 @@ let progressBarController: Tween | undefined = $state(undefined); let videoPlayer: HTMLVideoElement | undefined = $state(); const asHref = (asset: { id: string }) => `?${QueryParameter.ID}=${asset.id}`; + const handleNavigate = async (asset?: { id: string }) => { if ($isViewing) { return asset; @@ -95,6 +96,7 @@ await goto(asHref(asset)); }; + const setProgressDuration = (asset: TimelineAsset) => { if (asset.isVideo) { const timeParts = asset.duration!.split(':').map(Number); @@ -108,6 +110,7 @@ }); } }; + const handleNextAsset = () => handleNavigate(current?.next?.asset); const handlePreviousAsset = () => handleNavigate(current?.previous?.asset); const handleNextMemory = () => handleNavigate(current?.nextMemory?.assets[0]); @@ -115,6 +118,7 @@ const handleEscape = async () => goto(AppRoute.PHOTOS); const handleSelectAll = () => assetInteraction.selectAssets(current?.memory.assets.map((a) => toTimelineAsset(a)) || []); + const handleAction = async (callingContext: string, action: 'reset' | 'pause' | 'play') => { // leaving these log statements here as comments. Very useful to figure out what's going on during dev! // console.log(`handleAction[${callingContext}] called with: ${action}`); @@ -154,6 +158,7 @@ } } }; + const handleProgress = async (progress: number) => { if (!progressBarController) { return; @@ -184,6 +189,7 @@ memoryStore.hideAssetsFromMemory(ids); init(page); }; + const handleDeleteMemoryAsset = async () => { if (!current) { return; @@ -192,6 +198,7 @@ await memoryStore.deleteAssetFromMemory(current.asset.id); init(page); }; + const handleDeleteMemory = async () => { if (!current) { return; @@ -201,6 +208,7 @@ notificationController.show({ message: $t('removed_memory'), type: NotificationType.Info }); init(page); }; + const handleSaveMemory = async () => { if (!current) { return; @@ -214,10 +222,12 @@ }); init(page); }; + const handleGalleryScrollsIntoView = () => { galleryInView = true; handlePromiseError(handleAction('galleryInView', 'pause')); }; + const handleGalleryScrollsOutOfView = () => { galleryInView = false; // only call play after the first page load. When page first loads the gallery will not be visible @@ -246,16 +256,22 @@ playerInitialized = false; }; + const resetAndPlay = () => { + handlePromiseError(handleAction('resetAndPlay', 'reset')); + handlePromiseError(handleAction('resetAndPlay', 'play')); + }; + const initPlayer = () => { - const isVideoAssetButPlayerHasNotLoadedYet = current && current.asset.isVideo && !videoPlayer; + const isVideo = current && current.asset.isVideo; + const isVideoAssetButPlayerHasNotLoadedYet = isVideo && !videoPlayer; if (playerInitialized || isVideoAssetButPlayerHasNotLoadedYet) { return; } if ($isViewing) { handlePromiseError(handleAction('initPlayer[AssetViewOpen]', 'pause')); - } else { - handlePromiseError(handleAction('initPlayer[AssetViewClosed]', 'reset')); - handlePromiseError(handleAction('initPlayer[AssetViewClosed]', 'play')); + } else if (isVideo) { + // Image assets will start playing when the image is loaded. Only autostart video assets. + resetAndPlay(); } playerInitialized = true; }; @@ -474,7 +490,7 @@ videoViewerVolume={$videoViewerVolume} /> {:else} - + {/if} {/key}