Implement screen sharing
This commit is contained in:
@@ -116,8 +116,6 @@ import ringtoneOgg from "../sound/ringtone.ogg?url";
|
|||||||
import { ConnectionLostError } from "../utils/errors.ts";
|
import { ConnectionLostError } from "../utils/errors.ts";
|
||||||
import { useTrackProcessorObservable$ } from "../livekit/TrackProcessorContext.tsx";
|
import { useTrackProcessorObservable$ } from "../livekit/TrackProcessorContext.tsx";
|
||||||
|
|
||||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
|
||||||
|
|
||||||
const maxTapDurationMs = 400;
|
const maxTapDurationMs = 400;
|
||||||
|
|
||||||
export interface ActiveCallProps
|
export interface ActiveCallProps
|
||||||
@@ -224,7 +222,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
// Merge the refs so they can attach to the same element
|
// Merge the refs so they can attach to the same element
|
||||||
const containerRef = useMergedRefs(containerRef1, containerRef2);
|
const containerRef = useMergedRefs(containerRef1, containerRef2);
|
||||||
|
|
||||||
const { hideScreensharing, showControls } = useUrlParams();
|
const { showControls } = useUrlParams();
|
||||||
|
|
||||||
const muteAllAudio = useBehavior(muteAllAudio$);
|
const muteAllAudio = useBehavior(muteAllAudio$);
|
||||||
// Call pickup state and display names are needed for waiting overlay/sounds
|
// 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 showFooter = useBehavior(vm.showFooter$);
|
||||||
const earpieceMode = useBehavior(vm.earpieceMode$);
|
const earpieceMode = useBehavior(vm.earpieceMode$);
|
||||||
const audioOutputSwitcher = useBehavior(vm.audioOutputSwitcher$);
|
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.
|
// We need to set the proper timings on the animation based upon the sound length.
|
||||||
const ringDuration = pickupPhaseAudio?.soundDuration["waiting"] ?? 1;
|
const ringDuration = pickupPhaseAudio?.soundDuration["waiting"] ?? 1;
|
||||||
@@ -742,18 +741,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
|
|
||||||
const allLivekitRooms = useBehavior(vm.allLivekitRooms$);
|
const allLivekitRooms = useBehavior(vm.allLivekitRooms$);
|
||||||
const memberships = useBehavior(vm.memberships$);
|
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[] = [];
|
const buttons: JSX.Element[] = [];
|
||||||
|
|
||||||
@@ -775,13 +762,13 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
data-testid="incall_videomute"
|
data-testid="incall_videomute"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
if (canScreenshare && !hideScreensharing) {
|
if (vm.toggleScreenSharing !== null) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<ShareScreenButton
|
<ShareScreenButton
|
||||||
key="share_screen"
|
key="share_screen"
|
||||||
className={styles.shareScreen}
|
className={styles.shareScreen}
|
||||||
enabled={false} // TODO-MULTI-SFU
|
enabled={sharingScreen}
|
||||||
onClick={toggleScreensharing}
|
onClick={vm.toggleScreenSharing}
|
||||||
onTouchEnd={onControlsTouchEnd}
|
onTouchEnd={onControlsTouchEnd}
|
||||||
data-testid="incall_screenshare"
|
data-testid="incall_screenshare"
|
||||||
/>,
|
/>,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
type LocalParticipant,
|
type LocalParticipant,
|
||||||
ParticipantEvent,
|
ParticipantEvent,
|
||||||
type RemoteParticipant,
|
type RemoteParticipant,
|
||||||
|
type Participant,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
||||||
import {
|
import {
|
||||||
@@ -341,18 +342,7 @@ class UserMedia {
|
|||||||
|
|
||||||
this.presenter$ = this.scope.behavior(
|
this.presenter$ = this.scope.behavior(
|
||||||
this.participant$.pipe(
|
this.participant$.pipe(
|
||||||
switchMap(
|
switchMap((p) => (p === undefined ? of(false) : sharingScreen$(p))),
|
||||||
(p) =>
|
|
||||||
(p &&
|
|
||||||
observeParticipantEvents(
|
|
||||||
p,
|
|
||||||
ParticipantEvent.TrackPublished,
|
|
||||||
ParticipantEvent.TrackUnpublished,
|
|
||||||
ParticipantEvent.LocalTrackPublished,
|
|
||||||
ParticipantEvent.LocalTrackUnpublished,
|
|
||||||
).pipe(map((p) => p.isScreenShareEnabled))) ??
|
|
||||||
of(false),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -433,7 +423,19 @@ function getRoomMemberFromRtcMember(
|
|||||||
return { id, member };
|
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 {
|
export class CallViewModel extends ViewModel {
|
||||||
|
private readonly urlParams = getUrlParams();
|
||||||
|
|
||||||
private readonly livekitAlias = getLivekitAlias(this.matrixRTCSession);
|
private readonly livekitAlias = getLivekitAlias(this.matrixRTCSession);
|
||||||
|
|
||||||
private readonly livekitE2EEKeyProvider = getE2eeKeyProvider(
|
private readonly livekitE2EEKeyProvider = getE2eeKeyProvider(
|
||||||
@@ -1850,6 +1852,37 @@ export class CallViewModel extends ViewModel {
|
|||||||
filter((v) => v.playSounds),
|
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(
|
public constructor(
|
||||||
// A call is permanently tied to a single Matrix room
|
// A call is permanently tied to a single Matrix room
|
||||||
private readonly matrixRTCSession: MatrixRTCSession,
|
private readonly matrixRTCSession: MatrixRTCSession,
|
||||||
@@ -1913,7 +1946,7 @@ export class CallViewModel extends ViewModel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.leave$.pipe(this.scope.bind()).subscribe((reason) => {
|
this.leave$.pipe(this.scope.bind()).subscribe((reason) => {
|
||||||
const { confineToRoom } = getUrlParams();
|
const { confineToRoom } = this.urlParams;
|
||||||
leaveRTCSession(this.matrixRTCSession, "user")
|
leaveRTCSession(this.matrixRTCSession, "user")
|
||||||
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
|
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user