diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 39d6d080..b69c420f 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -1,4 +1,4 @@ -import { Palette, Trash2, Upload, X } from "lucide-react"; +import { Film, Palette, Trash2, Upload, X } from "lucide-react"; import { AnimatePresence, LayoutGroup, motion } from "motion/react"; import { useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; @@ -15,7 +15,7 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { getAssetPath, getRenderableAssetUrl } from "@/lib/assetPath"; import { cn } from "@/lib/utils"; import type { BuiltInWallpaper } from "@/lib/wallpapers"; -import { BUILT_IN_WALLPAPERS, getAvailableWallpapers } from "@/lib/wallpapers"; +import { BUILT_IN_WALLPAPERS, getAvailableWallpapers, isVideoWallpaperSource } from "@/lib/wallpapers"; import { type AspectRatio } from "@/utils/aspectRatioUtils"; import minimalCursorUrl from "../../../Minimal Cursor.svg"; import amongusCursorUrl from "../../assets/cursors/amongus/default.png"; @@ -105,7 +105,7 @@ const CAPTION_ANIMATION_OPTIONS: Array<{ value: AutoCaptionAnimation; label: str { value: "pop", label: "Pop" }, ]; -type BackgroundTab = "image" | "color" | "gradient"; +type BackgroundTab = "image" | "video" | "color" | "gradient"; export type EditorEffectSection = | "scene" | "cursor" @@ -128,6 +128,10 @@ function getBackgroundTabForWallpaper(value: string): BackgroundTab { return "color"; } + if (isVideoWallpaperSource(value)) { + return "video"; + } + return "image"; } @@ -713,7 +717,18 @@ export function SettingsPanel({ if (selected.startsWith("data:image") && !customImages.includes(selected)) { setCustomImages((prev) => [selected, ...prev]); } - }, [customImages, selected]); + + const isBuiltInWallpaper = + builtInWallpaperPaths.includes(selected) || wallpaperPreviewPaths.includes(selected); + + if ( + !isBuiltInWallpaper && + isVideoWallpaperSource(selected) && + !customImages.includes(selected) + ) { + setCustomImages((prev) => [selected, ...prev]); + } + }, [builtInWallpaperPaths, customImages, selected, wallpaperPreviewPaths]); useEffect(() => { saveEditorPreferences({ customWallpapers: customImages }); @@ -767,7 +782,7 @@ export function SettingsPanel({ ); const renderWallpaperImageTile = ( - imageUrl: string, + wallpaperUrl: string, isSelected: boolean, props?: { key?: string; @@ -786,16 +801,25 @@ export function SettingsPanel({ role="button" >
- { + {isVideoWallpaperSource(wallpaperUrl) ? ( +
+ + + {props?.title ?? props?.ariaLabel ?? tSettings("background.video", "Video background")} + +
+ ) : ( + { + )}
{props?.children} @@ -1000,10 +1024,11 @@ export function SettingsPanel({
-
+
{( [ { value: "image", label: tSettings("background.image") }, + { value: "video", label: tSettings("background.video", "Video") }, { value: "color", label: tSettings("background.color") }, { value: "gradient", label: tSettings("background.gradient") }, ] as const @@ -1069,6 +1094,12 @@ export function SettingsPanel({ const isSelected = getWallpaperTileState(imageUrl); return renderWallpaperImageTile(imageUrl, isSelected, { key: `custom-${idx}`, + ariaLabel: isVideoWallpaperSource(imageUrl) + ? imageUrl.split(/[\\/]/).pop() ?? tSettings("background.video", "Video background") + : undefined, + title: isVideoWallpaperSource(imageUrl) + ? imageUrl.split(/[\\/]/).pop() + : undefined, onClick: () => onWallpaperChange(imageUrl), children: (
+ ) : backgroundTab === "video" ? ( +
+ Video backgrounds coming next. +
) : backgroundTab === "color" ? (
( const [resolvedWallpaper, setResolvedWallpaper] = useState( null, ); + const [resolvedWallpaperKind, setResolvedWallpaperKind] = useState< + "image" | "video" | "style" + >("image"); useEffect(() => { let mounted = true; + let revokeResolvedWallpaper = () => {}; (async () => { try { if (!wallpaper) { const def = await getAssetPath(DEFAULT_WALLPAPER_RELATIVE_PATH); - if (mounted) setResolvedWallpaper(def); + if (mounted) { + setResolvedWallpaper(def); + setResolvedWallpaperKind("image"); + } return; } @@ -1489,13 +1498,29 @@ const VideoPlayback = forwardRef( wallpaper.startsWith("linear-gradient") || wallpaper.startsWith("radial-gradient") ) { - if (mounted) setResolvedWallpaper(wallpaper); + if (mounted) { + setResolvedWallpaper(wallpaper); + setResolvedWallpaperKind("style"); + } + return; + } + + if (isVideoWallpaperSource(wallpaper)) { + const resolved = await resolveMediaElementSource(wallpaper); + revokeResolvedWallpaper = resolved.revoke; + if (mounted) { + setResolvedWallpaper(resolved.src); + setResolvedWallpaperKind("video"); + } return; } // If it's a data URL (custom uploaded image), use as-is if (wallpaper.startsWith("data:")) { - if (mounted) setResolvedWallpaper(wallpaper); + if (mounted) { + setResolvedWallpaper(wallpaper); + setResolvedWallpaperKind("image"); + } return; } @@ -1505,20 +1530,31 @@ const VideoPlayback = forwardRef( wallpaper.startsWith("/") ) { const renderable = await getRenderableAssetUrl(wallpaper); - if (mounted) setResolvedWallpaper(renderable); + if (mounted) { + setResolvedWallpaper(renderable); + setResolvedWallpaperKind("image"); + } return; } const p = await getRenderableAssetUrl( await getAssetPath(wallpaper.replace(/^\//, "")), ); - if (mounted) setResolvedWallpaper(p); + if (mounted) { + setResolvedWallpaper(p); + setResolvedWallpaperKind("image"); + } } catch (err) { - if (mounted) + if (mounted) { setResolvedWallpaper(wallpaper || DEFAULT_WALLPAPER_PATH); + setResolvedWallpaperKind( + isVideoWallpaperSource(wallpaper || "") ? "video" : "image", + ); + } } })(); return () => { mounted = false; + revokeResolvedWallpaper(); }; }, [wallpaper]); @@ -1531,7 +1567,7 @@ const VideoPlayback = forwardRef( }; }, []); - const isImageUrl = Boolean( + const isImageUrl = resolvedWallpaperKind === "image" && Boolean( resolvedWallpaper && (resolvedWallpaper.startsWith("file://") || resolvedWallpaper.startsWith("http") || @@ -1540,7 +1576,9 @@ const VideoPlayback = forwardRef( ); const backgroundStyle = isImageUrl ? { backgroundImage: `url(${resolvedWallpaper || ""})` } - : { background: resolvedWallpaper || "" }; + : resolvedWallpaperKind === "video" + ? {} + : { background: resolvedWallpaper || "" }; const nativeAspectRatio = (() => { const locked = lockedVideoDimensionsRef.current; @@ -1569,13 +1607,28 @@ const VideoPlayback = forwardRef( }} > {/* Background layer */} -
0 ? `blur(${backgroundBlur}px)` : "none", - }} - /> + {resolvedWallpaperKind === "video" && resolvedWallpaper ? ( +