Skip to content

Include extension click sounds in MP4 exports#665

Open
Hasbi1605 wants to merge 4 commits into
webadderallorg:mainfrom
Hasbi1605:fix/export-extension-audio-cues
Open

Include extension click sounds in MP4 exports#665
Hasbi1605 wants to merge 4 commits into
webadderallorg:mainfrom
Hasbi1605:fix/export-extension-audio-cues

Conversation

@Hasbi1605
Copy link
Copy Markdown

@Hasbi1605 Hasbi1605 commented Jun 7, 2026

Summary

  • Capture extension playSound() calls during MP4 cursor-interaction export collection.
  • Convert captured extension sounds into temporary audio regions so the existing audio exporter mixes them with source/timeline audio.
  • Add focused tests for extension export audio capture and document the export behavior.
  • Remove an unused connected-zoom transition helper so tsc --noEmit passes on this branch.

Root cause

Preview playback emits cursor:click events and extension sounds play through browser Audio, but MP4 export only mixed source audio and explicit timeline audioRegions. 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.ts
  • npx tsc --noEmit
  • npm test -- src/components/video-editor/extensionExportAudio.test.ts src/lib/extensions/extensionHost.test.ts
  • npm test fails on existing electron/ipc/paths/binaries.test.ts Windows helper path expectation; 776/777 tests passed.
  • npm run lint still fails on existing repo-wide Biome formatting/import issues outside this patch.
  • npm audit --audit-level=high still fails on existing dependency advisories.

Summary by CodeRabbit

  • New Features

    • MP4 exports now capture sounds triggered by extension cursor interactions during the export capture window.
  • Behavior Change

    • Connected-zoom transitions no longer interpolate between regions; gaps use a hold behavior when connected zooms are enabled.
  • Documentation

    • Clarified that only sounds triggered within the export capture window are included in MP4 exports.
  • Tests

    • Added tests for extension export audio capture, edge cases, and failure handling.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 7, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 21dc24c5-e6e2-47e4-ae93-675050d57560

📥 Commits

Reviewing files that changed from the base of the PR and between e5809b7 and 12a2e4c.

📒 Files selected for processing (2)
  • src/components/video-editor/extensionExportAudio.ts
  • src/lib/extensions/extensionHost.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/extensions/extensionHost.ts
  • src/components/video-editor/extensionExportAudio.ts

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Extension Audio Export Feature

Layer / File(s) Summary
Extension Host Audio Capture API
src/lib/extensions/extensionHost.ts, src/lib/extensions/extensionHost.test.ts
Adds ExtensionExportAudioCue, capture state and lifecycle methods (beginExportAudioCapture, setExportAudioCaptureTime, finishExportAudioCapture, cancelExportAudioCapture), playSound capture integration, and expanded tests for clamping and non-finite times/volumes.
Export utilities and tests
src/components/video-editor/extensionExportAudio.ts, src/components/video-editor/extensionExportAudio.test.ts
Filters exportable cursor interactions, builds cursor:click payloads, orchestrates capture (begin → set time → emit → finish → convert), implements extensionAudioCuesToRegions and collectExtensionAudioRegionsForExport, and adds tests for success, error handling, and conversion boundaries.
Video Editor MP4 export integration & preview refactor
src/components/video-editor/VideoEditor.tsx
Imports and uses collectExtensionAudioRegionsForExport during MP4 export, merges/sorts extension regions with editor audioRegions and passes them to the exporter; refactors preview skip back/forward keyframe logic and reformats preview volume clamping.
Zoom connected region cleanup
src/components/video-editor/videoPlayback/zoomRegionUtils.ts
Removes interpolation helpers and connected-pan transition computation; findDominantRegion no longer returns a connected transition and only supports connected "hold".
Feature documentation
EXTENSIONS.md
Adds note under “Assets and Audio” clarifying that sounds triggered by export-time cursor handlers are captured in MP4 exports; sounds outside the capture window are excluded.

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[]
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 A hop, a click, a tiny tune—

Captured in frames beneath the moon.
Cues to regions, neat and spry,
MP4s now hum when cursors fly.
Hop on, extensions—sing and try!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Include extension click sounds in MP4 exports' clearly and directly summarizes the main objective of the PR: enabling extension-triggered sounds to be included in MP4 exports.
Description check ✅ Passed The PR description provides a clear summary, root cause analysis, and validation steps, though it does not follow the repository's template structure with all required sections like Type of Change, Related Issues, Screenshots/Video, and Testing Guide.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

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

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.

❤️ Share

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

@Hasbi1605 Hasbi1605 marked this pull request as ready for review June 7, 2026 08:59
Copilot AI review requested due to automatic review settings June 7, 2026 08:59
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

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 ExtensionHost with an “export audio capture” mode that records playSound calls 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.

Comment thread EXTENSIONS.md Outdated
api.log("hello", payload);
```

Sounds played from cursor interaction handlers are also included in MP4 exports.
Comment on lines +929 to +933
return () => {
if (!cue || !host.exportAudioCues) return;
const index = host.exportAudioCues.indexOf(cue);
if (index >= 0) host.exportAudioCues.splice(index, 1);
};
Comment on lines +4360 to +4367
const extensionAudioRegions = collectExtensionAudioRegionsForExport(
extensionHost,
effectiveCursorTelemetry,
);
const audioRegionsForExport =
extensionAudioRegions.length > 0
? [...audioRegions, ...extensionAudioRegions]
: audioRegions;
Comment on lines +377 to +383
setExportAudioCaptureTime(timeMs: number | null): void {
if (!this.exportAudioCues) return;
this.exportAudioCaptureTimeMs =
typeof timeMs === "number" && Number.isFinite(timeMs)
? Math.max(0, Math.round(timeMs))
: null;
}
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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7ca2de2 and b09478e.

📒 Files selected for processing (7)
  • EXTENSIONS.md
  • src/components/video-editor/VideoEditor.tsx
  • src/components/video-editor/extensionExportAudio.test.ts
  • src/components/video-editor/extensionExportAudio.ts
  • src/components/video-editor/videoPlayback/zoomRegionUtils.ts
  • src/lib/extensions/extensionHost.test.ts
  • src/lib/extensions/extensionHost.ts

Comment thread src/lib/extensions/extensionHost.ts 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.

🧹 Nitpick comments (1)
src/lib/extensions/extensionHost.test.ts (1)

67-76: ⚡ Quick win

Align 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

📥 Commits

Reviewing files that changed from the base of the PR and between b09478e and d2b41b5.

📒 Files selected for processing (4)
  • EXTENSIONS.md
  • src/components/video-editor/VideoEditor.tsx
  • src/lib/extensions/extensionHost.test.ts
  • src/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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants