2026-09-18 23:54:25 -04:00
|
|
|
/*
|
|
|
|
|
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";
|
2026-10-19 14:21:52 -04:00
|
|
|
import { LotusWidgetActions } from "./lotusActions";
|
|
|
|
|
import { lotusSendToHost } from "./lotusWidget";
|
2026-09-18 23:54:25 -04:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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$,
|
|
|
|
|
);
|
2026-10-19 14:21:52 -04:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [cinny #277] Whether any remote participant's screenshare AUDIO is being
|
|
|
|
|
* received, so the host shows its "Mute Screenshare Audio" control only when
|
|
|
|
|
* there is something to mute. Each `LivekitRoomAudioRenderer` (one per SFU)
|
|
|
|
|
* reports for its own room; the host gets `io.lotus.screenshare_audio_state
|
|
|
|
|
* { present }` whenever the combined answer changes.
|
|
|
|
|
*/
|
|
|
|
|
const presentByRoom = new Map<string, boolean>();
|
|
|
|
|
let lastSent: boolean | undefined;
|
|
|
|
|
|
|
|
|
|
function sendIfChanged(): void {
|
|
|
|
|
const present = [...presentByRoom.values()].some(Boolean);
|
|
|
|
|
if (present === lastSent) return;
|
|
|
|
|
lastSent = present;
|
|
|
|
|
lotusSendToHost(LotusWidgetActions.ScreenshareAudioState, { present });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function reportScreenshareAudio(
|
|
|
|
|
roomKey: string,
|
|
|
|
|
present: boolean,
|
|
|
|
|
): void {
|
|
|
|
|
presentByRoom.set(roomKey, present);
|
|
|
|
|
sendIfChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** The renderer for `roomKey` went away (call left, SFU switched). */
|
|
|
|
|
export function clearScreenshareAudio(roomKey: string): void {
|
|
|
|
|
presentByRoom.delete(roomKey);
|
|
|
|
|
sendIfChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Test-only: forget all reports. */
|
|
|
|
|
export function resetScreenshareAudioPresence(): void {
|
|
|
|
|
presentByRoom.clear();
|
|
|
|
|
lastSent = undefined;
|
|
|
|
|
}
|