From da4f22db73b05831839780f516d81fc3f93a4816 Mon Sep 17 00:00:00 2001 From: mertalev <101130780+mertalev@users.noreply.github.com> Date: Thu, 21 May 2026 18:44:59 -0400 Subject: [PATCH] refactor commit on release --- .../asset-viewer/VideoNativeViewer.svelte | 50 +++------------- .../asset-viewer/immich-time-range.ts | 57 +++++++++++++++++++ 2 files changed, 66 insertions(+), 41 deletions(-) create mode 100644 web/src/lib/components/asset-viewer/immich-time-range.ts diff --git a/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte b/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte index 7cbd075d3c..88f0821a73 100644 --- a/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte +++ b/web/src/lib/components/asset-viewer/VideoNativeViewer.svelte @@ -31,7 +31,7 @@ import 'media-chrome/media-play-button'; import 'media-chrome/media-playback-rate-button'; import 'media-chrome/media-time-display'; - import 'media-chrome/media-time-range'; + import './immich-time-range'; import 'media-chrome/media-volume-range'; import 'media-chrome/menu/media-playback-rate-menu'; import 'media-chrome/menu/media-rendition-menu'; @@ -279,43 +279,9 @@ } }); - // Suppress mediaseekrequest events while the user is dragging the time range and - // replay only the last one on pointerup. - const commitOnRelease = (node: HTMLElement) => { - let dragging = false; - let pending: number | undefined; - const onPointerDown = () => (dragging = true); - const onPointerUp = () => { - if (!dragging) { - return; - } - dragging = false; - if (pending !== undefined) { - node.dispatchEvent(new CustomEvent('mediaseekrequest', { composed: true, bubbles: true, detail: pending })); - // Update time display immediately without waiting for the new fragment to load - videoPlayer?.dispatchEvent(new Event('timeupdate')); - pending = undefined; - } - }; - const onSeekRequest = (event: Event) => { - if (dragging) { - pending = (event as CustomEvent).detail; - event.stopImmediatePropagation(); - } - }; - node.addEventListener('pointerdown', onPointerDown); - node.addEventListener('pointerup', onPointerUp); - node.addEventListener('pointercancel', onPointerUp); - node.addEventListener('mediaseekrequest', onSeekRequest, { capture: true }); - return { - destroy() { - node.removeEventListener('pointerdown', onPointerDown); - node.removeEventListener('pointerup', onPointerUp); - node.removeEventListener('pointercancel', onPointerUp); - node.removeEventListener('mediaseekrequest', onSeekRequest, { capture: true }); - }, - }; - }; + // The time is only refreshed on HLS fragment decode by default, + // so manually emit events on seek to update it immediately. + const onSeeking = (event: Event) => event.currentTarget?.dispatchEvent(new Event('timeupdate')); {#if showVideo} @@ -356,6 +322,7 @@ class="h-full object-contain" oncanplay={(e: Event) => handleCanPlay(e.currentTarget as HTMLVideoElement)} onended={onVideoEnded} + onseeking={onSeeking} onplaying={(e: Event) => { if (!hasFocused) { (e.currentTarget as HTMLElement).focus(); @@ -377,6 +344,7 @@ class="h-full object-contain" oncanplay={(e) => handleCanPlay(e.currentTarget)} onended={onVideoEnded} + onseeking={onSeeking} onplaying={(e) => { if (!hasFocused) { e.currentTarget.focus(); @@ -442,7 +410,7 @@ {/if} - + @@ -495,12 +463,12 @@ font-variant-numeric: tabular-nums; } - media-time-range, + immich-time-range, media-volume-range { --media-control-hover-background: none; } - media-time-range:hover, + immich-time-range:hover, media-volume-range:hover { --media-range-thumb-opacity: 1; } diff --git a/web/src/lib/components/asset-viewer/immich-time-range.ts b/web/src/lib/components/asset-viewer/immich-time-range.ts new file mode 100644 index 0000000000..730e5f553a --- /dev/null +++ b/web/src/lib/components/asset-viewer/immich-time-range.ts @@ -0,0 +1,57 @@ +import MediaTimeRange from 'media-chrome/media-time-range'; + +const COMMIT_DELAY_MS = 750; + +/** Custom MediaTimeRange that only seeks after pointer release to avoid hammering the server. + * Keyboard input uses timed debouncing instead since there's no release event. */ +class ImmichTimeRange extends MediaTimeRange { + private pointerSeek = false; + private pendingSeek = false; + private commitTimer: ReturnType | undefined; + + override connectedCallback() { + super.connectedCallback(); + this.addEventListener('pointercancel', this); // The base only wires pointerdown/up + } + + override handleEvent(event: Event) { + switch (event.type) { + case 'pointerdown': { + this.pointerSeek = true; + break; + } + case 'input': { + this.pendingSeek = true; + this.updateBar(); + if (this.pointerSeek) { + return; + } + clearTimeout(this.commitTimer); + this.commitTimer = setTimeout(() => this.commit(), COMMIT_DELAY_MS); + return; + } + case 'pointerup': + case 'pointercancel': { + super.handleEvent(event); + if (this.pointerSeek) { + this.pointerSeek = false; + this.commit(); + } + return; + } + } + super.handleEvent(event); + } + + private commit() { + clearTimeout(this.commitTimer); + if (this.pendingSeek) { + this.pendingSeek = false; + super.handleEvent(new Event('input')); + } + } +} + +if (!globalThis.customElements.get('immich-time-range')) { + globalThis.customElements.define('immich-time-range', ImmichTimeRange); +}