Files
element-call/src/lotus/lotusScreenshareAudio.ts
T
Lotus CIandClaude Opus 5 ea579cb998 fix(lotus): screenshare-audio mute survives a re-share — mute via the renderer, not setVolume
"Mute Screenshare Audio" (io.lotus.set_deafen screenshareAudioMuted) used
RemoteParticipant.setVolume(0, ScreenShareAudio). EC's own createVolumeControls
writes volume 1 through the same setter the moment a new screenshare media item
resolves, so when the sharer stopped and re-shared (or a late joiner shared)
the audio came back at full volume while the host button still said
"Unmute Screenshare Audio". Reproduced on the local calls stack with two
headless clients: after a re-share the screen_share_audio element read vol=1.

Now the flag is a global behavior (muteScreenshareAudio$) that
LivekitRoomAudioRenderer turns into the `muted` prop of every
Track.Source.ScreenShareAudio element — the exact mechanism deafen already uses
(pub.setEnabled(false): the server stops sending). Verified via the
RemoteTrackPublication behind each <audio>: the re-published track (new sid)
mounts with enabled=false while muted and re-enables on unmute; deafen +
undeafen leaves it muted; teardown resets the flag so the next call starts
clean. Unit tests updated; renderer test asserts only ScreenShareAudio
elements get muted by the new prop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-18 23:54:25 -04:00

28 lines
1.1 KiB
TypeScript

/*
Copyright 2026 Lotus Guild
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import { BehaviorSubject } from "rxjs";
import { globalScope } from "../state/ObservableScope";
/**
* Whether the host has muted remote screenshare AUDIO (`io.lotus.set_deafen`
* `screenshareAudioMuted`). Consumed by `LivekitRoomAudioRenderer` exactly the
* way `muteAllAudio$` is: it becomes the `muted` prop of every
* `Track.Source.ScreenShareAudio` element, so it survives re-renders, applies
* to tracks that are published LATER (a sharer stopping and re-sharing, a late
* joiner) and never fights EC's per-tile volume controls — which was the
* failure mode of the old `RemoteParticipant.setVolume(0, ScreenShareAudio)`
* approach: `createVolumeControls` writes its own volume (1) through the same
* sink the moment a new screenshare media item appears, un-muting it.
*/
export const setScreenshareAudioMuted$ = new BehaviorSubject(false);
export const muteScreenshareAudio$ = globalScope.behavior(
setScreenshareAudioMuted$,
);