Skip to content

Commit 33b196b

Browse files
committed
impl: getUserMedia settings
1 parent 791ef98 commit 33b196b

File tree

7 files changed

+380
-21
lines changed

7 files changed

+380
-21
lines changed

js/examples/call/src/components/CallRoom.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ export function CallRoom({ session, onLeave }: CallRoomProps) {
5454
selectedVideoDeviceId,
5555
selectedAudioDeviceId,
5656
selectVideoDevice,
57-
selectAudioDevice
57+
selectAudioDevice,
58+
captureSettings,
59+
updateCaptureSettings,
60+
applyCaptureSettings
5861
} = useCallMedia(session)
5962

6063
const [room, setRoom] = useState<Room>(() => ({
@@ -201,6 +204,9 @@ export function CallRoom({ session, onLeave }: CallRoomProps) {
201204
selectedAudioEncoding={selectedAudioEncoding}
202205
onSelectAudioEncoding={selectAudioEncoding}
203206
audioEncoderError={audioEncoderError}
207+
captureSettings={captureSettings}
208+
onChangeCaptureSettings={updateCaptureSettings}
209+
onApplyCaptureSettings={applyCaptureSettings}
204210
/>
205211
</main>
206212

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import type { CaptureSettingsState } from '../types/captureConstraints'
2+
3+
interface GetUserMediaFormProps {
4+
settings: CaptureSettingsState
5+
onChange: (settings: Partial<CaptureSettingsState>) => void
6+
onApply: () => void
7+
cameraBusy: boolean
8+
microphoneBusy: boolean
9+
resolutionOptions: { id: string; label: string; width: number; height: number }[]
10+
}
11+
12+
export function GetUserMediaForm({
13+
settings,
14+
onChange,
15+
onApply,
16+
cameraBusy,
17+
microphoneBusy,
18+
resolutionOptions
19+
}: GetUserMediaFormProps) {
20+
const disabled = cameraBusy || microphoneBusy
21+
const matchedPreset = resolutionOptions.find((opt) => opt.width === settings.width && opt.height === settings.height)
22+
23+
return (
24+
<div className="space-y-3 rounded-md border border-white/10 bg-white/5 p-3">
25+
<div className="flex items-center justify-between">
26+
<div className="text-sm font-semibold text-white">getUserMedia settings</div>
27+
<button
28+
type="button"
29+
onClick={onApply}
30+
disabled={disabled}
31+
className={`rounded-md px-3 py-1 text-sm font-semibold transition ${
32+
disabled ? 'bg-white/10 text-white/60' : 'bg-blue-500 text-white hover:bg-blue-600'
33+
}`}
34+
>
35+
Apply & getUserMedia
36+
</button>
37+
</div>
38+
<p className="text-xs text-blue-100/80">
39+
Configure capture before starting. Resolution/codec follow the selectors above.
40+
</p>
41+
<div className="grid grid-cols-2 gap-3 text-sm text-blue-50">
42+
<label className="flex items-center gap-2">
43+
<input
44+
type="checkbox"
45+
checked={settings.videoEnabled}
46+
onChange={(e) => onChange({ videoEnabled: e.target.checked })}
47+
/>
48+
Enable camera
49+
</label>
50+
<label className="flex items-center gap-2">
51+
<input
52+
type="checkbox"
53+
checked={settings.audioEnabled}
54+
onChange={(e) => onChange({ audioEnabled: e.target.checked })}
55+
/>
56+
Enable microphone
57+
</label>
58+
<label className="col-span-2 flex items-center justify-between gap-2">
59+
<span>Resolution preset</span>
60+
<select
61+
value={matchedPreset ? matchedPreset.id : 'custom'}
62+
onChange={(e) => {
63+
const next = resolutionOptions.find((opt) => opt.id === e.target.value)
64+
if (next) {
65+
onChange({ width: next.width, height: next.height })
66+
}
67+
}}
68+
className="w-40 rounded-md bg-white/10 px-2 py-1 text-sm text-white outline-none ring-1 ring-white/10 focus:ring-blue-400"
69+
>
70+
{resolutionOptions.map((opt) => (
71+
<option key={opt.id} value={opt.id}>
72+
{opt.label}
73+
</option>
74+
))}
75+
{!matchedPreset && <option value="custom">{`${settings.width}x${settings.height} (custom)`}</option>}
76+
</select>
77+
</label>
78+
<div className="col-span-2 grid grid-cols-2 gap-3">
79+
<label className="flex items-center justify-between gap-2">
80+
<span>Width</span>
81+
<input
82+
type="number"
83+
min={160}
84+
max={7680}
85+
value={settings.width}
86+
onChange={(e) => onChange({ width: Number(e.target.value) || settings.width })}
87+
className="w-24 rounded-md bg-white/10 px-2 py-1 text-right text-white outline-none ring-1 ring-white/10 focus:ring-blue-400"
88+
/>
89+
</label>
90+
<label className="flex items-center justify-between gap-2">
91+
<span>Height</span>
92+
<input
93+
type="number"
94+
min={120}
95+
max={4320}
96+
value={settings.height}
97+
onChange={(e) => onChange({ height: Number(e.target.value) || settings.height })}
98+
className="w-24 rounded-md bg-white/10 px-2 py-1 text-right text-white outline-none ring-1 ring-white/10 focus:ring-blue-400"
99+
/>
100+
</label>
101+
</div>
102+
<label className="col-span-2 flex items-center justify-between gap-2">
103+
<span>Frame rate</span>
104+
<input
105+
type="number"
106+
min={1}
107+
max={120}
108+
value={settings.frameRate}
109+
onChange={(e) => onChange({ frameRate: Number(e.target.value) || settings.frameRate })}
110+
className="w-24 rounded-md bg-white/10 px-2 py-1 text-right text-white outline-none ring-1 ring-white/10 focus:ring-blue-400"
111+
/>
112+
</label>
113+
</div>
114+
<div className="grid grid-cols-1 gap-2 text-sm text-blue-50">
115+
<label className="flex items-center justify-between gap-2">
116+
<span>Echo cancellation</span>
117+
<input
118+
type="checkbox"
119+
checked={settings.echoCancellation}
120+
onChange={(e) => onChange({ echoCancellation: e.target.checked })}
121+
/>
122+
</label>
123+
<label className="flex items-center justify-between gap-2">
124+
<span>Noise suppression</span>
125+
<input
126+
type="checkbox"
127+
checked={settings.noiseSuppression}
128+
onChange={(e) => onChange({ noiseSuppression: e.target.checked })}
129+
/>
130+
</label>
131+
<label className="flex items-center justify-between gap-2">
132+
<span>Auto gain control</span>
133+
<input
134+
type="checkbox"
135+
checked={settings.autoGainControl}
136+
onChange={(e) => onChange({ autoGainControl: e.target.checked })}
137+
/>
138+
</label>
139+
</div>
140+
</div>
141+
)
142+
}

js/examples/call/src/components/MemberGrid.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { Mic, MicOff, Monitor, Settings2, Video, VideoOff } from 'lucide-react'
88
import { DeviceSelector } from './DeviceSelector'
99
import type { VideoEncodingSettings } from '../types/videoEncoding'
1010
import type { AudioEncodingSettings } from '../types/audioEncoding'
11+
import type { CaptureSettingsState } from '../types/captureConstraints'
12+
import { GetUserMediaForm } from './GetUserMediaForm'
1113

1214
interface MemberGridProps {
1315
localMember: LocalMember
@@ -52,6 +54,9 @@ interface MemberGridProps {
5254
selectedAudioEncoding: AudioEncodingSettings
5355
onSelectAudioEncoding: (settings: Partial<AudioEncodingSettings>) => void
5456
audioEncoderError?: string | null
57+
captureSettings: CaptureSettingsState
58+
onChangeCaptureSettings: (settings: Partial<CaptureSettingsState>) => void
59+
onApplyCaptureSettings: () => void
5560
}
5661

5762
export function MemberGrid({
@@ -88,7 +93,10 @@ export function MemberGrid({
8893
audioEncodingOptions,
8994
selectedAudioEncoding,
9095
onSelectAudioEncoding,
91-
audioEncoderError
96+
audioEncoderError,
97+
captureSettings,
98+
onChangeCaptureSettings,
99+
onApplyCaptureSettings
92100
}: MemberGridProps) {
93101
const [isDeviceModalOpen, setIsDeviceModalOpen] = useState(false)
94102
const [jitterModalTarget, setJitterModalTarget] = useState<string | null>(null)
@@ -166,6 +174,14 @@ export function MemberGrid({
166174
selectedDeviceId={selectedAudioDeviceId}
167175
onChange={onSelectAudioDevice}
168176
/>
177+
<GetUserMediaForm
178+
settings={captureSettings}
179+
onChange={onChangeCaptureSettings}
180+
onApply={onApplyCaptureSettings}
181+
cameraBusy={cameraBusy}
182+
microphoneBusy={microphoneBusy}
183+
resolutionOptions={videoEncodingOptions.resolutionOptions}
184+
/>
169185
<div className="space-y-2 rounded-md border border-white/10 bg-white/5 p-3">
170186
<div className="text-sm font-semibold text-white">Video</div>
171187
<EncodingSelector

0 commit comments

Comments
 (0)