fix(webinar): panelist mute state after returning from breakout session#5007
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bb661a8dbf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (muted) { | ||
| this.state.client.localMute = true; | ||
| this.muteLocalStream(meeting, true, 'remotelyMuted'); | ||
| } |
There was a problem hiding this comment.
Preserve server-driven video unmute handling
When a host clears a participant's remote video mute, Meeting delivers SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED with payload.muted === false directly to this method, and there is no video equivalent of LOCAL_UNMUTE_REQUIRED to clear the camera stream afterward. With the new if (muted) guard, that false update only changes state.server.remoteMute; it never calls setServerMuted(false, 'remotelyMuted'), so the LocalCameraStream remains user-muted from the earlier remote mute and the participant stays muted even though the server unmuted them.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
this is fine, we don't have a feature for hosts to unmute video, only user can unmute their video
|
This pull request is automatically being deployed by Amplify Hosting (learn more). |
This pull request addresses
https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-814773 — Panelists can hear each other after returning from breakout to main session.
Problem
In a webinar, after returning from a breakout session to the main session, two regression scenarios occur:
Case 1 — Panelist who was unmuted then re-muted by host:
Case 2 — Attendee promoted to panelist (no operations during the session):
Root Cause
When returning from a breakout to the main session,
LocusInforeplays the cached main-session locus viamainSessionLocusCache. Partial DTOs received during the breakout can pollute this cache, causingparseSelfto diff out a spuriousremoteMuted: true → falsetransition. This fires a fakehandleServerRemoteMuteUpdate(muted=false)event inmuteState, even though the server never actually cleared the remote mute.The old
handleServerRemoteMuteUpdateblindly calledmuteLocalStream(false, 'remotelyMuted')whenevermuted=false, which caused:Background — isMuted() is the OR of three flags: client.localMute (local intent), server.localMute (Locus controls.audio.muted), and server.remoteMute (host-muted state). If any one is true, the user is considered muted.
Before BO, the host's unmute had already cleared client.localMute via the LOCAL_UNMUTE_REQUIRED path. The host's later re-mute set server.remoteMute back to true, so the user stayed muted going into BO. On BO→Main, the locus cache replays a stale SELF_REMOTE_MUTE_STATUS_UPDATED(muted=false) (entry point: meeting/index.ts:3714). The old handleServerRemoteMuteUpdate unconditionally:
Sets server.remoteMute = false.
Calls muteLocalStream(false), which clears systemMute on the stream; the resulting handleLocalStreamMuteStateChange reads the stream back and sets client.localMute = false too.
Never re-syncs the user's true intent back to Locus.
server.localMute was already false from the earlier unmute, so all three flags collapse to false → isMuted() returns false → mic opens locally, and because Locus controls.audio.muted was never corrected, other participants' tiles drop the mute icon.
client.localMute=truewas never cleared), but other participants can hear the user.muteLocalStream(false)opened the stream the same way; only theclient.localMutesafety flag prevented the UI from also flipping.by making the following changes
plugin-meetings/src/meeting/muteState.ts—handleServerRemoteMuteUpdate:muted=true: also setclient.localMute=true. A later staleremoteMute=falsecan no longer flipisMuted()to false, becauseclient.localMuteholds the line.muted=false: do not touch the stream. A bareremoteMute=falseonly means "hard-mute lock released"; the legitimate server-driven unmute path isLOCAL_UNMUTE_REQUIRED(handleServerLocalUnmuteRequired), which already callsmuteLocalStream(false, 'localUnmuteRequired').applyClientStateToServer: when the stale event makes the server'scontrols.audio.muteddiverge from the client's intent, this pushes the client's true state back to the server so other participants' tiles render the mute icon correctly.Change Type
The following scenarios were tested
Unit tests
Added four new unit tests in
test/unit/spec/meeting/muteState.js:does not unmute the local stream when server clears remote mute while user is locally muted (breakout -> main regression)— verifies that when the user is already locally muted, aremoteMute=falseevent does not callsetServerMutedon the stream.does not touch the local stream when remote mute stays false (no transition)— verifies that aremoteMute=falseevent with no priorremoteMute=truehas no stream side effects.keeps isMuted() true when a stale remoteMute=false replays after remote mute— covers Case 1: after host unmutes then mutes the user, a subsequent staleremoteMute=falsefrom the locus cache must not flipisMuted()to false or open the stream (validates theclient.localMute=truelock + no-op onmuted=false).syncs client mute intent back to server on stale remoteMute=false— covers Case 2: when the client's intent and server'scontrols.audio.muteddiverge, the stale event must triggerapplyClientStateToServerto re-sync, so other participants' tiles continue to show the mute icon.Existing tests around
handleServerRemoteMuteUpdatecontinue to pass.Risk
muteState.ts. Semantic change is asymmetric:muted=truekeeps existing behavior plus a defensive flag lock;muted=falseno longer touches the stream. The latter is a deliberate behavior change but matches the original design — server-forced unmute belongs toLOCAL_UNMUTE_REQUIRED, not toremoteMute=false.The GAI Coding Policy And Copyright Annotation Best Practices
I certified that
Make sure to have followed the contributing guidelines before submitting.