39948-vm/frontend/src/components/Constructor/CanvasBackground.tsx

145 lines
4.2 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';
import PreviousBackgroundOverlay from '../PreviousBackgroundOverlay';
interface CanvasBackgroundProps {
backgroundImageUrl?: string;
backgroundVideoUrl?: string;
backgroundAudioUrl?: string;
previousBgImageUrl?: string;
previousBgVideoUrl?: string;
isSwitching?: boolean;
isNewBgReady?: boolean;
isFadingIn?: 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,
previousBgVideoUrl,
isSwitching = false,
isNewBgReady = false,
isFadingIn = 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, shouldBlockAutoplay } = useBackgroundVideoPlayback({
videoUrl: backgroundVideoUrl,
autoplay: videoAutoplay,
loop: videoLoop,
muted: videoMuted,
startTime: videoStartTime,
endTime: videoEndTime,
});
// Block autoplay if video already played this session (when loop=false)
const effectiveAutoplay = videoAutoplay && !shouldBlockAutoplay;
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 overlays - show during loading AND crossfade.
Uses CSS animation for fade-out effect during crossfade.
z-0 keeps them BELOW new backgrounds (z-1). */}
<PreviousBackgroundOverlay
imageUrl={previousBgImageUrl}
videoUrl={previousBgVideoUrl}
isSwitching={isSwitching}
isNewBgReady={isNewBgReady}
isFadingIn={isFadingIn}
/>
{/* 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={effectiveAutoplay}
loop={useNativeLoop}
muted={videoMuted}
playsInline
/>
)}
{/* Background audio */}
{backgroundAudioUrl && (
<audio
key={`bg_audio_${backgroundAudioUrl}`}
src={backgroundAudioUrl}
autoPlay
loop
hidden
/>
)}
</>
);
};
export default CanvasBackground;