diff --git a/public/wallpapers/wispysky.mp4 b/public/wallpapers/wispysky.mp4 new file mode 100644 index 00000000..3454d916 Binary files /dev/null and b/public/wallpapers/wispysky.mp4 differ diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index b69c420f..c8c22f78 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -1,4 +1,4 @@ -import { Film, Palette, Trash2, Upload, X } from "lucide-react"; +import { 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"; @@ -802,12 +802,16 @@ export function SettingsPanel({ >
{isVideoWallpaperSource(wallpaperUrl) ? ( -
- - - {props?.title ?? props?.ariaLabel ?? tSettings("background.video", "Video background")} - -
+
) : backgroundTab === "video" ? ( -
- Video backgrounds coming next. +
+ + +
+ {customImages.filter(isVideoWallpaperSource).map((videoUrl, idx) => { + const isSelected = getWallpaperTileState(videoUrl); + return renderWallpaperImageTile(videoUrl, isSelected, { + key: `custom-video-${idx}`, + ariaLabel: videoUrl.split(/[\\/]/).pop() ?? "Video background", + title: videoUrl.split(/[\\/]/).pop(), + onClick: () => onWallpaperChange(videoUrl), + children: ( + + ), + }); + })} + + {BUILT_IN_WALLPAPERS.filter(w => isVideoWallpaperSource(w.publicPath)).map((wallpaper) => { + const wallpaperValue = wallpaper.publicPath; + const isSelected = selected === wallpaperValue; + return renderWallpaperImageTile(wallpaperValue, isSelected, { + key: wallpaperValue, + ariaLabel: wallpaper.label, + title: wallpaper.label, + onClick: () => onWallpaperChange(wallpaperValue), + }); + })} +
) : backgroundTab === "color" ? (
diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index e4c6b647..9c9691fa 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -15,7 +15,6 @@ import { DEFAULT_WALLPAPER_RELATIVE_PATH, isVideoWallpaperSource, } from "@/lib/wallpapers"; -import { resolveMediaElementSource } from "@/lib/exporter/localMediaSource"; import { Application, Container, @@ -329,6 +328,7 @@ const VideoPlayback = forwardRef( const trimRegionsRef = useRef([]); const speedRegionsRef = useRef([]); const lastWebcamSyncTimeRef = useRef(null); + const bgVideoRef = useRef(null); const zoomMotionBlurRef = useRef(zoomMotionBlur); const connectZoomsRef = useRef(connectZooms); const zoomInDurationMsRef = useRef(zoomInDurationMs); @@ -753,8 +753,25 @@ const VideoPlayback = forwardRef( useEffect(() => { isPlayingRef.current = isPlaying; + const bgVideo = bgVideoRef.current; + if (bgVideo) { + if (isPlaying) { + bgVideo.play().catch(() => {}); + } else { + bgVideo.pause(); + } + } }, [isPlaying]); + // Sync background video wallpaper with timeline scrubbing + useEffect(() => { + const bgVideo = bgVideoRef.current; + if (!bgVideo) return; + if (!isPlaying && bgVideo.duration && Number.isFinite(bgVideo.duration)) { + bgVideo.currentTime = currentTime % bgVideo.duration; + } + }, [currentTime, isPlaying]); + useEffect(() => { trimRegionsRef.current = trimRegions; }, [trimRegions]); @@ -1506,10 +1523,12 @@ const VideoPlayback = forwardRef( } if (isVideoWallpaperSource(wallpaper)) { - const resolved = await resolveMediaElementSource(wallpaper); - revokeResolvedWallpaper = resolved.revoke; + let videoSrc = wallpaper; + if (wallpaper.startsWith("/") && !wallpaper.startsWith("//")) { + videoSrc = await getAssetPath(wallpaper.replace(/^\//, "")); + } if (mounted) { - setResolvedWallpaper(resolved.src); + setResolvedWallpaper(videoSrc); setResolvedWallpaperKind("video"); } return; @@ -1610,11 +1629,11 @@ const VideoPlayback = forwardRef( {resolvedWallpaperKind === "video" && resolvedWallpaper ? (