Goal
Add advanced call audio controls to the SDK: mute/unmute, input device switching, output device selection, and an audio level meter.
Applications should be able to build a real call UI on top of the SDK instead of only starting and ending calls.
Why this matters
A usable voice-call experience needs more than a peer connection. Users expect to mute themselves, change microphones, select speakers/headphones, and see whether their microphone is active.
These controls also teach important WebRTC media-track lifecycle concepts.
Functional requirements
- Add mute/unmute controls for local microphone audio.
- Add a
setMuted(boolean) style API for explicit state control.
- Expose current mute state.
- Add input device switching by device id.
- Use
RTCRtpSender.replaceTrack() when switching microphones to avoid rebuilding the call.
- Stop old tracks after switching devices.
- Add output device selection where supported by the browser.
- Add audio level meter support for local input, and remote level when stats/browser support allows it.
- Handle unsupported browser APIs gracefully.
- Clean up media streams, tracks, audio elements, and audio contexts on call end.
Suggested API
const call = await chat.startCall();
call.mute();
call.unmute();
call.setMuted(true);
console.log(call.muted);
await call.switchInputDevice('microphone-device-id');
await call.setOutputDevice('speaker-device-id');
const metrics = await call.getStatsSnapshot();
console.log(metrics.localAudioLevel);
Learning material
This issue teaches how WebRTC audio tracks and devices work.
Muting with MediaStreamTrack.enabled
The common way to mute a microphone in WebRTC is:
- keep the track alive
- set
track.enabled = false
This sends silence/disabled media behavior without tearing down the sender. Unmuting sets track.enabled = true.
This is different from stopping a track.
Stopping a track
Calling track.stop() permanently ends that track. It is useful during cleanup or after replacing an old microphone track, but it is not ideal for a temporary mute button.
Switching microphones with replaceTrack()
To change input devices during a call:
- Call
navigator.mediaDevices.getUserMedia() with the new audio device id.
- Extract the new audio track.
- Find the existing audio
RTCRtpSender.
- Call
sender.replaceTrack(newTrack).
- Stop the old track.
This avoids renegotiating the entire call in many common cases.
Output device selection
Some browsers support HTMLMediaElement.setSinkId(deviceId), which lets an app choose the speaker/headset output device.
Important browser reality:
- not all browsers support
setSinkId
- it may require HTTPS/secure context
- permission and device availability can change
- SDK should throw a clear error or no-op gracefully when unsupported
Audio level meter
A local audio meter can be built using the Web Audio API:
- create an
AudioContext
- create a
MediaStreamSource from the local stream
- connect it to an
AnalyserNode
- read time-domain samples
- calculate RMS volume level
Remote audio level may also be available through WebRTC stats, depending on browser support.
Device lifecycle
Media devices are dynamic:
- microphones can be unplugged
- Bluetooth devices can disconnect
- browser permissions can be revoked
- selected device ids may become invalid
The SDK should fail clearly and clean up old/new tracks carefully.
Implementation notes
- Keep mute/unmute local to outgoing audio tracks.
- Do not call
track.stop() for normal mute.
- Preserve mute state when switching input devices.
- Prefer
replaceTrack() over adding a second audio sender.
- Avoid leaking stopped tracks or dangling
AudioContext instances.
- Extend the audio sink abstraction for output-device selection.
- Treat audio level fields as optional because browser support differs.
- Document browser support caveats.
Testing ideas
- Verify
mute() sets local audio track enabled = false.
- Verify
unmute() sets local audio track enabled = true.
- Verify
setMuted(true/false) is idempotent.
- Verify
switchInputDevice() calls getUserMedia() with the selected device id.
- Verify
replaceTrack() is called with the new track.
- Verify old tracks are stopped after successful replacement.
- Verify mute state is preserved after switching devices.
- Verify
setOutputDevice() delegates to setSinkId() when available.
- Verify unsupported output selection produces a clear failure.
- Verify cleanup stops tracks and detaches audio on call end.
Acceptance criteria
Goal
Add advanced call audio controls to the SDK: mute/unmute, input device switching, output device selection, and an audio level meter.
Applications should be able to build a real call UI on top of the SDK instead of only starting and ending calls.
Why this matters
A usable voice-call experience needs more than a peer connection. Users expect to mute themselves, change microphones, select speakers/headphones, and see whether their microphone is active.
These controls also teach important WebRTC media-track lifecycle concepts.
Functional requirements
setMuted(boolean)style API for explicit state control.RTCRtpSender.replaceTrack()when switching microphones to avoid rebuilding the call.Suggested API
Learning material
This issue teaches how WebRTC audio tracks and devices work.
Muting with
MediaStreamTrack.enabledThe common way to mute a microphone in WebRTC is:
track.enabled = falseThis sends silence/disabled media behavior without tearing down the sender. Unmuting sets
track.enabled = true.This is different from stopping a track.
Stopping a track
Calling
track.stop()permanently ends that track. It is useful during cleanup or after replacing an old microphone track, but it is not ideal for a temporary mute button.Switching microphones with
replaceTrack()To change input devices during a call:
navigator.mediaDevices.getUserMedia()with the new audio device id.RTCRtpSender.sender.replaceTrack(newTrack).This avoids renegotiating the entire call in many common cases.
Output device selection
Some browsers support
HTMLMediaElement.setSinkId(deviceId), which lets an app choose the speaker/headset output device.Important browser reality:
setSinkIdAudio level meter
A local audio meter can be built using the Web Audio API:
AudioContextMediaStreamSourcefrom the local streamAnalyserNodeRemote audio level may also be available through WebRTC stats, depending on browser support.
Device lifecycle
Media devices are dynamic:
The SDK should fail clearly and clean up old/new tracks carefully.
Implementation notes
track.stop()for normal mute.replaceTrack()over adding a second audio sender.AudioContextinstances.Testing ideas
mute()sets local audio trackenabled = false.unmute()sets local audio trackenabled = true.setMuted(true/false)is idempotent.switchInputDevice()callsgetUserMedia()with the selected device id.replaceTrack()is called with the new track.setOutputDevice()delegates tosetSinkId()when available.Acceptance criteria
replaceTrack()where possible.