From 4c2caf14fe2a6fd15f58f1dea3aff4913fd55301 Mon Sep 17 00:00:00 2001 From: harshithpabbati Date: Wed, 6 Apr 2022 10:53:50 +0530 Subject: [PATCH] use useDevices hook --- custom/basic-call/README.md | 3 - custom/fitness-demo/README.md | 3 - .../shared/components/HairCheck/HairCheck.js | 49 ++-- custom/shared/components/Tray/BasicTray.js | 17 +- custom/shared/contexts/MediaDeviceProvider.js | 107 +++---- custom/shared/contexts/useDevices.js | 262 ------------------ 6 files changed, 85 insertions(+), 356 deletions(-) delete mode 100644 custom/shared/contexts/useDevices.js 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..8bf3e77 100644 --- a/custom/shared/components/Tray/BasicTray.js +++ b/custom/shared/components/Tray/BasicTray.js @@ -1,10 +1,9 @@ -import React, { useRef, useState, useEffect } from 'react'; +import React, { useRef, useState, useEffect, useMemo } from 'react'; import { NETWORK_ASIDE } from '@custom/shared/components/Aside/NetworkAside'; 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 { useLocalParticipant, useDevices } from '@daily-co/daily-react-hooks'; import { Tray, TrayButton } from './Tray'; export const BasicTray = () => { @@ -24,7 +24,18 @@ export const BasicTray = () => { const [showMore, setShowMore] = useState(false); const { callObject, leave } = useCallState(); const { customTrayComponent, openModal, toggleAside } = useUIState(); - const { isCamMuted, isMicMuted } = useMediaDevices(); + const localParticipant = useLocalParticipant(); + const { hasCamError, hasMicError } = useDevices(); + + const isCamMuted = useMemo(() => { + const videoState = localParticipant?.tracks?.video?.state; + return videoState === 'off' || videoState === 'blocked' || hasCamError; + }, [hasCamError, localParticipant?.tracks?.video?.state]); + + const isMicMuted = useMemo(() => { + const audioState = localParticipant?.tracks?.audio?.state; + return audioState === 'off' || audioState === 'blocked' || hasMicError; + }, [hasMicError, localParticipant?.tracks?.audio?.state]); const toggleCamera = (newState) => { if (!callObject) return false; diff --git a/custom/shared/contexts/MediaDeviceProvider.js b/custom/shared/contexts/MediaDeviceProvider.js index b0b4349..afe5a82 100644 --- a/custom/shared/contexts/MediaDeviceProvider.js +++ b/custom/shared/contexts/MediaDeviceProvider.js @@ -1,82 +1,63 @@ -import React, { createContext, useContext, useCallback } from 'react'; +import React, { createContext, useContext, useMemo } from 'react'; +import { useDevices, useLocalParticipant } 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 localParticipant = useLocalParticipant(); - 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;