From fe82dbb318568f18ae538be4ff57f464b3d05708 Mon Sep 17 00:00:00 2001 From: Dmitri Date: Thu, 23 Apr 2026 07:45:11 +0400 Subject: [PATCH] improved background video behavior - when loop disabled the video plays once and stops in the last frame --- .../Constructor/CanvasBackground.tsx | 7 +++- .../src/hooks/useBackgroundVideoPlayback.ts | 42 ++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Constructor/CanvasBackground.tsx b/frontend/src/components/Constructor/CanvasBackground.tsx index 7395324..1a4217b 100644 --- a/frontend/src/components/Constructor/CanvasBackground.tsx +++ b/frontend/src/components/Constructor/CanvasBackground.tsx @@ -46,7 +46,7 @@ const CanvasBackground: React.FC = ({ videoEndTime = null, }) => { // Use background video playback hook for custom start/end time handling - const { videoRef } = useBackgroundVideoPlayback({ + const { videoRef, shouldBlockAutoplay } = useBackgroundVideoPlayback({ videoUrl: backgroundVideoUrl, autoplay: videoAutoplay, loop: videoLoop, @@ -55,6 +55,9 @@ const CanvasBackground: React.FC = ({ endTime: videoEndTime, }); + // Block autoplay if video already played this session (when loop=false) + const effectiveAutoplay = videoAutoplay && !shouldBlockAutoplay; + const handleLoad = () => { onBackgroundReady?.(); }; @@ -117,7 +120,7 @@ const CanvasBackground: React.FC = ({ key={`bg_video_${backgroundVideoUrl}`} className='absolute inset-0 z-1 h-full w-full object-contain' src={backgroundVideoUrl} - autoPlay={videoAutoplay} + autoPlay={effectiveAutoplay} loop={useNativeLoop} muted={videoMuted} playsInline diff --git a/frontend/src/hooks/useBackgroundVideoPlayback.ts b/frontend/src/hooks/useBackgroundVideoPlayback.ts index d425043..28aab6f 100644 --- a/frontend/src/hooks/useBackgroundVideoPlayback.ts +++ b/frontend/src/hooks/useBackgroundVideoPlayback.ts @@ -3,11 +3,18 @@ * * Manages background video playback with custom start/end times. * Follows patterns from useTransitionPlayback for video time control. + * + * When loop is disabled, videos are tracked per-session so they only play once + * and show the last frame on subsequent page visits (until browser refresh). */ import { useEffect, useRef, useCallback, type RefObject } from 'react'; import { logger } from '../lib/logger'; +// Session-scoped tracking of videos that have finished playing (when loop=false) +// Key: videoUrl, cleared on browser refresh +const playedVideos = new Set(); + export interface UseBackgroundVideoPlaybackOptions { /** URL of the video to play */ videoUrl?: string; @@ -26,6 +33,8 @@ export interface UseBackgroundVideoPlaybackOptions { export interface UseBackgroundVideoPlaybackResult { /** Ref to attach to the video element */ videoRef: RefObject; + /** Whether autoplay should be blocked (video already played this session) */ + shouldBlockAutoplay: boolean; } /** @@ -56,6 +65,9 @@ export function useBackgroundVideoPlayback({ endTime = null, }: UseBackgroundVideoPlaybackOptions): UseBackgroundVideoPlaybackResult { const videoRef = useRef(null); + + // Block autoplay if video already played this session (only when loop=false) + const shouldBlockAutoplay = !loop && videoUrl ? playedVideos.has(videoUrl) : false; // Store current values in refs for event handlers to access const startTimeRef = useRef(startTime); const endTimeRef = useRef(endTime); @@ -174,7 +186,35 @@ export function useBackgroundVideoPlayback({ video.muted = muted; }, [muted]); - return { videoRef }; + // Session-scoped "play once" behavior when loop is disabled + // Videos that have already played show last frame on revisit + useEffect(() => { + const video = videoRef.current; + if (!video || !videoUrl || loop) return; + + // If video already played this session, show last frame + if (playedVideos.has(videoUrl)) { + const showLastFrame = () => { + video.currentTime = video.duration - 0.01; + video.pause(); + }; + if (video.readyState >= 1) { + showLastFrame(); + } else { + video.addEventListener('loadedmetadata', showLastFrame, { once: true }); + } + return; + } + + // Mark video as played when it ends + const handleEnded = () => { + playedVideos.add(videoUrl); + }; + video.addEventListener('ended', handleEnded); + return () => video.removeEventListener('ended', handleEnded); + }, [videoUrl, loop]); + + return { videoRef, shouldBlockAutoplay }; } export default useBackgroundVideoPlayback;