From 8b01b55b3661880fabfea9b4551ecf605e78b1b0 Mon Sep 17 00:00:00 2001 From: Siddharth Date: Wed, 15 Oct 2025 17:10:50 -0700 Subject: [PATCH] canvas draw post recording --- src/App.tsx | 2 +- src/components/VideoEditor.tsx | 215 ++++++++++++++++++++++++++------- 2 files changed, 172 insertions(+), 45 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index caa4436..8ba9b1c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import { LaunchWindow } from "./components/LaunchWindow"; import { SourceSelector } from "./components/SourceSelector"; -import { VideoEditor } from "./components/VideoEditor"; +import VideoEditor from "./components/VideoEditor"; import { useEffect, useState } from "react"; export default function App() { diff --git a/src/components/VideoEditor.tsx b/src/components/VideoEditor.tsx index 236dc7d..51f97bc 100644 --- a/src/components/VideoEditor.tsx +++ b/src/components/VideoEditor.tsx @@ -1,31 +1,106 @@ -import { useEffect, useState } from "react"; -export function VideoEditor() { +import { useEffect, useRef, useState } from "react"; + +export default function VideoEditor() { + // --- State --- const [videoPath, setVideoPath] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [isPlaying, setIsPlaying] = useState(false); + const [currentTime, setCurrentTime] = useState(0); + const [duration, setDuration] = useState(0); + // --- Refs --- + const videoRef = useRef(null); + const canvasRef = useRef(null); + + // --- Load video path on mount --- useEffect(() => { + async function loadVideo() { + try { + const result = await window.electronAPI.getRecordedVideoPath(); + if (result.success && result.path) { + setVideoPath(`file://${result.path}`); + } else { + setError(result.message || 'Failed to load video'); + } + } catch (err) { + setError('Error loading video: ' + String(err)); + } finally { + setLoading(false); + } + } loadVideo(); }, []); - const loadVideo = async () => { - try { - const result = await window.electronAPI.getRecordedVideoPath(); - - if (result.success && result.path) { - setVideoPath(`file://${result.path}`); - console.log('Loading video from:', result.path); - } else { - setError(result.message || 'Failed to load video'); - } - } catch (err) { - setError('Error loading video: ' + String(err)); - } finally { - setLoading(false); - } - }; + // --- Canvas drawing and video event listeners --- + useEffect(() => { + const video = videoRef.current; + const canvas = canvasRef.current; + if (!video || !canvas) return; + let animationId: number; + function drawFrame() { + if (!video || !canvas) return; + if (video.paused || video.ended) return; + // Keep canvas size in sync with video + if (canvas.width !== video.videoWidth || canvas.height !== video.videoHeight) { + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + } + const ctx = canvas.getContext('2d'); + if (ctx) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(video, 0, 0, canvas.width, canvas.height); + } + animationId = requestAnimationFrame(drawFrame); + } + + function handlePlay() { + drawFrame(); + } + function handlePause() { + cancelAnimationFrame(animationId); + } + + video.addEventListener('play', handlePlay); + video.addEventListener('pause', handlePause); + video.addEventListener('ended', handlePause); + + return () => { + video.removeEventListener('play', handlePlay); + video.removeEventListener('pause', handlePause); + video.removeEventListener('ended', handlePause); + cancelAnimationFrame(animationId); + }; + }, [videoPath]); + + // --- Handlers --- + function togglePlayPause() { + if (!videoRef.current) return; + if (isPlaying) { + videoRef.current.pause(); + } else { + videoRef.current.play(); + } + } + + function handleSeek(e: React.ChangeEvent) { + if (!videoRef.current) return; + const newTime = parseFloat(e.target.value); + videoRef.current.currentTime = newTime; + } + + function formatTime(seconds: number) { + if (!isFinite(seconds) || isNaN(seconds) || seconds < 0) { + return '0:00'; + } + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins}:${secs.toString().padStart(2, '0')}`; + } + + // --- Early returns for loading/error --- if (loading) { return (
@@ -33,7 +108,6 @@ export function VideoEditor() {
); } - if (error) { return (
@@ -42,36 +116,89 @@ export function VideoEditor() { ); } + // --- Main render --- return (
-

Video Editor

- -
+
{videoPath && ( -
- -
- Video path: {videoPath} + +
+
+ + + {formatTime(currentTime)} + + + + {formatTime(duration)} + +
); -} +} \ No newline at end of file