use useDevices hook
This commit is contained in:
parent
ce280e2084
commit
4c2caf14fe
|
|
@ -38,9 +38,6 @@ This demo puts to work the following [shared libraries](../shared):
|
||||||
**[MediaDeviceProvider.js](../shared/contexts/MediaDeviceProvider.js)**
|
**[MediaDeviceProvider.js](../shared/contexts/MediaDeviceProvider.js)**
|
||||||
Convenience context that provides an interface to media devices throughout app
|
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)**
|
**[CallProvider.js](../shared/contexts/CallProvider.js)**
|
||||||
Primary call context that manages Daily call state, participant state and call object interaction
|
Primary call context that manages Daily call state, participant state and call object interaction
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,6 @@ This demo puts to work the following [shared libraries](../shared):
|
||||||
**[MediaDeviceProvider.js](../shared/contexts/MediaDeviceProvider.js)**
|
**[MediaDeviceProvider.js](../shared/contexts/MediaDeviceProvider.js)**
|
||||||
Convenience context that provides an interface to media devices throughout app
|
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)**
|
**[CallProvider.js](../shared/contexts/CallProvider.js)**
|
||||||
Primary call context that manages Daily call state, participant state and call object interaction
|
Primary call context that manages Daily call state, participant state and call object interaction
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,15 @@ import MuteButton from '@custom/shared/components/MuteButton';
|
||||||
import Tile from '@custom/shared/components/Tile';
|
import Tile from '@custom/shared/components/Tile';
|
||||||
import { ACCESS_STATE_LOBBY } from '@custom/shared/constants';
|
import { ACCESS_STATE_LOBBY } from '@custom/shared/constants';
|
||||||
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
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 {
|
import {
|
||||||
DEVICE_STATE_BLOCKED,
|
DEVICE_STATE_BLOCKED,
|
||||||
DEVICE_STATE_NOT_FOUND,
|
DEVICE_STATE_NOT_FOUND,
|
||||||
DEVICE_STATE_IN_USE,
|
DEVICE_STATE_IN_USE,
|
||||||
DEVICE_STATE_PENDING,
|
DEVICE_STATE_PENDING,
|
||||||
DEVICE_STATE_LOADING,
|
useMediaDevices,
|
||||||
DEVICE_STATE_GRANTED,
|
} from '@custom/shared/contexts/MediaDeviceProvider';
|
||||||
} from '@custom/shared/contexts/useDevices';
|
import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
|
||||||
|
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||||
import IconSettings from '@custom/shared/icons/settings-sm.svg';
|
import IconSettings from '@custom/shared/icons/settings-sm.svg';
|
||||||
|
|
||||||
import { useDeepCompareMemo } from 'use-deep-compare';
|
import { useDeepCompareMemo } from 'use-deep-compare';
|
||||||
|
|
@ -33,7 +31,8 @@ export const HairCheck = () => {
|
||||||
const { callObject } = useCallState();
|
const { callObject } = useCallState();
|
||||||
const { localParticipant } = useParticipants();
|
const { localParticipant } = useParticipants();
|
||||||
const {
|
const {
|
||||||
deviceState,
|
camState,
|
||||||
|
micState,
|
||||||
camError,
|
camError,
|
||||||
micError,
|
micError,
|
||||||
isCamMuted,
|
isCamMuted,
|
||||||
|
|
@ -100,21 +99,14 @@ export const HairCheck = () => {
|
||||||
[localParticipant]
|
[localParticipant]
|
||||||
);
|
);
|
||||||
|
|
||||||
const isLoading = useMemo(() => deviceState === DEVICE_STATE_LOADING, [
|
const isLoading = useMemo(() => camState === DEVICE_STATE_PENDING || micState === DEVICE_STATE_PENDING, [
|
||||||
deviceState,
|
camState, micState,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const hasError = useMemo(() => {
|
const hasError = useMemo(() => camError || micError, [camError, micError]);
|
||||||
return !(!deviceState ||
|
|
||||||
[
|
|
||||||
DEVICE_STATE_LOADING,
|
|
||||||
DEVICE_STATE_PENDING,
|
|
||||||
DEVICE_STATE_GRANTED,
|
|
||||||
].includes(deviceState));
|
|
||||||
}, [deviceState]);
|
|
||||||
|
|
||||||
const camErrorVerbose = useMemo(() => {
|
const camErrorVerbose = useMemo(() => {
|
||||||
switch (camError) {
|
switch (camState) {
|
||||||
case DEVICE_STATE_BLOCKED:
|
case DEVICE_STATE_BLOCKED:
|
||||||
return 'Camera blocked by user';
|
return 'Camera blocked by user';
|
||||||
case DEVICE_STATE_NOT_FOUND:
|
case DEVICE_STATE_NOT_FOUND:
|
||||||
|
|
@ -124,7 +116,20 @@ export const HairCheck = () => {
|
||||||
default:
|
default:
|
||||||
return 'unknown';
|
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(() => {
|
const showWaitingMessage = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -206,14 +211,14 @@ export const HairCheck = () => {
|
||||||
<div className="overlay-message">{camErrorVerbose}</div>
|
<div className="overlay-message">{camErrorVerbose}</div>
|
||||||
)}
|
)}
|
||||||
{micError && (
|
{micError && (
|
||||||
<div className="overlay-message">{micError}</div>
|
<div className="overlay-message">{micErrorVerbose}</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mute-buttons">
|
<div className="mute-buttons">
|
||||||
<MuteButton isMuted={isCamMuted} disabled={!!camError} />
|
<MuteButton isMuted={isCamMuted} disabled={camError} />
|
||||||
<MuteButton mic isMuted={isMicMuted} disabled={!!micError} />
|
<MuteButton mic isMuted={isMicMuted} disabled={micError} />
|
||||||
</div>
|
</div>
|
||||||
{tileMemo}
|
{tileMemo}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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 { NETWORK_ASIDE } from '@custom/shared/components/Aside/NetworkAside';
|
||||||
import { PEOPLE_ASIDE } from '@custom/shared/components/Aside/PeopleAside';
|
import { PEOPLE_ASIDE } from '@custom/shared/components/Aside/PeopleAside';
|
||||||
import Button from '@custom/shared/components/Button';
|
import Button from '@custom/shared/components/Button';
|
||||||
import { DEVICE_MODAL } from '@custom/shared/components/DeviceSelectModal';
|
import { DEVICE_MODAL } from '@custom/shared/components/DeviceSelectModal';
|
||||||
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
||||||
import { useMediaDevices } from '@custom/shared/contexts/MediaDeviceProvider';
|
|
||||||
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||||
import { useResponsive } from '@custom/shared/hooks/useResponsive';
|
import { useResponsive } from '@custom/shared/hooks/useResponsive';
|
||||||
import { ReactComponent as IconCameraOff } from '@custom/shared/icons/camera-off-md.svg';
|
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 IconNetwork } from '@custom/shared/icons/network-md.svg';
|
||||||
import { ReactComponent as IconPeople } from '@custom/shared/icons/people-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 { ReactComponent as IconSettings } from '@custom/shared/icons/settings-md.svg';
|
||||||
|
import { useLocalParticipant, useDevices } from '@daily-co/daily-react-hooks';
|
||||||
import { Tray, TrayButton } from './Tray';
|
import { Tray, TrayButton } from './Tray';
|
||||||
|
|
||||||
export const BasicTray = () => {
|
export const BasicTray = () => {
|
||||||
|
|
@ -24,7 +24,18 @@ export const BasicTray = () => {
|
||||||
const [showMore, setShowMore] = useState(false);
|
const [showMore, setShowMore] = useState(false);
|
||||||
const { callObject, leave } = useCallState();
|
const { callObject, leave } = useCallState();
|
||||||
const { customTrayComponent, openModal, toggleAside } = useUIState();
|
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) => {
|
const toggleCamera = (newState) => {
|
||||||
if (!callObject) return false;
|
if (!callObject) return false;
|
||||||
|
|
|
||||||
|
|
@ -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 PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useCallState } from './CallProvider';
|
export const DEVICE_STATE_LOADING = 'loading';
|
||||||
import { useParticipants } from './ParticipantsProvider';
|
export const DEVICE_STATE_PENDING = 'pending';
|
||||||
import { useDevices } from './useDevices';
|
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 MediaDeviceContext = createContext();
|
||||||
|
|
||||||
export const MediaDeviceProvider = ({ children }) => {
|
export const MediaDeviceProvider = ({ children }) => {
|
||||||
const { callObject } = useCallState();
|
|
||||||
const { localParticipant } = useParticipants();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
camError,
|
hasCamError,
|
||||||
cams,
|
cameras,
|
||||||
currentCam,
|
camState,
|
||||||
currentMic,
|
setCamera,
|
||||||
currentSpeaker,
|
hasMicError,
|
||||||
deviceState,
|
microphones,
|
||||||
micError,
|
micState,
|
||||||
mics,
|
setMicrophone,
|
||||||
refreshDevices,
|
|
||||||
setCurrentCam,
|
|
||||||
setCurrentMic,
|
|
||||||
setCurrentSpeaker,
|
|
||||||
speakers,
|
speakers,
|
||||||
} = useDevices(callObject);
|
setSpeaker,
|
||||||
|
refreshDevices,
|
||||||
|
} = useDevices();
|
||||||
|
|
||||||
const selectCamera = useCallback(
|
const localParticipant = useLocalParticipant();
|
||||||
async (newCam) => {
|
|
||||||
if (!callObject || newCam.deviceId === currentCam?.deviceId) return;
|
|
||||||
const { camera } = await callObject.setInputDevicesAsync({
|
|
||||||
videoDeviceId: newCam.deviceId,
|
|
||||||
});
|
|
||||||
setCurrentCam(camera);
|
|
||||||
},
|
|
||||||
[callObject, currentCam, setCurrentCam]
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectMic = useCallback(
|
const isCamMuted = useMemo(() => {
|
||||||
async (newMic) => {
|
const videoState = localParticipant?.tracks?.video?.state;
|
||||||
if (!callObject || newMic.deviceId === currentMic?.deviceId) return;
|
return videoState === DEVICE_STATE_OFF || videoState === DEVICE_STATE_BLOCKED || hasCamError;
|
||||||
const { mic } = await callObject.setInputDevicesAsync({
|
}, [hasCamError, localParticipant?.tracks?.video?.state]);
|
||||||
audioDeviceId: newMic.deviceId,
|
|
||||||
});
|
|
||||||
setCurrentMic(mic);
|
|
||||||
},
|
|
||||||
[callObject, currentMic, setCurrentMic]
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectSpeaker = useCallback(
|
const isMicMuted = useMemo(() => {
|
||||||
(newSpeaker) => {
|
const audioState = localParticipant?.tracks?.audio?.state;
|
||||||
if (!callObject || newSpeaker.deviceId === currentSpeaker?.deviceId) return;
|
return audioState === DEVICE_STATE_OFF || audioState === DEVICE_STATE_BLOCKED || hasMicError;
|
||||||
callObject.setOutputDevice({
|
}, [hasMicError, localParticipant?.tracks?.audio?.state]);
|
||||||
outputDeviceId: newSpeaker.deviceId,
|
|
||||||
});
|
|
||||||
setCurrentSpeaker(newSpeaker);
|
|
||||||
},
|
|
||||||
[callObject, currentSpeaker, setCurrentSpeaker]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MediaDeviceContext.Provider
|
<MediaDeviceContext.Provider
|
||||||
value={{
|
value={{
|
||||||
camError,
|
isCamMuted,
|
||||||
cams,
|
isMicMuted,
|
||||||
currentCam,
|
camError: hasCamError,
|
||||||
currentMic,
|
cams: cameras,
|
||||||
currentSpeaker,
|
camState,
|
||||||
deviceState,
|
micError: hasMicError,
|
||||||
isCamMuted: localParticipant.isCamMuted,
|
mics: microphones,
|
||||||
isMicMuted: localParticipant.isMicMuted,
|
micState,
|
||||||
micError,
|
|
||||||
mics,
|
|
||||||
refreshDevices,
|
refreshDevices,
|
||||||
setCurrentCam: selectCamera,
|
setCurrentCam: setCamera,
|
||||||
setCurrentMic: selectMic,
|
setCurrentMic: setMicrophone,
|
||||||
setCurrentSpeaker: selectSpeaker,
|
setCurrentSpeaker: setSpeaker,
|
||||||
speakers,
|
speakers,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
Loading…
Reference in New Issue