2026-06-28 10:55:19 -04:00
|
|
|
import { useEffect, useState } from 'react';
|
2026-03-08 22:00:35 +11:00
|
|
|
import { CallEmbed } from '../plugins/call';
|
2026-09-15 22:43:31 -04:00
|
|
|
import { nextSpeakerSet } from '../utils/speakerSet';
|
2026-03-08 22:00:35 +11:00
|
|
|
import { useCallMembers, useCallSession } from './useCall';
|
|
|
|
|
import { useCallJoined } from './useCallEmbed';
|
|
|
|
|
|
2026-06-28 10:55:19 -04:00
|
|
|
/**
|
2026-09-26 22:04:54 -04:00
|
|
|
* Returns the set of Matrix user IDs currently speaking in the call, from the
|
|
|
|
|
* fork's io.lotus.call_state reports ([lotus #2]).
|
2026-06-28 10:55:19 -04:00
|
|
|
*
|
2026-09-26 22:04:54 -04:00
|
|
|
* [Gitea #43] The fallback that scraped EC's rendered tiles (the speaking
|
|
|
|
|
* ring's `::before` background) is gone: the bundled fork always reports call
|
|
|
|
|
* state, and the host no longer reads the call frame's DOM.
|
2026-06-28 10:55:19 -04:00
|
|
|
*/
|
2026-03-08 22:00:35 +11:00
|
|
|
export const useCallSpeakers = (callEmbed: CallEmbed): Set<string> => {
|
|
|
|
|
const [speakers, setSpeakers] = useState(new Set<string>());
|
|
|
|
|
const callSession = useCallSession(callEmbed.room);
|
2026-05-23 17:20:41 +05:30
|
|
|
const callMembers = useCallMembers(callSession);
|
2026-03-08 22:00:35 +11:00
|
|
|
const joined = useCallJoined(callEmbed);
|
|
|
|
|
|
2026-07-19 02:47:27 -04:00
|
|
|
// C-L5 — depend on a STABLE boolean, not the callMembers array (whose identity
|
2026-09-26 22:04:54 -04:00
|
|
|
// changes on every membership change). The io.lotus.call_state subscription
|
|
|
|
|
// below already tracks participants joining/leaving live.
|
2026-07-19 02:47:27 -04:00
|
|
|
const hasCallMembers = callMembers.length > 0;
|
|
|
|
|
|
2026-06-28 10:55:19 -04:00
|
|
|
useEffect(() => {
|
2026-07-19 02:47:27 -04:00
|
|
|
if (!hasCallMembers || !joined) {
|
2026-06-28 10:55:19 -04:00
|
|
|
setSpeakers(new Set<string>());
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
2026-03-08 22:00:35 +11:00
|
|
|
|
2026-06-28 10:55:19 -04:00
|
|
|
const syncState = (): void => {
|
2026-09-26 22:04:54 -04:00
|
|
|
// #32 — bail out of setState (and the re-render it causes) when the
|
|
|
|
|
// derived set is unchanged from the previous one.
|
|
|
|
|
setSpeakers((prev) => nextSpeakerSet(prev, callEmbed.getLotusParticipants() ?? []));
|
2026-06-28 10:55:19 -04:00
|
|
|
};
|
|
|
|
|
|
2026-09-15 22:43:31 -04:00
|
|
|
syncState();
|
2026-09-26 22:04:54 -04:00
|
|
|
return callEmbed.onLotusCallState(syncState);
|
2026-07-19 02:47:27 -04:00
|
|
|
}, [callEmbed, hasCallMembers, joined]);
|
2026-03-08 22:00:35 +11:00
|
|
|
|
|
|
|
|
return speakers;
|
|
|
|
|
};
|
2026-06-03 22:13:22 -04:00
|
|
|
|
|
|
|
|
/**
|
2026-09-26 22:04:54 -04:00
|
|
|
* Returns true when there is at least one REMOTE participant and every one of
|
|
|
|
|
* them has their microphone muted, from the fork's io.lotus.call_state reports.
|
2026-06-03 22:13:22 -04:00
|
|
|
*/
|
|
|
|
|
export const useRemoteAllMuted = (callEmbed: CallEmbed | undefined): boolean => {
|
|
|
|
|
const [muted, setMuted] = useState(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!callEmbed) return undefined;
|
|
|
|
|
|
2026-06-03 22:44:11 -04:00
|
|
|
const localUserId = callEmbed.room.client?.getUserId() ?? '';
|
2026-06-03 22:13:22 -04:00
|
|
|
|
|
|
|
|
const syncState = (): void => {
|
2026-09-26 22:04:54 -04:00
|
|
|
const remote = (callEmbed.getLotusParticipants() ?? []).filter(
|
|
|
|
|
(p) => p.userId !== localUserId,
|
|
|
|
|
);
|
|
|
|
|
setMuted(remote.length > 0 && remote.every((p) => !p.audioEnabled));
|
2026-06-03 22:13:22 -04:00
|
|
|
};
|
|
|
|
|
|
2026-09-15 22:43:31 -04:00
|
|
|
syncState();
|
2026-09-26 22:04:54 -04:00
|
|
|
return callEmbed.onLotusCallState(syncState);
|
2026-06-03 22:13:22 -04:00
|
|
|
}, [callEmbed]);
|
|
|
|
|
|
|
|
|
|
return muted;
|
|
|
|
|
};
|