fix(calls): the call-bar output menu uses the call frame's devices (#310)
deviceIds are hashed per frame/origin, so the ids the menu got from the chat page's enumerateDevices() were unknown inside the call iframe: the fork fell back to the default output while the menu showed a tick on the chosen device. Handle io.lotus.audio_output_state (also silencing the fork's "host did not ack" log) and build the menu from the fork's list and effective selection; older forks fall back to the old list. Fixes #310 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3878a85339
commit
ef7690135d
@@ -16,6 +16,7 @@ import {
|
||||
toRem,
|
||||
} from 'folds';
|
||||
import { CallEmbed } from '../../plugins/call';
|
||||
import { AUDIO_OUTPUT_STATE } from '../../plugins/call/CallControl';
|
||||
import { MobileTouchTarget } from '../../styles/mobile.css';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
|
||||
@@ -39,6 +40,21 @@ export function AudioOutputButton({ embed, disabled }: { embed: CallEmbed; disab
|
||||
const [devices, setDevices] = useState<OutputDevice[]>([]);
|
||||
const [selected, setSelected] = useState<string | undefined>(embed.control.audioOutputId);
|
||||
|
||||
// [Gitea #310] Prefer the fork's list: its ids are the ones valid inside
|
||||
// the call iframe. Older forks don't report one; fall back to our own.
|
||||
const [forkDevices, setForkDevices] = useState(embed.control.audioOutputs);
|
||||
useEffect(() => {
|
||||
const onState = () => {
|
||||
setForkDevices(embed.control.audioOutputs);
|
||||
setSelected(embed.control.audioOutputId);
|
||||
};
|
||||
onState();
|
||||
embed.control.on(AUDIO_OUTPUT_STATE, onState);
|
||||
return () => {
|
||||
embed.control.off(AUDIO_OUTPUT_STATE, onState);
|
||||
};
|
||||
}, [embed]);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
try {
|
||||
const all = await navigator.mediaDevices.enumerateDevices();
|
||||
@@ -53,11 +69,13 @@ export function AudioOutputButton({ embed, disabled }: { embed: CallEmbed; disab
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!anchor) return undefined;
|
||||
if (!anchor || forkDevices) return undefined;
|
||||
refresh();
|
||||
navigator.mediaDevices.addEventListener('devicechange', refresh);
|
||||
return () => navigator.mediaDevices.removeEventListener('devicechange', refresh);
|
||||
}, [anchor, refresh]);
|
||||
}, [anchor, refresh, forkDevices]);
|
||||
|
||||
const shown = forkDevices ?? devices;
|
||||
|
||||
const choose = (id: string) => {
|
||||
embed.control.setAudioOutput(id);
|
||||
@@ -90,13 +108,14 @@ export function AudioOutputButton({ embed, disabled }: { embed: CallEmbed; disab
|
||||
aria-label="Audio output"
|
||||
>
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
{devices.length === 0 && (
|
||||
{shown.length === 0 && (
|
||||
<Text size="T200" priority="300" style={{ padding: config.space.S200 }}>
|
||||
No audio outputs found.
|
||||
</Text>
|
||||
)}
|
||||
{devices.map((d) => {
|
||||
const isSelected = selected ? d.id === selected : d.id === 'default';
|
||||
{shown.map((d) => {
|
||||
const isSelected =
|
||||
selected !== undefined ? d.id === selected : d.id === 'default' || d.id === '';
|
||||
return (
|
||||
<MenuItem
|
||||
key={d.id}
|
||||
|
||||
Reference in New Issue
Block a user