Skip to content

feat: Media call - generated DTMF tone sounds#37074

Merged
kodiakhq[bot] merged 8 commits into
developfrom
feat/voipTones
Oct 17, 2025
Merged

feat: Media call - generated DTMF tone sounds#37074
kodiakhq[bot] merged 8 commits into
developfrom
feat/voipTones

Conversation

@gabriellsh
Copy link
Copy Markdown
Member

@gabriellsh gabriellsh commented Sep 25, 2025

Proposed changes (including videos or screenshots)

Issue(s)

VGA-8

Steps to test or reproduce

Further comments

Summary by CodeRabbit

  • New Features

    • Added audio feedback (DTMF tones) to the Voice Call Dialpad when pressing keys.
    • Tones play through the selected audio output device.
    • Key presses only play validated tones to prevent unintended sounds.
    • Added a demo/story showcasing dialpad tone playback.
  • Chores

    • Prepared a minor release for @rocket.chat/ui-voip and documented the new dialpad audio feedback feature.

@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot Bot commented Sep 25, 2025

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is targeting the wrong base branch. It should target 7.12.0, but it targets 7.11.0

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Sep 25, 2025

🦋 Changeset detected

Latest commit: 9d38ddc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 42 packages
Name Type
@rocket.chat/ui-voip Minor
@rocket.chat/meteor Patch
@rocket.chat/core-typings Patch
@rocket.chat/rest-typings Patch
@rocket.chat/uikit-playground Patch
@rocket.chat/api-client Patch
@rocket.chat/apps Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/ddp-client Patch
@rocket.chat/freeswitch Patch
@rocket.chat/fuselage-ui-kit Patch
@rocket.chat/gazzodown Patch
@rocket.chat/http-router Patch
@rocket.chat/livechat Patch
@rocket.chat/model-typings Patch
@rocket.chat/ui-avatar Patch
@rocket.chat/ui-client Patch
@rocket.chat/ui-contexts Patch
@rocket.chat/web-ui-registration Patch
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/federation-service Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/stream-hub-service Patch
@rocket.chat/federation-matrix Patch
@rocket.chat/license Patch
@rocket.chat/media-calls Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/models Patch
@rocket.chat/network-broker Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-video-conf Patch
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 25, 2025

Walkthrough

Adds a Web Audio–based DTMF-like tone playback hook and integrates it into the VoIP dialpad: new useTonePlayer hook, tone validation, MediaCallProvider playback wiring, a Storybook story demonstrating tone playback, and a changeset declaring a minor release.

Changes

Cohort / File(s) Summary
Tone playback engine (hook)
packages/ui-voip/src/v2/useTonePlayer.ts
New Web Audio–based TonePlayer and React hook useTonePlayer plus isValidTone. Maps digits to frequency pairs, supports optional sinkId via an HTMLAudioElement, exposes playTone, and performs cleanup on unmount.
Provider integration
packages/ui-voip/src/v2/MediaCallProvider.tsx
Imports useTonePlayer and isValidTone; obtains selected audioOutput; creates playTone via the hook and calls it from onTone only after isValidTone check.
Storybook keypad with tones
packages/ui-voip/src/v2/components/Keypad/Keypad.stories.tsx
Adds KeypadStoryWithTone story that uses useTonePlayer to play tones on key presses; existing stories remain unchanged.
Release metadata
.changeset/plenty-tips-care.md
New changeset declaring a minor release for @rocket.chat/ui-voip, documenting dialpad audio feedback feature.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Keypad as Keypad UI
  participant Provider as MediaCallProvider
  participant Hook as useTonePlayer
  participant Engine as TonePlayer (Web Audio)
  participant Audio as HTMLAudioElement / Sink

  User->>Keypad: Press digit
  Keypad->>Provider: onTone(digit)
  Provider->>Provider: isValidTone(digit)?
  alt Valid tone
    Provider->>Hook: playTone(digit)
    Hook->>Engine: play(freqHi, freqLo, duration)
    Engine->>Audio: setSinkId? / ensure playback
    Audio-->>User: Sound via selected audioOutput
    Engine-->>Engine: stop & disconnect oscillators after duration
  else Invalid tone
    Provider-->>Keypad: ignore playback
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

I tap the keys — a tiny hop, a tune,
Two bouncing tones beneath the moon.
Through circuits, pipes, and silver thread,
The dialpad sings where feet have tread.
Hippity-hop — a ringing tune! 🐇

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “feat: Media call - generated DTMF tone sounds” succinctly and accurately describes the core feature added in this changeset, namely generating DTMF tones for media calls. It is specific to the primary change without extraneous details, making it clear for anyone reviewing the pull request. The phrasing follows conventional commit style, indicating a feature and the affected area.
Linked Issues Check ✅ Passed The pull request introduces the useTonePlayer hook with a DIGIT_TONE_MAP for DTMF frequencies, integrates it into MediaCallProvider to play valid tones on dialpad events, and adds a corresponding story and documentation. These changes fulfill the core requirement of VGA-8 to implement DTMF sound in media calls by generating and playing the correct dual‐tone frequencies upon keypad input. No required functionality for DTMF tone generation appears to be missing.
Out of Scope Changes Check ✅ Passed All modifications focus exclusively on adding DTMF tone playback—implementing the TonePlayer class, the useTonePlayer hook, integrating tone playback in MediaCallProvider, updating stories, and documenting the feature—without introducing unrelated code or functionality. There are no changes outside the scope of implementing sound for DTMF.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/voipTones

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented Sep 25, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.65%. Comparing base (d166e2a) to head (9d38ddc).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #37074      +/-   ##
===========================================
+ Coverage    67.63%   67.65%   +0.01%     
===========================================
  Files         3341     3339       -2     
  Lines       114016   113920      -96     
  Branches     20667    20616      -51     
