diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 157ee46a..14f18fb7 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -116,8 +116,6 @@ import ringtoneOgg from "../sound/ringtone.ogg?url"; import { ConnectionLostError } from "../utils/errors.ts"; import { useTrackProcessorObservable$ } from "../livekit/TrackProcessorContext.tsx"; -const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); - const maxTapDurationMs = 400; export interface ActiveCallProps @@ -224,7 +222,7 @@ export const InCallView: FC = ({ // Merge the refs so they can attach to the same element const containerRef = useMergedRefs(containerRef1, containerRef2); - const { hideScreensharing, showControls } = useUrlParams(); + const { showControls } = useUrlParams(); const muteAllAudio = useBehavior(muteAllAudio$); // Call pickup state and display names are needed for waiting overlay/sounds @@ -299,6 +297,7 @@ export const InCallView: FC = ({ const showFooter = useBehavior(vm.showFooter$); const earpieceMode = useBehavior(vm.earpieceMode$); const audioOutputSwitcher = useBehavior(vm.audioOutputSwitcher$); + const sharingScreen = useBehavior(vm.sharingScreen$); // We need to set the proper timings on the animation based upon the sound length. const ringDuration = pickupPhaseAudio?.soundDuration["waiting"] ?? 1; @@ -742,18 +741,6 @@ export const InCallView: FC = ({ const allLivekitRooms = useBehavior(vm.allLivekitRooms$); const memberships = useBehavior(vm.memberships$); - const toggleScreensharing = useCallback(() => { - // TODO-MULTI-SFU implement screensharing - throw new Error("TODO-MULTI-SFU"); - // localParticipant - // .setScreenShareEnabled(!isScreenShareEnabled, { - // audio: true, - // selfBrowserSurface: "include", - // surfaceSwitching: "include", - // systemAudio: "include", - // }) - // .catch(logger.error); - }, []); const buttons: JSX.Element[] = []; @@ -775,13 +762,13 @@ export const InCallView: FC = ({ data-testid="incall_videomute" />, ); - if (canScreenshare && !hideScreensharing) { + if (vm.toggleScreenSharing !== null) { buttons.push( , diff --git a/src/state/CallViewModel.ts b/src/state/CallViewModel.ts index 40828357..af750a9b 100644 --- a/src/state/CallViewModel.ts +++ b/src/state/CallViewModel.ts @@ -15,6 +15,7 @@ import { type LocalParticipant, ParticipantEvent, type RemoteParticipant, + type Participant, } from "livekit-client"; import E2EEWorker from "livekit-client/e2ee-worker?worker"; import { @@ -341,18 +342,7 @@ class UserMedia { this.presenter$ = this.scope.behavior( this.participant$.pipe( - switchMap( - (p) => - (p && - observeParticipantEvents( - p, - ParticipantEvent.TrackPublished, - ParticipantEvent.TrackUnpublished, - ParticipantEvent.LocalTrackPublished, - ParticipantEvent.LocalTrackUnpublished, - ).pipe(map((p) => p.isScreenShareEnabled))) ?? - of(false), - ), + switchMap((p) => (p === undefined ? of(false) : sharingScreen$(p))), ), ); } @@ -433,7 +423,19 @@ function getRoomMemberFromRtcMember( return { id, member }; } +function sharingScreen$(p: Participant): Observable { + return observeParticipantEvents( + p, + ParticipantEvent.TrackPublished, + ParticipantEvent.TrackUnpublished, + ParticipantEvent.LocalTrackPublished, + ParticipantEvent.LocalTrackUnpublished, + ).pipe(map((p) => p.isScreenShareEnabled)); +} + export class CallViewModel extends ViewModel { + private readonly urlParams = getUrlParams(); + private readonly livekitAlias = getLivekitAlias(this.matrixRTCSession); private readonly livekitE2EEKeyProvider = getE2eeKeyProvider( @@ -1850,6 +1852,37 @@ export class CallViewModel extends ViewModel { filter((v) => v.playSounds), ); + /** + * Whether we are sharing our screen. + */ + public readonly sharingScreen$ = this.scope.behavior( + from(this.localConnection).pipe( + switchMap((c) => sharingScreen$(c.livekitRoom.localParticipant)), + startWith(false), + ), + ); + + /** + * Callback for toggling screen sharing. If null, screen sharing is not + * available. + */ + public readonly toggleScreenSharing = + "getDisplayMedia" in (navigator.mediaDevices ?? {}) && + !this.urlParams.hideScreensharing + ? (): void => + void this.localConnection.then( + (c) => + void c.livekitRoom.localParticipant + .setScreenShareEnabled(!this.sharingScreen$.value, { + audio: true, + selfBrowserSurface: "include", + surfaceSwitching: "include", + systemAudio: "include", + }) + .catch(logger.error), + ) + : null; + public constructor( // A call is permanently tied to a single Matrix room private readonly matrixRTCSession: MatrixRTCSession, @@ -1913,7 +1946,7 @@ export class CallViewModel extends ViewModel { }); this.leave$.pipe(this.scope.bind()).subscribe((reason) => { - const { confineToRoom } = getUrlParams(); + const { confineToRoom } = this.urlParams; leaveRTCSession(this.matrixRTCSession, "user") // Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts. .then(() => {