fix(lotus): report an ended screenshare even when LiveKit unpublishes first (#39)

LiveKit's own ended handler runs before ours and unpublishes the share;
a listener removed during that dispatch never fires. Decide from the
track's readyState on LocalTrackUnpublished as well. Verified end to end:
closing the shared source now toasts in the host.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-20 15:51:43 -04:00
co-authored by Claude Opus 5
parent e7c27dd8a3
commit 9f472fd710
2 changed files with 32 additions and 2 deletions
+15 -2
View File
@@ -45,6 +45,7 @@ export function startLotusScreenshareWatch(
const attach = (room: LivekitRoom): void => {
let cleanupTrack: (() => void) | undefined;
let endedHook: (() => void) | undefined;
const watchPublication = (pub: LocalTrackPublication): void => {
if (pub.source !== Track.Source.ScreenShare) return;
@@ -55,7 +56,12 @@ export function startLotusScreenshareWatch(
let sentNoFrames = false;
let sentAlone = false;
const onEnded = (): void => notify("ended");
let sentEnded = false;
const onEnded = (): void => {
if (sentEnded) return;
sentEnded = true;
notify("ended");
};
const onMute = (): void => {
if (sentNoFrames) return;
noFramesTimer = setTimeout(() => {
@@ -67,6 +73,7 @@ export function startLotusScreenshareWatch(
if (noFramesTimer !== undefined) clearTimeout(noFramesTimer);
noFramesTimer = undefined;
};
endedHook = onEnded;
mst?.addEventListener("ended", onEnded);
mst?.addEventListener("mute", onMute);
mst?.addEventListener("unmute", onUnmute);
@@ -86,11 +93,17 @@ export function startLotusScreenshareWatch(
if (noFramesTimer !== undefined) clearTimeout(noFramesTimer);
clearInterval(aloneTimer);
cleanupTrack = undefined;
endedHook = undefined;
};
};
// LiveKit's own `ended` handler runs first and unpublishes the share; a
// listener removed during that dispatch never fires, so decide from the
// track's state here as well.
const onUnpublished = (pub: LocalTrackPublication): void => {
if (pub.source === Track.Source.ScreenShare) cleanupTrack?.();
if (pub.source !== Track.Source.ScreenShare) return;
if (pub.track?.mediaStreamTrack?.readyState === "ended") endedHook?.();
cleanupTrack?.();
};
room.on(RoomEvent.LocalTrackPublished, watchPublication);