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
This commit is contained in:
co-authored by
Claude Opus 5
parent
bc0e5ed432
commit
ea579cb998
@@ -52,9 +52,16 @@ afterEach(() => {
|
||||
vi.mock("@livekit/components-react", async (importOriginal) => {
|
||||
return {
|
||||
...(await importOriginal()),
|
||||
AudioTrack: (props: { trackRef: TrackReference }): ReactNode => {
|
||||
AudioTrack: (props: {
|
||||
trackRef: TrackReference;
|
||||
muted?: boolean;
|
||||
}): ReactNode => {
|
||||
return (
|
||||
<audio data-testid={"audio"}>
|
||||
<audio
|
||||
data-testid={"audio"}
|
||||
data-source={props.trackRef.publication.source}
|
||||
data-muted={String(!!props.muted)}
|
||||
>
|
||||
{getTrackReferenceId(props.trackRef)}
|
||||
</audio>
|
||||
);
|
||||
@@ -83,6 +90,7 @@ function renderTestComponent(
|
||||
kind: Track.Kind;
|
||||
source: Track.Source;
|
||||
}[],
|
||||
props: { muted?: boolean; screenshareAudioMuted?: boolean } = {},
|
||||
): RenderResult {
|
||||
const liveKitParticipants = livekitParticipantIdentities.map((identity) =>
|
||||
mockRemoteParticipant({ identity }),
|
||||
@@ -117,6 +125,7 @@ function renderTestComponent(
|
||||
validIdentities={participants.map((p) => p.identity)}
|
||||
livekitRoom={livekitRoom}
|
||||
url={""}
|
||||
{...props}
|
||||
/>
|
||||
</MediaDevicesProvider>,
|
||||
);
|
||||
@@ -286,3 +295,55 @@ it("should setup audioContext gain and pan", () => {
|
||||
expect(testAudioContext.gain.gain.value).toEqual(0.1);
|
||||
expect(testAudioContext.pan.pan.value).toEqual(1);
|
||||
});
|
||||
|
||||
// [lotus] The host's "Mute Screenshare Audio" mutes ONLY screenshare-audio
|
||||
// elements, via the same `muted` prop path as deafen (so it survives re-renders
|
||||
// and later-published shares); deafen still mutes everything.
|
||||
it("screenshareAudioMuted mutes only ScreenShareAudio tracks", () => {
|
||||
const explicitTracks = [
|
||||
{
|
||||
participantId: "@alice:DEV0",
|
||||
kind: Track.Kind.Audio,
|
||||
source: Track.Source.Microphone,
|
||||
},
|
||||
{
|
||||
participantId: "@alice:DEV0",
|
||||
kind: Track.Kind.Audio,
|
||||
source: Track.Source.ScreenShareAudio,
|
||||
},
|
||||
];
|
||||
const mutedBySource = (r: RenderResult): Record<string, string> =>
|
||||
Object.fromEntries(
|
||||
r
|
||||
.queryAllByTestId("audio")
|
||||
.map((el) => [el.dataset.source, el.dataset.muted]),
|
||||
);
|
||||
|
||||
expect(
|
||||
mutedBySource(
|
||||
renderTestComponent(
|
||||
[{ userId: "@alice", deviceId: "DEV0" }],
|
||||
["@alice:DEV0"],
|
||||
explicitTracks,
|
||||
{ screenshareAudioMuted: true },
|
||||
),
|
||||
),
|
||||
).toEqual({
|
||||
[Track.Source.Microphone]: "false",
|
||||
[Track.Source.ScreenShareAudio]: "true",
|
||||
});
|
||||
|
||||
expect(
|
||||
mutedBySource(
|
||||
renderTestComponent(
|
||||
[{ userId: "@alice", deviceId: "DEV0" }],
|
||||
["@alice:DEV0"],
|
||||
explicitTracks,
|
||||
{ muted: true },
|
||||
),
|
||||
),
|
||||
).toEqual({
|
||||
[Track.Source.Microphone]: "true",
|
||||
[Track.Source.ScreenShareAudio]: "true",
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user