feat(timeline): muted hover-to-play preview on video tiles

PhotoPrism plays a silent preview of the actual video when you hover
its grid tile; this mirrors that. After a 250ms debounce the tile
mounts a muted, looping <video> over the thumbnail and cross-fades it
in on first decoded frame, so cursor-skimming doesn't fire N requests
and the tile never blanks mid-fetch. The byte-prefetch helper added
in af96922 is now redundant — the hover <video> warms the same caches
on its own.

Also tells Vidstack the playback URL is video/mp4 via a nested
<source>: our /api/v1/videos/.../avc URL has no extension, so
Vidstack's suffix sniff was failing, falling back to a HEAD probe,
and picking the wrong loader (which surfaced as
NS_ERROR_DOM_MEDIA_METADATA_ERR in Firefox).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 08:20:16 +02:00
parent af96922e89
commit 9d955d6b94
3 changed files with 56 additions and 72 deletions

View File

@@ -34,23 +34,29 @@
</script>
{#if ready}
<!-- svelte-ignore element_invalid_self_closing_tag -->
<!--
Note: `src` is *not* set as an attribute on <media-player>. Our backend
URL ends in `/avc` (not `.mp4`), so Vidstack's URL-suffix sniff fails
and it falls back to a HEAD probe — which can pick the wrong loader
and produce decode errors. Declaring the source via a nested
<source type="video/mp4"> tells Vidstack to use the native MP4 loader
directly, no probe needed.
-->
<media-player
class="vds-player max-h-full max-w-full rounded-md shadow-2xl"
title={title ?? ''}
{src}
{poster}
autoplay
muted
playsinline
load="eager"
posterLoad="eager"
crossorigin=""
preferNativeHLS="false"
streamType="on-demand"
viewType="video"
>
<media-provider></media-provider>
<media-provider>
<source {src} type="video/mp4" />
</media-provider>
<media-video-layout></media-video-layout>
</media-player>
{:else}