Integrations
elements
lifecycle
browser
- useAudio
- useBattery
- useBluetooth
- useBreakpoints
- useBroadcastChannel
- useBrowserLocation
- useClipboard
- useCopy
- useCssVar
- useDeviceList
- useDisplayMedia
- useDocumentEvent
- useDocumentTitle
- useDocumentVisibility
- useEventListener
- useEventSource
- useEyeDropper
- useFavicon
- useFileSystemAccess
- useFps
- useFullscreen
- useGamepad
- useGeolocation
- useMeasure
- useMediaControls
- useMediaQuery
- useMediaStream
- useMemory
- useNetwork
- useObjectUrl
- useOnline
- useOtpCredential
- usePermission
- usePictureInPicture
- usePointerLock
- usePostMessage
- useRaf
- useShare
- useSpeechRecognition
- useSpeechSynthesis
- useSticky
- useVibrate
- useVirtualKeyboard
- useWakeLock
- useWebSocket
utilities
state
- useBoolean
- useControllableState
- useCookie
- useCookies
- useCounter
- useCycleList
- useDefault
- useDisclosure
- useField
- useHash
- useList
- useLocalStorage
- useMap
- useMask
- useMergedRef
- useObject
- useOffsetPagination
- useQueue
- useRafState
- useRefState
- useSessionStorage
- useSet
- useStateHistory
- useStep
- useStorage
- useToggle
- useUrlSearchParam
- useUrlSearchParams
- useValidatedState
- useWizard
user
sensors
- useDeviceMotion
- useDeviceOrientation
- useHotkeys
- useIdle
- useInfiniteScroll
- useIntersectionObserver
- useKeyboard
- useKeyPress
- useKeysPressed
- useMouse
- useMutationObserver
- useOrientation
- usePageLeave
- useParallax
- usePerformanceObserver
- useResizeObserver
- useScroll
- useScrollIntoView
- useScrollTo
- useSwipe
- useTextSelection
- useVisibility
- useWindowEvent
- useWindowFocus
- useWindowScroll
- useWindowSize
API not supported, make sure to check for compatibility with different browsers when using this API
import { useClickOutside, useDeviceList, useDisclosure } from '@siberiacancode/reactuse';
import { CameraIcon, CheckIcon, MicIcon } from 'lucide-react';
import { useState } from 'react';
const Demo = () => {
const microphoneMenu = useDisclosure();
const cameraMenu = useDisclosure();
const microphoneRef = useClickOutside<HTMLDivElement>(() => microphoneMenu.close());
const cameraRef = useClickOutside<HTMLDivElement>(() => cameraMenu.close());
const [selectedDeviceIds, setSelectedDeviceIds] = useState<{
audio?: string;
video?: string;
}>({});
const deviceList = useDeviceList((list) => {
setSelectedDeviceIds({
audio: list.find((device) => device.kind === 'audioinput')?.deviceId,
video: list.find((device) => device.kind === 'videoinput')?.deviceId
});
});
const onMicrophoneOpen = () => microphoneMenu.toggle();
const onCameraOpen = () => cameraMenu.toggle();
const onDeviceSelect = (device: MediaDeviceInfo, menu: 'audio' | 'video') => {
setSelectedDeviceIds((current) => ({ ...current, [menu]: device.deviceId }));
if (menu === 'audio') {
microphoneMenu.close();
return;
}
cameraMenu.close();
};
if (!deviceList.supported) {
return (
<p>
API not supported, make sure to check for compatibility with different browsers when using
this{' '}
<a
href='https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices'
rel='noreferrer'
target='_blank'
>
API
</a>
</p>
);
}
return (
<section className='flex w-full max-w-sm justify-center p-8'>
<div className='flex items-center gap-3'>
<div ref={microphoneRef} className='relative'>
<button
aria-expanded={microphoneMenu.opened}
aria-label='Select microphone'
className='rounded-full!'
data-size='icon'
data-variant='outline'
disabled={!deviceList.audioInputs.length}
type='button'
onClick={onMicrophoneOpen}
>
<MicIcon className='text-foreground size-5' />
</button>
{microphoneMenu.opened && (
<div
className='absolute top-full left-1/2 z-10 mt-3 w-64 -translate-x-1/2'
data-slot='dropdown-menu-content'
>
<div className='text-muted-foreground px-2 py-1.5 text-xs font-medium'>
Microphones
</div>
{!deviceList.audioInputs.length && (
<div data-slot='dropdown-menu-item'>No microphones found</div>
)}
{deviceList.audioInputs.map((device, index) => {
const selected = device.deviceId === selectedDeviceIds.audio;
return (
<div
key={device.deviceId}
data-slot='dropdown-menu-item'
onClick={() => onDeviceSelect(device, 'audio')}
>
<span className='truncate'>{device.label || `Microphone ${index + 1}`}</span>
{selected && <CheckIcon className='size-4 shrink-0' />}
</div>
);
})}
</div>
)}
</div>
<div ref={cameraRef} className='relative'>
<button
aria-expanded={cameraMenu.opened}
aria-label='Select camera'
className='rounded-full!'
data-size='icon'
data-variant='outline'
disabled={!deviceList.videoInputs.length}
type='button'
onClick={onCameraOpen}
>
<CameraIcon className='text-foreground size-5' />
</button>
{cameraMenu.opened && (
<div
className='absolute top-full left-1/2 z-10 mt-3 w-64 -translate-x-1/2'
data-slot='dropdown-menu-content'
>
<div className='text-muted-foreground px-2 py-1.5 text-xs font-medium'>Cameras</div>
{!deviceList.videoInputs.length && (
<div data-slot='dropdown-menu-item'>No cameras found</div>
)}
{deviceList.videoInputs.map((device, index) => {
const selected = device.deviceId === selectedDeviceIds.video;
return (
<div
key={device.deviceId}
data-slot='dropdown-menu-item'
onClick={() => onDeviceSelect(device, 'video')}
>
<span className='truncate'>{device.label || `Camera ${index + 1}`}</span>
{selected && <CheckIcon className='size-4 shrink-0' />}
</div>
);
})}
</div>
)}
</div>
</div>
</section>
);
};
export default Demo;
This hook uses navigator.mediaDevices.enumerateDevices browser api to provide enhanced functionality. Make sure to check for compatibility with different browsers when using this api
Installation
pnpm add @siberiacancode/reactuseUsage
const { devices, videoInputs, audioInputs, audioOutputs, update, trigger } = useDeviceList((devices) => console.log(devices));
// or
const { devices, videoInputs, audioInputs, audioOutputs, update, trigger } = useDeviceList({ immediately: true });Type Declarations
export type UseDeviceListCallback = (devices: MediaDeviceInfo[]) => void;
export interface UseDeviceListOptions {
/** The constraints passed to `getUserMedia` when requesting permissions */
constraints?: MediaStreamConstraints;
/** Whether the device list should be requested immediately */
immediately?: boolean;
/** The callback fired when the device list updates */
onUpdate?: UseDeviceListCallback;
}
export interface UseDeviceListReturn {
/** The available audio input devices (microphones) */
audioInputs: MediaDeviceInfo[];
/** The available audio output devices (speakers) */
audioOutputs: MediaDeviceInfo[];
/** All available media devices */
devices: MediaDeviceInfo[];
/** Whether `mediaDevices.enumerateDevices` is supported by the browser */
supported: boolean;
/** The available video input devices (cameras) */
videoInputs: MediaDeviceInfo[];
/** Request permissions for media devices and re-read the available device list */
trigger: () => Promise<MediaDeviceInfo[]>;
/** Re-read the list of available devices */
update: () => Promise<MediaDeviceInfo[]>;
}
export interface UseDeviceList {
(callback?: UseDeviceListCallback): UseDeviceListReturn;
(options?: UseDeviceListOptions): UseDeviceListReturn;
}API
Parameters
| Name | Type | Default | Note |
|---|---|---|---|
| callback | (devices: MediaDeviceInfo[]) => void | - | The callback fired when the device list updates |
Returns
UseDeviceListReturnParameters
| Name | Type | Default | Note |
|---|---|---|---|
| options.immediately | boolean | true | Whether the device list should be requested immediately |
| options.onUpdate | (devices: MediaDeviceInfo[]) => void | - | The callback fired when the device list updates |
Returns
UseDeviceListReturnContributors
ddebabin
Last updated on