diff --git a/custom/basic-call/README.md b/custom/basic-call/README.md
index 648f221..7d4b1e6 100644
--- a/custom/basic-call/README.md
+++ b/custom/basic-call/README.md
@@ -38,9 +38,6 @@ This demo puts to work the following [shared libraries](../shared):
**[MediaDeviceProvider.js](../shared/contexts/MediaDeviceProvider.js)**
Convenience context that provides an interface to media devices throughout app
-**[useDevices.js](../shared/contexts/useDevices.js)**
-Hook for managing the enumeration and status of client media devices)
-
**[CallProvider.js](../shared/contexts/CallProvider.js)**
Primary call context that manages Daily call state, participant state and call object interaction
diff --git a/custom/fitness-demo/README.md b/custom/fitness-demo/README.md
index 1bec2c5..833f476 100644
--- a/custom/fitness-demo/README.md
+++ b/custom/fitness-demo/README.md
@@ -38,9 +38,6 @@ This demo puts to work the following [shared libraries](../shared):
**[MediaDeviceProvider.js](../shared/contexts/MediaDeviceProvider.js)**
Convenience context that provides an interface to media devices throughout app
-**[useDevices.js](../shared/contexts/useDevices.js)**
-Hook for managing the enumeration and status of client media devices)
-
**[CallProvider.js](../shared/contexts/CallProvider.js)**
Primary call context that manages Daily call state, participant state and call object interaction
diff --git a/custom/shared/components/HairCheck/HairCheck.js b/custom/shared/components/HairCheck/HairCheck.js
index 4cf0659..3a0e378 100644
--- a/custom/shared/components/HairCheck/HairCheck.js
+++ b/custom/shared/components/HairCheck/HairCheck.js
@@ -7,17 +7,15 @@ import MuteButton from '@custom/shared/components/MuteButton';
import Tile from '@custom/shared/components/Tile';
import { ACCESS_STATE_LOBBY } from '@custom/shared/constants';
import { useCallState } from '@custom/shared/contexts/CallProvider';
-import { useMediaDevices } from '@custom/shared/contexts/MediaDeviceProvider';
-import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
-import { useUIState } from '@custom/shared/contexts/UIStateProvider';
import {
DEVICE_STATE_BLOCKED,
DEVICE_STATE_NOT_FOUND,
DEVICE_STATE_IN_USE,
DEVICE_STATE_PENDING,
- DEVICE_STATE_LOADING,
- DEVICE_STATE_GRANTED,
-} from '@custom/shared/contexts/useDevices';
+ useMediaDevices,
+} from '@custom/shared/contexts/MediaDeviceProvider';
+import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
+import { useUIState } from '@custom/shared/contexts/UIStateProvider';
import IconSettings from '@custom/shared/icons/settings-sm.svg';
import { useDeepCompareMemo } from 'use-deep-compare';
@@ -33,7 +31,8 @@ export const HairCheck = () => {
const { callObject } = useCallState();
const { localParticipant } = useParticipants();
const {
- deviceState,
+ camState,
+ micState,
camError,
micError,
isCamMuted,
@@ -100,21 +99,14 @@ export const HairCheck = () => {
[localParticipant]
);
- const isLoading = useMemo(() => deviceState === DEVICE_STATE_LOADING, [
- deviceState,
+ const isLoading = useMemo(() => camState === DEVICE_STATE_PENDING || micState === DEVICE_STATE_PENDING, [
+ camState, micState,
]);
- const hasError = useMemo(() => {
- return !(!deviceState ||
- [
- DEVICE_STATE_LOADING,
- DEVICE_STATE_PENDING,
- DEVICE_STATE_GRANTED,
- ].includes(deviceState));
- }, [deviceState]);
+ const hasError = useMemo(() => camError || micError, [camError, micError]);
const camErrorVerbose = useMemo(() => {
- switch (camError) {
+ switch (camState) {
case DEVICE_STATE_BLOCKED:
return 'Camera blocked by user';
case DEVICE_STATE_NOT_FOUND:
@@ -124,7 +116,20 @@ export const HairCheck = () => {
default:
return 'unknown';
}
- }, [camError]);
+ }, [camState]);
+
+ const micErrorVerbose = useMemo(() => {
+ switch (micState) {
+ case DEVICE_STATE_BLOCKED:
+ return 'Microphone blocked by user';
+ case DEVICE_STATE_NOT_FOUND:
+ return 'Microphone not found';
+ case DEVICE_STATE_IN_USE:
+ return 'Microphone in use';
+ default:
+ return 'unknown';
+ }
+ }, [micState]);
const showWaitingMessage = useMemo(() => {
return (
@@ -206,14 +211,14 @@ export const HairCheck = () => {
{camErrorVerbose}
)}
{micError && (
- {micError}
+ {micErrorVerbose}
)}
>
)}
-
-
+
+
{tileMemo}
diff --git a/custom/shared/components/Tray/BasicTray.js b/custom/shared/components/Tray/BasicTray.js
index 4ad4b41..fa89a20 100644
--- a/custom/shared/components/Tray/BasicTray.js
+++ b/custom/shared/components/Tray/BasicTray.js
@@ -4,7 +4,6 @@ import { PEOPLE_ASIDE } from '@custom/shared/components/Aside/PeopleAside';
import Button from '@custom/shared/components/Button';
import { DEVICE_MODAL } from '@custom/shared/components/DeviceSelectModal';
import { useCallState } from '@custom/shared/contexts/CallProvider';
-import { useMediaDevices } from '@custom/shared/contexts/MediaDeviceProvider';
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
import { useResponsive } from '@custom/shared/hooks/useResponsive';
import { ReactComponent as IconCameraOff } from '@custom/shared/icons/camera-off-md.svg';
@@ -16,6 +15,7 @@ import { ReactComponent as IconMore } from '@custom/shared/icons/more-md.svg';
import { ReactComponent as IconNetwork } from '@custom/shared/icons/network-md.svg';
import { ReactComponent as IconPeople } from '@custom/shared/icons/people-md.svg';
import { ReactComponent as IconSettings } from '@custom/shared/icons/settings-md.svg';
+import { useMediaDevices } from '../../contexts/MediaDeviceProvider';
import { Tray, TrayButton } from './Tray';
export const BasicTray = () => {
@@ -24,7 +24,7 @@ export const BasicTray = () => {
const [showMore, setShowMore] = useState(false);
const { callObject, leave } = useCallState();
const { customTrayComponent, openModal, toggleAside } = useUIState();
- const { isCamMuted, isMicMuted } = useMediaDevices();
+ const { isMicMuted, isCamMuted } = useMediaDevices();
const toggleCamera = (newState) => {
if (!callObject) return false;
diff --git a/custom/shared/contexts/MediaDeviceProvider.js b/custom/shared/contexts/MediaDeviceProvider.js
index b0b4349..6230429 100644
--- a/custom/shared/contexts/MediaDeviceProvider.js
+++ b/custom/shared/contexts/MediaDeviceProvider.js
@@ -1,82 +1,64 @@
-import React, { createContext, useContext, useCallback } from 'react';
+import React, { createContext, useContext, useMemo } from 'react';
+import { useDaily, useDevices } from '@daily-co/daily-react-hooks';
import PropTypes from 'prop-types';
-import { useCallState } from './CallProvider';
-import { useParticipants } from './ParticipantsProvider';
-import { useDevices } from './useDevices';
+export const DEVICE_STATE_LOADING = 'loading';
+export const DEVICE_STATE_PENDING = 'pending';
+export const DEVICE_STATE_ERROR = 'error';
+export const DEVICE_STATE_GRANTED = 'granted';
+export const DEVICE_STATE_NOT_FOUND = 'not-found';
+export const DEVICE_STATE_NOT_SUPPORTED = 'not-supported';
+export const DEVICE_STATE_BLOCKED = 'blocked';
+export const DEVICE_STATE_IN_USE = 'in-use';
+export const DEVICE_STATE_OFF = 'off';
+export const DEVICE_STATE_PLAYABLE = 'playable';
+export const DEVICE_STATE_SENDABLE = 'sendable';
export const MediaDeviceContext = createContext();
export const MediaDeviceProvider = ({ children }) => {
- const { callObject } = useCallState();
- const { localParticipant } = useParticipants();
-
const {
- camError,
- cams,
- currentCam,
- currentMic,
- currentSpeaker,
- deviceState,
- micError,
- mics,
- refreshDevices,
- setCurrentCam,
- setCurrentMic,
- setCurrentSpeaker,
+ hasCamError,
+ cameras,
+ camState,
+ setCamera,
+ hasMicError,
+ microphones,
+ micState,
+ setMicrophone,
speakers,
- } = useDevices(callObject);
+ setSpeaker,
+ refreshDevices,
+ } = useDevices();
- const selectCamera = useCallback(
- async (newCam) => {
- if (!callObject || newCam.deviceId === currentCam?.deviceId) return;
- const { camera } = await callObject.setInputDevicesAsync({
- videoDeviceId: newCam.deviceId,
- });
- setCurrentCam(camera);
- },
- [callObject, currentCam, setCurrentCam]
- );
+ const daily = useDaily();
+ const localParticipant = daily?.participants().local;
- const selectMic = useCallback(
- async (newMic) => {
- if (!callObject || newMic.deviceId === currentMic?.deviceId) return;
- const { mic } = await callObject.setInputDevicesAsync({
- audioDeviceId: newMic.deviceId,
- });
- setCurrentMic(mic);
- },
- [callObject, currentMic, setCurrentMic]
- );
+ const isCamMuted = useMemo(() => {
+ const videoState = localParticipant?.tracks?.video?.state;
+ return videoState === DEVICE_STATE_OFF || videoState === DEVICE_STATE_BLOCKED || hasCamError;
+ }, [hasCamError, localParticipant?.tracks?.video?.state]);
- const selectSpeaker = useCallback(
- (newSpeaker) => {
- if (!callObject || newSpeaker.deviceId === currentSpeaker?.deviceId) return;
- callObject.setOutputDevice({
- outputDeviceId: newSpeaker.deviceId,
- });
- setCurrentSpeaker(newSpeaker);
- },
- [callObject, currentSpeaker, setCurrentSpeaker]
- );
+ const isMicMuted = useMemo(() => {
+ const audioState = localParticipant?.tracks?.audio?.state;
+ return audioState === DEVICE_STATE_OFF || audioState === DEVICE_STATE_BLOCKED || hasMicError;
+ }, [hasMicError, localParticipant?.tracks?.audio?.state]);
return (
diff --git a/custom/shared/contexts/useDevices.js b/custom/shared/contexts/useDevices.js
deleted file mode 100644
index ee4362a..0000000
--- a/custom/shared/contexts/useDevices.js
+++ /dev/null
@@ -1,262 +0,0 @@
-import { useState, useCallback, useEffect } from 'react';
-import { sortByKey } from '../lib/sortByKey';
-
-export const DEVICE_STATE_LOADING = 'loading';
-export const DEVICE_STATE_PENDING = 'pending';
-export const DEVICE_STATE_ERROR = 'error';
-export const DEVICE_STATE_GRANTED = 'granted';
-export const DEVICE_STATE_NOT_FOUND = 'not-found';
-export const DEVICE_STATE_NOT_SUPPORTED = 'not-supported';
-export const DEVICE_STATE_BLOCKED = 'blocked';
-export const DEVICE_STATE_IN_USE = 'in-use';
-export const DEVICE_STATE_OFF = 'off';
-export const DEVICE_STATE_PLAYABLE = 'playable';
-export const DEVICE_STATE_SENDABLE = 'sendable';
-
-export const useDevices = (callObject) => {
- const [deviceState, setDeviceState] = useState(DEVICE_STATE_LOADING);
- const [currentCam, setCurrentCam] = useState(null);
- const [currentMic, setCurrentMic] = useState(null);
- const [currentSpeaker, setCurrentSpeaker] = useState(null);
-
- const [cams, setCams] = useState([]);
- const [mics, setMics] = useState([]);
- const [speakers, setSpeakers] = useState([]);
-
- const [camError, setCamError] = useState(null);
- const [micError, setMicError] = useState(null);
-
- const updateDeviceState = useCallback(async () => {
- if (
- typeof navigator?.mediaDevices?.getUserMedia === 'undefined' ||
- typeof navigator?.mediaDevices?.enumerateDevices === 'undefined'
- ) {
- setDeviceState(DEVICE_STATE_NOT_SUPPORTED);
- return;
- }
-
- try {
- const { devices } = await callObject.enumerateDevices();
-
- const { camera, mic, speaker } = await callObject.getInputDevices();
-
- setCurrentCam(camera ?? null);
- setCurrentMic(mic ?? null);
- setCurrentSpeaker(speaker ?? null);
-
- const [defaultCam, ...videoDevices] = devices.filter(
- (d) => d.kind === 'videoinput' && d.deviceId !== ''
- );
- setCams(
- [
- defaultCam,
- ...videoDevices.sort((a, b) => sortByKey(a, b, 'label', false)),
- ].filter(Boolean)
- );
- const [defaultMic, ...micDevices] = devices.filter(
- (d) => d.kind === 'audioinput' && d.deviceId !== ''
- );
- setMics(
- [
- defaultMic,
- ...micDevices.sort((a, b) => sortByKey(a, b, 'label', false)),
- ].filter(Boolean)
- );
- const [defaultSpeaker, ...speakerDevices] = devices.filter(
- (d) => d.kind === 'audiooutput' && d.deviceId !== ''
- );
- setSpeakers(
- [
- defaultSpeaker,
- ...speakerDevices.sort((a, b) => sortByKey(a, b, 'label', false)),
- ].filter(Boolean)
- );
-
- console.log(`Current cam: ${camera.label}`);
- console.log(`Current mic: ${mic.label}`);
- console.log(`Current speakers: ${speaker.label}`);
- } catch (e) {
- setDeviceState(DEVICE_STATE_NOT_SUPPORTED);
- }
- }, [callObject]);
-
- const updateDeviceErrors = useCallback(() => {
- if (!callObject) return;
- const { tracks } = callObject.participants().local;
-
- if (tracks.video?.blocked?.byPermissions) {
- setCamError(DEVICE_STATE_BLOCKED);
- } else if (tracks.video?.blocked?.byDeviceMissing) {
- setCamError(DEVICE_STATE_NOT_FOUND);
- } else if (tracks.video?.blocked?.byDeviceInUse) {
- setCamError(DEVICE_STATE_IN_USE);
- }
-
- if (
- [
- DEVICE_STATE_LOADING,
- DEVICE_STATE_OFF,
- DEVICE_STATE_PLAYABLE,
- DEVICE_STATE_SENDABLE,
- ].includes(tracks.video.state)
- ) {
- setCamError(null);
- }
-
- if (tracks.audio?.blocked?.byPermissions) {
- setMicError(DEVICE_STATE_BLOCKED);
- } else if (tracks.audio?.blocked?.byDeviceMissing) {
- setMicError(DEVICE_STATE_NOT_FOUND);
- } else if (tracks.audio?.blocked?.byDeviceInUse) {
- setMicError(DEVICE_STATE_IN_USE);
- }
-
- if (
- [
- DEVICE_STATE_LOADING,
- DEVICE_STATE_OFF,
- DEVICE_STATE_PLAYABLE,
- DEVICE_STATE_SENDABLE,
- ].includes(tracks.audio.state)
- ) {
- setMicError(null);
- }
- }, [callObject]);
-
- const handleParticipantUpdated = useCallback(
- ({ participant }) => {
- if (!callObject || deviceState === 'not-supported' || !participant.local) return;
-
- switch (participant?.tracks.video.state) {
- case DEVICE_STATE_BLOCKED:
- setDeviceState(DEVICE_STATE_ERROR);
- break;
- case DEVICE_STATE_OFF:
- case DEVICE_STATE_PLAYABLE:
- updateDeviceState();
- setDeviceState(DEVICE_STATE_GRANTED);
- break;
- }
-
- updateDeviceErrors();
- },
- [callObject, deviceState, updateDeviceErrors, updateDeviceState]
- );
-
- useEffect(() => {
- if (!callObject) return;
-
- /**
- If the user is slow to allow access, we'll update the device state
- so our app can show a prompt requesting access
- */
- let pendingAccessTimeout;
-
- const handleJoiningMeeting = () => {
- pendingAccessTimeout = setTimeout(() => {
- setDeviceState(DEVICE_STATE_PENDING);
- }, 2000);
- };
-
- const handleJoinedMeeting = () => {
- clearTimeout(pendingAccessTimeout);
- // Note: setOutputDevice() is not honored before join() so we must enumerate again
- updateDeviceState();
- };
-
- updateDeviceState();
- callObject.on('joining-meeting', handleJoiningMeeting);
- callObject.on('joined-meeting', handleJoinedMeeting);
- callObject.on('participant-updated', handleParticipantUpdated);
- return () => {
- clearTimeout(pendingAccessTimeout);
- callObject.off('joining-meeting', handleJoiningMeeting);
- callObject.off('joined-meeting', handleJoinedMeeting);
- callObject.off('participant-updated', handleParticipantUpdated);
- };
- }, [callObject, handleParticipantUpdated, updateDeviceState]);
-
- useEffect(() => {
- if (!callObject) return;
-
- console.log('💻 Device provider events bound');
-
- const handleCameraError = ({
- errorMsg: { errorMsg, audioOk, videoOk },
- error,
- }) => {
- switch (error?.type) {
- case 'cam-in-use':
- setDeviceState(DEVICE_STATE_ERROR);
- setCamError(DEVICE_STATE_IN_USE);
- break;
- case 'mic-in-use':
- setDeviceState(DEVICE_STATE_ERROR);
- setMicError(DEVICE_STATE_IN_USE);
- break;
- case 'cam-mic-in-use':
- setDeviceState(DEVICE_STATE_ERROR);
- setCamError(DEVICE_STATE_IN_USE);
- setMicError(DEVICE_STATE_IN_USE);
- break;
- default:
- switch (errorMsg) {
- case 'devices error':
- setDeviceState(DEVICE_STATE_ERROR);
- setCamError(videoOk ? null : DEVICE_STATE_NOT_FOUND);
- setMicError(audioOk ? null : DEVICE_STATE_NOT_FOUND);
- break;
- case 'not allowed':
- setDeviceState(DEVICE_STATE_ERROR);
- updateDeviceErrors();
- break;
- default:
- break;
- }
- break;
- }
- };
-
- const handleError = ({ errorMsg }) => {
- switch (errorMsg) {
- case 'not allowed':
- setDeviceState(DEVICE_STATE_ERROR);
- updateDeviceErrors();
- break;
- default:
- break;
- }
- };
-
- const handleStartedCamera = () => {
- updateDeviceErrors();
- };
-
- callObject.on('camera-error', handleCameraError);
- callObject.on('error', handleError);
- callObject.on('started-camera', handleStartedCamera);
- return () => {
- callObject.off('camera-error', handleCameraError);
- callObject.off('error', handleError);
- callObject.off('started-camera', handleStartedCamera);
- };
- }, [callObject, updateDeviceErrors]);
-
- return {
- camError,
- cams,
- currentCam,
- currentMic,
- currentSpeaker,
- deviceState,
- micError,
- mics,
- refreshDevices: updateDeviceState,
- setCurrentCam,
- setCurrentMic,
- setCurrentSpeaker,
- speakers,
- };
-};
-
-export default useDevices;