Implement screen sharing

This commit is contained in:
Robin
2025-09-24 13:54:54 -04:00
parent f99a256c86
commit edd3eb8747
2 changed files with 51 additions and 31 deletions

View File

@@ -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<InCallViewProps> = ({
// 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<InCallViewProps> = ({
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<InCallViewProps> = ({
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<InCallViewProps> = ({
data-testid="incall_videomute"
/>,
);
if (canScreenshare && !hideScreensharing) {
if (vm.toggleScreenSharing !== null) {
buttons.push(
<ShareScreenButton
key="share_screen"
className={styles.shareScreen}
enabled={false} // TODO-MULTI-SFU
onClick={toggleScreensharing}
enabled={sharingScreen}
onClick={vm.toggleScreenSharing}
onTouchEnd={onControlsTouchEnd}
data-testid="incall_screenshare"
/>,

View File

@@ -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<boolean> {
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(() => {