Audio Bleed Between Breakout Rooms — Local Mute and Subsession Approaches Both Have Issues in Brave

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:

  1. Approach 1 (Legacy): Local mute / unmute via muteUserAudioLocally / unmuteUserAudioLocally

  2. 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 muteUserAudioLocally guaranteed 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:

  1. 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?

  2. 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-subsession but by that time, old streams may not yet be torn down.

  3. Is there a recommended way to force-close the audio track from the previous subsession context before the new one opens?

  4. 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-protocols or its fingerprinting protection)?

  5. Is isAutoJoinSubsession: true in openSubsessions() 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_TIMEOUT on openSubsessions() 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 muteUserAudioLocally persists 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”