Skip to content

Fix/audio wrong layer and audio playback#482

Merged
ExtraBinoss merged 25 commits into
webadderallorg:mainfrom
ExtraBinoss:fix/audio-wrong-layer
May 9, 2026
Merged

Fix/audio wrong layer and audio playback#482
ExtraBinoss merged 25 commits into
webadderallorg:mainfrom
ExtraBinoss:fix/audio-wrong-layer

Conversation

@ExtraBinoss
Copy link
Copy Markdown
Collaborator

@ExtraBinoss ExtraBinoss commented May 9, 2026

Description

This PR adresses the issue of the audio being at the wrong layer.
While doing that, we also discovered issue with audio playback, audio stuttering, audio exports....
In order to guarantee better code quality, the audio engine is now seperated from the video-editor to avoid bloating even more the video-editor, which is needed for the future refactor

All logic for the audio is inside video-editor/audio

Changes include

  • mute audio / toggle audio
  • on a per clip basis (if you cut it, it will have independent audio)
  • audio waveform on web workers
  • waveform is also available for added musics (add layer -> audio)
  • system and mic audio are seperated and can be controlled seperatly (volume/normalize)
  • seperated audio code from video-editor code, video editor was managing audio, when it's supposed to ONLY manage video
  • translation for audio parameters
  • Audio uses WebAudio API instead of the HTMLAudio which is better (no more stutters in my testing

Type of Change

  • New Feature
  • Bug Fix
  • Refactor / Code Cleanup

Related Issue(s)

#476 Audio on the wrong layer

Video (wherever possible):

Testing Guide

Record a video with only microphone, export with different volumes.. mute parameters...
Record video with Mic + system audio
Record a video with nothing

verify everything is okay both in playback and in export.

Checklist

  • I have performed a self-review of my code.
  • I have added any necessary screenshots or videos.
  • I have linked related issue(s) and updated the changelog if applicable.

Thank you for contributing!

Summary by CodeRabbit

  • New Features

    • Full source-audio support: per-track volume/normalize controls, clip-level mute/separate options, synchronized preview, waveform generation, and export-aware routing.
  • UI/UX Improvements

    • Timeline shows source-audio rows, waveform overlays, live span previews; Settings and annotation panels reorganized for audio controls.
  • Bug Fixes

    • Improved handling of local sidecar audio files and safer cross-platform audio cleanup; project loading now recognizes and approves referenced audio paths.
  • Tests

    • Added tests for local audio approval and routing policy.
  • Localization

    • Added audio-related UI strings across locales.

@ExtraBinoss ExtraBinoss marked this pull request as ready for review May 9, 2026 18:32
Copilot AI review requested due to automatic review settings May 9, 2026 18:32
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

I can generate the full review stack and walkthrough, but the hidden review artifact must list every provided rangeId exactly once. That’s a large, precise task — I need a bit more time to produce it correctly. Do you want me to proceed and generate the complete artifact now?

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restructures audio handling across the editor and exporter to address incorrect audio layering and improve playback/export stability. It introduces a dedicated audio routing layer (system/mic/mixed vs embedded), per-clip muting and source-track controls, and worker-based waveform generation for timeline rendering.

Changes:

  • Refactors audio routing for playback/export (embedded vs sidecar tracks) and adds per-clip mute + per-track (system/mic/mixed) volume/normalize settings.
  • Moves preview-audio synchronization logic out of VideoEditor into a dedicated audio hook/module and extends exporter pipelines to honor new audio settings.
  • Adds waveform generation via Web Workers and extends timeline UI to render source-audio tracks and per-audio-item waveforms.

Reviewed changes

Copilot reviewed 44 out of 44 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/lib/exporter/videoExporter.ts Passes clip/source-track audio settings into export and triggers audio render when mute/settings require it.
src/lib/exporter/modernVideoExporter.ts Mirrors exporter changes for the modern pipeline (clip mute + source-track settings).
src/lib/exporter/audioEncoder.ts Updates audio processing to use source-track routing policy, clip-mute ranges, and per-track gains/normalization.
src/lib/exporter/audioRoutingEngine.ts Introduces a resolved audio plan abstraction (embedded + sidecar + user regions).
src/lib/exporter/sourceTrackRoutingPolicy.ts Wraps routing engine to produce a policy used by editor/exporter decisions.
src/lib/exporter/sourceTrackRoutingPolicy.test.ts Adds Vitest coverage for routing policy behavior across sidecar combinations.
src/components/video-editor/VideoEditor.tsx Removes inline audio preview sync and delegates to useVideoEditorAudio; threads new audio settings into export + UI.
src/components/video-editor/types.ts Adds audio effect section, clip showSourceAudio, and audio-region normalize; re-exports source track settings types.
src/components/video-editor/timeline/TimelineEditor.tsx Adds source-audio track support and live span previews to drive waveform/row previews during drag/resize.
src/components/video-editor/timeline/model/timelineModel.ts Extends timeline render items with clip mute/showSourceAudio and audio item waveform metadata.
src/components/video-editor/timeline/Item.tsx Renders audio waveforms on items and a muted overlay for source-audio items.
src/components/video-editor/timeline/hooks/useTimelineAudioPeaks.ts Switches waveform extraction to worker-based generator and adds sidecar fallback logic.
src/components/video-editor/timeline/core/timelineTypes.ts Extends render item shape for audio waveform + clip audio flags.
src/components/video-editor/timeline/core/constants.ts Adds SOURCE_AUDIO_ROW_ID and a shared default peak count.
src/components/video-editor/timeline/components/wrapper/TimelineWrapper.tsx Adds live span preview callbacks during drag/resize.
src/components/video-editor/timeline/components/waveform/AudioWaveform.tsx Enhances waveform renderer with segment bounds, gain/normalize shaping, and configurable className.
src/components/video-editor/timeline/components/viewport/TimelineCanvas.tsx Adds dedicated source-audio rows per track (system/mic/mixed) and per-audio-item waveform rendering.
src/components/video-editor/SettingsPanel.tsx Adds an Audio effect section + clip source-audio controls and normalization toggles.
src/components/video-editor/projectPersistence.ts Persists/normalizes new clip/audio fields and source-audio track settings.
src/components/video-editor/audio/waveform/WaveformGenerator.ts Adds worker-backed peak computation with caching and shared generator instance.
src/components/video-editor/audio/waveform/waveform.worker.ts Implements worker peak calculation for waveform bins.
src/components/video-editor/audio/useVideoEditorAudio.ts Centralizes editor audio orchestration (fallback discovery, routing, clip mute, preview sync).
src/components/video-editor/audio/useSourceAudioTrackSettings.ts Manages default/per-clip source track settings and exposes setters for UI.
src/components/video-editor/audio/useSourceAudioFallback.ts Encapsulates fetching companion audio paths + delay metadata via Electron API.
src/components/video-editor/audio/useClipAudioSettingsController.ts Bridges source-track settings into preview gain calculations and UI callbacks.
src/components/video-editor/audio/useAudioPreviewSync.ts Reimplements preview sync for user regions + companion tracks with per-track gain nodes.
src/components/video-editor/audio/clipAudio.ts Adds helpers to resolve active clip and muted state at a given time.
src/components/video-editor/audio/audioTypes.ts Defines source track setting/meta types and shared audio constants/IDs.
src/components/video-editor/AnnotationSettingsPanel.tsx Adjusts panel layout so delete action is pinned in a footer area.
electron/ipc/recording/windows.ts Stops deleting sidecar audio after mux and updates mux result metadata accordingly.
electron/ipc/recording/mac.ts Stops deleting sidecar audio files after mux on macOS.
electron/ipc/recording/diagnostics.ts Expands fallback discovery to include system sidecars (not just mic) when embedded audio exists.
electron/ipc/recording/audioFilters.ts Updates sidecar debug env comment and retains env constant export.
electron/ipc/project/manager.ts Approves editor.audioRegions[].audioPath for local media access policy on project load.
electron/ipc/project/manager.test.ts Adds test ensuring audio region paths are approved during project load.
src/i18n/locales/en/settings.json Adds new clip/audio settings strings (mute state, separate audio, source track labels).
src/i18n/locales/es/settings.json Adds new clip/audio settings strings.
src/i18n/locales/fr/settings.json Adds new clip/audio settings strings.
src/i18n/locales/ko/settings.json Adds new clip/audio settings strings.
src/i18n/locales/nl/settings.json Adds new clip/audio settings strings.
src/i18n/locales/pt-BR/settings.json Adds new clip/audio settings strings.
src/i18n/locales/ru/settings.json Adds new clip/audio settings strings.
src/i18n/locales/zh-CN/settings.json Adds new clip/audio settings strings and updates GitHub CTA wording.
src/i18n/locales/zh-TW/settings.json Adds new clip/audio settings strings.

Comment thread src/components/video-editor/timeline/TimelineEditor.tsx
Comment thread electron/ipc/recording/audioFilters.ts
Comment thread src/lib/exporter/audioRoutingEngine.ts
Comment thread src/lib/exporter/audioEncoder.ts
Comment thread src/lib/exporter/audioEncoder.ts
Comment thread src/components/video-editor/audio/waveform/waveform.worker.ts Outdated
Comment thread src/lib/exporter/audioEncoder.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
electron/ipc/recording/windows.ts (1)

281-331: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

keptAudioSidecars is now inconsistent with actual sidecar handling.

On Line 483 you always return keptAudioSidecars: true, but Line 296-Line 298 can still delete sidecars and Line 331 can still return keepAudioSidecars (possibly false). This can misreport state and break downstream cleanup/diagnostics logic.

Suggested alignment (always-keep policy)
 import {
-	shouldKeepRecordingAudioSidecars,
 	WINDOWS_NATIVE_MIC_PRE_FILTERS,
 } from "./audioFilters";
@@
-	const keepAudioSidecars = shouldKeepRecordingAudioSidecars();
 	const inputs: string[] = ["-i", videoPath];
@@
 			if (stat.size <= 0) {
 				console.warn(`[mux-win] Skipping ${label} audio: file is empty (${audioPath})`);
-				if (!keepAudioSidecars) {
-					await fs.rm(audioPath, { force: true }).catch(() => undefined);
-				}
 				continue;
 			}
@@
 	if (audioInputs.length === 0) {
 		return {
 			muxed: false,
 			videoDurationSeconds: videoDuration,
 			muxTimeoutMs,
 			audioInputs,
 			audio,
-			keptAudioSidecars: keepAudioSidecars,
+			keptAudioSidecars: true,
 		};
 	}

Also applies to: 483-483

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/ipc/recording/windows.ts` around lines 281 - 331, The returned
keptAudioSidecars value is inconsistent: use the policy flag instead of a
hardcoded true. Replace any occurrences of a literal true in returned objects
with the local variable keepAudioSidecars (from
shouldKeepRecordingAudioSidecars()), e.g. update the return in the early-exit
after audioInputs.length === 0 and the later return that currently sets
keptAudioSidecars: true to keptAudioSidecars: keepAudioSidecars so the returned
state matches actual deletion behavior controlled by
shouldKeepRecordingAudioSidecars().
src/lib/exporter/modernVideoExporter.ts (2)

757-770: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pass clipRegions into the browser audio-processing path.

buildNativeAudioPlan() now switches to edited-track mode for muted clips, but this AudioProcessor.process(...) call still omits clipRegions. On the WebCodecs path, muted clip audio can therefore leak back into the export while the native/offline paths mute it correctly.

Suggested fix
 							this.audioProcessor!.process(
 								demuxer,
 								this.muxer!,
 								this.config.videoUrl,
 								this.config.trimRegions,
 								this.config.speedRegions,
 								undefined,
 								this.config.audioRegions,
 								this.config.sourceAudioFallbackPaths,
 								this.config.sourceAudioFallbackStartDelayMsByPath,
 								this.config.sourceAudioTrackSettings,
+								this.config.clipRegions,
 							),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/exporter/modernVideoExporter.ts` around lines 757 - 770, The browser
audio-processing path is missing clipRegions when calling
AudioProcessor.process, causing muted clips to not be honored by
buildNativeAudioPlan; update the call inside measureFinalizationStage /
awaitWithFinalizationTimeout to pass this.config.clipRegions as the clipRegions
argument to AudioProcessor.process (keeping the same argument order as the
method signature), so the browser/WebCodecs path receives clipRegions just like
the native/offline paths and muted clips are correctly silenced.

1195-1230: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Force offline rendering when source-track edits are present.

This branch can still pick filtergraph-fast-path when the only reason we entered "edited-track" is sourceAudioTrackSettings or muted clipRegions. classifyEditedTrackStrategy(...) never sees those edits, so native/FFmpeg export can silently drop per-track gain/normalize or clip mute state.

Suggested fix
+			const requiresRenderedEditedTrack =
+				hasNonDefaultSourceTrackSettings(this.config.sourceAudioTrackSettings) ||
+				(this.config.clipRegions ?? []).some((clip) => Boolean(clip.muted));
 			const canUsePrimaryAudioFiltergraph =
 				Boolean(primaryAudioSourcePath) &&
 				!hasTimedSourceAudioFallback &&
 				(usesEmbeddedPrimaryAudio ||
 					sourceAudioFallbackPaths.includes(primaryAudioSourcePath ?? "")) &&
 				typeof primaryAudioSourceSampleRate === "number" &&
 				Number.isFinite(primaryAudioSourceSampleRate) &&
 				primaryAudioSourceSampleRate > 0;
-			const strategy = canUsePrimaryAudioFiltergraph
+			const strategy = canUsePrimaryAudioFiltergraph && !requiresRenderedEditedTrack
 				? classifyEditedTrackStrategy({
 						primaryAudioSourcePath,
 						sourceDurationMs,
 						trimRegions,
 						speedRegions,
 						audioRegions,
 						sourceAudioFallbackPaths,
 					})
 				: "offline-render-fallback";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/exporter/modernVideoExporter.ts` around lines 1195 - 1230, The
current strategy selection can still pick "filtergraph-fast-path" even when the
only edits are per-track source settings or muted clipRegions, which
classifyEditedTrackStrategy doesn't see; update the logic around
canUsePrimaryAudioFiltergraph/strategy so that if
hasNonDefaultSourceTrackSettings(this.config.sourceAudioTrackSettings) or
(this.config.clipRegions ?? []).some(c => Boolean(c.muted)) is true you force
strategy = "offline-render-fallback" (skip calling classifyEditedTrackStrategy),
otherwise preserve the existing behavior that uses classifyEditedTrackStrategy({
primaryAudioSourcePath, sourceDurationMs, trimRegions, speedRegions,
audioRegions, sourceAudioFallbackPaths }) when canUsePrimaryAudioFiltergraph is
true.
🧹 Nitpick comments (3)
src/components/video-editor/audio/useSourceAudioTrackSettings.ts (1)

116-128: ⚡ Quick win

Skip no-op state writes in selected-track update handlers.

Both handlers always allocate a new state object, even when the incoming value equals current state. This can create avoidable rerenders during rapid slider/toggle interactions.

♻️ Proposed refactor
   const onSelectedClipSourceAudioTrackVolumeChange = useCallback(
     (id: string, volume: number) => {
       if (!selectedClipId) return;
       setSourceAudioTrackSettingsByClip((prev) => {
         const prevClip = prev[selectedClipId] ?? defaultSourceAudioTrackSettings;
+        const nextVolume = Number.isFinite(volume)
+          ? Math.max(0, Math.min(2, volume))
+          : (prevClip[id]?.volume ?? 1);
+        const prevNormalize = prevClip[id]?.normalize ?? false;
+        if (prevClip[id]?.volume === nextVolume && prevNormalize === (prevClip[id]?.normalize ?? false)) {
+          return prev;
+        }
         return {
           ...prev,
           [selectedClipId]: {
             ...prevClip,
             [id]: {
-              volume: Math.max(0, Math.min(2, volume)),
-              normalize: prevClip[id]?.normalize ?? false,
+              volume: nextVolume,
+              normalize: prevNormalize,
             },
           },
         };
       });
     },
     [defaultSourceAudioTrackSettings, selectedClipId],
   );

   const onSelectedClipSourceAudioTrackNormalizeChange = useCallback(
     (id: string, normalize: boolean) => {
       if (!selectedClipId) return;
       setSourceAudioTrackSettingsByClip((prev) => {
         const prevClip = prev[selectedClipId] ?? defaultSourceAudioTrackSettings;
+        const prevVolume = prevClip[id]?.volume ?? 1;
+        if (prevClip[id]?.normalize === normalize) {
+          return prev;
+        }
         return {
           ...prev,
           [selectedClipId]: {
             ...prevClip,
             [id]: {
-              volume: prevClip[id]?.volume ?? 1,
+              volume: prevVolume,
               normalize,
             },
           },
         };
       });
     },
     [defaultSourceAudioTrackSettings, selectedClipId],
   );

Also applies to: 136-148

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/video-editor/audio/useSourceAudioTrackSettings.ts` around
lines 116 - 128, The selected-track update always creates a new state object
even when nothing changes; update setSourceAudioTrackSettingsByClip to first
read prevClip = prev[selectedClipId] ?? defaultSourceAudioTrackSettings and
compare the new computed entry (for key id using bounded volume and normalize
fallback) against prevClip[id]; if identical, return prev to skip the no-op
write, otherwise return the updated object. Apply the same early-return check to
the analogous handler around lines 136-148 so both the volume slider and
normalize toggle avoid reallocating state when values are unchanged (referencing
setSourceAudioTrackSettingsByClip, selectedClipId,
defaultSourceAudioTrackSettings, id, volume).
src/components/video-editor/audio/useVideoEditorAudio.ts (1)

1-1: 💤 Low value

Unused React namespace import.

Same as useClipAudioSettingsController.ts - only useMemo is used from the import.

♻️ Suggested fix
-import React, { useMemo } from "react";
+import { useMemo } from "react";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/video-editor/audio/useVideoEditorAudio.ts` at line 1, The
import in useVideoEditorAudio.ts currently brings in the entire React namespace
but only useMemo is used; update the import to remove the unused React symbol
and import useMemo directly (i.e., change the import that references React and
useMemo to just import useMemo) so that the module no longer includes an unused
React namespace import.
src/components/video-editor/audio/useClipAudioSettingsController.ts (1)

1-1: 💤 Low value

Unused React namespace import.

The React namespace is imported but only useCallback and useMemo are used. The named imports are sufficient.

♻️ Suggested fix
-import React, { useCallback, useMemo } from "react";
+import { useCallback, useMemo } from "react";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/video-editor/audio/useClipAudioSettingsController.ts` at line
1, The module imports the React namespace but only uses hooks; update the import
so it only imports the named hooks (remove the unused default React import) —
e.g. replace the current import line with one that imports useCallback and
useMemo from "react" to eliminate the unused React symbol; check the hook
useClipAudioSettingsController to confirm no other React namespace usages remain
before committing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/video-editor/AnnotationSettingsPanel.tsx`:
- Line 143: The footer (the JSX block that renders the delete action) is
currently inside the scrolling container with class "flex-1 min-h-0 p-4
overflow-y-auto custom-scrollbar", so the delete action scrolls away; fix it by
moving that footer JSX out of that div and making it a sibling footer container
(e.g., a div after the scrollable area) so it stays fixed at the panel bottom,
ensuring the parent container uses a column flex layout (flex flex-col) and the
scrollable area remains "flex-1 overflow-y-auto"; add appropriate padding/border
(e.g., p-4 border-t) to the footer so styling matches the panel.

In `@src/components/video-editor/audio/clipAudio.ts`:
- Around line 1-13: The current getActiveClipIdAtSourceTime function incorrectly
maps source time to timeline time via mapSourceTimeToTimelineTime which can snap
out-of-range source timestamps into the first kept clip; instead check
source-span membership directly on each ClipRegion: for each clip in clipRegions
(use the ClipRegion shape), compare the raw sourceTimeSeconds (converted to ms)
against the clip's source-span fields (e.g., sourceStartMs and sourceEndMs or
whatever the ClipRegion uses for source start/end) and return clip.id only if
sourceMs is >= sourceStartMs and < sourceEndMs; remove the reliance on
mapSourceTimeToTimelineTime for this membership test so out-of-range source
times don’t erroneously mark a clip active.

In `@src/components/video-editor/audio/useSourceAudioTrackSettings.ts`:
- Around line 123-124: The volume clamp expression can still produce NaN when
volume is NaN; update where volume is saved (the volume property currently set
with Math.max(0, Math.min(2, volume))) to first check Number.isFinite(volume)
and fallback to a safe default (e.g., 1.0) before clamping, e.g. use
Number.isFinite(volume) ? Math.max(0, Math.min(2, volume)) : 1; keep the
normalize assignment (prevClip[id]?.normalize ?? false) as-is.

In `@src/components/video-editor/audio/waveform/WaveformGenerator.ts`:
- Around line 28-49: The current computePeaksWithWorker attaches a per-request
"error" listener to the shared Worker which will fire for any worker error and
reject all pending requests; instead have the worker return errors tied to
requestId via postMessage and handle them with a per-request "message" handler
that checks message.requestId and message.error, resolving or rejecting only the
matching promise. Update computePeaksWithWorker to stop adding per-request error
listeners (remove onError usage), add a one-time message listener that inspects
evt.data.requestId and evt.data.error/peaks to call the resolver from
this.workerResolvers.get(requestId) or reject with the provided error, and
ensure you clean up the message listener and this.workerResolvers entry after
resolving/rejecting; keep using this.worker.postMessage({ requestId,
channelData, samples }, [channelData.buffer]) to send the job.

In `@src/components/video-editor/projectPersistence.ts`:
- Around line 994-1003: The persisted fields sourceAudioTrackSettingsByClip and
defaultSourceAudioTrackSettings are being passed through raw (allowing
arrays/malformed objects) — instead, normalize and sanitize their entries before
persisting: ensure the outer value is a plain object (not an array), iterate its
keys and coerce each entry into the expected shape (e.g., { volume: number,
normalize: boolean, ... }), clamp numeric fields (e.g., volume) to valid ranges,
default missing properties, and discard/ignore invalid nested entries; implement
this normalization in projectPersistence.ts where these fields are set (or call
a new helper like normalizeSourceAudioTrackSettings) so loader/clamping logic is
preserved and only valid settings reach runtime.

In `@src/components/video-editor/SettingsPanel.tsx`:
- Around line 3695-3729: The footer renders an extra destructive delete button
for zooms causing a duplicate CTA when activeEffectSection === "zoom" and
selectedZoomId is set; remove one of the duplicates by either deleting the
footer branch that renders the zoom delete (the conditional block checking
activeEffectSection === "zoom" && selectedZoomId which calls onZoomDelete) or by
removing the inline zoom delete inside the zoom editor so only a single delete
remains; keep the handler name onZoomDelete and the selectedZoomId check intact
and ensure only one place invokes onZoomDelete(selectedZoomId).

In `@src/components/video-editor/timeline/components/viewport/TimelineCanvas.tsx`:
- Around line 392-405: The waveformSegmentSpan currently uses the clip's
timeline span which misaligns with track.peaks (source-time); change the prop
passed to Item so waveformSegmentSpan uses the clip's source-time span instead:
replace waveformSegmentSpan={liveSpanPreviewById?.[item.id] ?? item.span} with
waveformSegmentSpan={liveSpanPreviewById?.[item.id] ?? item.sourceSpan ??
item.span} (and ensure the Item/clip object exposes sourceSpan or equivalent),
so the source-audio row (track.peaks) and waveformSegmentSpan are in the same
timebase.

In `@src/components/video-editor/timeline/components/waveform/AudioWaveform.tsx`:
- Around line 52-55: The draw() throttling currently returns early if called
within 33ms and never re-queues a follow-up RAF, causing the waveform to freeze;
update draw() to, when it returns early, schedule a single requestAnimationFrame
if one isn't already pending (use a pendingRafRef or similar flag) so a redraw
will occur after the throttle window, and clear the flag when that RAF runs;
apply the same pattern to the second throttled draw block referenced around the
other draw usage (the same pendingRafRef and enqueue-if-not-pending logic).

In `@src/components/video-editor/timeline/components/wrapper/TimelineWrapper.tsx`:
- Around line 149-152: The live-preview movement test only checks horizontal
motion (Math.abs(event.delta?.x ?? 0) > 0.01), so vertical drags are ignored;
update the moved calculation in TimelineWrapper (where moved is computed and
onLiveSpanPreviewChange is invoked) to consider both axes—for example compute
movement magnitude using both delta.x and delta.y (Math.hypot(event.delta?.x ??
0, event.delta?.y ?? 0) > 0.01) or check Math.abs for either axis—then keep the
existing onLiveSpanPreviewChange call unchanged so vertical drags also trigger
the preview.

In `@src/components/video-editor/VideoEditor.tsx`:
- Around line 4419-4424: The export callback handleExport is closing over
clipRegions and audio.activeSourceAudioTrackSettings (and related source-audio
fields) but their values are not included in its dependency array, causing stale
exports; update the useCallback (or whatever creates handleExport) to include
clipRegions, audio.activeSourceAudioTrackSettings,
audio.sourceAudioFallbackPaths, and audio.sourceAudioFallbackStartDelayMsByPath
so the callback is recreated when those settings change, and make the same
dependency additions for the other export-related callback/functionality later
in the file that similarly references these audio/clip symbols.

In `@src/i18n/locales/fr/settings.json`:
- Around line 193-197: Update the French translations for the audio settings
keys to use natural grammar and clearer action wording: change
"sourceTracksTitle" value to "Source audio du clip", adjust "systemLabel" to use
sentence-style capitalization "Son système", keep "micLabel" as "Microphone" (no
change), ensure "mixedLabel" reads "Source" if intended, and change
"deleteRegion" to "Supprimer la zone audio" to reflect region deletion
semantics; locate and edit these keys (sourceTracksTitle, systemLabel, micLabel,
mixedLabel, deleteRegion) in the fr settings JSON and replace their string
values accordingly.

---

Outside diff comments:
In `@electron/ipc/recording/windows.ts`:
- Around line 281-331: The returned keptAudioSidecars value is inconsistent: use
the policy flag instead of a hardcoded true. Replace any occurrences of a
literal true in returned objects with the local variable keepAudioSidecars (from
shouldKeepRecordingAudioSidecars()), e.g. update the return in the early-exit
after audioInputs.length === 0 and the later return that currently sets
keptAudioSidecars: true to keptAudioSidecars: keepAudioSidecars so the returned
state matches actual deletion behavior controlled by
shouldKeepRecordingAudioSidecars().

In `@src/lib/exporter/modernVideoExporter.ts`:
- Around line 757-770: The browser audio-processing path is missing clipRegions
when calling AudioProcessor.process, causing muted clips to not be honored by
buildNativeAudioPlan; update the call inside measureFinalizationStage /
awaitWithFinalizationTimeout to pass this.config.clipRegions as the clipRegions
argument to AudioProcessor.process (keeping the same argument order as the
method signature), so the browser/WebCodecs path receives clipRegions just like
the native/offline paths and muted clips are correctly silenced.
- Around line 1195-1230: The current strategy selection can still pick
"filtergraph-fast-path" even when the only edits are per-track source settings
or muted clipRegions, which classifyEditedTrackStrategy doesn't see; update the
logic around canUsePrimaryAudioFiltergraph/strategy so that if
hasNonDefaultSourceTrackSettings(this.config.sourceAudioTrackSettings) or
(this.config.clipRegions ?? []).some(c => Boolean(c.muted)) is true you force
strategy = "offline-render-fallback" (skip calling classifyEditedTrackStrategy),
otherwise preserve the existing behavior that uses classifyEditedTrackStrategy({
primaryAudioSourcePath, sourceDurationMs, trimRegions, speedRegions,
audioRegions, sourceAudioFallbackPaths }) when canUsePrimaryAudioFiltergraph is
true.

---

Nitpick comments:
In `@src/components/video-editor/audio/useClipAudioSettingsController.ts`:
- Line 1: The module imports the React namespace but only uses hooks; update the
import so it only imports the named hooks (remove the unused default React
import) — e.g. replace the current import line with one that imports useCallback
and useMemo from "react" to eliminate the unused React symbol; check the hook
useClipAudioSettingsController to confirm no other React namespace usages remain
before committing.

In `@src/components/video-editor/audio/useSourceAudioTrackSettings.ts`:
- Around line 116-128: The selected-track update always creates a new state
object even when nothing changes; update setSourceAudioTrackSettingsByClip to
first read prevClip = prev[selectedClipId] ?? defaultSourceAudioTrackSettings
and compare the new computed entry (for key id using bounded volume and
normalize fallback) against prevClip[id]; if identical, return prev to skip the
no-op write, otherwise return the updated object. Apply the same early-return
check to the analogous handler around lines 136-148 so both the volume slider
and normalize toggle avoid reallocating state when values are unchanged
(referencing setSourceAudioTrackSettingsByClip, selectedClipId,
defaultSourceAudioTrackSettings, id, volume).

In `@src/components/video-editor/audio/useVideoEditorAudio.ts`:
- Line 1: The import in useVideoEditorAudio.ts currently brings in the entire
React namespace but only useMemo is used; update the import to remove the unused
React symbol and import useMemo directly (i.e., change the import that
references React and useMemo to just import useMemo) so that the module no
longer includes an unused React namespace import.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5bfb0c0c-23c6-481d-8799-06906ab76aa8

📥 Commits

Reviewing files that changed from the base of the PR and between de45469 and bac15c9.

📒 Files selected for processing (44)
  • electron/ipc/project/manager.test.ts
  • electron/ipc/project/manager.ts
  • electron/ipc/recording/audioFilters.ts
  • electron/ipc/recording/diagnostics.ts
  • electron/ipc/recording/mac.ts
  • electron/ipc/recording/windows.ts
  • src/components/video-editor/AnnotationSettingsPanel.tsx
  • src/components/video-editor/SettingsPanel.tsx
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/audio/audioTypes.ts
  • src/components/video-editor/audio/clipAudio.ts
  • src/components/video-editor/audio/useAudioPreviewSync.ts
  • src/components/video-editor/audio/useClipAudioSettingsController.ts
  • src/components/video-editor/audio/useSourceAudioFallback.ts
  • src/components/video-editor/audio/useSourceAudioTrackSettings.ts
  • src/components/video-editor/audio/useVideoEditorAudio.ts
  • src/components/video-editor/audio/waveform/WaveformGenerator.ts
  • src/components/video-editor/audio/waveform/waveform.worker.ts
  • src/components/video-editor/projectPersistence.ts
  • src/components/video-editor/timeline/Item.tsx
  • src/components/video-editor/timeline/TimelineEditor.tsx
  • src/components/video-editor/timeline/components/viewport/TimelineCanvas.tsx
  • src/components/video-editor/timeline/components/waveform/AudioWaveform.tsx
  • src/components/video-editor/timeline/components/wrapper/TimelineWrapper.tsx
  • src/components/video-editor/timeline/core/constants.ts
  • src/components/video-editor/timeline/core/timelineTypes.ts
  • src/components/video-editor/timeline/hooks/useTimelineAudioPeaks.ts
  • src/components/video-editor/timeline/model/timelineModel.ts
  • src/components/video-editor/types.ts
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/es/settings.json
  • src/i18n/locales/fr/settings.json
  • src/i18n/locales/ko/settings.json
  • src/i18n/locales/nl/settings.json
  • src/i18n/locales/pt-BR/settings.json
  • src/i18n/locales/ru/settings.json
  • src/i18n/locales/zh-CN/settings.json
  • src/i18n/locales/zh-TW/settings.json
  • src/lib/exporter/audioEncoder.ts
  • src/lib/exporter/audioRoutingEngine.ts
  • src/lib/exporter/modernVideoExporter.ts
  • src/lib/exporter/sourceTrackRoutingPolicy.test.ts
  • src/lib/exporter/sourceTrackRoutingPolicy.ts
  • src/lib/exporter/videoExporter.ts
💤 Files with no reviewable changes (1)
  • electron/ipc/recording/mac.ts

Comment thread src/components/video-editor/AnnotationSettingsPanel.tsx
Comment thread src/components/video-editor/audio/clipAudio.ts Outdated
Comment thread src/components/video-editor/audio/useSourceAudioTrackSettings.ts Outdated
Comment thread src/components/video-editor/audio/waveform/WaveformGenerator.ts Outdated
Comment thread src/components/video-editor/audio/waveform/WaveformGenerator.ts Outdated
Comment thread src/components/video-editor/timeline/components/wrapper/TimelineWrapper.tsx Outdated
Comment thread src/components/video-editor/VideoEditor.tsx Outdated
Comment thread src/i18n/locales/fr/settings.json Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/video-editor/audio/waveform/waveform.worker.ts`:
- Around line 16-21: The current bucketization uses a fixed step computed once
which can leave tail samples unscanned and distort peaks; update the loop in
waveform.worker.ts to compute bucket boundaries proportionally per index using
the buffer length and samples (e.g., start = Math.floor(i * firstChannel.length
/ samples) and end = Math.floor((i + 1) * firstChannel.length / samples) so the
last bucket covers the tail), remove reliance on the single step variable, and
then compute the peak/min/max over firstChannel[start..end] to populate
result[i].
- Line 10: The code uses (self as any).postMessage(...) which violates
noExplicitAny; replace the any casts with a typed worker scope (e.g. const
workerScope = self as unknown as DedicatedWorkerGlobalScope) and call
workerScope.postMessage({ requestId, peaks: empty }, [empty.buffer]); do the
same for the other occurrences (the other postMessage calls referencing
requestId and empty around lines 32–34), ensuring you import/declare the correct
DOM lib types or add a local type alias if needed so TypeScript sees
DedicatedWorkerGlobalScope without using any.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0fc17541-a856-4bf4-8e2f-5eb300259cf7

📥 Commits

Reviewing files that changed from the base of the PR and between 28a8a22 and 327d809.

📒 Files selected for processing (6)
  • src/components/video-editor/AnnotationSettingsPanel.tsx
  • src/components/video-editor/audio/clipAudio.ts
  • src/components/video-editor/audio/useSourceAudioTrackSettings.ts
  • src/components/video-editor/audio/waveform/WaveformGenerator.ts
  • src/components/video-editor/audio/waveform/waveform.worker.ts
  • src/lib/exporter/modernVideoExporter.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/video-editor/audio/clipAudio.ts
  • src/components/video-editor/audio/waveform/WaveformGenerator.ts
  • src/components/video-editor/audio/useSourceAudioTrackSettings.ts
  • src/components/video-editor/AnnotationSettingsPanel.tsx

Comment thread src/components/video-editor/audio/waveform/waveform.worker.ts Outdated
Comment thread src/components/video-editor/audio/waveform/waveform.worker.ts Outdated
@ExtraBinoss ExtraBinoss merged commit 62a1ed2 into webadderallorg:main May 9, 2026
2 checks passed
@ExtraBinoss ExtraBinoss deleted the fix/audio-wrong-layer branch May 9, 2026 20:29
@coderabbitai coderabbitai Bot mentioned this pull request May 10, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants