From dc98960d8d6c5746cedff39812f217a6e2c79af2 Mon Sep 17 00:00:00 2001 From: Daniel Abramov Date: Tue, 6 Jun 2023 13:16:36 +0200 Subject: [PATCH] Properly use LiveKit screen sharing --- src/room/InCallView.tsx | 93 ++++++++++++--------------- src/video-grid/VideoTile.tsx | 25 +++++-- src/video-grid/VideoTileContainer.tsx | 23 +++---- 3 files changed, 70 insertions(+), 71 deletions(-) diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 14cac9c..1ce6341 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -72,6 +72,7 @@ import { RageshakeRequestModal } from "./RageshakeRequestModal"; import { MatrixInfo } from "./VideoPreview"; import { useJoinRule } from "./useJoinRule"; import { ParticipantInfo } from "./useGroupCall"; +import { TileContent } from "../video-grid/VideoTile"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); // There is currently a bug in Safari our our code with cloning and sending MediaStreams @@ -234,18 +235,6 @@ export function InCallView({ const reducedControls = boundsValid && bounds.width <= 400; const noControls = reducedControls && bounds.height <= 400; - const items = useParticipantTiles(livekitRoom, participants, { - userId: client.getUserId()!, - deviceId: client.getDeviceId()!, - }); - - // The maximised participant: the focused (active) participant if the - // window is too small to show everyone. - const maximisedParticipant = useMemo( - () => (noControls ? items.find((item) => item.focused) ?? null : null), - [noControls, items] - ); - const renderAvatar = useCallback( (roomMember: RoomMember, width: number, height: number) => { const avatarUrl = roomMember.getMxcAvatarUrl(); @@ -266,6 +255,8 @@ export function InCallView({ const prefersReducedMotion = usePrefersReducedMotion(); + const items = useParticipantTiles(livekitRoom, participants); + const renderContent = (): JSX.Element => { if (items.length === 0) { return ( @@ -274,18 +265,6 @@ export function InCallView({ ); } - if (maximisedParticipant) { - return ( - - ); - } return ( ( @@ -311,7 +289,7 @@ export function InCallView({ } = useRageshakeRequestModal(groupCall.room.roomId); const containerClasses = classNames(styles.inRoom, { - [styles.maximised]: maximisedParticipant, + [styles.maximised]: undefined, }); let footer: JSX.Element | null; @@ -337,16 +315,14 @@ export function InCallView({ onPress={toggleScreenSharing} /> )} - {!maximisedParticipant && ( - - )} + ); @@ -354,7 +330,7 @@ export function InCallView({ return (
- {!hideHeader && !maximisedParticipant && ( + {!hideHeader && (
>, - local: ParticipantID + participants: Map> ): TileDescriptor[] { const sfuParticipants = useParticipants({ room: livekitRoom, }); - const [localUserId, localDeviceId] = [local.userId, local.deviceId]; - const items = useMemo(() => { const tiles: TileDescriptor[] = []; + for (const [member, participantMap] of participants) { for (const [deviceId] of participantMap) { const id = `${member.userId}:${deviceId}`; const sfuParticipant = sfuParticipants.find((p) => p.identity === id); - const hasScreenShare = - sfuParticipant?.getTrack(Track.Source.ScreenShare) !== undefined; + // Skip rendering participants that did not connect to the SFU. + if (!sfuParticipant) { + continue; + } - const descriptor = { + const userMediaTile = { id, - focused: hasScreenShare && !sfuParticipant?.isLocal, - local: member.userId == localUserId && deviceId == localDeviceId, + focused: false, + local: sfuParticipant.isLocal, data: { member, sfuParticipant, + content: TileContent.UserMedia, }, }; - tiles.push(descriptor); + // Add a tile for user media. + tiles.push(userMediaTile); + + // If there is a screen sharing enabled for this participant, create a tile for it as well. + if (sfuParticipant.isScreenShareEnabled) { + const screenShareTile = { + ...userMediaTile, + id: `${id}:screen-share`, + focused: true, + data: { + ...userMediaTile.data, + content: TileContent.ScreenShare, + }, + }; + tiles.push(screenShareTile); + } } } @@ -434,7 +421,7 @@ function useParticipantTiles( ); return tiles; - }, [localUserId, localDeviceId, participants, sfuParticipants]); + }, [participants, sfuParticipants]); return items; } diff --git a/src/video-grid/VideoTile.tsx b/src/video-grid/VideoTile.tsx index 4c3838f..0855264 100644 --- a/src/video-grid/VideoTile.tsx +++ b/src/video-grid/VideoTile.tsx @@ -29,20 +29,29 @@ import styles from "./VideoTile.module.css"; import { ReactComponent as MicMutedIcon } from "../icons/MicMuted.svg"; import { ReactComponent as VideoMutedIcon } from "../icons/VideoMuted.svg"; +export enum TileContent { + UserMedia = "user-media", + ScreenShare = "screen-share", +} + interface Props { - name: string; avatar?: JSX.Element; className?: string; + + name: string; sfuParticipant: LocalParticipant | RemoteParticipant; + content: TileContent; } export const VideoTile = forwardRef( - ({ name, avatar, className, sfuParticipant, ...rest }, ref) => { + ({ name, avatar, className, sfuParticipant, content, ...rest }, ref) => { const { t } = useTranslation(); const audioEl = React.useRef(null); const { isMuted: microphoneMuted } = useMediaTrack( - Track.Source.Microphone, + content === TileContent.UserMedia + ? Track.Source.Microphone + : Track.Source.ScreenShareAudio, sfuParticipant, { element: audioEl, @@ -56,7 +65,6 @@ export const VideoTile = forwardRef( [styles.speaking]: sfuParticipant.isSpeaking, [styles.muted]: microphoneMuted, [styles.screenshare]: false, - [styles.maximised]: false, })} ref={ref} {...rest} @@ -80,7 +88,14 @@ export const VideoTile = forwardRef(
))} - +