From 62323ee3ee308ff2b3bb52597c3d0d838280ef55 Mon Sep 17 00:00:00 2001 From: wiiiii123 Date: Sat, 30 May 2026 01:20:36 +0700 Subject: [PATCH] revert: undo PRs 625 and 626 --- electron/electron-env.d.ts | 1 - electron/hudOverlayBounds.test.ts | 4 - electron/hudOverlayBounds.ts | 3 +- electron/ipc/recording/mac.ts | 7 +- .../ipc/recording/macCompanionAudio.test.ts | 35 ------- electron/ipc/recording/macCompanionAudio.ts | 16 ---- electron/preload.ts | 3 - electron/windows.ts | 33 +------ .../video-editor/timeline/TimelineEditor.tsx | 92 ++++++++++-------- .../timeline/hooks/useTimelineAudioPeaks.ts | 32 +++---- .../timeline/sourceAudioTracks.test.ts | 90 ------------------ .../timeline/sourceAudioTracks.ts | 68 -------------- src/hooks/useScreenRecorder.test.ts | 27 ------ src/hooks/useScreenRecorder.ts | 39 +------- src/lib/exporter/audioEncoder.test.ts | 93 +------------------ src/lib/exporter/audioEncoder.ts | 39 -------- 16 files changed, 73 insertions(+), 509 deletions(-) delete mode 100644 electron/ipc/recording/macCompanionAudio.test.ts delete mode 100644 electron/ipc/recording/macCompanionAudio.ts delete mode 100644 src/components/video-editor/timeline/sourceAudioTracks.test.ts delete mode 100644 src/components/video-editor/timeline/sourceAudioTracks.ts diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index c0cf0cdb..9e686c1c 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -204,7 +204,6 @@ interface RendererNativeExportCapabilities { interface Window { electronAPI: { hudOverlaySetIgnoreMouse: (ignore: boolean) => void; - hudOverlaySetSourceSelectionActive: (active: boolean) => void; hudOverlayDrag: (phase: "start" | "move" | "end", screenX: number, screenY: number) => void; hudOverlayHide: () => void; hudOverlayClose: () => void; diff --git a/electron/hudOverlayBounds.test.ts b/electron/hudOverlayBounds.test.ts index e9daa53a..b45c9a56 100644 --- a/electron/hudOverlayBounds.test.ts +++ b/electron/hudOverlayBounds.test.ts @@ -158,10 +158,6 @@ describe("shouldResizeHudOverlayFallback", () => { it("keeps the recording HUD compact in non-passthrough mode", () => { expect(shouldResizeHudOverlayFallback(false, true)).toBe(false); }); - - it("keeps the fallback stable while source selection is active", () => { - expect(shouldResizeHudOverlayFallback(false, false, true)).toBe(false); - }); }); describe("shouldExpandHudOverlayFallback", () => { diff --git a/electron/hudOverlayBounds.ts b/electron/hudOverlayBounds.ts index 2a8bff82..e56c3e8b 100644 --- a/electron/hudOverlayBounds.ts +++ b/electron/hudOverlayBounds.ts @@ -41,9 +41,8 @@ export function getHudOverlayWindowBounds( export function shouldResizeHudOverlayFallback( mousePassthroughSupported: boolean, recordingActive: boolean, - interactionLocked = false, ): boolean { - return !mousePassthroughSupported && !recordingActive && !interactionLocked; + return !mousePassthroughSupported && !recordingActive; } export function shouldExpandHudOverlayFallback({ diff --git a/electron/ipc/recording/mac.ts b/electron/ipc/recording/mac.ts index 01951345..bb6fd356 100644 --- a/electron/ipc/recording/mac.ts +++ b/electron/ipc/recording/mac.ts @@ -30,7 +30,6 @@ import { validateRecordedVideo, } from "./diagnostics"; import { emitRecordingInterrupted } from "./events"; -import { getFinalMacCompanionAudioPath } from "./macCompanionAudio"; import { pruneAutoRecordings } from "./prune"; export function waitForNativeCaptureStart(process: ChildProcessWithoutNullStreams) { @@ -120,10 +119,12 @@ export async function muxNativeMacRecordingWithAudio( ) { console.log("[mac-mux] Optimization active: keeping tracks separate."); + const videoPathWithoutExt = videoPath.replace(/\.[^.]+$/u, ""); + // Optimization: instead of heavy FFmpeg muxing, we ensure audio sidecars // are available alongside the video for the editor. if (systemAudioPath) { - const finalSystemPath = getFinalMacCompanionAudioPath(videoPath, systemAudioPath, "system"); + const finalSystemPath = `${videoPathWithoutExt}.system.wav`; try { const stat = await fs.stat(systemAudioPath); if (stat.size > 0 && systemAudioPath !== finalSystemPath) { @@ -135,7 +136,7 @@ export async function muxNativeMacRecordingWithAudio( } if (microphonePath) { - const finalMicPath = getFinalMacCompanionAudioPath(videoPath, microphonePath, "mic"); + const finalMicPath = `${videoPathWithoutExt}.mic.wav`; try { const stat = await fs.stat(microphonePath); if (stat.size > 0 && microphonePath !== finalMicPath) { diff --git a/electron/ipc/recording/macCompanionAudio.test.ts b/electron/ipc/recording/macCompanionAudio.test.ts deleted file mode 100644 index 269a7662..00000000 --- a/electron/ipc/recording/macCompanionAudio.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { getFinalMacCompanionAudioPath } from "./macCompanionAudio"; - -describe("mac companion audio paths", () => { - it("preserves the helper's AAC container extension", () => { - expect( - getFinalMacCompanionAudioPath( - "/Users/egg/Recordly/recording-1.mp4", - "/Users/egg/Recordly/recording-1.mic.m4a", - "mic", - ), - ).toBe("/Users/egg/Recordly/recording-1.mic.m4a"); - }); - - it("preserves legacy sidecar extensions instead of renaming bytes", () => { - expect( - getFinalMacCompanionAudioPath( - "/Users/egg/Recordly/recording-1.mp4", - "/tmp/recordly-native.system.webm", - "system", - ), - ).toBe("/Users/egg/Recordly/recording-1.system.webm"); - }); - - it("keeps dotted directories when the video path has no extension", () => { - expect( - getFinalMacCompanionAudioPath( - "/Users/egg/Recordly.videos/recording-1", - "/tmp/recordly-native.mic.m4a", - "mic", - ), - ).toBe("/Users/egg/Recordly.videos/recording-1.mic.m4a"); - }); -}); diff --git a/electron/ipc/recording/macCompanionAudio.ts b/electron/ipc/recording/macCompanionAudio.ts deleted file mode 100644 index fdfc9e26..00000000 --- a/electron/ipc/recording/macCompanionAudio.ts +++ /dev/null @@ -1,16 +0,0 @@ -import path from "node:path"; - -export type MacCompanionAudioSuffix = "system" | "mic"; - -export function getFinalMacCompanionAudioPath( - videoPath: string, - sourceAudioPath: string, - suffix: MacCompanionAudioSuffix, -) { - const separatorIndex = Math.max(videoPath.lastIndexOf("/"), videoPath.lastIndexOf("\\")); - const videoDirectory = separatorIndex >= 0 ? videoPath.slice(0, separatorIndex + 1) : ""; - const videoFileName = separatorIndex >= 0 ? videoPath.slice(separatorIndex + 1) : videoPath; - const videoPathWithoutExt = `${videoDirectory}${path.parse(videoFileName).name}`; - const sourceExtension = path.extname(sourceAudioPath).toLowerCase() || ".m4a"; - return `${videoPathWithoutExt}.${suffix}${sourceExtension}`; -} diff --git a/electron/preload.ts b/electron/preload.ts index 384682a1..04251937 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -167,9 +167,6 @@ contextBridge.exposeInMainWorld("electronAPI", { hudOverlaySetIgnoreMouse: (ignore: boolean) => { ipcRenderer.send("hud-overlay-set-ignore-mouse", ignore); }, - hudOverlaySetSourceSelectionActive: (active: boolean) => { - ipcRenderer.send("hud-overlay-set-source-selection-active", active); - }, hudOverlayDrag: (phase: "start" | "move" | "end", screenX: number, screenY: number) => { ipcRenderer.send("hud-overlay-drag", phase, screenX, screenY); }, diff --git a/electron/windows.ts b/electron/windows.ts index 6173fc03..6a5abf67 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -32,7 +32,6 @@ let hudOverlayHiddenFromCapture = true; let hudOverlayCaptureProtectionLoaded = false; let hudOverlayFallbackExpanded = false; let hudOverlayIgnoringMouse = true; -let hudOverlaySourceSelectionActive = false; let hudOverlayMouseReassertTimer: NodeJS.Timeout | null = null; let hudOverlayRecordingActive = false; let hudOverlayWebcamPreviewVisible = false; @@ -288,12 +287,7 @@ function setHudOverlayFallbackExpanded(expanded: boolean) { } function setHudOverlayMousePassthrough(ignore: boolean) { - hudOverlayIgnoringMouse = - hudOverlaySourceSelectionActive && !hudOverlayRecordingActive - ? true - : hudOverlayRecordingActive - ? false - : ignore; + hudOverlayIgnoringMouse = hudOverlayRecordingActive ? false : ignore; if (hudOverlayMouseReassertTimer) { clearTimeout(hudOverlayMouseReassertTimer); @@ -311,21 +305,9 @@ function setHudOverlayMousePassthrough(ignore: boolean) { return; } - if (hudOverlaySourceSelectionActive) { - hudOverlayFallbackExpanded = false; - hudOverlayWindow.setIgnoreMouseEvents(false); - return; - } - const mousePassthroughSupported = isHudOverlayMousePassthroughSupported(); if (!mousePassthroughSupported) { - if ( - shouldResizeHudOverlayFallback( - mousePassthroughSupported, - hudOverlayRecordingActive, - hudOverlaySourceSelectionActive, - ) - ) { + if (shouldResizeHudOverlayFallback(mousePassthroughSupported, hudOverlayRecordingActive)) { setHudOverlayFallbackExpanded(!ignore); } hudOverlayWindow.setIgnoreMouseEvents(false); @@ -344,17 +326,6 @@ ipcMain.on("hud-overlay-set-ignore-mouse", (_event, ignore: boolean) => { setHudOverlayMousePassthrough(Boolean(ignore)); }); -ipcMain.on("hud-overlay-set-source-selection-active", (_event, active: boolean) => { - hudOverlaySourceSelectionActive = Boolean(active); - if (hudOverlaySourceSelectionActive) { - hudOverlayFallbackExpanded = false; - applyHudOverlayBounds(); - return; - } - - setHudOverlayMousePassthrough(hudOverlayIgnoringMouse); -}); - // Keep compatibility with existing drag IPC/state. let hudUserPosition: { x: number; y: number } | null = null; let hudDragOffset: { x: number; y: number } | null = null; diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx index 041bc318..a51e506f 100644 --- a/src/components/video-editor/timeline/TimelineEditor.tsx +++ b/src/components/video-editor/timeline/TimelineEditor.tsx @@ -4,6 +4,7 @@ import { forwardRef, useEffect, useMemo, useRef, useState } from "react"; import type { SourceAudioTrackMeta, SourceAudioTrackSettings, + SourceAudioTrackWithPeaks, } from "@/components/video-editor/audio/audioTypes"; import { useScopedT } from "@/contexts/I18nContext"; import { useShortcuts } from "@/contexts/ShortcutsContext"; @@ -25,10 +26,6 @@ import { calculateTimelineScale } from "./core/time"; import { useTimelineAudioPeaks } from "./hooks/useTimelineAudioPeaks"; import { useTimelineEditorRuntime } from "./hooks/useTimelineEditorRuntime"; import { useTimelineRange } from "./hooks/useTimelineRange"; -import { - buildSourceSidecarPathCandidates, - buildTimelineSourceAudioTracks, -} from "./sourceAudioTracks"; export interface TimelineEditorProps { videoDuration: number; @@ -93,6 +90,16 @@ function extractLocalPathFromMediaServerUrl(input: string | null | undefined): s } } +function buildSourceSidecarPath(source: string, suffix: "mic" | "system"): string { + const normalized = source.replace(/\\/g, "/"); + const lastSlash = normalized.lastIndexOf("/"); + const dir = lastSlash >= 0 ? normalized.slice(0, lastSlash + 1) : ""; + const fileName = lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized; + const dotIndex = fileName.lastIndexOf("."); + const baseName = dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName; + return `${dir}${baseName}.${suffix}.wav`; +} + export interface TimelineEditorHandle { addZoom: () => void; suggestZooms: () => void; @@ -227,8 +234,12 @@ const TimelineEditor = forwardRef( return { previewSpans, hiddenZoomIds }; }, [clipRegions, liveSpanPreviewById, zoomRegions]); const { shortcuts: keyShortcuts, isMac } = useShortcuts(); - const { peaks: sourceAudioPeaks, loading: sourceAudioLoading } = - useTimelineAudioPeaks(videoPath); + const { peaks: sourceAudioPeaks, loading: sourceAudioLoading } = useTimelineAudioPeaks( + videoPath, + { + enableSourceSidecarFallback: true, + }, + ); const localSourcePath = useMemo(() => { if (!videoPath) return null; return ( @@ -236,44 +247,45 @@ const TimelineEditor = forwardRef( (/^file:\/\//i.test(videoPath) ? fromFileUrl(videoPath) : videoPath) ); }, [videoPath]); - const micSidecarPaths = useMemo( - () => (localSourcePath ? buildSourceSidecarPathCandidates(localSourcePath, "mic") : []), + const micSidecarPath = useMemo( + () => (localSourcePath ? buildSourceSidecarPath(localSourcePath, "mic") : null), [localSourcePath], ); - const micSidecarFallbackPaths = useMemo(() => micSidecarPaths.slice(1), [micSidecarPaths]); - const systemSidecarPaths = useMemo( - () => - localSourcePath ? buildSourceSidecarPathCandidates(localSourcePath, "system") : [], + const systemSidecarPath = useMemo( + () => (localSourcePath ? buildSourceSidecarPath(localSourcePath, "system") : null), [localSourcePath], ); - const systemSidecarFallbackPaths = useMemo( - () => systemSidecarPaths.slice(1), - [systemSidecarPaths], - ); - const { peaks: micSidecarPeaks, loading: micSidecarLoading } = useTimelineAudioPeaks( - micSidecarPaths[0] ?? null, - { fallbackResources: micSidecarFallbackPaths }, - ); - const { peaks: systemSidecarPeaks, loading: systemSidecarLoading } = useTimelineAudioPeaks( - systemSidecarPaths[0] ?? null, - { - fallbackResources: systemSidecarFallbackPaths, - }, - ); - const sourceAudioTracks = useMemo( - () => - buildTimelineSourceAudioTracks({ - sourceAudioPeaks, - micSidecarPeaks, - systemSidecarPeaks, - labels: { - system: t("audio.systemLabel", "Source System"), - mic: t("audio.micLabel", "Source Mic"), - mixed: t("audio.mixedLabel", "Source"), - }, - }), - [micSidecarPeaks, sourceAudioPeaks, systemSidecarPeaks, t], - ); + const { peaks: micSidecarPeaks, loading: micSidecarLoading } = + useTimelineAudioPeaks(micSidecarPath); + const { peaks: systemSidecarPeaks, loading: systemSidecarLoading } = + useTimelineAudioPeaks(systemSidecarPath); + const sourceAudioTracks = useMemo(() => { + if (systemSidecarPeaks || micSidecarPeaks) { + const tracks: SourceAudioTrackWithPeaks[] = []; + if (systemSidecarPeaks) + tracks.push({ + id: "system", + label: t("audio.systemLabel", "Source System"), + peaks: systemSidecarPeaks, + }); + if (micSidecarPeaks) + tracks.push({ + id: "mic", + label: t("audio.micLabel", "Source Mic"), + peaks: micSidecarPeaks, + }); + return tracks; + } + return sourceAudioPeaks + ? [ + { + id: "mixed", + label: t("audio.mixedLabel", "Source"), + peaks: sourceAudioPeaks, + }, + ] + : []; + }, [micSidecarPeaks, sourceAudioPeaks, systemSidecarPeaks, t]); const isLoading = useMemo(() => { // If we are still actively trying to load audio peaks (main or sidecars) diff --git a/src/components/video-editor/timeline/hooks/useTimelineAudioPeaks.ts b/src/components/video-editor/timeline/hooks/useTimelineAudioPeaks.ts index cf5ca393..8af9c562 100644 --- a/src/components/video-editor/timeline/hooks/useTimelineAudioPeaks.ts +++ b/src/components/video-editor/timeline/hooks/useTimelineAudioPeaks.ts @@ -5,8 +5,6 @@ import { fromFileUrl } from "../../projectPersistence"; import { WAVEFORM_DEFAULT_PEAK_COUNT } from "../core/constants"; import type { AudioPeaksData } from "../core/timelineTypes"; -const EMPTY_FALLBACK_RESOURCES: string[] = []; - function buildSidecarAudioCandidates(sourcePath: string): string[] { const normalized = sourcePath.replace(/\\/g, "/"); const lastSlash = normalized.lastIndexOf("/"); @@ -20,8 +18,6 @@ function buildSidecarAudioCandidates(sourcePath: string): string[] { `${dir}${baseName}.mic.wav`, `${dir}${baseName}.system.m4a`, `${dir}${baseName}.mic.m4a`, - `${dir}${baseName}.system.webm`, - `${dir}${baseName}.mic.webm`, ]; } @@ -41,7 +37,6 @@ function extractLocalPathFromMediaServerUrl(input: string): string | null { interface TimelineAudioPeaksOptions { enableSourceSidecarFallback?: boolean; - fallbackResources?: string[]; peakCount?: number; } @@ -58,7 +53,6 @@ export function useTimelineAudioPeaks( const [loading, setLoading] = useState(false); const sourceRef = useRef(mediaResource); const enableSourceSidecarFallback = options.enableSourceSidecarFallback ?? false; - const fallbackResources = options.fallbackResources ?? EMPTY_FALLBACK_RESOURCES; const peakCount = options.peakCount ?? WAVEFORM_DEFAULT_PEAK_COUNT; useEffect(() => { @@ -89,29 +83,25 @@ export function useTimelineAudioPeaks( // fallthrough } - if (!enableSourceSidecarFallback && fallbackResources.length === 0) { + if (!enableSourceSidecarFallback) { if (!cancelled && sourceRef.current === mediaResource) { setLoading(false); } return; } - let sourceSidecarCandidates: string[] = []; - if (enableSourceSidecarFallback) { - const localPathFromServer = extractLocalPathFromMediaServerUrl(mediaResource); - const localSourcePath = - localPathFromServer || - (/^file:\/\//i.test(mediaResource) - ? fromFileUrl(mediaResource) - : mediaResource); - if (localSourcePath) { - sourceSidecarCandidates = buildSidecarAudioCandidates(localSourcePath); + const localPathFromServer = extractLocalPathFromMediaServerUrl(mediaResource); + const localSourcePath = + localPathFromServer || + (/^file:\/\//i.test(mediaResource) ? fromFileUrl(mediaResource) : mediaResource); + if (!localSourcePath) { + if (!cancelled && sourceRef.current === mediaResource) { + setLoading(false); } + return; } - const candidates = Array.from( - new Set([...fallbackResources, ...sourceSidecarCandidates]), - ); + const candidates = buildSidecarAudioCandidates(localSourcePath); for (const candidate of candidates) { try { const result = await tryGenerate(candidate); @@ -135,7 +125,7 @@ export function useTimelineAudioPeaks( return () => { cancelled = true; }; - }, [mediaResource, enableSourceSidecarFallback, fallbackResources, peakCount]); + }, [mediaResource, enableSourceSidecarFallback, peakCount]); return { peaks, loading }; } diff --git a/src/components/video-editor/timeline/sourceAudioTracks.test.ts b/src/components/video-editor/timeline/sourceAudioTracks.test.ts deleted file mode 100644 index f7c809e6..00000000 --- a/src/components/video-editor/timeline/sourceAudioTracks.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { describe, expect, it } from "vitest"; -import type { AudioPeaksData } from "./core/timelineTypes"; -import { - buildSourceSidecarPathCandidates, - buildTimelineSourceAudioTracks, -} from "./sourceAudioTracks"; - -function peaks(id: number): AudioPeaksData { - return { - durationMs: 1000, - peaks: new Float32Array([id]), - }; -} - -const labels = { - system: "Source System", - mic: "Source Mic", - mixed: "Source", -}; - -describe("timeline source audio tracks", () => { - it("builds candidates for Windows and macOS sidecar containers", () => { - expect(buildSourceSidecarPathCandidates("C:\\Recordly\\recording-1.mp4", "mic")).toEqual([ - "C:/Recordly/recording-1.mic.wav", - "C:/Recordly/recording-1.mic.m4a", - "C:/Recordly/recording-1.mic.webm", - ]); - }); - - it("keeps embedded system audio controllable when mic is a sidecar", () => { - const source = peaks(1); - const mic = peaks(2); - - expect( - buildTimelineSourceAudioTracks({ - sourceAudioPeaks: source, - micSidecarPeaks: mic, - systemSidecarPeaks: null, - labels, - }), - ).toEqual([ - { id: "system", label: "Source System", peaks: source }, - { id: "mic", label: "Source Mic", peaks: mic }, - ]); - }); - - it("does not invent a system track when only the mic sidecar exists", () => { - const mic = peaks(2); - - expect( - buildTimelineSourceAudioTracks({ - sourceAudioPeaks: null, - micSidecarPeaks: mic, - systemSidecarPeaks: null, - labels, - }), - ).toEqual([{ id: "mic", label: "Source Mic", peaks: mic }]); - }); - - it("uses dedicated sidecars over the embedded track when both source tracks exist", () => { - const source = peaks(1); - const system = peaks(2); - const mic = peaks(3); - - expect( - buildTimelineSourceAudioTracks({ - sourceAudioPeaks: source, - micSidecarPeaks: mic, - systemSidecarPeaks: system, - labels, - }), - ).toEqual([ - { id: "system", label: "Source System", peaks: system }, - { id: "mic", label: "Source Mic", peaks: mic }, - ]); - }); - - it("falls back to one mixed source track when no dedicated sidecar exists", () => { - const source = peaks(1); - - expect( - buildTimelineSourceAudioTracks({ - sourceAudioPeaks: source, - micSidecarPeaks: null, - systemSidecarPeaks: null, - labels, - }), - ).toEqual([{ id: "mixed", label: "Source", peaks: source }]); - }); -}); diff --git a/src/components/video-editor/timeline/sourceAudioTracks.ts b/src/components/video-editor/timeline/sourceAudioTracks.ts deleted file mode 100644 index 46a089ee..00000000 --- a/src/components/video-editor/timeline/sourceAudioTracks.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { SourceAudioTrackWithPeaks } from "@/components/video-editor/audio/audioTypes"; -import type { AudioPeaksData } from "./core/timelineTypes"; - -const SOURCE_SIDECAR_EXTENSIONS = [".wav", ".m4a", ".webm"] as const; - -export function buildSourceSidecarPathCandidates( - source: string, - suffix: "mic" | "system", -): string[] { - const normalized = source.replace(/\\/g, "/"); - const lastSlash = normalized.lastIndexOf("/"); - const dir = lastSlash >= 0 ? normalized.slice(0, lastSlash + 1) : ""; - const fileName = lastSlash >= 0 ? normalized.slice(lastSlash + 1) : normalized; - const dotIndex = fileName.lastIndexOf("."); - const baseName = dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName; - return SOURCE_SIDECAR_EXTENSIONS.map((extension) => `${dir}${baseName}.${suffix}${extension}`); -} - -export function buildTimelineSourceAudioTracks({ - sourceAudioPeaks, - micSidecarPeaks, - systemSidecarPeaks, - labels, -}: { - sourceAudioPeaks: AudioPeaksData | null; - micSidecarPeaks: AudioPeaksData | null; - systemSidecarPeaks: AudioPeaksData | null; - labels: { - system: string; - mic: string; - mixed: string; - }; -}): SourceAudioTrackWithPeaks[] { - if (systemSidecarPeaks || micSidecarPeaks) { - const tracks: SourceAudioTrackWithPeaks[] = []; - if (systemSidecarPeaks) { - tracks.push({ - id: "system", - label: labels.system, - peaks: systemSidecarPeaks, - }); - } else if (micSidecarPeaks && sourceAudioPeaks) { - tracks.push({ - id: "system", - label: labels.system, - peaks: sourceAudioPeaks, - }); - } - if (micSidecarPeaks) { - tracks.push({ - id: "mic", - label: labels.mic, - peaks: micSidecarPeaks, - }); - } - return tracks; - } - - return sourceAudioPeaks - ? [ - { - id: "mixed", - label: labels.mixed, - peaks: sourceAudioPeaks, - }, - ] - : []; -} diff --git a/src/hooks/useScreenRecorder.test.ts b/src/hooks/useScreenRecorder.test.ts index ed16b6a5..ae27d1c1 100644 --- a/src/hooks/useScreenRecorder.test.ts +++ b/src/hooks/useScreenRecorder.test.ts @@ -7,7 +7,6 @@ import { normalizeBrowserMicrophoneProfile, resolveBrowserCaptureCursorPolicy, resolveLinuxPortalCursorPresentation, - shouldLockHudDuringDisplaySelection, shouldUseLinuxPortalCapture, shouldUseNativeWindowsCaptureForSource, } from "./useScreenRecorder"; @@ -247,32 +246,6 @@ describe("shouldUseLinuxPortalCapture", () => { }); }); -describe("shouldLockHudDuringDisplaySelection", () => { - it("locks HUD fallback resizing while Linux portal selection is active", () => { - expect( - shouldLockHudDuringDisplaySelection({ - platform: "linux", - useLinuxPortal: true, - }), - ).toBe(true); - }); - - it("keeps non-portal capture flows interactive", () => { - expect( - shouldLockHudDuringDisplaySelection({ - platform: "linux", - useLinuxPortal: false, - }), - ).toBe(false); - expect( - shouldLockHudDuringDisplaySelection({ - platform: "win32", - useLinuxPortal: true, - }), - ).toBe(false); - }); -}); - describe("getScreenCaptureCursorSetting", () => { it("normalizes only supported screen-capture cursor settings", () => { expect(getScreenCaptureCursorSetting({ cursor: "motion" } as MediaTrackSettings)).toBe( diff --git a/src/hooks/useScreenRecorder.ts b/src/hooks/useScreenRecorder.ts index bb1f4e3d..8455a0bf 100644 --- a/src/hooks/useScreenRecorder.ts +++ b/src/hooks/useScreenRecorder.ts @@ -145,16 +145,6 @@ export function shouldUseLinuxPortalCapture({ ); } -export function shouldLockHudDuringDisplaySelection({ - platform, - useLinuxPortal, -}: { - platform?: string; - useLinuxPortal: boolean; -}) { - return platform === "linux" && useLinuxPortal; -} - type UseScreenRecorderReturn = { recording: boolean; paused: boolean; @@ -1439,16 +1429,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { return; } - let hudSourceSelectionActive = false; - const setHudSourceSelectionActive = (active: boolean) => { - if (hudSourceSelectionActive === active) { - return; - } - - hudSourceSelectionActive = active; - window.electronAPI?.hudOverlaySetSourceSelectionActive?.(active); - }; - hasPromptedForReselect.current = false; startInFlight.current = true; setStarting(true); @@ -1648,14 +1628,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { } setRecording(true); - try { - await window.electronAPI?.setRecordingState(true); - } catch (stateError) { - console.warn( - "Failed to notify main process that native recording started:", - stateError, - ); - } + window.electronAPI?.setRecordingState(true); return; } @@ -1703,9 +1676,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { browserCaptureSourceId: browserCaptureSource.id, selectedSourceId: selectedSource.id, }); - if (shouldLockHudDuringDisplaySelection({ platform, useLinuxPortal })) { - setHudSourceSelectionActive(true); - } const browserScreenVideoConstraints = { mandatory: { chromeMediaSource: CHROME_MEDIA_SOURCE, @@ -2020,11 +1990,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn { webcamStartTime.current === null ? 0 : webcamStartTime.current - mainStartedAt; recorder.start(RECORDER_TIMESLICE_MS); setRecording(true); - try { - await window.electronAPI?.setRecordingState(true); - } catch (stateError) { - console.warn("Failed to notify main process that recording started:", stateError); - } + window.electronAPI?.setRecordingState(true); } catch (error) { console.error("Failed to start recording:", error); alert( @@ -2042,7 +2008,6 @@ export function useScreenRecorder(): UseScreenRecorderReturn { await stopWebcamRecorder(); } } finally { - setHudSourceSelectionActive(false); startInFlight.current = false; setStarting(false); } diff --git a/src/lib/exporter/audioEncoder.test.ts b/src/lib/exporter/audioEncoder.test.ts index 6047b548..309ff8fe 100644 --- a/src/lib/exporter/audioEncoder.test.ts +++ b/src/lib/exporter/audioEncoder.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; -import { AudioProcessor, softLimitOfflineMixPeaksInPlace } from "./audioEncoder"; +import { AudioProcessor } from "./audioEncoder"; type OfflineRenderTestHarness = AudioProcessor & { decodeAudioFromUrl(url: string): Promise; @@ -26,32 +26,8 @@ type OfflineRenderTestHarness = AudioProcessor & { sourceAudioFallbackStartDelayMsByPath: Record | undefined, muxer: unknown, ): Promise; - renderChunked( - prepared: { - mainBufferEntry: null; - companionEntries: []; - regionEntries: []; - mutedSourceOutputRangesSec: []; - slices: []; - outputDurationMs: number; - numChannels: number; - }, - totalOutputSec: number, - onChunk: ( - rendered: AudioBuffer, - outputOffsetSec: number, - chunkIndex: number, - ) => Promise, - ): Promise; }; -function fakeAudioBuffer(channels: Float32Array[]): AudioBuffer { - return { - numberOfChannels: channels.length, - getChannelData: (channel: number) => channels[channel], - } as AudioBuffer; -} - describe("AudioProcessor offline render preparation", () => { it("keeps embedded source audio separate from external companion sidecars", async () => { const processor = new AudioProcessor() as unknown as OfflineRenderTestHarness; @@ -160,71 +136,4 @@ describe("AudioProcessor offline render preparation", () => { expect(loadAudioFileDemuxer).not.toHaveBeenCalled(); expect(renderAndMuxOfflineAudio).toHaveBeenCalled(); }); - - it("soft-limits mixed peaks before encoding or WAV conversion", () => { - const samples = new Float32Array([ - -1.6, - -0.5, - Number.NEGATIVE_INFINITY, - Number.NaN, - 0, - 0.5, - 0.95, - Number.POSITIVE_INFINITY, - 1.6, - ]); - const changed = softLimitOfflineMixPeaksInPlace(fakeAudioBuffer([samples])); - - expect(changed).toBe(true); - expect(samples[0]).toBeGreaterThanOrEqual(-0.986); - expect(samples[1]).toBe(-0.5); - expect(samples[2]).toBe(0); - expect(samples[3]).toBe(0); - expect(samples[5]).toBe(0.5); - expect(samples[6]).toBeLessThan(0.95); - expect(samples[6]).toBeGreaterThan(0.9); - expect(samples[7]).toBe(0); - expect(samples[8]).toBeLessThanOrEqual(0.986); - }); - - it("runs the offline mix limiter for every rendered chunk", async () => { - const processor = new AudioProcessor() as unknown as OfflineRenderTestHarness; - const renderedSamples = new Float32Array([1.4]); - const renderedBuffer = fakeAudioBuffer([renderedSamples]); - const originalOfflineAudioContext = globalThis.OfflineAudioContext; - ( - globalThis as unknown as { OfflineAudioContext: typeof OfflineAudioContext } - ).OfflineAudioContext = class { - constructor() {} - - startRendering() { - return Promise.resolve(renderedBuffer); - } - } as unknown as typeof OfflineAudioContext; - - try { - let observedPeak = Number.POSITIVE_INFINITY; - await processor.renderChunked( - { - mainBufferEntry: null, - companionEntries: [], - regionEntries: [], - mutedSourceOutputRangesSec: [], - slices: [], - outputDurationMs: 100, - numChannels: 1, - }, - 0.1, - async (rendered) => { - observedPeak = rendered.getChannelData(0)[0] ?? 0; - }, - ); - - expect(observedPeak).toBeLessThanOrEqual(0.986); - } finally { - ( - globalThis as unknown as { OfflineAudioContext: typeof OfflineAudioContext } - ).OfflineAudioContext = originalOfflineAudioContext; - } - }); }); diff --git a/src/lib/exporter/audioEncoder.ts b/src/lib/exporter/audioEncoder.ts index d66e0bea..51107224 100644 --- a/src/lib/exporter/audioEncoder.ts +++ b/src/lib/exporter/audioEncoder.ts @@ -21,44 +21,6 @@ const MP4_AUDIO_CODEC = "mp4a.40.2"; const OFFLINE_AUDIO_SAMPLE_RATE = 48_000; const OFFLINE_ENCODE_CHUNK_FRAMES = 1024; const OFFLINE_CHUNK_DURATION_SEC = 30; -const OFFLINE_MIX_SOFT_LIMITER_THRESHOLD = 0.9; -const OFFLINE_MIX_SOFT_LIMITER_CEILING = 0.985; - -function softLimitSample(sample: number): number { - const magnitude = Math.abs(sample); - if (magnitude <= OFFLINE_MIX_SOFT_LIMITER_THRESHOLD) { - return sample; - } - - const sign = sample < 0 ? -1 : 1; - const kneeRange = 1 - OFFLINE_MIX_SOFT_LIMITER_THRESHOLD; - const limitedMagnitude = - OFFLINE_MIX_SOFT_LIMITER_THRESHOLD + - kneeRange * Math.tanh((magnitude - OFFLINE_MIX_SOFT_LIMITER_THRESHOLD) / kneeRange); - return sign * Math.min(OFFLINE_MIX_SOFT_LIMITER_CEILING, limitedMagnitude); -} - -export function softLimitOfflineMixPeaksInPlace(buffer: AudioBuffer): boolean { - let changed = false; - for (let channel = 0; channel < buffer.numberOfChannels; channel += 1) { - const data = buffer.getChannelData(channel); - for (let index = 0; index < data.length; index += 1) { - const sample = data[index]; - if (!Number.isFinite(sample)) { - data[index] = 0; - changed = true; - continue; - } - - const limited = softLimitSample(sample); - if (limited !== sample) { - data[index] = limited; - changed = true; - } - } - } - return changed; -} function resolveSourceTrackGain( sourceAudioTrackSettings: SourceAudioTrackSettings | undefined, @@ -985,7 +947,6 @@ export class AudioProcessor { const rendered = await offlineCtx.startRendering(); if (this.cancelled) break; - softLimitOfflineMixPeaksInPlace(rendered); await onChunk(rendered, outputOffsetSec, i);