fix(calls): no stale "muted + deafened" render when a call starts

useCallControlState kept the previous control's state (the all-off
default before a call) for one render after the control changed, until
its effect caught up. The thumbbar, SMTC and tray hooks pushed that render
to the OS, so every join flashed "muted + deafened" first (recorded:
idle → active+muted+deafened → active). The state now remembers which
control it belongs to and reads the new control directly when they differ
(now: idle → active).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-23 21:53:43 -04:00
co-authored by Claude Opus 5.5
parent 6edfe70020
commit b77720c3da
+14 -5
View File
@@ -35,20 +35,29 @@ export const useSendClientWidgetApiAction = (api: ClientWidgetApi) => {
const DEFAULT_CONTROL_STATE = new CallControlState(false, false, false);
export const useCallControlState = (control: CallControl | undefined): CallControlState => {
const [state, setState] = useState(control?.getState() ?? DEFAULT_CONTROL_STATE);
// Remember which control the stored state came from. When a call starts the
// control changes, and the effect below only catches up after a render; until
// then the old (default: all off) state would be returned for the NEW call —
// one render of "muted + deafened" that the thumbbar, SMTC and tray (#4)
// pushed to the OS as a flicker. Read the new control directly instead.
const [entry, setEntry] = useState(() => ({
control,
state: control?.getState() ?? DEFAULT_CONTROL_STATE,
}));
useEffect(() => {
if (!control) {
setState(DEFAULT_CONTROL_STATE);
setEntry({ control, state: DEFAULT_CONTROL_STATE });
return;
}
setState(control.getState());
const handleUpdate = () => setState(control.getState());
setEntry({ control, state: control.getState() });
const handleUpdate = () => setEntry({ control, state: control.getState() });
control.on(CallControlEvent.StateUpdate, handleUpdate);
return () => {
control.off(CallControlEvent.StateUpdate, handleUpdate);
};
}, [control]);
return state;
if (entry.control !== control) return control?.getState() ?? DEFAULT_CONTROL_STATE;
return entry.state;
};