39948-vm/frontend/src/components/Constructor/CanvasBackground.tsx
2026-04-13 16:32:23 +00:00

139 lines
3.9 KiB
TypeScript

/**
* CanvasBackground Component
*
* Background image, video, and audio for the constructor canvas.
* Handles blob URLs, Next.js Image optimization, and previous background overlay.
* Supports custom video playback settings (autoplay, loop, muted, start/end time).
*/
import React from 'react';
import NextImage from 'next/image';
import { useBackgroundVideoPlayback } from '../../hooks/useBackgroundVideoPlayback';
interface CanvasBackgroundProps {
backgroundImageUrl?: string;
backgroundVideoUrl?: string;
backgroundAudioUrl?: string;
previousBgImageUrl?: string;
isSwitching?: boolean;
isNewBgReady?: boolean;
onBackgroundReady?: () => void;
// Video playback settings
videoAutoplay?: boolean;
videoLoop?: boolean;
videoMuted?: boolean;
videoStartTime?: number | null;
videoEndTime?: number | null;
}
const CanvasBackground: React.FC<CanvasBackgroundProps> = ({
backgroundImageUrl,
backgroundVideoUrl,
backgroundAudioUrl,
previousBgImageUrl,
isSwitching = false,
isNewBgReady = false,
onBackgroundReady,
videoAutoplay = true,
videoLoop = true,
videoMuted = true,
videoStartTime = null,
videoEndTime = null,
}) => {
// Use background video playback hook for custom start/end time handling
const { videoRef } = useBackgroundVideoPlayback({
videoUrl: backgroundVideoUrl,
autoplay: videoAutoplay,
loop: videoLoop,
muted: videoMuted,
startTime: videoStartTime,
endTime: videoEndTime,
});
const handleLoad = () => {
onBackgroundReady?.();
};
const handleError = () => {
onBackgroundReady?.();
};
// When endTime is set, we disable native loop and handle it via the hook
const useNativeLoop = videoEndTime == null ? videoLoop : false;
return (
<>
{/* Background image - z-1 keeps it below backdrop blur layer (z-5) */}
{backgroundImageUrl && (
<div className='pointer-events-none absolute inset-0 z-1 h-full w-full select-none'>
{backgroundImageUrl.startsWith('blob:') ? (
// eslint-disable-next-line @next/next/no-img-element
<img
key={`bg_image_${backgroundImageUrl}`}
src={backgroundImageUrl}
alt='Background'
className='absolute inset-0 h-full w-full object-contain'
draggable={false}
onLoad={handleLoad}
onError={handleError}
/>
) : (
<NextImage
key={`bg_image_${backgroundImageUrl}`}
src={backgroundImageUrl}
alt='Background'
fill
sizes='100vw'
className='object-contain'
draggable={false}
unoptimized
onLoad={handleLoad}
onError={handleError}
/>
)}
</div>
)}
{/* Previous background overlay - shows during page switch until new bg is ready */}
{previousBgImageUrl && isSwitching && !isNewBgReady && (
<div
className='pointer-events-none absolute inset-0 z-10'
style={{
backgroundImage: `url("${previousBgImageUrl}")`,
backgroundSize: 'contain',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
}}
/>
)}
{/* Background video - z-1 keeps it below backdrop blur layer (z-5) */}
{backgroundVideoUrl && (
<video
ref={videoRef}
key={`bg_video_${backgroundVideoUrl}`}
className='absolute inset-0 z-1 h-full w-full object-contain'
src={backgroundVideoUrl}
autoPlay={videoAutoplay}
loop={useNativeLoop}
muted={videoMuted}
playsInline
/>
)}
{/* Background audio */}
{backgroundAudioUrl && (
<audio
key={`bg_audio_${backgroundAudioUrl}`}
src={backgroundAudioUrl}
autoPlay
loop
hidden
/>
)}
</>
);
};
export default CanvasBackground;