Skip to content

Commit 450cf40

Browse files
authored
fix issues with device selection and UI (#69)
1 parent 9837b6c commit 450cf40

File tree

2 files changed

+57
-48
lines changed

2 files changed

+57
-48
lines changed

ui/src/components/room.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export const Room = () => {
171171
const [config, setConfig] = useState<StreamConfig>({
172172
streamUrl: "",
173173
frameRate: 0,
174-
selectedDeviceId: "",
174+
selectedVideoDeviceId: "",
175175
selectedAudioDeviceId: "", // New property for audio device
176176
prompts: null,
177177
});
@@ -245,7 +245,7 @@ export const Room = () => {
245245
<div className="absolute bottom-[8px] right-[8px] w-[70px] h-[70px] sm:w-[90px] sm:h-[90px] bg-slate-800 block md:hidden">
246246
<Webcam
247247
onStreamReady={onStreamReady}
248-
deviceId={config.selectedDeviceId}
248+
deviceId={config.selectedVideoDeviceId}
249249
frameRate={config.frameRate}
250250
selectedAudioDeviceId={config.selectedAudioDeviceId}
251251
/>
@@ -255,7 +255,7 @@ export const Room = () => {
255255
<div className="hidden md:flex w-full sm:w-full md:w-full h-[50dvh] sm:h-auto md:h-auto max-w-[512px] max-h-[512px] aspect-square justify-center items-center lg:border-2 lg:rounded-md bg-slate-800">
256256
<Webcam
257257
onStreamReady={onStreamReady}
258-
deviceId={config.selectedDeviceId}
258+
deviceId={config.selectedVideoDeviceId}
259259
frameRate={config.frameRate}
260260
selectedAudioDeviceId={config.selectedAudioDeviceId}
261261
/>

ui/src/components/settings.tsx

+54-45
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ export interface StreamConfig {
4141
streamUrl: string;
4242
frameRate: number;
4343
prompts?: any;
44-
selectedDeviceId: string | undefined;
45-
selectedAudioDeviceId: string | undefined;
44+
selectedVideoDeviceId: string;
45+
selectedAudioDeviceId: string;
4646
}
4747

48-
interface VideoDevice {
48+
interface AVDevice {
4949
deviceId: string;
5050
label: string;
5151
}
@@ -54,8 +54,8 @@ export const DEFAULT_CONFIG: StreamConfig = {
5454
streamUrl:
5555
process.env.NEXT_PUBLIC_DEFAULT_STREAM_URL || "http://127.0.0.1:8889",
5656
frameRate: 30,
57-
selectedDeviceId: undefined,
58-
selectedAudioDeviceId: undefined,
57+
selectedVideoDeviceId: "none",
58+
selectedAudioDeviceId: "none",
5959
};
6060

6161
interface StreamSettingsProps {
@@ -137,10 +137,10 @@ export const usePrompt = () => useContext(PromptContext);
137137
function ConfigForm({ config, onSubmit }: ConfigFormProps) {
138138
const [prompts, setPrompts] = useState<any[]>([]);
139139
const { setOriginalPrompts } = usePrompt();
140-
const [videoDevices, setVideoDevices] = useState<VideoDevice[]>([]);
141-
const [audioDevices, setAudioDevices] = useState<VideoDevice[]>([]);
142-
const [selectedDevice, setSelectedDevice] = useState<string | undefined>(config.selectedDeviceId);
143-
const [selectedAudioDevice, setSelectedAudioDevice] = useState<string | undefined>(config.selectedDeviceId);
140+
const [videoDevices, setVideoDevices] = useState<AVDevice[]>([]);
141+
const [audioDevices, setAudioDevices] = useState<AVDevice[]>([]);
142+
const [selectedVideoDevice, setSelectedVideoDevice] = useState<string | undefined>(config.selectedVideoDeviceId);
143+
const [selectedAudioDevice, setSelectedAudioDevice] = useState<string | undefined>(config.selectedAudioDeviceId);
144144

145145
const form = useForm<z.infer<typeof formSchema>>({
146146
resolver: zodResolver(formSchema),
@@ -152,35 +152,32 @@ function ConfigForm({ config, onSubmit }: ConfigFormProps) {
152152
*/
153153
const getVideoDevices = useCallback(async () => {
154154
try {
155-
await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
155+
await navigator.mediaDevices.getUserMedia({ video: true });
156156

157157
const devices = await navigator.mediaDevices.enumerateDevices();
158158
const videoDevices = [
159159
{ deviceId: "none", label: "No Video" },
160160
...devices
161-
.filter((device) => device.kind === "videoinput")
162-
.map((device) => ({
163-
deviceId: device.deviceId,
164-
label: device.label || `Camera ${device.deviceId.slice(0, 5)}...`,
165-
}))
161+
.filter((device) => device.kind === "videoinput")
162+
.map((device) => ({
163+
deviceId: device.deviceId,
164+
label: device.label || `Camera ${device.deviceId.slice(0, 5)}...`,
165+
}))
166166
];
167167

168168
setVideoDevices(videoDevices);
169169
// Set default to first available camera if no selection yet
170-
if (!selectedDevice && videoDevices.length > 1) {
171-
setSelectedDevice(videoDevices[1].deviceId); // Index 1 because 0 is "No Video"
170+
if (selectedVideoDevice == "none" && videoDevices.length > 1) {
171+
setSelectedVideoDevice(videoDevices[1].deviceId); // Index 1 because 0 is "No Video"
172172
}
173-
} catch (err) {
174-
console.error("Failed to get video devices");
175-
// If we can't access video devices, still provide the None option
176-
const videoDevices = [{ deviceId: "none", label: "No Video" }];
177-
setVideoDevices(videoDevices);
178-
setSelectedDevice("none");
173+
} catch (err){
174+
console.log(`Failed to get video devices: ${err}`);
179175
}
180-
}, [selectedDevice]);
176+
}, []);
181177

182178
const getAudioDevices = useCallback(async () => {
183179
try {
180+
await navigator.mediaDevices.getUserMedia({ audio: true });
184181
const devices = await navigator.mediaDevices.enumerateDevices();
185182
const audioDevices = [
186183
{ deviceId: "none", label: "No Audio" },
@@ -194,17 +191,13 @@ function ConfigForm({ config, onSubmit }: ConfigFormProps) {
194191

195192
setAudioDevices(audioDevices);
196193
// Set default to first available microphone if no selection yet
197-
if (!selectedAudioDevice && audioDevices.length > 1) {
198-
setSelectedAudioDevice(audioDevices[0].deviceId); // Default to "No Audio" for now
194+
if (selectedAudioDevice == "none" && audioDevices.length > 1) {
195+
setSelectedAudioDevice(audioDevices[1].deviceId); // Index 1 because 0 is "No Audio"
199196
}
200197
} catch (err) {
201-
console.error("Failed to get audio devices");
202-
// If we can't access audio devices, still provide the None option
203-
const audioDevices = [{ deviceId: "none", label: "No Audio" }];
204-
setAudioDevices(audioDevices);
205-
setSelectedAudioDevice("none");
198+
console.log(`Failed to get audio devices: ${err}`);
206199
}
207-
}, [selectedAudioDevice]);
200+
}, []);
208201

209202
// Handle device change events.
210203
useEffect(() => {
@@ -232,9 +225,9 @@ function ConfigForm({ config, onSubmit }: ConfigFormProps) {
232225
? values.streamUrl.replace(/\/+$/, "")
233226
: values.streamUrl,
234227
prompts: prompts,
235-
selectedDeviceId: selectedDevice,
236-
selectedAudioDeviceId: selectedAudioDevice,
237-
});
228+
selectedVideoDeviceId: selectedVideoDevice || "none",
229+
selectedAudioDeviceId: selectedAudioDevice || "none",
230+
});
238231
};
239232

240233
const handlePromptsChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -260,11 +253,18 @@ function ConfigForm({ config, onSubmit }: ConfigFormProps) {
260253
* @param deviceId
261254
*/
262255
const handleCameraSelect = (deviceId: string) => {
263-
if (deviceId !== selectedDevice) {
264-
setSelectedDevice(deviceId);
256+
if (deviceId !== selectedVideoDevice) {
257+
setSelectedVideoDevice(deviceId);
258+
}
259+
};
260+
261+
const handleMicrophoneSelect = (deviceId: string) => {
262+
if (deviceId !== selectedAudioDevice) {
263+
setSelectedAudioDevice(deviceId);
265264
}
266265
};
267266

267+
268268
return (
269269
<Form {...form}>
270270
<form onSubmit={form.handleSubmit(handleSubmit)} autoComplete="off">
@@ -300,11 +300,11 @@ function ConfigForm({ config, onSubmit }: ConfigFormProps) {
300300
<Label>Camera</Label>
301301
<Select
302302
required={true}
303-
value={selectedDevice}
303+
value={selectedVideoDevice}
304304
onValueChange={handleCameraSelect}
305305
>
306306
<Select.Trigger className="w-full mt-2">
307-
{selectedDevice ? (videoDevices.find((d) => d.deviceId === selectedDevice)?.label || "None") : "None"}
307+
{selectedVideoDevice ? (videoDevices.find((d) => d.deviceId === selectedVideoDevice)?.label || "None") : "None"}
308308
</Select.Trigger>
309309
<Select.Content>
310310
{videoDevices.length === 0 ? (
@@ -324,28 +324,37 @@ function ConfigForm({ config, onSubmit }: ConfigFormProps) {
324324

325325
<div className="mt-4 mb-4">
326326
<Label>Microphone</Label>
327-
<Select value={selectedAudioDevice} onValueChange={setSelectedAudioDevice}>
327+
<Select value={selectedAudioDevice} onValueChange={handleMicrophoneSelect}>
328328
<Select.Trigger className="w-full mt-2">
329329
{selectedAudioDevice ? (audioDevices.find((d) => d.deviceId === selectedAudioDevice)?.label || "None") : "None"}
330330
</Select.Trigger>
331331
<Select.Content>
332-
{audioDevices.map((device) => (
333-
<Select.Option key={device.deviceId} value={device.deviceId}>
334-
{device.label}
332+
{audioDevices.length === 0 ? (
333+
<Select.Option disabled value="no-devices">
334+
No audio devices found
335335
</Select.Option>
336-
))}
336+
) : (
337+
audioDevices
338+
.filter((device) => device.deviceId !== undefined && device.deviceId != "")
339+
.map((device) => (
340+
<Select.Option key={device.deviceId} value={device.deviceId}>
341+
{device.label}
342+
</Select.Option>
343+
))
344+
)}
337345
</Select.Content>
338346
</Select>
339347
</div>
340348

341349
<div className="mt-4 mb-4 grid max-w-sm items-center gap-3">
342350
<Label>Comfy Workflows</Label>
343351
<Input
344-
id="video-workflow"
352+
id="workflow"
345353
type="file"
346354
accept=".json"
347355
multiple
348356
onChange={handlePromptsChange}
357+
required={true}
349358
/>
350359
</div>
351360

0 commit comments

Comments
 (0)