Include extension click sounds in MP4 exports#665
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds ExtensionHost session APIs to capture extension-triggered audio cues, converts cues into AudioRegion objects, merges them into VideoEditor MP4 exports, adds tests and docs, and removes connected-zoom transition logic while refactoring preview skip handlers. ChangesExtension Audio Export Feature
Sequence Diagram(s)sequenceDiagram
participant VideoEditor
participant ExtensionHost
participant Extension
VideoEditor->>ExtensionHost: beginExportAudioCapture()
VideoEditor->>ExtensionHost: setExportAudioCaptureTime(timeMs)
VideoEditor->>Extension: emit "cursor:click" (via host API)
Extension->>ExtensionHost: playSound(audioPath, volume)
ExtensionHost-->>ExtensionHost: record ExtensionExportAudioCue (id, timeMs, audioPath, volume)
VideoEditor->>ExtensionHost: finishExportAudioCapture()
ExtensionHost-->>VideoEditor: ExtensionExportAudioCue[]
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds support for capturing extension-triggered sounds during export so they can be rendered into final MP4 output.
Changes:
- Extend
ExtensionHostwith an “export audio capture” mode that recordsplaySoundcalls as cues instead of playing audio. - Add utilities to translate captured cues into exportable audio regions and integrate them into the export pipeline.
- Remove unused zoom/pan transition helpers and add tests for the new audio-export flow.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/extensions/extensionHost.ts | Introduces cue capture state and records playSound calls as export audio cues. |
| src/lib/extensions/extensionHost.test.ts | Adds tests validating cue capture behavior and stop/removal semantics. |
| src/components/video-editor/videoPlayback/zoomRegionUtils.ts | Removes unused bezier/lerp-connected pan transition helpers and related imports. |
| src/components/video-editor/extensionExportAudio.ts | Adds conversion + collection logic to turn captured cues into AudioRegions for export. |
| src/components/video-editor/extensionExportAudio.test.ts | Adds tests for telemetry filtering, cue-to-region mapping, and failure cancellation. |
| src/components/video-editor/VideoEditor.tsx | Hooks cue collection into export and passes merged regions to exporter. |
| EXTENSIONS.md | Documents that cursor-interaction sounds are included in exports. |
| api.log("hello", payload); | ||
| ``` | ||
|
|
||
| Sounds played from cursor interaction handlers are also included in MP4 exports. |
| return () => { | ||
| if (!cue || !host.exportAudioCues) return; | ||
| const index = host.exportAudioCues.indexOf(cue); | ||
| if (index >= 0) host.exportAudioCues.splice(index, 1); | ||
| }; |
| const extensionAudioRegions = collectExtensionAudioRegionsForExport( | ||
| extensionHost, | ||
| effectiveCursorTelemetry, | ||
| ); | ||
| const audioRegionsForExport = | ||
| extensionAudioRegions.length > 0 | ||
| ? [...audioRegions, ...extensionAudioRegions] | ||
| : audioRegions; |
| setExportAudioCaptureTime(timeMs: number | null): void { | ||
| if (!this.exportAudioCues) return; | ||
| this.exportAudioCaptureTimeMs = | ||
| typeof timeMs === "number" && Number.isFinite(timeMs) | ||
| ? Math.max(0, Math.round(timeMs)) | ||
| : null; | ||
| } |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/lib/extensions/extensionHost.ts`:
- Around line 915-916: The current clamp for volume in the playSound call uses
Math.max/Math.min on options?.volume which still produces NaN if options.volume
is NaN; update the logic in the playSound path that defines the volume variable
(where options?.volume is read) to first coerce or validate with Number.isFinite
(or global isFinite) and fall back to a safe default (e.g., 1) before clamping,
i.e., compute a finiteVolume = Number.isFinite(options?.volume) ? options.volume
: 1 and then clamp that finiteVolume between 0 and 1 so NaN cannot leak into
captured/exported audio cues.
🪄 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: ec0f82ec-68db-4b11-bb2c-9bc9f77b4e77
📒 Files selected for processing (7)
EXTENSIONS.mdsrc/components/video-editor/VideoEditor.tsxsrc/components/video-editor/extensionExportAudio.test.tssrc/components/video-editor/extensionExportAudio.tssrc/components/video-editor/videoPlayback/zoomRegionUtils.tssrc/lib/extensions/extensionHost.test.tssrc/lib/extensions/extensionHost.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/extensions/extensionHost.test.ts (1)
67-76: ⚡ Quick winAlign with the established test pattern for consistency.
This test directly indexes the result array and uses a partial assertion, whereas tests 1–3 and 5 consistently store the result and assert against the full array or expected structure. Following the pattern from test 1 (lines 34–42) improves maintainability and provides clearer failure messages.
♻️ Suggested refactor for consistency
it("clamps negative export capture times to zero", () => { const host = new ExtensionHost(); const api = createAudioApi(host); host.beginExportAudioCapture(); host.setExportAudioCaptureTime(-10); api.playSound("sounds/click.mp3"); - - expect(host.finishExportAudioCapture()[0]).toMatchObject({ timeMs: 0 }); + const cues = host.finishExportAudioCapture(); + + expect(cues).toEqual([ + { + id: "com.test.click-sound-sound-0", + extensionId: "com.test.click-sound", + timeMs: 0, + audioPath: "file:///tmp/recordly-extension/sounds/click.mp3", + volume: 1, + }, + ]); });🤖 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/extensions/extensionHost.test.ts` around lines 67 - 76, The test "clamps negative export capture times to zero" should follow the same pattern as the other tests: store the result of finishExportAudioCapture() in a variable and assert the full expected structure instead of indexing and using a partial matcher. Update the test to call ExtensionHost(), createAudioApi(host), host.beginExportAudioCapture(), host.setExportAudioCaptureTime(-10), api.playSound(...), then const result = host.finishExportAudioCapture() and assert result equals the expected array/object (including timeMs: 0) using the same full-structure assertion style used in the other tests to improve consistency and failure clarity.
🤖 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.
Nitpick comments:
In `@src/lib/extensions/extensionHost.test.ts`:
- Around line 67-76: The test "clamps negative export capture times to zero"
should follow the same pattern as the other tests: store the result of
finishExportAudioCapture() in a variable and assert the full expected structure
instead of indexing and using a partial matcher. Update the test to call
ExtensionHost(), createAudioApi(host), host.beginExportAudioCapture(),
host.setExportAudioCaptureTime(-10), api.playSound(...), then const result =
host.finishExportAudioCapture() and assert result equals the expected
array/object (including timeMs: 0) using the same full-structure assertion style
used in the other tests to improve consistency and failure clarity.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: bb44357b-52a7-40d0-abd9-8be3deb32b9a
📒 Files selected for processing (4)
EXTENSIONS.mdsrc/components/video-editor/VideoEditor.tsxsrc/lib/extensions/extensionHost.test.tssrc/lib/extensions/extensionHost.ts
✅ Files skipped from review due to trivial changes (1)
- EXTENSIONS.md
🚧 Files skipped from review as they are similar to previous changes (2)
- src/lib/extensions/extensionHost.ts
- src/components/video-editor/VideoEditor.tsx
Summary
playSound()calls during MP4 cursor-interaction export collection.tsc --noEmitpasses on this branch.Root cause
Preview playback emits
cursor:clickevents and extension sounds play through browserAudio, but MP4 export only mixed source audio and explicit timelineaudioRegions. Extension-triggered sounds were therefore preview-only and never reached the exported audio mix.Validation
npx biome check EXTENSIONS.md src/components/video-editor/VideoEditor.tsx src/components/video-editor/extensionExportAudio.ts src/components/video-editor/extensionExportAudio.test.ts src/components/video-editor/videoPlayback/zoomRegionUtils.ts src/lib/extensions/extensionHost.ts src/lib/extensions/extensionHost.test.tsnpx tsc --noEmitnpm test -- src/components/video-editor/extensionExportAudio.test.ts src/lib/extensions/extensionHost.test.tsnpm testfails on existingelectron/ipc/paths/binaries.test.tsWindows helper path expectation; 776/777 tests passed.npm run lintstill fails on existing repo-wide Biome formatting/import issues outside this patch.npm audit --audit-level=highstill fails on existing dependency advisories.Summary by CodeRabbit
New Features
Behavior Change
Documentation
Tests