summary
I’m building a breakout room feature using the Zoom Video SDK for Web. I’ve implemented two separate approaches to achieve participant audio isolation between rooms:
-
Approach 1 (Legacy): Local mute / unmute via
muteUserAudioLocally/unmuteUserAudioLocally -
Approach 2 (Current): Zoom native subsessions via
SubsessionClient
In both approaches, participants in one breakout room can sometimes hear audio from another room. The issue is intermittent in Chrome/Edge, but consistently reproducible in Brave browser.
Environment
-
SDK:
@zoom/videosdk(web) Version (2.3.14) -
Framework: React (TypeScript)
-
Affected browsers: Brave (consistent), Chrome/Edge (intermittent)
Approach 1 — Local Mute / Unmute
How It Works
When breakout rooms start, I determine which participants belong to each room. Participants outside a user’s room are muted locally using muteUserAudioLocally, and participants inside the room are unmuted using unmuteUserAudioLocally.
// Muting a participant locally (from the listener's perspective)
const stream = client.getMediaStream()
await stream.muteUserAudioLocally(zoomUserId)
// Unmuting a participant locally
await stream.unmuteUserAudioLocally(zoomUserId)
The Problem
-
Works correctly most of the time in Chrome and Edge
-
In Brave, the local mute does not always take effect, or its effect is reversed when the WebRTC connection renegotiates
-
Occasionally in Chrome/Edge, a participant in Room A can hear audio from Room B — suggesting the mute state gets lost
-
The issue appears to be timing-related — if participants’ WebRTC streams connect or reconnect after the mute is applied, they arrive in an unmuted state
Questions:
-
Is
muteUserAudioLocallyguaranteed to persist across WebRTC renegotiations? -
Is there a way to be notified when a participant’s audio stream is re-established, so we can re-apply local mute state?
-
Does Brave’s aggressive fingerprinting / WebRTC policies interfere with
muteUserAudioLocally?
Approach 2 — Native Subsessions
How It Works
I use the
SubsessionClient with manual assignment mode to create subsessions and pre-assign users before opening.
typescript
// Step 1: Create subsession containers (manual assignment mode = 2)
const result = await subsessionClient.createSubsessions(
['Room A', 'Room B'],
2 // manual assignment mode
)
// Step 2: Get subsession list, pre-assign users to each room's userList
const subsessionList = subsessionClient.getSubsessionList()
subsessionList[0].userList = [{ userId: studentAZoomId }]
subsessionList[1].userList = [{ userId: studentBZoomId }]
// Add a 1.5s delay — without this, openSubsessions can throw OPERATION_TIMEOUT
await new Promise(resolve => setTimeout(resolve, 1500))
// Step 3: Open with pre-assigned users in one call
await subsessionClient.openSubsessions(subsessionList)
typescript
/
/ Teacher joining a room
await subsessionClient.joinSubsession(subsessionId)
// Teacher leaving (takes no arguments per SDK docs)
await subsessionClient.leaveSubsession()
The Problem
When I test with two students in separate subsessions on Brave browser, the student in Room A can still hear the student in Room B.
My theory is that Brave’s WebRTC privacy shields prevent the old WebRTC connection from being fully torn down when the user is moved to a new subsession. The “zombie” stream from the previous context continues to deliver audio.
As a mitigation, I’m additionally calling muteUserAudioLocally on all out-of-room participants even when subsessions are active — but this has the same Brave-specific reliability issues as Approach 1.
Questions:
-
When
openSubsessions()moves a participant to a subsession, does the SDK guarantee that the previous WebRTC connection is fully closed before the new subsession connection is established? -
Is there a specific Zoom SDK event I should listen to that fires after the subsession audio/video streams are fully re-established? I currently listen to
user-joined-subsessionbut by that time, old streams may not yet be torn down. -
Is there a recommended way to force-close the audio track from the previous subsession context before the new one opens?
-
Does the Zoom Video SDK have any built-in handling for browsers that implement restrictive WebRTC policies (like Brave’s
--disable-webrtc-allow-legacy-tls-protocolsor its fingerprinting protection)? -
Is
isAutoJoinSubsession: trueinopenSubsessions()relevant only to the caller (teacher), or does it affect how student streams are initialized?
Additional Context
-
I also tried applying local mute as a secondary layer on top of subsessions (muting all out-of-room participants even when subsessions are active). This still doesn’t fully resolve the issue in Brave
-
The
OPERATION_TIMEOUTonopenSubsessions()is fairly common — I’m currently working around it with a 1.5 second delay and 3 retries. Is this a known issue and is there a recommended fix? -
The issue on page refresh (browser reload mid-session) is more pronounced — the WebRTC reconnection seems to re-establish streams in an unmuted state before our isolation logic re-fires
What I’m Looking For
-
Confirmation on whether
muteUserAudioLocallypersists across WebRTC renegotiations -
Best practice for re-applying audio isolation after a subsession transition
-
Guidance on Brave / privacy-hardened browser compatibility with subsession audio routing
-
Whether there is a more reliable SDK event or hook to know when subsession audio streams are “settled”