From 9d3b8df4bf5e974ab9195d0ed3bca98f307ae20a Mon Sep 17 00:00:00 2001 From: legop3 Date: Thu, 8 Oct 2026 13:05:41 -0400 Subject: [PATCH] cleanupses --- webui/src/mic/MicApp.jsx | 42 ++++++++++++++++++-- webui/src/mic/useMicPtt.js | 80 -------------------------------------- 2 files changed, 38 insertions(+), 84 deletions(-) delete mode 100644 webui/src/mic/useMicPtt.js diff --git a/webui/src/mic/MicApp.jsx b/webui/src/mic/MicApp.jsx index 0cbeff2a..a83591ad 100644 --- a/webui/src/mic/MicApp.jsx +++ b/webui/src/mic/MicApp.jsx @@ -4,9 +4,20 @@ import { useSessionSelector } from '../context/SessionContext.jsx'; import useUserIdentitySync from '../hooks/useUserIdentitySync.js'; import { RESTART_DELAY_MS } from '../lib/whepPlayback.js'; import { waitForIceGatheringComplete } from '../components/vip/VipAudioUploadCard/whipTransport.js'; -import useMicPtt from './useMicPtt.js'; +import { ControlSystemProvider, KeyboardInputManager, GamepadInputManager, useControlSelector } from '../controls/index.js'; +import ControlHint from '../components/ControlHint/index.jsx'; export default function MicApp() { + return ( + + + + + + ); +} + +function MicPage() { const socket = useSocket(); const connected = useSessionSelector((state) => state.connected); const local = useSessionSelector((state) => state.session?.isLocalNetwork); @@ -14,7 +25,10 @@ export default function MicApp() { const [status, setStatus] = useState('Connecting…'); const [pttMode, setPttMode] = useState(false); const available = Boolean(connected && local && enabled); - const { held, setPointerHeld, setButtonKeyHeld, keyLabel } = useMicPtt(available && pttMode); + const pttActive = useControlSelector((control) => Boolean(control.state.mic?.pttActive)); + const [pointerHeld, setPointerHeld] = useState(false); + const [buttonKeyHeld, setButtonKeyHeld] = useState(false); + const held = available && pttMode && (pttActive || pointerHeld || buttonKeyHeld); const transmitting = available && (!pttMode || held); const mediaRef = useRef(null); const transmittingRef = useRef(transmitting); @@ -22,6 +36,22 @@ export default function MicApp() { transmittingRef.current = transmitting; mediaRef.current?.getAudioTracks().forEach((track) => { track.enabled = transmitting; }); }, [transmitting]); + useEffect(() => { + // Pointer capture can outlive focus; release the page button when leaving the page. + function releaseButton() { + setPointerHeld(false); + setButtonKeyHeld(false); + } + function visibilityChange() { + if (document.hidden) releaseButton(); + } + window.addEventListener('blur', releaseButton); + document.addEventListener('visibilitychange', visibilityChange); + return () => { + window.removeEventListener('blur', releaseButton); + document.removeEventListener('visibilitychange', visibilityChange); + }; + }, []); useUserIdentitySync(); useEffect(() => { if (!connected || !local || !enabled) return undefined; @@ -131,7 +161,11 @@ export default function MicApp() { {[false, true].map((ptt) => ( ))} @@ -162,7 +196,7 @@ export default function MicApp() { onBlur={() => setButtonKeyHeld(false)}> {held ? 'Talking…' : 'Hold to talk'} -

Hold the button, {keyLabel}, or your configured controller PTT control.

+

Hold the button or to talk.

)} diff --git a/webui/src/mic/useMicPtt.js b/webui/src/mic/useMicPtt.js deleted file mode 100644 index 59e308c2..00000000 --- a/webui/src/mic/useMicPtt.js +++ /dev/null @@ -1,80 +0,0 @@ -import { useEffect, useMemo, useState } from 'react'; -import { useSettingsNamespace } from '../settings/index.js'; -import { DEFAULT_KEYMAP } from '../controls/constants.js'; -import { formatKeyLabel, normalizeKeymapEntries, tokensForEvent } from '../controls/keymapUtils.js'; -import { GAMEPAD_PROFILE_DEFAULT, GAMEPAD_SETTINGS_DEFAULTS } from '../settings/namespaces.js'; -import { computeGamepadOutputs, resolveGamepadProfile } from '../controls/inputs/gamepadBindings.js'; -import { subscribeGamepadHub } from '../controls/inputs/gamepadHub.js'; -import { isTextInputElement, isTextEntryActive } from '../controls/inputs/inputFocusUtils.js'; -import { isKeyboardCaptureLocked } from '../controls/inputs/keyboardCaptureLock.js'; -import { isControllerControlLocked } from '../controls/inputs/controllerRuntime.js'; - -export default function useMicPtt(enabled) { - const { value: controls } = useSettingsNamespace('controls', { keymap: DEFAULT_KEYMAP }); - const { value: gamepad } = useSettingsNamespace('gamepad', GAMEPAD_SETTINGS_DEFAULTS); - const bindings = controls?.keymap?.micPtt ?? DEFAULT_KEYMAP.micPtt; - const tokens = useMemo(() => normalizeKeymapEntries({ micPtt: bindings }).micPtt, [bindings]); - const [keyboardHeld, setKeyboardHeld] = useState(false); - const [controllerHeld, setControllerHeld] = useState(false); - const [pointerHeld, setPointerHeld] = useState(false); - const [buttonKeyHeld, setButtonKeyHeld] = useState(false); - - useEffect(() => { - if (!enabled) return undefined; - const pressed = new Set(); - function reset() { - pressed.clear(); - setKeyboardHeld(false); - setControllerHeld(false); - setPointerHeld(false); - setButtonKeyHeld(false); - } - function keyDown(event) { - if (isKeyboardCaptureLocked() || isTextInputElement(event.target)) return; - const matching = tokensForEvent(event).filter((token) => tokens.has(token)); - if (!matching.length) return; - event.preventDefault(); - matching.forEach((token) => pressed.add(token)); - setKeyboardHeld(true); - } - function keyUp(event) { - tokensForEvent(event).forEach((token) => pressed.delete(token)); - setKeyboardHeld(pressed.size > 0); - } - function visibilityChange() { - if (document.hidden) reset(); - } - // Reuse the configured bindings and shared poller without mounting rover controls on /mic. - const unsubscribe = subscribeGamepadHub(({ pads }) => { - if (document.hidden || !document.hasFocus() || isTextEntryActive() || isControllerControlLocked()) { - setControllerHeld(false); - return; - } - const pad = pads.find((entry) => entry.instanceKey === gamepad?.activeInstanceKey) ?? pads[0]; - const profile = resolveGamepadProfile( - gamepad?.profiles?.[pad?.signature] ?? gamepad?.defaults?.profile, - GAMEPAD_PROFILE_DEFAULT, - ); - setControllerHeld(Boolean(pad && computeGamepadOutputs(pad, profile).buttons.micPtt)); - }); - window.addEventListener('keydown', keyDown); - window.addEventListener('keyup', keyUp); - window.addEventListener('blur', reset); - document.addEventListener('visibilitychange', visibilityChange); - return () => { - unsubscribe(); - window.removeEventListener('keydown', keyDown); - window.removeEventListener('keyup', keyUp); - window.removeEventListener('blur', reset); - document.removeEventListener('visibilitychange', visibilityChange); - reset(); - }; - }, [enabled, tokens, gamepad]); - - return { - held: enabled && (keyboardHeld || controllerHeld || pointerHeld || buttonKeyHeld), - setPointerHeld, - setButtonKeyHeld, - keyLabel: (Array.isArray(bindings) ? bindings : [bindings]).map(formatKeyLabel).join(' / '), - }; -}