improved background video behavior - when loop disabled the video plays once and stops in the last frame

This commit is contained in:
Dmitri 2026-04-23 07:45:11 +04:00
parent b66bced6ab
commit fe82dbb318
2 changed files with 46 additions and 3 deletions

View File

@ -46,7 +46,7 @@ const CanvasBackground: React.FC<CanvasBackgroundProps> = ({
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<CanvasBackgroundProps> = ({
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<CanvasBackgroundProps> = ({
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

View File

@ -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<string>();
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<HTMLVideoElement | null>;
/** 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<HTMLVideoElement | null>(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;