From e717c812a733ea66f257909914ec6adfc32f174e Mon Sep 17 00:00:00 2001
From: webadderall <131426131+webadderall@users.noreply.github.com>
Date: Sun, 29 Mar 2026 11:07:34 +1100
Subject: [PATCH] fix invalid/stale mic selection windows
---
src/components/launch/LaunchWindow.tsx | 21 +--
src/hooks/useMicrophoneDevices.ts | 178 +++++++++++++------------
2 files changed, 105 insertions(+), 94 deletions(-)
diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx
index 09684c7c..f9ba3fe5 100644
--- a/src/components/launch/LaunchWindow.tsx
+++ b/src/components/launch/LaunchWindow.tsx
@@ -111,8 +111,8 @@ function DropdownItem({
);
}
-function Separator() {
- return
;
+function Separator({ dropdown = false }: { dropdown?: boolean }) {
+ return ;
}
function MicDeviceRow({
@@ -196,6 +196,7 @@ export function LaunchWindow() {
const showWebcamControls = webcamEnabled && !recording;
const { devices, selectedDeviceId, setSelectedDeviceId } = useMicrophoneDevices(
microphoneEnabled || micDropdownOpen,
+ microphoneDeviceId,
);
const {
devices: videoDevices,
@@ -206,9 +207,11 @@ export function LaunchWindow() {
const supportsHudCaptureProtection = platform !== "linux";
useEffect(() => {
- if (selectedDeviceId && selectedDeviceId !== "default") {
- setMicrophoneDeviceId(selectedDeviceId);
+ if (!selectedDeviceId) {
+ return;
}
+
+ setMicrophoneDeviceId(selectedDeviceId === "default" ? undefined : selectedDeviceId);
}, [selectedDeviceId, setMicrophoneDeviceId]);
useEffect(() => {
@@ -691,7 +694,7 @@ export function LaunchWindow() {
/>
-
+
-
+
-
+
{
setMicrophoneEnabled(true);
setSelectedDeviceId(device.deviceId);
- setMicrophoneDeviceId(device.deviceId);
+ setMicrophoneDeviceId(
+ device.deviceId === "default" ? undefined : device.deviceId,
+ );
}}
/>
))}
diff --git a/src/hooks/useMicrophoneDevices.ts b/src/hooks/useMicrophoneDevices.ts
index 4ae21eb2..f47710cf 100644
--- a/src/hooks/useMicrophoneDevices.ts
+++ b/src/hooks/useMicrophoneDevices.ts
@@ -1,107 +1,113 @@
-import { useEffect, useState } from 'react'
+import { useEffect, useState } from "react";
export interface MicrophoneDevice {
- deviceId: string
- label: string
- groupId: string
+ deviceId: string;
+ label: string;
+ groupId: string;
}
-let hasRequestedMicrophoneLabels = false
+let hasRequestedMicrophoneLabels = false;
-export function useMicrophoneDevices(enabled: boolean = true) {
- const [devices, setDevices] = useState([])
- const [selectedDeviceId, setSelectedDeviceId] = useState('default')
- const [isLoading, setIsLoading] = useState(false)
- const [error, setError] = useState(null)
+export function useMicrophoneDevices(enabled: boolean = true, preferredDeviceId?: string) {
+ const [devices, setDevices] = useState([]);
+ const [selectedDeviceId, setSelectedDeviceId] = useState("default");
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
- useEffect(() => {
- if (!enabled) {
- return
- }
+ useEffect(() => {
+ if (!enabled) {
+ return;
+ }
- let mounted = true
+ let mounted = true;
- const loadDevices = async () => {
- let permissionStream: MediaStream | null = null
+ const loadDevices = async () => {
+ let permissionStream: MediaStream | null = null;
- try {
- setIsLoading(true)
- setError(null)
+ try {
+ setIsLoading(true);
+ setError(null);
- let allDevices = await navigator.mediaDevices.enumerateDevices()
- let audioInputs = allDevices
- .filter((device) => device.kind === 'audioinput')
- .map((device) => ({
- deviceId: device.deviceId,
- label: device.label || `Microphone ${device.deviceId.slice(0, 8)}`,
- groupId: device.groupId,
- }))
+ let allDevices = await navigator.mediaDevices.enumerateDevices();
+ let audioInputs = allDevices
+ .filter((device) => device.kind === "audioinput")
+ .map((device) => ({
+ deviceId: device.deviceId,
+ label: device.label || `Microphone ${device.deviceId.slice(0, 8)}`,
+ groupId: device.groupId,
+ }));
- const needsLabelPermission =
- audioInputs.length > 0 && audioInputs.every((device) => !device.label.trim())
+ const needsLabelPermission =
+ audioInputs.length > 0 && audioInputs.every((device) => !device.label.trim());
- if (needsLabelPermission && !hasRequestedMicrophoneLabels) {
- hasRequestedMicrophoneLabels = true
- permissionStream = await navigator.mediaDevices.getUserMedia({ audio: true })
- allDevices = await navigator.mediaDevices.enumerateDevices()
- audioInputs = allDevices
- .filter((device) => device.kind === 'audioinput')
- .map((device) => ({
- deviceId: device.deviceId,
- label: device.label || `Microphone ${device.deviceId.slice(0, 8)}`,
- groupId: device.groupId,
- }))
- }
+ if (needsLabelPermission && !hasRequestedMicrophoneLabels) {
+ hasRequestedMicrophoneLabels = true;
+ permissionStream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ allDevices = await navigator.mediaDevices.enumerateDevices();
+ audioInputs = allDevices
+ .filter((device) => device.kind === "audioinput")
+ .map((device) => ({
+ deviceId: device.deviceId,
+ label: device.label || `Microphone ${device.deviceId.slice(0, 8)}`,
+ groupId: device.groupId,
+ }));
+ }
- if (mounted) {
- setDevices(audioInputs)
- setSelectedDeviceId((currentDeviceId) => {
- if (currentDeviceId === 'default' && audioInputs.length > 0) {
- return audioInputs[0].deviceId
- }
+ if (mounted) {
+ setDevices(audioInputs);
+ setSelectedDeviceId((currentDeviceId) => {
+ const normalizedPreferredDeviceId = preferredDeviceId ?? "default";
+ if (audioInputs.some((device) => device.deviceId === normalizedPreferredDeviceId)) {
+ return normalizedPreferredDeviceId;
+ }
- if (
- currentDeviceId !== 'default' &&
- audioInputs.some((device) => device.deviceId === currentDeviceId)
- ) {
- return currentDeviceId
- }
+ if (
+ currentDeviceId !== "default" &&
+ audioInputs.some((device) => device.deviceId === currentDeviceId)
+ ) {
+ return currentDeviceId;
+ }
- return audioInputs[0]?.deviceId ?? 'default'
- })
- setIsLoading(false)
- }
- } catch (error) {
- if (mounted) {
- const message = error instanceof Error ? error.message : 'Failed to enumerate audio devices'
- setError(message)
- setIsLoading(false)
- console.error('Error loading microphone devices:', error)
- }
- } finally {
- permissionStream?.getTracks().forEach((track) => track.stop())
- }
- }
+ return (
+ audioInputs.find((device) => device.deviceId !== "default")?.deviceId ??
+ audioInputs[0]?.deviceId ??
+ "default"
+ );
+ });
+ setIsLoading(false);
+ }
+ } catch (error) {
+ if (mounted) {
+ const message =
+ error instanceof Error ? error.message : "Failed to enumerate audio devices";
+ setError(message);
+ setIsLoading(false);
+ console.error("Error loading microphone devices:", error);
+ }
+ } finally {
+ permissionStream?.getTracks().forEach((track) => track.stop());
+ }
+ };
- void loadDevices()
+ void loadDevices();
- const handleDeviceChange = () => {
- void loadDevices()
- }
+ const handleDeviceChange = () => {
+ void loadDevices();
+ };
- navigator.mediaDevices.addEventListener('devicechange', handleDeviceChange)
+ navigator.mediaDevices.addEventListener("devicechange", handleDeviceChange);
- return () => {
- mounted = false
- navigator.mediaDevices.removeEventListener('devicechange', handleDeviceChange)
- }
- }, [enabled])
+ return () => {
+ mounted = false;
+ navigator.mediaDevices.removeEventListener("devicechange", handleDeviceChange);
+ };
+ }, [enabled, preferredDeviceId]);
- return {
- devices,
- selectedDeviceId,
- setSelectedDeviceId,
- isLoading,
- error,
- }
+ return {
+ devices,
+ selectedDeviceId,
+ setSelectedDeviceId,
+ isLoading,
+ error,
+ };
}