From 1df48c55c1bb0673df6eb9fb5dba3ea0851ad1d5 Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 30 Apr 2026 22:51:07 +0200 Subject: [PATCH] (fix): sharing screen and then stopping screenshare causes empty gray box to remain --- packages/call/src/components/modals/base.tsx | 26 ++++----- packages/call/src/store.tsx | 6 ++- packages/call/src/views/main/focused.tsx | 57 +++++++++++++++----- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/packages/call/src/components/modals/base.tsx b/packages/call/src/components/modals/base.tsx index 37629e2..8a0c2c1 100644 --- a/packages/call/src/components/modals/base.tsx +++ b/packages/call/src/components/modals/base.tsx @@ -7,7 +7,7 @@ import { ContextMenuTrigger, } from "@tensamin/ui"; import { focusParticipant, setCallView, useCall } from "../../store"; -import { type Participant } from "livekit-client"; +import { Track, type Participant } from "livekit-client"; import { useEffect, useState } from "react"; import { useUser, type User } from "@tensamin/user/context"; import VideoViewer from "../videoViewer"; @@ -67,6 +67,9 @@ export default function Base({ const focusedParticipantId = useCall((state) => state.focusedParticipantId); const view = useCall((state) => state.view); const [user, setUser] = useState(null); + const screenSharePublication = participant?.getTrackPublication( + Track.Source.ScreenShare, + ); useEffect(() => { const participantId = Number(participant?.identity); @@ -139,18 +142,15 @@ export default function Base({ {/* Detect video / user and place here */} {type === "stream" && - Array.from(participant.videoTrackPublications.values()).map( - (publication) => - publication.isSubscribed && publication.track ? ( - - ) : ( - - ), - )} + (screenSharePublication?.isSubscribed && screenSharePublication.track ? ( + + ) : ( + + ))} {type === "user" &&

{user.display}

} diff --git a/packages/call/src/store.tsx b/packages/call/src/store.tsx index 21bb401..9bf9ba7 100644 --- a/packages/call/src/store.tsx +++ b/packages/call/src/store.tsx @@ -157,7 +157,7 @@ function getActiveScreenShareParticipantIds(): number[] { .map((participant) => ({ participantId: getParticipantId(participant.identity), hasScreenShare: - participant.getTrackPublication(Track.Source.ScreenShare) != null, + participant.getTrackPublication(Track.Source.ScreenShare)?.track != null, })) .filter( (entry): entry is { participantId: number; hasScreenShare: true } => @@ -841,6 +841,8 @@ export function useInitializeCall() { room.on(RoomEvent.Disconnected, onDisconnected); room.on(RoomEvent.TrackSubscribed, onTrackSubscribed); room.on(RoomEvent.TrackUnsubscribed, onTrackUnsubscribed); + room.on(RoomEvent.TrackPublished, onParticipantStateChange); + room.on(RoomEvent.TrackUnpublished, onParticipantStateChange); room.on(RoomEvent.ParticipantConnected, onParticipantConnected); room.on(RoomEvent.ParticipantDisconnected, onParticipantDisconnected); room.on(RoomEvent.TrackMuted, onParticipantStateChange); @@ -860,6 +862,8 @@ export function useInitializeCall() { room.off(RoomEvent.Disconnected, onDisconnected); room.off(RoomEvent.TrackSubscribed, onTrackSubscribed); room.off(RoomEvent.TrackUnsubscribed, onTrackUnsubscribed); + room.off(RoomEvent.TrackPublished, onParticipantStateChange); + room.off(RoomEvent.TrackUnpublished, onParticipantStateChange); room.off(RoomEvent.ParticipantConnected, onParticipantConnected); room.off(RoomEvent.ParticipantDisconnected, onParticipantDisconnected); room.off(RoomEvent.TrackMuted, onParticipantStateChange); diff --git a/packages/call/src/views/main/focused.tsx b/packages/call/src/views/main/focused.tsx index 5926690..3bcb276 100644 --- a/packages/call/src/views/main/focused.tsx +++ b/packages/call/src/views/main/focused.tsx @@ -1,4 +1,5 @@ -import { useLayoutEffect, useMemo, useRef, useState } from "react"; +import { RoomEvent } from "livekit-client"; +import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { useCall } from "../../store"; import Base from "../../components/modals/base"; @@ -16,6 +17,20 @@ export default function View() { const containerRef = useRef(null); const [focusedTileSize, setFocusedTileSize] = useState({ width: 0, height: 0 }); const [isFocusedTileFlush, setIsFocusedTileFlush] = useState(false); + const [participantVersion, setParticipantVersion] = useState(0); + + useEffect(() => { + const syncParticipants = () => { + setParticipantVersion((version) => version + 1); + }; + room.on(RoomEvent.ParticipantConnected, syncParticipants); + room.on(RoomEvent.ParticipantDisconnected, syncParticipants); + + return () => { + room.off(RoomEvent.ParticipantConnected, syncParticipants); + room.off(RoomEvent.ParticipantDisconnected, syncParticipants); + }; + }, [room]); const users = useMemo(() => { const participants = [ @@ -27,12 +42,17 @@ export default function View() { const participantId = Number(participant.identity); return Number.isInteger(participantId) && participantId > 0; }); - }, [room]); + // eslint-disable-next-line + }, [participantVersion, room]); const userIds = useMemo( () => users.map((participant) => Number(participant.identity)), [users], ); + const activeScreenShareParticipantIdSet = useMemo( + () => new Set(activeScreenShareParticipantIds), + [activeScreenShareParticipantIds], + ); const tiles = useMemo( () => [ @@ -45,14 +65,19 @@ export default function View() { })), ...userIds .filter((id) => id !== focusedParticipantId) - .filter((id) => !activeScreenShareParticipantIds.includes(id)) + .filter((id) => !activeScreenShareParticipantIdSet.has(id)) .map((participantId) => ({ key: `user:${participantId}`, kind: "user" as const, participantId, })), ], - [activeScreenShareParticipantIds, userIds, focusedParticipantId], + [ + activeScreenShareParticipantIds, + activeScreenShareParticipantIdSet, + userIds, + focusedParticipantId, + ], ); useLayoutEffect(() => { @@ -103,9 +128,17 @@ export default function View() { return null; } - const focusedParticipant = room.getParticipantByIdentity( - String(focusedParticipantId), - ); + function getParticipantById(participantId: number) { + if (Number(room.localParticipant.identity) === participantId) { + return room.localParticipant; + } + + return room.getParticipantByIdentity(String(participantId)); + } + + const focusedParticipant = getParticipantById(focusedParticipantId); + const focusedParticipantHasActiveScreenShare = + activeScreenShareParticipantIdSet.has(focusedParticipantId); return (
@@ -117,10 +150,8 @@ export default function View() {
@@ -133,9 +164,7 @@ export default function View() {
))}