===========================================
- Hits         77120    77073      -47     
+ Misses       34218    34176      -42     
+ Partials      2678     2671       -7     
Flag Coverage Δ
e2e 57.34% <ø> (+0.01%) ⬆️
unit 71.60% <ø> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gabriellsh gabriellsh marked this pull request as ready for review October 1, 2025 14:49
@gabriellsh gabriellsh added this to the 7.12.0 milestone Oct 1, 2025
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

🧹 Nitpick comments (7)
.changeset/plenty-tips-care.md (1)

5-5: Clarify changelog wording for searchability.

Spell out “DTMF”/“keypad” so folks can find it via changelog grep.

-Introduces audio feedback for the Voice Call Dialpad.
+Introduces DTMF keypad audio feedback for the Voice Call Dialpad.
packages/ui-voip/src/v2/MediaCallProvider.tsx (1)

184-189: Type‑safe call; drop the any cast.

isValidTone already narrows tone to the correct union. Pass it directly.

- if (isValidTone(tone)) {
-   playTone(tone as any);
- }
+ if (isValidTone(tone)) {
+   playTone(tone);
+ }
packages/ui-voip/src/v2/components/Keypad/Keypad.stories.tsx (1)

12-15: Remove any and (optionally) validate keypress.

Keep the story type‑safe and mirror runtime behavior.

-import { useTonePlayer } from '../../useTonePlayer';
+import { useTonePlayer, isValidTone } from '../../useTonePlayer';
@@
-export const KeypadStoryWithTone: StoryFn<typeof Keypad> = () => {
-  const playTone = useTonePlayer();
-  return <Keypad onKeyPress={(key) => playTone(key as any)} />;
-};
+export const KeypadStoryWithTone: StoryFn<typeof Keypad> = () => {
+  const playTone = useTonePlayer();
+  return <Keypad onKeyPress={(key) => isValidTone(key) && playTone(key)} />;
+};
packages/ui-voip/src/v2/useTonePlayer.ts (4)

50-73: Harden playback: resume context, de‑click envelope, and avoid overlapping timers.

Improves reliability on browsers that auto‑suspend AudioContext; reduces clicks; prevents overlap artifacts.

