diff --git a/packages/client/src/devices/BrowserPermission.ts b/packages/client/src/devices/BrowserPermission.ts index 299481202e..ce4a299970 100644 --- a/packages/client/src/devices/BrowserPermission.ts +++ b/packages/client/src/devices/BrowserPermission.ts @@ -22,7 +22,7 @@ export class BrowserPermission { this.ready = (async () => { const assumeGranted = (error?: unknown) => { - this.setState('granted'); + this.setState('prompt'); }; if (!canQueryPermissions()) { @@ -88,12 +88,14 @@ export class BrowserPermission { this.permission.constraints, ); disposeOfMediaStream(stream); + this.setState('granted'); return true; } catch (e) { if (e instanceof DOMException && e.name === 'NotAllowedError') { this.logger('info', 'Browser permission was not granted', { permission: this.permission, }); + this.setState('denied'); if (throwOnNotAllowed) { throw e; diff --git a/packages/client/src/devices/devices.ts b/packages/client/src/devices/devices.ts index 107d534ffe..2bbf19a93d 100644 --- a/packages/client/src/devices/devices.ts +++ b/packages/client/src/devices/devices.ts @@ -11,6 +11,7 @@ import { import { getLogger } from '../logger'; import { BrowserPermission } from './BrowserPermission'; import { lazy } from '../helpers/lazy'; +import { isFirefox } from '../helpers/browsers'; /** * Returns an Observable that emits the list of available devices @@ -32,7 +33,12 @@ const getDevices = (permission: BrowserPermission, kind: MediaDeviceKind) => { await permission.prompt({ throwOnNotAllowed: true }); devices = await navigator.mediaDevices.enumerateDevices(); } - return devices.filter((d) => d.kind === kind); + return devices.filter( + (device) => + device.kind === kind && + device.label !== '' && + device.deviceId !== 'default', + ); })(), ); }; @@ -125,7 +131,7 @@ export const getAudioDevices = lazy(() => { * if devices are added/removed the list is updated, and if the permission is revoked, * the observable errors. */ -export const getVideoDevices = () => { +export const getVideoDevices = lazy(() => { return merge( getDeviceChangeObserver(), getVideoBrowserPermission().asObservable(), @@ -134,7 +140,7 @@ export const getVideoDevices = () => { concatMap(() => getDevices(getVideoBrowserPermission(), 'videoinput')), shareReplay(1), ); -}; +}); /** * Prompts the user for a permission to use video devices (if not already granted @@ -142,7 +148,7 @@ export const getVideoDevices = () => { * if devices are added/removed the list is updated, and if the permission is revoked, * the observable errors. */ -export const getAudioOutputDevices = () => { +export const getAudioOutputDevices = lazy(() => { return merge( getDeviceChangeObserver(), getAudioBrowserPermission().asObservable(), @@ -151,10 +157,17 @@ export const getAudioOutputDevices = () => { concatMap(() => getDevices(getAudioBrowserPermission(), 'audiooutput')), shareReplay(1), ); -}; +}); const getStream = async (constraints: MediaStreamConstraints) => { - return await navigator.mediaDevices.getUserMedia(constraints); + const stream = await navigator.mediaDevices.getUserMedia(constraints); + if (isFirefox()) { + // When enumerating devices, Firefox will hide device labels unless there's been + // an active user media stream on the page. So we force device list updates after + // every successful getUserMedia call. + navigator.mediaDevices.dispatchEvent(new Event('devicechange')); + } + return stream; }; /** diff --git a/packages/react-sdk/src/components/DeviceSettings/DeviceSelector.tsx b/packages/react-sdk/src/components/DeviceSettings/DeviceSelector.tsx index 6c0074bcf5..50131061be 100644 --- a/packages/react-sdk/src/components/DeviceSettings/DeviceSelector.tsx +++ b/packages/react-sdk/src/components/DeviceSettings/DeviceSelector.tsx @@ -3,6 +3,7 @@ import { ChangeEventHandler, useCallback } from 'react'; import { DropDownSelect, DropDownSelectOption } from '../DropdownSelect'; import { useMenuContext } from '../Menu'; +import { useI18n } from '@stream-io/video-react-bindings'; type DeviceSelectorOptionProps = { id: string; @@ -57,26 +58,9 @@ const DeviceSelectorList = (props: { title?: string; onChange?: (deviceId: string) => void; }) => { - const { - devices = [], - selectedDeviceId: selectedDeviceFromProps, - title, - type, - onChange, - } = props; - + const { devices = [], selectedDeviceId, title, type, onChange } = props; const { close } = useMenuContext(); - - // sometimes the browser (Chrome) will report the system-default device - // with an id of 'default'. In case when it doesn't, we'll select the first - // available device. - let selectedDeviceId = selectedDeviceFromProps; - if ( - devices.length > 0 && - !devices.find((d) => d.deviceId === selectedDeviceId) - ) { - selectedDeviceId = devices[0].deviceId; - } + const { t } = useI18n(); return (
@@ -85,10 +69,10 @@ const DeviceSelectorList = (props: { {title}
)} - {!devices.length ? ( + {devices.length === 0 ? ( void; - visualType?: 'list' | 'dropdown'; icon: string; - placeholder?: string; }) => { - const { - devices = [], - selectedDeviceId: selectedDeviceFromProps, - title, - onChange, - icon, - } = props; - - // sometimes the browser (Chrome) will report the system-default device - // with an id of 'default'. In case when it doesn't, we'll select the first - // available device. - let selectedDeviceId = selectedDeviceFromProps; - if ( - devices.length > 0 && - !devices.find((d) => d.deviceId === selectedDeviceId) - ) { - selectedDeviceId = devices[0].deviceId; - } + const { devices = [], selectedDeviceId, title, onChange, icon } = props; + const { t } = useI18n(); const selectedIndex = devices.findIndex( (d) => d.deviceId === selectedDeviceId, @@ -164,11 +130,13 @@ const DeviceSelectorDropdown = (props: { - {devices.map((device) => { - return ( + {devices.length === 0 ? ( + + ) : ( + devices.map((device) => ( - ); - })} + )) + )} ); @@ -199,7 +167,5 @@ export const DeviceSelector = (props: { if (visualType === 'list') { return ; } - return ( - - ); + return ; }; diff --git a/packages/react-sdk/src/translations/en.json b/packages/react-sdk/src/translations/en.json index 05ba62a465..bf1176a60f 100644 --- a/packages/react-sdk/src/translations/en.json +++ b/packages/react-sdk/src/translations/en.json @@ -38,6 +38,7 @@ "Me": "Me", "Unknown": "Unknown", "Toggle device menu": "Toggle device menu", + "Default": "Default", "Call Recordings": "Call Recordings", "Refresh": "Refresh", "Check your browser video permissions": "Check your browser video permissions",