mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-27 00:05:39 +00:00
Fix WGC display selection and webcam sync
This commit is contained in:
@@ -456,7 +456,7 @@ export default function VideoEditor() {
|
||||
]);
|
||||
|
||||
const syncRecordingSessionWebcam = useCallback(
|
||||
async (webcamPath: string | null) => {
|
||||
async (webcamPath: string | null, timeOffsetMs = 0) => {
|
||||
const sourcePath = videoSourcePath ?? (videoPath ? fromFileUrl(videoPath) : null);
|
||||
if (!sourcePath || !window.electronAPI.setCurrentRecordingSession) {
|
||||
return;
|
||||
@@ -465,6 +465,7 @@ export default function VideoEditor() {
|
||||
await window.electronAPI.setCurrentRecordingSession({
|
||||
videoPath: sourcePath,
|
||||
webcamPath,
|
||||
timeOffsetMs,
|
||||
});
|
||||
},
|
||||
[videoPath, videoSourcePath],
|
||||
@@ -480,9 +481,10 @@ export default function VideoEditor() {
|
||||
...prev,
|
||||
enabled: true,
|
||||
sourcePath: result.path ?? null,
|
||||
timeOffsetMs: 0,
|
||||
}));
|
||||
|
||||
await syncRecordingSessionWebcam(result.path);
|
||||
await syncRecordingSessionWebcam(result.path, 0);
|
||||
toast.success(t("settings.effects.webcamFootageAdded"));
|
||||
}, [syncRecordingSessionWebcam, t]);
|
||||
|
||||
@@ -491,9 +493,10 @@ export default function VideoEditor() {
|
||||
...prev,
|
||||
enabled: false,
|
||||
sourcePath: null,
|
||||
timeOffsetMs: 0,
|
||||
}));
|
||||
|
||||
await syncRecordingSessionWebcam(null);
|
||||
await syncRecordingSessionWebcam(null, 0);
|
||||
toast.success(t("settings.effects.webcamFootageRemoved"));
|
||||
}, [syncRecordingSessionWebcam, t]);
|
||||
|
||||
@@ -557,6 +560,7 @@ export default function VideoEditor() {
|
||||
...prev,
|
||||
enabled: Boolean(sessionResult.session?.webcamPath),
|
||||
sourcePath: sessionResult.session?.webcamPath ?? null,
|
||||
timeOffsetMs: sessionResult.session?.timeOffsetMs ?? 0,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
@@ -572,6 +576,7 @@ export default function VideoEditor() {
|
||||
...prev,
|
||||
enabled: false,
|
||||
sourcePath: null,
|
||||
timeOffsetMs: 0,
|
||||
}));
|
||||
} else {
|
||||
setError("No video to load. Please record or select a video.");
|
||||
|
||||
@@ -706,10 +706,13 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
return;
|
||||
}
|
||||
|
||||
const targetTime = Math.max(0, currentTime);
|
||||
if (Math.abs(webcamVideo.currentTime - targetTime) > (isPlaying ? 0.1 : 0.01)) {
|
||||
const targetTime = Math.max(0, currentTime - (webcam.timeOffsetMs ?? 0) / 1000);
|
||||
const clampedTargetTime = Number.isFinite(webcamVideo.duration)
|
||||
? Math.min(targetTime, Math.max(0, webcamVideo.duration))
|
||||
: targetTime;
|
||||
if (Math.abs(webcamVideo.currentTime - clampedTargetTime) > (isPlaying ? 0.1 : 0.01)) {
|
||||
try {
|
||||
webcamVideo.currentTime = targetTime;
|
||||
webcamVideo.currentTime = clampedTargetTime;
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
DEFAULT_WEBCAM_REACT_TO_ZOOM,
|
||||
DEFAULT_WEBCAM_SHADOW,
|
||||
DEFAULT_WEBCAM_SIZE,
|
||||
DEFAULT_WEBCAM_TIME_OFFSET_MS,
|
||||
DEFAULT_FIGURE_DATA,
|
||||
DEFAULT_PLAYBACK_SPEED,
|
||||
DEFAULT_ZOOM_DEPTH,
|
||||
@@ -403,6 +404,9 @@ export function normalizeProjectEditor(editor: Partial<ProjectEditorState>): Pro
|
||||
enabled:
|
||||
typeof webcam.enabled === "boolean" ? webcam.enabled : DEFAULT_WEBCAM_OVERLAY.enabled,
|
||||
sourcePath: webcamSourcePath,
|
||||
timeOffsetMs: isFiniteNumber(webcam.timeOffsetMs)
|
||||
? Math.round(clamp(webcam.timeOffsetMs, -30_000, 30_000))
|
||||
: DEFAULT_WEBCAM_TIME_OFFSET_MS,
|
||||
mirror: typeof webcam.mirror === "boolean" ? webcam.mirror : DEFAULT_WEBCAM_OVERLAY.mirror,
|
||||
corner:
|
||||
webcam.corner === "top-left" ||
|
||||
|
||||
@@ -49,6 +49,7 @@ export type WebcamCorner = "top-left" | "top-right" | "bottom-left" | "bottom-ri
|
||||
export interface WebcamOverlaySettings {
|
||||
enabled: boolean;
|
||||
sourcePath: string | null;
|
||||
timeOffsetMs: number;
|
||||
mirror: boolean;
|
||||
corner: WebcamCorner;
|
||||
size: number;
|
||||
@@ -69,10 +70,12 @@ export const DEFAULT_WEBCAM_REACT_TO_ZOOM = true;
|
||||
export const DEFAULT_WEBCAM_CORNER_RADIUS = 18;
|
||||
export const DEFAULT_WEBCAM_SHADOW = 0.35;
|
||||
export const DEFAULT_WEBCAM_MARGIN = 24;
|
||||
export const DEFAULT_WEBCAM_TIME_OFFSET_MS = 0;
|
||||
|
||||
export const DEFAULT_WEBCAM_OVERLAY: WebcamOverlaySettings = {
|
||||
enabled: false,
|
||||
sourcePath: null,
|
||||
timeOffsetMs: DEFAULT_WEBCAM_TIME_OFFSET_MS,
|
||||
mirror: true,
|
||||
corner: "bottom-right",
|
||||
size: DEFAULT_WEBCAM_SIZE,
|
||||
|
||||
@@ -78,6 +78,8 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
const chunks = useRef<Blob[]>([]);
|
||||
const webcamChunks = useRef<Blob[]>([]);
|
||||
const startTime = useRef<number>(0);
|
||||
const webcamStartTime = useRef<number | null>(null);
|
||||
const webcamTimeOffsetMs = useRef(0);
|
||||
const recordingSessionTimestamp = useRef<number | null>(null);
|
||||
const nativeScreenRecording = useRef(false);
|
||||
const wgcRecording = useRef(false);
|
||||
@@ -190,6 +192,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
await window.electronAPI.setCurrentRecordingSession({
|
||||
videoPath,
|
||||
webcamPath,
|
||||
timeOffsetMs: webcamTimeOffsetMs.current,
|
||||
});
|
||||
} else {
|
||||
await window.electronAPI.setCurrentVideoPath(videoPath);
|
||||
@@ -218,6 +221,8 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
const startWebcamRecorder = useCallback(async () => {
|
||||
if (!webcamEnabled) {
|
||||
pendingWebcamPathPromise.current = Promise.resolve(null);
|
||||
webcamStartTime.current = null;
|
||||
webcamTimeOffsetMs.current = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -270,7 +275,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
return;
|
||||
}
|
||||
|
||||
const duration = Date.now() - startTime.current;
|
||||
const duration = Math.max(0, Date.now() - (webcamStartTime.current ?? Date.now()));
|
||||
const webcamBlob = new Blob(webcamChunks.current, { type: mimeType });
|
||||
webcamChunks.current = [];
|
||||
const fixedBlob = await fixWebmDuration(webcamBlob, duration);
|
||||
@@ -283,6 +288,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
} finally {
|
||||
webcamStopResolver.current = null;
|
||||
webcamRecorder.current = null;
|
||||
webcamStartTime.current = null;
|
||||
if (webcamStream.current) {
|
||||
webcamStream.current.getTracks().forEach((track) => track.stop());
|
||||
webcamStream.current = null;
|
||||
@@ -290,12 +296,15 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
}
|
||||
};
|
||||
|
||||
webcamStartTime.current = Date.now();
|
||||
recorder.start(RECORDER_TIMESLICE_MS);
|
||||
} catch (error) {
|
||||
console.warn("Failed to start webcam recording; continuing without webcam layer:", error);
|
||||
pendingWebcamPathPromise.current = Promise.resolve(null);
|
||||
webcamStopPromise.current = Promise.resolve(null);
|
||||
webcamRecorder.current = null;
|
||||
webcamStartTime.current = null;
|
||||
webcamTimeOffsetMs.current = 0;
|
||||
if (webcamStream.current) {
|
||||
webcamStream.current.getTracks().forEach((track) => track.stop());
|
||||
webcamStream.current = null;
|
||||
@@ -441,6 +450,8 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
|
||||
recordingSessionTimestamp.current = Date.now();
|
||||
startTime.current = recordingSessionTimestamp.current;
|
||||
webcamStartTime.current = null;
|
||||
webcamTimeOffsetMs.current = 0;
|
||||
await startWebcamRecorder();
|
||||
|
||||
const platform = await window.electronAPI.getPlatform();
|
||||
@@ -513,9 +524,13 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
}
|
||||
|
||||
if (nativeResult.success) {
|
||||
const mainStartedAt = Date.now();
|
||||
nativeScreenRecording.current = true;
|
||||
wgcRecording.current = useWgcCapture;
|
||||
startTime.current = Date.now();
|
||||
startTime.current = mainStartedAt;
|
||||
webcamTimeOffsetMs.current = webcamStartTime.current === null
|
||||
? 0
|
||||
: webcamStartTime.current - mainStartedAt;
|
||||
setRecording(true);
|
||||
window.electronAPI?.setRecordingState(true);
|
||||
|
||||
@@ -732,8 +747,12 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
recorder.onerror = () => {
|
||||
setRecording(false);
|
||||
};
|
||||
const mainStartedAt = Date.now();
|
||||
startTime.current = mainStartedAt;
|
||||
webcamTimeOffsetMs.current = webcamStartTime.current === null
|
||||
? 0
|
||||
: webcamStartTime.current - mainStartedAt;
|
||||
recorder.start(RECORDER_TIMESLICE_MS);
|
||||
startTime.current = Date.now();
|
||||
setRecording(true);
|
||||
window.electronAPI?.setRecordingState(true);
|
||||
} catch (error) {
|
||||
@@ -797,6 +816,8 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
webcamRecorder.current = null;
|
||||
webcamStream.current?.getTracks().forEach((t) => t.stop());
|
||||
webcamStream.current = null;
|
||||
webcamStartTime.current = null;
|
||||
webcamTimeOffsetMs.current = 0;
|
||||
pendingWebcamPathPromise.current = null;
|
||||
|
||||
if (nativeScreenRecording.current) {
|
||||
|
||||
Reference in New Issue
Block a user