diff --git a/frontend/src/components/Constructor/CanvasBackground.tsx b/frontend/src/components/Constructor/CanvasBackground.tsx index 5f2a6ff..3cd7b5d 100644 --- a/frontend/src/components/Constructor/CanvasBackground.tsx +++ b/frontend/src/components/Constructor/CanvasBackground.tsx @@ -33,6 +33,8 @@ interface CanvasBackgroundProps { videoMuted?: boolean; videoStartTime?: number | null; videoEndTime?: number | null; + /** Original storage path for video - used for play-once tracking (not the resolved blob URL) */ + videoStoragePath?: string; } const CanvasBackground: React.FC = ({ @@ -50,10 +52,13 @@ const CanvasBackground: React.FC = ({ videoMuted = true, videoStartTime = null, videoEndTime = null, + videoStoragePath, }) => { // Use background video playback hook for custom start/end time handling + // Use storagePath for play-once tracking (falls back to videoUrl if not provided) const { videoRef, shouldBlockAutoplay } = useBackgroundVideoPlayback({ videoUrl: backgroundVideoUrl, + videoStoragePath: videoStoragePath || backgroundVideoUrl, autoplay: videoAutoplay, loop: videoLoop, muted: videoMuted, diff --git a/frontend/src/components/RuntimePresentation.tsx b/frontend/src/components/RuntimePresentation.tsx index 39184b5..6f2e1b9 100644 --- a/frontend/src/components/RuntimePresentation.tsx +++ b/frontend/src/components/RuntimePresentation.tsx @@ -653,6 +653,7 @@ export default function RuntimePresentation({ videoMuted={videoMuted} videoStartTime={videoStartTime} videoEndTime={videoEndTime} + videoStoragePath={selectedPage?.background_video_url} /> {/* End page background wrapper */} diff --git a/frontend/src/hooks/useBackgroundVideoPlayback.ts b/frontend/src/hooks/useBackgroundVideoPlayback.ts index 28aab6f..10e4440 100644 --- a/frontend/src/hooks/useBackgroundVideoPlayback.ts +++ b/frontend/src/hooks/useBackgroundVideoPlayback.ts @@ -16,8 +16,10 @@ import { logger } from '../lib/logger'; const playedVideos = new Set(); export interface UseBackgroundVideoPlaybackOptions { - /** URL of the video to play */ + /** URL of the video to play (may be a blob URL) */ videoUrl?: string; + /** Original storage path for play-once tracking (stable across navigations) */ + videoStoragePath?: string; /** Whether to autoplay the video (default: true) */ autoplay?: boolean; /** Whether to loop the video (default: true) */ @@ -58,6 +60,7 @@ export interface UseBackgroundVideoPlaybackResult { */ export function useBackgroundVideoPlayback({ videoUrl, + videoStoragePath, autoplay = true, loop = true, muted = true, @@ -66,8 +69,12 @@ export function useBackgroundVideoPlayback({ }: UseBackgroundVideoPlaybackOptions): UseBackgroundVideoPlaybackResult { const videoRef = useRef(null); + // Use storage path for tracking (stable across blob URL changes) + // Falls back to videoUrl if no storage path provided + const trackingKey = videoStoragePath || videoUrl; + // Block autoplay if video already played this session (only when loop=false) - const shouldBlockAutoplay = !loop && videoUrl ? playedVideos.has(videoUrl) : false; + const shouldBlockAutoplay = !loop && trackingKey ? playedVideos.has(trackingKey) : false; // Store current values in refs for event handlers to access const startTimeRef = useRef(startTime); const endTimeRef = useRef(endTime); @@ -188,12 +195,13 @@ export function useBackgroundVideoPlayback({ // Session-scoped "play once" behavior when loop is disabled // Videos that have already played show last frame on revisit + // Uses trackingKey (storage path) for stable tracking across blob URL changes useEffect(() => { const video = videoRef.current; - if (!video || !videoUrl || loop) return; + if (!video || !videoUrl || !trackingKey || loop) return; // If video already played this session, show last frame - if (playedVideos.has(videoUrl)) { + if (playedVideos.has(trackingKey)) { const showLastFrame = () => { video.currentTime = video.duration - 0.01; video.pause(); @@ -208,11 +216,11 @@ export function useBackgroundVideoPlayback({ // Mark video as played when it ends const handleEnded = () => { - playedVideos.add(videoUrl); + playedVideos.add(trackingKey); }; video.addEventListener('ended', handleEnded); return () => video.removeEventListener('ended', handleEnded); - }, [videoUrl, loop]); + }, [videoUrl, trackingKey, loop]); return { videoRef, shouldBlockAutoplay }; } diff --git a/frontend/src/pages/constructor.tsx b/frontend/src/pages/constructor.tsx index 23e0bc8..dc02626 100644 --- a/frontend/src/pages/constructor.tsx +++ b/frontend/src/pages/constructor.tsx @@ -1534,6 +1534,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => { videoMuted={backgroundVideoMuted} videoStartTime={backgroundVideoStartTime} videoEndTime={backgroundVideoEndTime} + videoStoragePath={backgroundVideoUrl || activePage?.background_video_url} />