mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-29 01:05:37 +00:00
Revert "Fix WGC display selection and webcam sync"
This reverts commit e5becc30a3.
This commit is contained in:
Vendored
+1
-2
@@ -183,11 +183,10 @@ interface Window {
|
||||
setCurrentRecordingSession: (session: {
|
||||
videoPath: string;
|
||||
webcamPath?: string | null;
|
||||
timeOffsetMs?: number;
|
||||
}) => Promise<{ success: boolean }>;
|
||||
getCurrentRecordingSession: () => Promise<{
|
||||
success: boolean;
|
||||
session?: { videoPath: string; webcamPath?: string | null; timeOffsetMs?: number };
|
||||
session?: { videoPath: string; webcamPath?: string | null };
|
||||
}>;
|
||||
getCurrentVideoPath: () => Promise<{ success: boolean; path?: string }>;
|
||||
clearCurrentVideoPath: () => Promise<{ success: boolean }>;
|
||||
|
||||
+24
-81
@@ -81,14 +81,12 @@ type WindowBounds = {
|
||||
type RecordingSessionData = {
|
||||
videoPath: string
|
||||
webcamPath?: string | null
|
||||
timeOffsetMs?: number
|
||||
}
|
||||
|
||||
type RecordingSessionManifest = {
|
||||
version: 1 | 2
|
||||
version: 1
|
||||
videoFileName: string
|
||||
webcamFileName?: string | null
|
||||
timeOffsetMs?: number
|
||||
}
|
||||
|
||||
type ProjectLibraryEntry = {
|
||||
@@ -386,7 +384,6 @@ async function loadProjectFromPath(projectPath: string) {
|
||||
currentRecordingSession = {
|
||||
videoPath: mediaSources.videoPath,
|
||||
webcamPath: mediaSources.webcamPath,
|
||||
timeOffsetMs: mediaSources.timeOffsetMs,
|
||||
}
|
||||
await rememberRecentProject(normalizedPath)
|
||||
|
||||
@@ -423,7 +420,6 @@ async function resolveProjectMediaSources(project: unknown): Promise<
|
||||
success: true
|
||||
videoPath: string
|
||||
webcamPath: string | null
|
||||
timeOffsetMs: number
|
||||
}
|
||||
| {
|
||||
success: false
|
||||
@@ -457,9 +453,6 @@ async function resolveProjectMediaSources(project: unknown): Promise<
|
||||
typeof (project as { editor?: { webcam?: { sourcePath?: unknown } } }).editor?.webcam?.sourcePath === 'string'
|
||||
? ((project as { editor?: { webcam?: { sourcePath?: string } } }).editor?.webcam?.sourcePath ?? null)
|
||||
: null
|
||||
const timeOffsetMs = normalizeRecordingTimeOffsetMs(
|
||||
(project as { editor?: { webcam?: { timeOffsetMs?: unknown } } }).editor?.webcam?.timeOffsetMs,
|
||||
)
|
||||
const normalizedWebcamPath = normalizeVideoSourcePath(rawWebcamPath)
|
||||
|
||||
if (!normalizedWebcamPath) {
|
||||
@@ -467,7 +460,6 @@ async function resolveProjectMediaSources(project: unknown): Promise<
|
||||
success: true,
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: null,
|
||||
timeOffsetMs,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,24 +469,16 @@ async function resolveProjectMediaSources(project: unknown): Promise<
|
||||
success: true,
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: normalizedWebcamPath,
|
||||
timeOffsetMs,
|
||||
}
|
||||
} catch {
|
||||
return {
|
||||
success: true,
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: null,
|
||||
timeOffsetMs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeRecordingTimeOffsetMs(value: unknown): number {
|
||||
return typeof value === 'number' && Number.isFinite(value)
|
||||
? Math.round(value)
|
||||
: 0
|
||||
}
|
||||
|
||||
function getRecordingSessionManifestPath(videoPath: string) {
|
||||
const extension = path.extname(videoPath)
|
||||
const baseName = path.basename(videoPath, extension)
|
||||
@@ -516,10 +500,9 @@ async function persistRecordingSessionManifest(session: RecordingSessionData): P
|
||||
}
|
||||
|
||||
const manifest: RecordingSessionManifest = {
|
||||
version: 2,
|
||||
version: 1,
|
||||
videoFileName: path.basename(normalizedVideoPath),
|
||||
webcamFileName: path.basename(normalizedWebcamPath),
|
||||
timeOffsetMs: normalizeRecordingTimeOffsetMs(session.timeOffsetMs),
|
||||
}
|
||||
|
||||
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8')
|
||||
@@ -536,7 +519,7 @@ async function resolveRecordingSessionManifest(videoPath?: string | null): Promi
|
||||
try {
|
||||
const content = await fs.readFile(manifestPath, 'utf-8')
|
||||
const parsed = JSON.parse(content) as Partial<RecordingSessionManifest>
|
||||
if (parsed.version !== 1 && parsed.version !== 2) {
|
||||
if (parsed.version !== 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -548,7 +531,6 @@ async function resolveRecordingSessionManifest(videoPath?: string | null): Promi
|
||||
return {
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: null,
|
||||
timeOffsetMs: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,7 +540,6 @@ async function resolveRecordingSessionManifest(videoPath?: string | null): Promi
|
||||
return {
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath,
|
||||
timeOffsetMs: normalizeRecordingTimeOffsetMs(parsed.timeOffsetMs),
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
@@ -613,7 +594,6 @@ async function resolveRecordingSession(videoPath?: string | null): Promise<Recor
|
||||
return {
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: linkedWebcamPath,
|
||||
timeOffsetMs: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1742,6 +1722,10 @@ function waitForWindowsCaptureStop(proc: ChildProcessWithoutNullStreams) {
|
||||
resolve(match[1].trim())
|
||||
return
|
||||
}
|
||||
if (code === 0 && windowsCaptureTargetPath) {
|
||||
resolve(windowsCaptureTargetPath)
|
||||
return
|
||||
}
|
||||
reject(new Error(windowsCaptureOutputBuffer.trim() || `Native Windows capture exited with code ${code ?? 'unknown'}`))
|
||||
}
|
||||
|
||||
@@ -1863,43 +1847,6 @@ async function muxNativeWindowsVideoWithAudio(videoPath: string, systemAudioPath
|
||||
}
|
||||
}
|
||||
|
||||
async function probeRecordedMediaStream(videoPath: string, streamType: 'video' | 'audio') {
|
||||
const ffmpegPath = getFfmpegBinaryPath()
|
||||
const args = streamType === 'video'
|
||||
? ['-v', 'error', '-i', videoPath, '-map', '0:v:0', '-frames:v', '1', '-f', 'null', '-']
|
||||
: ['-v', 'error', '-i', videoPath, '-map', '0:a:0', '-frames:a', '1', '-f', 'null', '-']
|
||||
|
||||
await execFileAsync(ffmpegPath, args, {
|
||||
timeout: 30000,
|
||||
maxBuffer: 4 * 1024 * 1024,
|
||||
})
|
||||
}
|
||||
|
||||
async function validateRecordedVideoFile(videoPath: string, options?: { requiresAudio?: boolean }) {
|
||||
await fs.access(videoPath, fsConstants.R_OK)
|
||||
|
||||
const stats = await fs.stat(videoPath)
|
||||
if (stats.size <= 0) {
|
||||
throw new Error('Recorded video file is empty')
|
||||
}
|
||||
|
||||
try {
|
||||
await probeRecordedMediaStream(videoPath, 'video')
|
||||
} catch (error) {
|
||||
throw new Error(`Recorded video file is unreadable or missing a video stream: ${String(error)}`)
|
||||
}
|
||||
|
||||
if (!options?.requiresAudio) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await probeRecordedMediaStream(videoPath, 'audio')
|
||||
} catch (error) {
|
||||
throw new Error(`Recorded video is missing the requested audio track: ${String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
function waitForNativeCaptureStart(process: ChildProcessWithoutNullStreams) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -2498,7 +2445,6 @@ function snapshotCursorTelemetryForPersistence() {
|
||||
}
|
||||
|
||||
async function finalizeStoredVideo(videoPath: string) {
|
||||
await validateRecordedVideoFile(videoPath)
|
||||
snapshotCursorTelemetryForPersistence()
|
||||
currentVideoPath = videoPath
|
||||
currentProjectPath = null
|
||||
@@ -3061,9 +3007,6 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
const micPath = path.join(recordingsDir, `recording-${timestamp}.mic.wav`)
|
||||
config.captureMic = true
|
||||
config.micOutputPath = micPath
|
||||
if (options.microphoneDeviceId) {
|
||||
config.micDeviceId = options.microphoneDeviceId
|
||||
}
|
||||
if (options.microphoneLabel) {
|
||||
config.micDeviceName = options.microphoneLabel
|
||||
}
|
||||
@@ -3264,7 +3207,6 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
await moveFileWithOverwrite(tempVideoPath, finalVideoPath)
|
||||
}
|
||||
|
||||
await validateRecordedVideoFile(finalVideoPath)
|
||||
windowsPendingVideoPath = finalVideoPath
|
||||
return { success: true, path: finalVideoPath }
|
||||
} catch (error) {
|
||||
@@ -3331,13 +3273,14 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
await moveFileWithOverwrite(tempVideoPath, finalVideoPath)
|
||||
}
|
||||
|
||||
const requiresAudio = Boolean(preferredSystemAudioPath || preferredMicrophonePath)
|
||||
|
||||
if (requiresAudio) {
|
||||
await muxNativeMacRecordingWithAudio(finalVideoPath, preferredSystemAudioPath, preferredMicrophonePath)
|
||||
if (preferredSystemAudioPath || preferredMicrophonePath) {
|
||||
try {
|
||||
await muxNativeMacRecordingWithAudio(finalVideoPath, preferredSystemAudioPath, preferredMicrophonePath)
|
||||
} catch (error) {
|
||||
console.warn('Failed to mux native macOS audio into capture:', error)
|
||||
}
|
||||
}
|
||||
|
||||
await validateRecordedVideoFile(finalVideoPath, { requiresAudio })
|
||||
return await finalizeStoredVideo(finalVideoPath)
|
||||
} catch (error) {
|
||||
console.error('Failed to stop native ScreenCaptureKit recording:', error)
|
||||
@@ -3471,21 +3414,22 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
}
|
||||
|
||||
try {
|
||||
const requiresAudio = Boolean(windowsSystemAudioPath || windowsMicAudioPath)
|
||||
|
||||
if (requiresAudio) {
|
||||
if (windowsSystemAudioPath || windowsMicAudioPath) {
|
||||
await muxNativeWindowsVideoWithAudio(videoPath, windowsSystemAudioPath, windowsMicAudioPath)
|
||||
windowsSystemAudioPath = null
|
||||
windowsMicAudioPath = null
|
||||
}
|
||||
|
||||
await validateRecordedVideoFile(videoPath, { requiresAudio })
|
||||
return await finalizeStoredVideo(videoPath)
|
||||
} catch (error) {
|
||||
console.error('Failed to mux native Windows recording:', error)
|
||||
windowsSystemAudioPath = null
|
||||
windowsMicAudioPath = null
|
||||
return { success: false, message: 'Failed to mux native Windows recording', error: String(error) }
|
||||
try {
|
||||
return await finalizeStoredVideo(videoPath)
|
||||
} catch {
|
||||
return { success: false, message: 'Failed to mux native Windows recording', error: String(error) }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4309,13 +4253,12 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
return { success: true, webcamPath: resolvedSession.webcamPath ?? null }
|
||||
})
|
||||
|
||||
ipcMain.handle('set-current-recording-session', async (_, session: { videoPath: string; webcamPath?: string | null; timeOffsetMs?: number }) => {
|
||||
ipcMain.handle('set-current-recording-session', async (_, session: { videoPath: string; webcamPath?: string | null }) => {
|
||||
const normalizedVideoPath = normalizeVideoSourcePath(session.videoPath) ?? session.videoPath
|
||||
currentVideoPath = normalizedVideoPath
|
||||
currentRecordingSession = {
|
||||
videoPath: normalizedVideoPath,
|
||||
webcamPath: normalizeVideoSourcePath(session.webcamPath ?? null),
|
||||
timeOffsetMs: normalizeRecordingTimeOffsetMs(session.timeOffsetMs),
|
||||
}
|
||||
currentProjectPath = null
|
||||
await persistRecordingSessionManifest(currentRecordingSession)
|
||||
@@ -4362,6 +4305,10 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('app:getVersion', () => {
|
||||
return app.getVersion()
|
||||
})
|
||||
|
||||
ipcMain.handle('get-platform', () => {
|
||||
return process.platform;
|
||||
});
|
||||
@@ -4498,9 +4445,5 @@ body{background:transparent;overflow:hidden;width:100vw;height:100vh}
|
||||
seconds: countdownInProgress ? countdownRemaining : null,
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('app:getVersion', () => {
|
||||
return app.getVersion()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ static std::mutex g_stopMutex;
|
||||
static std::condition_variable g_stopCv;
|
||||
|
||||
struct CaptureConfig {
|
||||
int64_t displayId = 0;
|
||||
int displayId = 0;
|
||||
int64_t windowHandle = 0;
|
||||
std::string outputPath;
|
||||
std::string audioOutputPath;
|
||||
@@ -97,7 +97,7 @@ static bool parseSimpleJson(const std::string& json, CaptureConfig& config) {
|
||||
config.outputPath = findString("outputPath");
|
||||
if (config.outputPath.empty()) return false;
|
||||
|
||||
int64_t displayId = findInt64("displayId");
|
||||
int displayId = findInt("displayId");
|
||||
if (displayId >= 0) config.displayId = displayId;
|
||||
|
||||
int64_t windowHandle = findInt64("windowHandle");
|
||||
|
||||
@@ -27,16 +27,20 @@ std::vector<MonitorInfo> enumerateMonitors() {
|
||||
}
|
||||
|
||||
// Electron uses the HMONITOR handle value cast to a number as the display ID.
|
||||
HMONITOR findMonitorByDisplayId(int64_t displayId) {
|
||||
HMONITOR findMonitorByDisplayId(int displayId) {
|
||||
auto monitors = enumerateMonitors();
|
||||
|
||||
for (const auto& m : monitors) {
|
||||
if (static_cast<int64_t>(reinterpret_cast<intptr_t>(m.handle)) == displayId) {
|
||||
if (static_cast<int>(reinterpret_cast<intptr_t>(m.handle)) == displayId) {
|
||||
return m.handle;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
if (!monitors.empty()) {
|
||||
return monitors[0].handle;
|
||||
}
|
||||
|
||||
return MonitorFromPoint({0, 0}, MONITOR_DEFAULTTOPRIMARY);
|
||||
}
|
||||
|
||||
MonitorInfo getMonitorInfo(HMONITOR monitor) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -15,5 +14,5 @@ struct MonitorInfo {
|
||||
};
|
||||
|
||||
std::vector<MonitorInfo> enumerateMonitors();
|
||||
HMONITOR findMonitorByDisplayId(int64_t displayId);
|
||||
HMONITOR findMonitorByDisplayId(int displayId);
|
||||
MonitorInfo getMonitorInfo(HMONITOR monitor);
|
||||
|
||||
+1
-1
@@ -194,7 +194,7 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
||||
setCurrentVideoPath: (path: string) => {
|
||||
return ipcRenderer.invoke("set-current-video-path", path);
|
||||
},
|
||||
setCurrentRecordingSession: (session: { videoPath: string; webcamPath?: string | null; timeOffsetMs?: number }) => {
|
||||
setCurrentRecordingSession: (session: { videoPath: string; webcamPath?: string | null }) => {
|
||||
return ipcRenderer.invoke("set-current-recording-session", session);
|
||||
},
|
||||
getCurrentRecordingSession: () => {
|
||||
|
||||
@@ -1175,7 +1175,7 @@ export default function VideoEditor() {
|
||||
}, [currentPersistedEditorState, currentSourcePath]);
|
||||
|
||||
const syncRecordingSessionWebcam = useCallback(
|
||||
async (webcamPath: string | null, timeOffsetMs = 0) => {
|
||||
async (webcamPath: string | null) => {
|
||||
if (!currentSourcePath || !window.electronAPI.setCurrentRecordingSession) {
|
||||
return;
|
||||
}
|
||||
@@ -1183,7 +1183,6 @@ export default function VideoEditor() {
|
||||
await window.electronAPI.setCurrentRecordingSession({
|
||||
videoPath: currentSourcePath,
|
||||
webcamPath,
|
||||
timeOffsetMs,
|
||||
});
|
||||
},
|
||||
[currentSourcePath],
|
||||
@@ -1214,10 +1213,9 @@ export default function VideoEditor() {
|
||||
...prev,
|
||||
enabled: true,
|
||||
sourcePath: result.path ?? null,
|
||||
timeOffsetMs: 0,
|
||||
}));
|
||||
|
||||
await syncRecordingSessionWebcam(result.path, 0);
|
||||
await syncRecordingSessionWebcam(result.path);
|
||||
toast.success(t("settings.effects.webcamFootageAdded"));
|
||||
}, [syncRecordingSessionWebcam, t]);
|
||||
|
||||
@@ -1226,10 +1224,9 @@ export default function VideoEditor() {
|
||||
...prev,
|
||||
enabled: false,
|
||||
sourcePath: null,
|
||||
timeOffsetMs: 0,
|
||||
}));
|
||||
|
||||
await syncRecordingSessionWebcam(null, 0);
|
||||
await syncRecordingSessionWebcam(null);
|
||||
toast.success(t("settings.effects.webcamFootageRemoved"));
|
||||
}, [syncRecordingSessionWebcam, t]);
|
||||
|
||||
@@ -1298,7 +1295,6 @@ export default function VideoEditor() {
|
||||
...prev,
|
||||
enabled: Boolean(sessionResult.session?.webcamPath),
|
||||
sourcePath: sessionResult.session?.webcamPath ?? null,
|
||||
timeOffsetMs: sessionResult.session?.timeOffsetMs ?? 0,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
@@ -1314,7 +1310,6 @@ export default function VideoEditor() {
|
||||
...prev,
|
||||
enabled: false,
|
||||
sourcePath: null,
|
||||
timeOffsetMs: 0,
|
||||
}));
|
||||
} else {
|
||||
setError("No video to load. Please record or select a video.");
|
||||
|
||||
@@ -945,7 +945,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
}
|
||||
|
||||
const targetTime = clampMediaTimeToDuration(
|
||||
Math.max(0, currentTime - (webcam.timeOffsetMs ?? 0) / 1000),
|
||||
currentTime,
|
||||
Number.isFinite(webcamVideo.duration) ? webcamVideo.duration : null,
|
||||
);
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ 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,
|
||||
@@ -586,9 +585,6 @@ 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,
|
||||
positionPreset:
|
||||
webcam.positionPreset === "top-left" ||
|
||||
|
||||
@@ -69,7 +69,6 @@ export type WebcamPositionPreset =
|
||||
export interface WebcamOverlaySettings {
|
||||
enabled: boolean;
|
||||
sourcePath: string | null;
|
||||
timeOffsetMs: number;
|
||||
mirror: boolean;
|
||||
corner: WebcamCorner;
|
||||
positionPreset: WebcamPositionPreset;
|
||||
@@ -105,12 +104,10 @@ export const DEFAULT_WEBCAM_MARGIN = 24;
|
||||
export const DEFAULT_WEBCAM_POSITION_PRESET: WebcamPositionPreset = "bottom-right";
|
||||
export const DEFAULT_WEBCAM_POSITION_X = 1;
|
||||
export const DEFAULT_WEBCAM_POSITION_Y = 1;
|
||||
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",
|
||||
positionPreset: DEFAULT_WEBCAM_POSITION_PRESET,
|
||||
|
||||
@@ -79,8 +79,6 @@ 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 nativeWindowsRecording = useRef(false);
|
||||
@@ -170,10 +168,11 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
|
||||
const selectMimeType = () => {
|
||||
const preferred = [
|
||||
"video/webm;codecs=av1",
|
||||
"video/webm;codecs=h264",
|
||||
"video/webm;codecs=vp9",
|
||||
"video/webm;codecs=vp8",
|
||||
"video/webm",
|
||||
"video/webm;codecs=av1",
|
||||
];
|
||||
|
||||
return preferred.find((type) => MediaRecorder.isTypeSupported(type)) ?? "video/webm";
|
||||
@@ -267,7 +266,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
await window.electronAPI.setCurrentRecordingSession({
|
||||
videoPath,
|
||||
webcamPath,
|
||||
timeOffsetMs: webcamTimeOffsetMs.current,
|
||||
});
|
||||
} else {
|
||||
await window.electronAPI.setCurrentVideoPath(videoPath);
|
||||
@@ -296,8 +294,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
const startWebcamRecorder = useCallback(async () => {
|
||||
if (!webcamEnabled) {
|
||||
pendingWebcamPathPromise.current = Promise.resolve(null);
|
||||
webcamStartTime.current = null;
|
||||
webcamTimeOffsetMs.current = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -350,7 +346,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
return;
|
||||
}
|
||||
|
||||
const duration = Math.max(0, getRecordingDurationMs(Date.now()) - webcamTimeOffsetMs.current);
|
||||
const duration = getRecordingDurationMs(Date.now());
|
||||
const webcamBlob = new Blob(webcamChunks.current, { type: mimeType });
|
||||
webcamChunks.current = [];
|
||||
const fixedBlob = await fixWebmDuration(webcamBlob, duration);
|
||||
@@ -363,7 +359,6 @@ 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;
|
||||
@@ -371,15 +366,12 @@ 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;
|
||||
@@ -410,13 +402,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
|
||||
if (isNativeWindows) {
|
||||
const muxResult = await window.electronAPI.muxNativeWindowsRecording();
|
||||
if (!muxResult?.success || !muxResult.path) {
|
||||
console.error("Failed to finalize native Windows recording:", muxResult?.error ?? muxResult?.message);
|
||||
alert("Recording could not be finalized because the captured video file is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
finalPath = muxResult.path;
|
||||
finalPath = muxResult?.path ?? result.path;
|
||||
}
|
||||
|
||||
await finalizeRecordingSession(finalPath, webcamPath);
|
||||
@@ -533,8 +519,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
|
||||
recordingSessionTimestamp.current = Date.now();
|
||||
resetRecordingClock(recordingSessionTimestamp.current);
|
||||
webcamStartTime.current = null;
|
||||
webcamTimeOffsetMs.current = 0;
|
||||
await startWebcamRecorder();
|
||||
|
||||
const platform = await window.electronAPI.getPlatform();
|
||||
@@ -607,13 +591,9 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
}
|
||||
|
||||
if (nativeResult.success) {
|
||||
const mainStartedAt = Date.now();
|
||||
nativeScreenRecording.current = true;
|
||||
nativeWindowsRecording.current = useNativeWindowsCapture;
|
||||
webcamTimeOffsetMs.current = webcamStartTime.current === null
|
||||
? 0
|
||||
: webcamStartTime.current - mainStartedAt;
|
||||
resetRecordingClock(mainStartedAt);
|
||||
resetRecordingClock(Date.now());
|
||||
setRecording(true);
|
||||
window.electronAPI?.setRecordingState(true);
|
||||
|
||||
@@ -839,12 +819,8 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
|
||||
recorder.onerror = () => {
|
||||
setRecording(false);
|
||||
};
|
||||
const mainStartedAt = Date.now();
|
||||
resetRecordingClock(mainStartedAt);
|
||||
webcamTimeOffsetMs.current = webcamStartTime.current === null
|
||||
? 0
|
||||
: webcamStartTime.current - mainStartedAt;
|
||||
recorder.start(RECORDER_TIMESLICE_MS);
|
||||
resetRecordingClock(Date.now());
|
||||
setRecording(true);
|
||||
window.electronAPI?.setRecordingState(true);
|
||||
} catch (error) {
|
||||
@@ -928,8 +904,6 @@ 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