diff --git a/src/app/components/CallEmbedProvider.tsx b/src/app/components/CallEmbedProvider.tsx index 9f150adcd..cfb4db070 100644 --- a/src/app/components/CallEmbedProvider.tsx +++ b/src/app/components/CallEmbedProvider.tsx @@ -26,6 +26,7 @@ import { RoomEvent, } from 'matrix-js-sdk'; import { IRTCNotificationContent, RTCNotificationType } from 'matrix-js-sdk/lib/matrixrtc/types'; +import { MatrixRTCSessionEvent } from 'matrix-js-sdk/lib/matrixrtc/MatrixRTCSession'; import { CryptoBackend } from 'matrix-js-sdk/lib/common-crypto/CryptoBackend'; import { CallEmbedContextProvider, @@ -559,6 +560,50 @@ function IncomingCallListener({ callEmbed, joined }: IncomingCallListenerProps) setCallInfo(undefined); }, []); + // [Gitea #161] Stop ringing here when the call was handled ELSEWHERE: + // - answered on another of our devices (our own m.call.member appears), + // - declined on another of our devices (our own RTCDecline for this ring), + // - the caller hung up before we picked up (nobody left in the session). + // Without this every other device kept ringing for the full lifetime. + useEffect(() => { + if (!callInfo) return undefined; + const { room, refEventId } = callInfo; + const myUserId = mx.getSafeUserId(); + const dismiss = () => + setCallInfo((current) => (current?.refEventId === refEventId ? undefined : current)); + + const session = mx.matrixRTC.getRoomSession(room); + const checkMemberships = () => { + const { memberships } = session; + if (memberships.some((m) => m.sender === myUserId)) + dismiss(); // answered elsewhere + else if (memberships.length === 0) dismiss(); // caller gone + }; + const onTimeline: EventTimelineSetHandlerMap[RoomEvent.Timeline] = ( + event, + eventRoom, + _s, + _r, + data, + ) => { + if (eventRoom?.roomId !== room.roomId || !data.liveEvent) return; + if (event.getType() === EventType.RTCDecline && event.getSender() === myUserId) { + const related = event.getRelation()?.event_id; + if (!related || related === refEventId) dismiss(); // declined elsewhere + } + }; + session.on(MatrixRTCSessionEvent.MembershipsChanged, checkMemberships); + mx.on(RoomEvent.Timeline, onTimeline); + // The caller's membership may not have arrived yet when the ring starts — + // only treat "empty" as hung-up after it has had a moment to sync. + const settle = setTimeout(checkMemberships, 5000); + return () => { + session.off(MatrixRTCSessionEvent.MembershipsChanged, checkMemberships); + mx.removeListener(RoomEvent.Timeline, onTimeline); + clearTimeout(settle); + }; + }, [mx, callInfo]); + const handleReject = useCallback( (room: Room, eventId: string) => { // Best-effort: the local UI dismisses regardless (below), but a failed