-  public play(highFreq: number, lowFreq: number, durationMs?: number) {
+  private stopTimer?: number;
+
+  public play(highFreq: number, lowFreq: number, durationMs?: number) {
+    // Resume if suspended (common on first user gesture)
+    if (this.audioContext.state === 'suspended') {
+      void this.audioContext.resume().catch(() => {});
+    }
+    // Stop any previous scheduled stop to avoid overlap artifacts
+    if (this.stopTimer) {
+      clearTimeout(this.stopTimer);
+      this.stopTimer = undefined;
+    }
     const highFrequencyOscillator = TonePlayer.setupOscillator(this.audioContext, this.gainNode);
     const lowFrequencyOscillator = TonePlayer.setupOscillator(this.audioContext, this.gainNode);
@@
-    lowFrequencyOscillator.start();
-    highFrequencyOscillator.start();
+    const now = this.audioContext.currentTime;
+    // Simple attack to avoid clicks
+    this.gainNode.gain.cancelScheduledValues(now);
+    this.gainNode.gain.setValueAtTime(0.0, now);
+    this.gainNode.gain.linearRampToValueAtTime(0.5, now + 0.01);
+    lowFrequencyOscillator.start(now);
+    highFrequencyOscillator.start(now);
@@
-    setTimeout(() => {
+    this.stopTimer = window.setTimeout(() => {
+      // Simple release to avoid clicks
+      const t = this.audioContext.currentTime;
+      this.gainNode.gain.cancelScheduledValues(t);
+      this.gainNode.gain.setValueAtTime(this.gainNode.gain.value, t);
+      this.gainNode.gain.linearRampToValueAtTime(0.0, t + 0.01);
       lowFrequencyOscillator.stop();
       highFrequencyOscillator.stop();
       highFrequencyOscillator.disconnect();
       lowFrequencyOscillator.disconnect();
-    }, durationMs ?? 400);
+      this.stopTimer = undefined;
+    }, durationMs ?? 400);

75-79: Clear pending timer and disconnect graph on destroy.

Avoids stray timeouts after teardown.

   public destroy() {
+    if (this.stopTimer) {
+      clearTimeout(this.stopTimer);
+      this.stopTimer = undefined;
+    }
     this.audioContext.close();
     this.audioElement.pause();
     this.audioElement.srcObject = null;
   }

97-99: Micro‑optimization and clearer type guard.

Use the in operator to narrow without allocating keys array.

-export const isValidTone = (tone: string): tone is keyof typeof DIGIT_TONE_MAP => {
-  return Object.keys(DIGIT_TONE_MAP).includes(tone);
-};
+export const isValidTone = (tone: string): tone is keyof typeof DIGIT_TONE_MAP => tone in DIGIT_TONE_MAP;

15-17: Optional: Safari fallback for older engines.

If you need to support legacy WebKit, consider webkitAudioContext.

-    this.audioContext = new AudioContext();
+    const Ctor = (window as any).AudioContext || (window as any).webkitAudioContext;
+    this.audioContext = new Ctor();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between affd1ac and 95dbb9e.

📒 Files selected for processing (4)
  • .changeset/plenty-tips-care.md (1 hunks)
  • packages/ui-voip/src/v2/MediaCallProvider.tsx (3 hunks)
  • packages/ui-voip/src/v2/components/Keypad/Keypad.stories.tsx (1 hunks)
  • packages/ui-voip/src/v2/useTonePlayer.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/ui-voip/src/v2/MediaCallProvider.tsx (1)
packages/ui-voip/src/v2/useTonePlayer.ts (2)
  • useTonePlayer (101-126)
  • isValidTone (97-99)
packages/ui-voip/src/v2/components/Keypad/Keypad.stories.tsx (1)
packages/ui-voip/src/v2/useTonePlayer.ts (1)
  • useTonePlayer (101-126)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: 🔨 Test Unit / Unit Tests
  • GitHub Check: 🔎 Code Check / Code Lint
  • GitHub Check: 🔨 Test Storybook / Test Storybook
  • GitHub Check: 🔎 Code Check / TypeScript
  • GitHub Check: 📦 Meteor Build - coverage
  • GitHub Check: CodeQL-Build
🔇 Additional comments (1)
packages/ui-voip/src/v2/MediaCallProvider.tsx (1)

184-189: Validate before sending to remote?

Do we intend to send non‑DTMF tokens upstream? If not, guard session.sendTone with the same validator to keep local/remote behavior consistent. If yes (e.g., supporting A–D), consider extending DIGIT_TONE_MAP and validator.

- const onTone = (tone: string) => {
-   session.sendTone(tone);
-   if (isValidTone(tone)) {
-     playTone(tone);
-   }
- };
+ const onTone = (tone: string) => {
+   if (isValidTone(tone)) {
+     session.sendTone(tone);
+     playTone(tone);
+   } else {
+     session.sendTone(tone); // keep if remote expects more tokens; remove otherwise
+   }
+ };

Comment thread packages/ui-voip/src/v2/useTonePlayer.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: 0

🧹 Nitpick comments (1)
packages/ui-voip/src/v2/MediaCallProvider.tsx (1)

184-189: Validate tones before sending
session.sendTone (in useMediaSession.ts) calls mainCall.sendDTMF(tone) directly and only catches errors to log them. To prevent unnecessary console errors or unintended DTMF behavior for invalid inputs, apply the same isValidTone guard before invoking session.sendTone.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 95dbb9e and c237968.

📒 Files selected for processing (1)
  • packages/ui-voip/src/v2/MediaCallProvider.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/ui-voip/src/v2/MediaCallProvider.tsx (1)
packages/ui-voip/src/v2/useTonePlayer.ts (2)
  • useTonePlayer (101-126)
  • isValidTone (97-99)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: CodeQL-Build
  • GitHub Check: CodeQL-Build
🔇 Additional comments (3)
packages/ui-voip/src/v2/MediaCallProvider.tsx (3)

24-24: LGTM!

The import correctly brings in the tone validation and playback hook from the new useTonePlayer module.


44-44: LGTM!

Adding audioOutput to the destructured values enables the tone player to route audio to the selected output device.


182-183: LGTM!

The tone player hook is correctly initialized with the selected audio output device ID, with proper handling of undefined values.

Comment thread packages/ui-voip/src/v2/useTonePlayer.ts
Comment thread packages/ui-voip/src/v2/components/Keypad/Keypad.stories.tsx
@gabriellsh gabriellsh added the stat: QA assured Means it has been tested and approved by a company insider label Oct 16, 2025
@dionisio-bot dionisio-bot Bot added the stat: ready to merge PR tested and approved waiting for merge label Oct 16, 2025
@kodiakhq kodiakhq Bot merged commit b85e96a into develop Oct 17, 2025
85 of 88 checks passed
@kodiakhq kodiakhq Bot deleted the feat/voipTones branch October 17, 2025 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stat: QA assured Means it has been tested and approved by a company insider stat: ready to merge PR tested and approved waiting for merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants