diff --git a/src/state/media/RemoteScreenShareViewModel.ts b/src/state/media/RemoteScreenShareViewModel.ts index 8c46aeb3..effb31c4 100644 --- a/src/state/media/RemoteScreenShareViewModel.ts +++ b/src/state/media/RemoteScreenShareViewModel.ts @@ -7,7 +7,7 @@ Please see LICENSE in the repository root for full details. */ import { Track, type RemoteParticipant } from "livekit-client"; -import { map, of, switchMap } from "rxjs"; +import { distinctUntilChanged, map, of, switchMap } from "rxjs"; import { type Behavior } from "../Behavior"; import { @@ -17,7 +17,7 @@ import { } from "./ScreenShareViewModel"; import { type ObservableScope } from "../ObservableScope"; import { createVolumeControls, type VolumeControls } from "../VolumeControls"; -import { observeTrackReference$ } from "../observeTrackReference"; +import { observeParticipantMedia } from "@livekit/components-core"; export interface RemoteScreenShareViewModel extends BaseScreenShareViewModel, VolumeControls { @@ -58,14 +58,24 @@ export function createRemoteScreenShare( videoEnabled$: scope.behavior( pretendToBeDisconnected$.pipe(map((disconnected) => !disconnected)), ), + // [lotus #38] "Has audio" means the sharer publishes screenshare audio AND + // hasn't muted it — a muted publication would show a speaker glyph for a + // share nobody can hear. audioEnabled$: scope.behavior( inputs.participant$.pipe( switchMap((p) => p - ? observeTrackReference$(p, Track.Source.ScreenShareAudio) - : of(null), + ? observeParticipantMedia(p).pipe( + map(() => { + const pub = p.getTrackPublication( + Track.Source.ScreenShareAudio, + ); + return !!pub && !pub.isMuted; + }), + ) + : of(false), ), - map(Boolean), + distinctUntilChanged(), ), ), }; diff --git a/src/tile/SpotlightTile.tsx b/src/tile/SpotlightTile.tsx index 036e044f..abc073fa 100644 --- a/src/tile/SpotlightTile.tsx +++ b/src/tile/SpotlightTile.tsx @@ -43,6 +43,7 @@ import { useReactiveState } from "../useReactiveState"; import { useLatest } from "../useLatest"; import { type SpotlightTileViewModel } from "../state/TileViewModel"; import { useBehavior } from "../useBehavior"; +import { muteScreenshareAudio$ } from "../lotus/lotusScreenshareAudio"; import { type MemberMediaViewModel } from "../state/media/MemberMediaViewModel"; import { type LocalUserMediaViewModel } from "../state/media/LocalUserMediaViewModel"; import { type RemoteUserMediaViewModel } from "../state/media/RemoteUserMediaViewModel"; @@ -322,11 +323,13 @@ const ScreenShareVolumeButton: FC = ({ vm }) => { const audioEnabled = useBehavior(vm.audioEnabled$); const playbackMuted = useBehavior(vm.playbackMuted$); const playbackVolume = useBehavior(vm.playbackVolume$); + // [lotus #38] The host's screenshare-audio mute (io.lotus.set_deafen) mutes + // at the renderer, not through the volume controls; show it as muted too. + const lotusMuted = useBehavior(muteScreenshareAudio$); + const shownMuted = playbackMuted || lotusMuted; - const VolumeIcon = playbackMuted ? VolumeOffIcon : VolumeOnIcon; - const VolumeSolidIcon = playbackMuted - ? VolumeOffSolidIcon - : VolumeOnSolidIcon; + const VolumeIcon = shownMuted ? VolumeOffIcon : VolumeOnIcon; + const VolumeSolidIcon = shownMuted ? VolumeOffSolidIcon : VolumeOnSolidIcon; const [volumeMenuOpen, setVolumeMenuOpen] = useState(false); const onMuteButtonClick = useCallback(() => vm.togglePlaybackMuted(), [vm]);