Skip to content

feat(#626): downmix stereo to mono in offscreen audio graph#822

Open
Shan7Usmani wants to merge 2 commits into
shouri123:mainfrom
Shan7Usmani:feat/audio-downmixing-626
Open

feat(#626): downmix stereo to mono in offscreen audio graph#822
Shan7Usmani wants to merge 2 commits into
shouri123:mainfrom
Shan7Usmani:feat/audio-downmixing-626

Conversation

@Shan7Usmani

@Shan7Usmani Shan7Usmani commented Jun 20, 2026

Copy link
Copy Markdown

Description

Insert a GainNode with channelCount=1, channelCountMode=explicit, and channelInterpretation=speakers between audio sources and the MediaRecorder destination in the offscreen audio graph. This tells the Web Audio API to sum stereo channels to mono, saving ~50% bandwidth and storage while preserving STT quality.

Changes

  • src/offscreenAudioGraph.ts: Added downmixNode: GainNode to the OffscreenAudioGraph interface. Routed all sources through the downmix node before recorderDestination. Analyser and playback paths remain untouched (raw stereo).
  • tests/offscreenAudioGraph.test.ts: Added MockGainNode with channel properties. Updated assertions to verify the new routing topology and mono configuration.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Checklist

  • My code follows the code style of this project
  • I have performed a self-review of my own code

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced security by escaping dynamic text in dashboard notices, empty states, and transcript rendering to prevent HTML injection attacks.
  • Refactor

    • Optimized audio downmixing for recording with improved intermediate routing node architecture.
  • Tests

    • Added comprehensive test coverage for text escaping and audio routing functionality.

@Shan7Usmani Shan7Usmani requested a review from shouri123 as a code owner June 20, 2026 09:43
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Thank You for Contributing to Late-Meet

Please ensure that:

  • the issue was assigned to you before opening this PR
  • the PR references the related issue
  • your changes follow repository contribution guidelines
  • the project builds successfully before submission

Unassigned, duplicate, or low-quality PRs may be closed.

Thank you for contributing 💙

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Shan7Usmani, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 59 minutes and 38 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c53e221b-28ff-4b28-b08c-516a18e6a3b4

📥 Commits

Reviewing files that changed from the base of the PR and between d408a2c and a4ec57d.

📒 Files selected for processing (3)
  • src/offscreenAudioGraph.ts
  • src/utils/domHelpers.test.ts
  • tests/offscreenAudioGraph.test.ts
📝 Walkthrough

Walkthrough

Two independent changes: escapeHtml is relocated from a DOM-serializer implementation in sanitize.ts to domHelpers, re-exported as deprecated, and applied to dashboard truncation notices, transcript entry rendering, copy button data-* attributes, and empty-state titles. Separately, OffscreenAudioGraph gains a downmixNode: GainNode that routes the recorder path through explicit mono downmix settings while keeping the analyser on the raw signal.

Changes

HTML Escaping Hardening

Layer / File(s) Summary
escapeHtml migration: domHelpers source and sanitize.ts re-export
src/utils/sanitize.ts, src/utils/sanitize.test.ts, src/utils/domHelpers.test.ts
escapeHtml implementation moves from a DOM-based approach in sanitize.ts to domHelpers; sanitize.ts re-exports it as @deprecated. Tests are split: sanitize.test.ts now imports escapeHtml from domHelpers, and a new domHelpers.test.ts adds comprehensive escaping, regression, formatDuration, and sanitizeTopicStatus tests.
Dashboard rendering: escapeHtml and sanitizeDataAttr applied
src/dashboard.ts
truncatedNoticeHtml escapes key; createTranscriptEntryHTML uses the already-escaped speaker for initials and isAudio detection, and populates copy button data-speaker, data-time, data-message via sanitizeDataAttr; getEmptyStateHTML escapes message.

Offscreen Audio Downmix Routing

Layer / File(s) Summary
downmixNode interface, wiring, and microphone routing
src/offscreenAudioGraph.ts
OffscreenAudioGraph interface adds downmixNode: GainNode; connectCaptureSource routes the recorder path through downmixNode instead of directly to recorderDestination; createOffscreenAudioGraph creates downmixNode with channelCount: 1, channelCountMode: "explicit", connects it to recorderDestination, and passes it into connectCaptureSource; connectMicrophoneToOffscreenAudioGraph is updated to use downmixNode.
offscreenAudioGraph tests: MockGainNode and downmix assertions
tests/offscreenAudioGraph.test.ts
Adds MockGainNode with downmix fields, tracks created gain nodes in MockAudioContext, and updates tab capture and microphone routing assertions to verify gain node connections and mono/explicit configuration.

Sequence Diagram(s)

sequenceDiagram
    participant TabSource as Tab MediaStream Source
    participant MicSource as Mic MediaStream Source
    participant downmixNode as downmixNode (GainNode, mono)
    participant analyser as AnalyserNode
    participant recorderDestination as MediaStreamAudioDestinationNode
    participant playbackDestination as AudioDestination

    TabSource->>downmixNode: connect (recorder path)
    TabSource->>analyser: connect (raw signal)
    TabSource->>playbackDestination: connect (playback)
    downmixNode->>recorderDestination: connect (channelCount=1, explicit)

    MicSource->>downmixNode: connect (recorder path)
    MicSource->>analyser: connect (raw signal)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • shouri123/Late-Meet#215: Both PRs modify src/dashboard.ts empty-state rendering; this PR adds escapeHtml(message) to getEmptyStateHTML, which builds on the rendering pattern introduced there.
  • shouri123/Late-Meet#719: Directly overlaps — that PR establishes the offscreen audio graph routing in src/offscreenAudioGraph.ts, and this PR extends the same wiring with a downmixNode and updates connectMicrophoneToOffscreenAudioGraph.

Suggested labels

component:audio, security-review

Suggested reviewers

  • shouri123

Poem

🐇 Hop, hop, the rabbit declares,
No raw HTML sneaks past my snares!
escapeHtml lives in a new home today,
And audio hops mono all the way.
Safe data attrs, a downmix so neat —
The codebase is tidy, the routing complete! 🎵

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: adding stereo-to-mono downmixing to the offscreen audio graph, which is the main feature across the core implementation files.
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.

@github-actions github-actions Bot added type:code Type: Code change type:test Type: Test files gssoc Official GSSoC contribution issue gssoc:approved GSSoC: PR approved and scored size/L type:feature Type: New Feature labels Jun 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

👋 Thank you @Shan7Usmani for your contribution to Late-Meet!

⚠️ Warning: This PR does not seem to link to an open issue. Please edit the PR description to include Closes #XX to ensure your contribution counts for GSSoC points.

Please review any automated suggestions or code review comments that may appear below! We will review your PR as soon as possible!


Please consider starring the repository ⭐ to show your support!

assert.equal(escapeHtml(imgXss), "<img src=x onerror="alert(1)">");

const safe = escapeHtml(xss);
assert.ok(!safe.includes("<script>"));

for (const payload of payloads) {
const escaped = escapeHtml(payload);
assert.ok(!escaped.includes("<script>"), `payload still contains <script>: ${payload}`);
Shan7Usmani added a commit to Shan7Usmani/Late-Meet that referenced this pull request Jun 21, 2026
The Semgrep rule javascript.lang.security.audit.unknown-value-with-script-tag
was flagging test assertions that check escaped output no longer contains
<script> tags. Since these are intentional safety checks on already-escaped
data, add nosemgrep comments to suppress the warnings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/utils/sanitize.test.ts (1)

3-4: ⚡ Quick win

Add a compatibility assertion for the deprecated sanitize.ts export path.

Line 3-4 now only validates escapeHtml via ./domHelpers; this won’t catch accidental breakage of the ./sanitize re-export that this migration is preserving.

Suggested test addition
 import { escapeHtml } from "./domHelpers";
-import { sanitizeClassName, sanitizeDataAttr } from "./sanitize";
+import {
+  escapeHtml as deprecatedEscapeHtml,
+  sanitizeClassName,
+  sanitizeDataAttr,
+} from "./sanitize";

 test("escapeHtml prevents XSS in text content", () => {
+  assert.equal(
+    deprecatedEscapeHtml("<b>x</b>"),
+    "&lt;b&gt;x&lt;/b&gt;",
+  );
   assert.equal(escapeHtml(null), "");
🤖 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/utils/sanitize.test.ts` around lines 3 - 4, The test file currently
imports escapeHtml only from the new location ./domHelpers, which means breakage
of the deprecated re-export path from ./sanitize would go undetected. Add an
import statement that also imports escapeHtml from ./sanitize to verify the
deprecated export path continues to work and won't break during the migration.
🤖 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/dashboard.ts`:
- Around line 1100-1102: The variables speaker, timeStr, and text are being
double-escaped in the data attributes at lines 1100-1102. Since these values are
already HTML-escaped, remove the sanitizeDataAttr() function wrapper from all
three data attributes (data-speaker, data-time, and data-message) and use the
raw variables directly. This will prevent double-encoding of entities and ensure
the copied plain text payload contains the correct unencoded values.

---

Nitpick comments:
In `@src/utils/sanitize.test.ts`:
- Around line 3-4: The test file currently imports escapeHtml only from the new
location ./domHelpers, which means breakage of the deprecated re-export path
from ./sanitize would go undetected. Add an import statement that also imports
escapeHtml from ./sanitize to verify the deprecated export path continues to
work and won't break during the migration.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 63672196-3689-4681-88a3-90e805a8d97d

📥 Commits

Reviewing files that changed from the base of the PR and between eed5d5e and d408a2c.

📒 Files selected for processing (6)
  • src/dashboard.ts
  • src/offscreenAudioGraph.ts
  • src/utils/domHelpers.test.ts
  • src/utils/sanitize.test.ts
  • src/utils/sanitize.ts
  • tests/offscreenAudioGraph.test.ts

Comment thread src/dashboard.ts Outdated
@Shan7Usmani

Copy link
Copy Markdown
Author

@shouri123 , I kindly request you to take a look at this pr , under GSSoC .

Insert a GainNode with channelCount=1, channelCountMode=explicit,
and channelInterpretation=speakers between sources and the recorder
destination. This instructs the Web Audio API to sum stereo channels
to mono, saving ~50% bandwidth/storage while STT quality is unaffected.

- connectCaptureSource now routes sources through downmixNode
- Analyser and playback bypass the downmix (raw stereo preserved)
- connectMicrophoneToOffscreenAudioGraph uses same downmix node
The Semgrep rule javascript.lang.security.audit.unknown-value-with-script-tag
was flagging test assertions that check escaped output no longer contains
<script> tags. Since these are intentional safety checks on already-escaped
data, add nosemgrep comments to suppress the warnings.
@sonarqubecloud

Copy link
Copy Markdown

@Shan7Usmani

Copy link
Copy Markdown
Author

@shouri123 this PR (#822 - audio downmixing) is rebased on latest main with all checks passing. Could you please review and merge when you get a chance? Thanks!

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

This PR has been marked as stale due to inactivity.

@github-actions github-actions Bot added the stale label Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved GSSoC: PR approved and scored gssoc Official GSSoC contribution issue needs-issue size/L stale type:code Type: Code change type:feature Type: New Feature type:test Type: Test files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants