import { useState, useRef, useEffect } from "react"; import { fixWebmDuration } from "@fix-webm-duration/fix"; type UseScreenRecorderReturn = { recording: boolean; toggleRecording: () => void; }; export function useScreenRecorder(): UseScreenRecorderReturn { const [recording, setRecording] = useState(false); const mediaRecorder = useRef(null); const stream = useRef(null); const chunks = useRef([]); const startTime = useRef(0); const stopRecording = useRef(() => { if (mediaRecorder.current?.state === "recording") { if (stream.current) { stream.current.getTracks().forEach(track => track.stop()); } mediaRecorder.current.stop(); setRecording(false); window.electronAPI.stopMouseTracking(); window.electronAPI?.setRecordingState(false); } }); useEffect(() => { let cleanup: (() => void) | undefined; if (window.electronAPI?.onStopRecordingFromTray) { cleanup = window.electronAPI.onStopRecordingFromTray(() => { stopRecording.current(); }); } return () => { if (cleanup) cleanup(); if (mediaRecorder.current?.state === "recording") { mediaRecorder.current.stop(); } if (stream.current) { stream.current.getTracks().forEach(track => track.stop()); stream.current = null; } }; }, []); const startRecording = async () => { try { const selectedSource = await window.electronAPI.getSelectedSource(); if (!selectedSource) { alert("Please select a source to record"); return; } await window.electronAPI.startMouseTracking(); const mediaStream = await (navigator.mediaDevices as any).getUserMedia({ audio: false, video: { mandatory: { chromeMediaSource: "desktop", chromeMediaSourceId: selectedSource.id, }, }, }); stream.current = mediaStream; if (!stream.current) { throw new Error("Media stream is not available."); } const videoTrack = stream.current.getVideoTracks()[0]; const { width = 1920, height = 1080 } = videoTrack.getSettings(); const totalPixels = width * height; let bitrate = 150_000_000; if (totalPixels > 1920 * 1080 && totalPixels <= 2560 * 1440) { bitrate = 250_000_000; } else if (totalPixels > 2560 * 1440) { bitrate = 400_000_000; } chunks.current = []; const mimeType = "video/webm;codecs=vp9"; const recorder = new MediaRecorder(stream.current, { mimeType, videoBitsPerSecond: bitrate }); mediaRecorder.current = recorder; recorder.ondataavailable = e => { if (e.data && e.data.size > 0) chunks.current.push(e.data); }; recorder.onstop = async () => { stream.current = null; if (chunks.current.length === 0) return; const duration = Date.now() - startTime.current; const buggyBlob = new Blob(chunks.current, { type: mimeType }); const timestamp = Date.now(); const videoFileName = `recording-${timestamp}.webm`; const trackingFileName = `recording-${timestamp}_tracking.json`; try { const videoBlob = await fixWebmDuration(buggyBlob, duration); const arrayBuffer = await videoBlob.arrayBuffer(); const videoResult = await window.electronAPI.storeRecordedVideo(arrayBuffer, videoFileName); if (!videoResult.success) { console.error('Failed to store video:', videoResult.message); return; } const trackingResult = await window.electronAPI.storeMouseTrackingData(trackingFileName); if (!trackingResult.success) { console.warn('Failed to store mouse tracking:', trackingResult.message); } await window.electronAPI.switchToEditor(); } catch (error) { console.error('Error saving recording:', error); } }; recorder.onerror = () => setRecording(false); recorder.start(1000); startTime.current = Date.now(); setRecording(true); window.electronAPI?.setRecordingState(true); } catch (error) { console.error('Failed to start recording:', error); setRecording(false); if (stream.current) { stream.current.getTracks().forEach(track => track.stop()); stream.current = null; } } }; const toggleRecording = () => { recording ? stopRecording.current() : startRecording(); }; return { recording, toggleRecording }; }