fix: PTT input guard, listener stability, focus restore mute, single badge
This commit is contained in:
@@ -59,14 +59,16 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
||||
const [pttKey] = useSetting(settingsAtom, 'pttKey');
|
||||
const [pttActive, setPttActive] = useState(false);
|
||||
|
||||
// Track microphone via ref so the PTT effect doesn't need it as a dep (avoids listener churn)
|
||||
const microphoneRef = useRef(microphone);
|
||||
useEffect(() => { microphoneRef.current = microphone; }, [microphone]);
|
||||
|
||||
// Handle PTT mode toggle mid-call
|
||||
const pttModeRef = useRef(pttMode);
|
||||
useEffect(() => {
|
||||
if (pttMode && !pttModeRef.current) {
|
||||
// PTT just enabled — mute mic so key handler can activate
|
||||
callEmbed.control.setMicrophone(false);
|
||||
} else if (!pttMode && pttModeRef.current) {
|
||||
// PTT just disabled — restore mic to on
|
||||
callEmbed.control.setMicrophone(true);
|
||||
}
|
||||
pttModeRef.current = pttMode;
|
||||
@@ -93,39 +95,53 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!pttMode) return;
|
||||
const iframeWindow = callEmbed.iframe.contentWindow;
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.code === pttKey && !e.repeat && !microphone) {
|
||||
e.preventDefault();
|
||||
callEmbed.control.setMicrophone(true);
|
||||
setPttActive(true);
|
||||
}
|
||||
if (e.code !== pttKey || e.repeat) return;
|
||||
// Don't intercept keys typed into a text input or editable element
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.contentEditable === 'true'
|
||||
) return;
|
||||
e.preventDefault();
|
||||
if (!microphoneRef.current) callEmbed.control.setMicrophone(true);
|
||||
setPttActive(true);
|
||||
};
|
||||
const onKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.code === pttKey) {
|
||||
callEmbed.control.setMicrophone(false);
|
||||
setPttActive(false);
|
||||
}
|
||||
if (e.code !== pttKey) return;
|
||||
callEmbed.control.setMicrophone(false);
|
||||
setPttActive(false);
|
||||
};
|
||||
// Release PTT if the tab loses focus — prevents mic getting stuck on after tab switching
|
||||
// Release PTT when the tab loses focus to prevent stuck-on mic
|
||||
const onBlur = () => {
|
||||
callEmbed.control.setMicrophone(false);
|
||||
setPttActive(false);
|
||||
};
|
||||
// The EC iframe captures keyboard events when it has focus, so listen on it too
|
||||
const iframeWindow = callEmbed.iframe.contentWindow;
|
||||
// Re-mute on focus restore: EC can re-assert audio_enabled:true on audio-context resume
|
||||
const onFocus = () => {
|
||||
callEmbed.control.setMicrophone(false);
|
||||
setPttActive(false);
|
||||
};
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
window.addEventListener('keyup', onKeyUp);
|
||||
window.addEventListener('blur', onBlur);
|
||||
window.addEventListener('focus', onFocus);
|
||||
iframeWindow?.addEventListener('keydown', onKeyDown);
|
||||
iframeWindow?.addEventListener('keyup', onKeyUp);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown);
|
||||
window.removeEventListener('keyup', onKeyUp);
|
||||
window.removeEventListener('blur', onBlur);
|
||||
window.removeEventListener('focus', onFocus);
|
||||
iframeWindow?.removeEventListener('keydown', onKeyDown);
|
||||
iframeWindow?.removeEventListener('keyup', onKeyUp);
|
||||
};
|
||||
}, [pttMode, pttKey, callEmbed, microphone]);
|
||||
// microphone intentionally read via microphoneRef — excluded from deps to avoid listener churn
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pttMode, pttKey, callEmbed]);
|
||||
|
||||
const [hangupState, hangup] = useAsyncCallback(
|
||||
useCallback(() => callEmbed.hangup(), [callEmbed])
|
||||
|
||||
Reference in New Issue
Block a user