From 34574178a90a8c8b0e29f08690c916b5e3a41008 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 19 Sep 2026 01:12:25 -0400 Subject: [PATCH] feat(calls): toast when a room admin turns off screen sharing / camera mid-call (#223) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-measured the revoke on the current stack (guard fix + fork lotus.4): the SFU drops the sharer's tracks and EC's own button, the captured tracks and our control bar all follow within ~2-4 s — the 30 s lag is gone. What remained was that the button simply vanished with no explanation, so a call-lifetime hook now toasts 'Screen sharing was turned off by a room admin.' (or the camera variant) when a track ends while the room policy forbids it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- src/app/components/CallEmbedProvider.tsx | 2 ++ src/app/hooks/useCallPolicyRevokedToast.ts | 39 ++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/app/hooks/useCallPolicyRevokedToast.ts diff --git a/src/app/components/CallEmbedProvider.tsx b/src/app/components/CallEmbedProvider.tsx index 54955b60a..c92fe33ad 100644 --- a/src/app/components/CallEmbedProvider.tsx +++ b/src/app/components/CallEmbedProvider.tsx @@ -46,6 +46,7 @@ import { useMatrixClient } from '../hooks/useMatrixClient'; import { previewRingtone, startRingtone, unlockRingtoneAudio } from '../utils/ringtones'; import { useCallMembersChange, useCallSession } from '../hooks/useCall'; import { useCallJoinLeaveSounds } from '../hooks/useCallJoinLeaveSounds'; +import { useCallPolicyRevokedToast } from '../hooks/useCallPolicyRevokedToast'; import { useCallHotkeys } from '../hooks/useCallHotkeys'; import { useAfkAutoMute } from '../hooks/useAfkAutoMute'; import { useCallQuality } from '../hooks/useCallQuality'; @@ -685,6 +686,7 @@ function CallUtils({ embed, joined }: { embed: CallEmbed; joined: boolean }) { useCallHotkeys(embed, joined); useAfkAutoMute(joined ? embed : undefined); useCallJoinLeaveSounds(embed); + useCallPolicyRevokedToast(embed, joined); useCallThemeSync(embed); useCallQuality(embed); useCallHangupEvent( diff --git a/src/app/hooks/useCallPolicyRevokedToast.ts b/src/app/hooks/useCallPolicyRevokedToast.ts new file mode 100644 index 000000000..e9decf91b --- /dev/null +++ b/src/app/hooks/useCallPolicyRevokedToast.ts @@ -0,0 +1,39 @@ +import { useEffect, useRef } from 'react'; +import { useSetAtom } from 'jotai'; +import { CallEmbed, useCallControlState } from '../plugins/call'; +import { useRoomCallPolicy } from './useRoomCallPolicy'; +import { toastQueueAtom } from '../state/toast'; + +/** + * [Gitea #223] When a room admin turns Allow Screen Sharing / Allow Camera + * off mid-call, the voice-limit-guard revokes the publish permission and the + * SFU drops our track within seconds — the button just vanishes with no + * explanation. Tell the user why: a track that went away while the room policy + * forbids it was not their doing. + */ +export function useCallPolicyRevokedToast(embed: CallEmbed, joined: boolean): void { + const { screenshare, video } = useCallControlState(embed.control); + const { allowScreenshare, allowCamera } = useRoomCallPolicy(embed.room); + const setToast = useSetAtom(toastQueueAtom); + const prev = useRef({ screenshare, video }); + + useEffect(() => { + const was = prev.current; + prev.current = { screenshare, video }; + if (!joined) return; + const notify = (body: string) => + setToast({ + id: `call-policy-${Date.now()}`, + displayName: 'Lotus Chat', + body, + roomName: embed.room.name ?? 'Voice call', + roomId: embed.roomId, + }); + if (was.screenshare && !screenshare && !allowScreenshare) { + notify('Screen sharing was turned off by a room admin.'); + } + if (was.video && !video && !allowCamera) { + notify('Your camera was turned off by a room admin.'); + } + }, [screenshare, video, allowScreenshare, allowCamera, joined, setToast, embed]); +}