Get context providers into parity with prebuilt
This commit is contained in:
parent
838948bf93
commit
d72a200c96
|
|
@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
|
||||||
import { Audio } from '@custom/shared/components/Audio';
|
import { Audio } from '@custom/shared/components/Audio';
|
||||||
import { BasicTray } from '@custom/shared/components/Tray';
|
import { BasicTray } from '@custom/shared/components/Tray';
|
||||||
import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
|
import { useParticipants } from '@custom/shared/contexts/ParticipantsProvider';
|
||||||
import useJoinSound from '@custom/shared/hooks/useJoinSound';
|
import { useJoinSound } from '@custom/shared/hooks/useJoinSound';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { WaitingRoom } from './WaitingRoom';
|
import { WaitingRoom } from './WaitingRoom';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,15 @@ export const DeviceSelect = () => {
|
||||||
cams,
|
cams,
|
||||||
mics,
|
mics,
|
||||||
speakers,
|
speakers,
|
||||||
currentDevices,
|
currentCam,
|
||||||
setCamDevice,
|
setCurrentCam,
|
||||||
setMicDevice,
|
currentMic,
|
||||||
setSpeakersDevice,
|
setCurrentMic,
|
||||||
|
currentSpeaker,
|
||||||
|
setCurrentSpeaker,
|
||||||
} = useMediaDevices();
|
} = useMediaDevices();
|
||||||
|
|
||||||
if (!currentDevices) {
|
if (!currentCam && !currentMic && !currentSpeaker) {
|
||||||
return <div>Loading devices...</div>;
|
return <div>Loading devices...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -22,9 +24,9 @@ export const DeviceSelect = () => {
|
||||||
<>
|
<>
|
||||||
<Field label="Select camera:">
|
<Field label="Select camera:">
|
||||||
<SelectInput
|
<SelectInput
|
||||||
onChange={(e) => setCamDevice(cams[e.target.value])}
|
onChange={(e) => setCurrentCam(cams[e.target.value])}
|
||||||
value={cams.findIndex(
|
value={cams.findIndex(
|
||||||
(i) => i.deviceId === currentDevices.camera.deviceId
|
(i) => i.deviceId === currentCam.deviceId
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{cams.map(({ deviceId, label }, i) => (
|
{cams.map(({ deviceId, label }, i) => (
|
||||||
|
|
@ -37,9 +39,9 @@ export const DeviceSelect = () => {
|
||||||
|
|
||||||
<Field label="Select microphone:">
|
<Field label="Select microphone:">
|
||||||
<SelectInput
|
<SelectInput
|
||||||
onChange={(e) => setMicDevice(mics[e.target.value])}
|
onChange={(e) => setCurrentMic(mics[e.target.value])}
|
||||||
value={mics.findIndex(
|
value={mics.findIndex(
|
||||||
(i) => i.deviceId === currentDevices.mic.deviceId
|
(i) => i.deviceId === currentMic.deviceId
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{mics.map(({ deviceId, label }, i) => (
|
{mics.map(({ deviceId, label }, i) => (
|
||||||
|
|
@ -56,9 +58,9 @@ export const DeviceSelect = () => {
|
||||||
{speakers.length > 0 && (
|
{speakers.length > 0 && (
|
||||||
<Field label="Select speakers:">
|
<Field label="Select speakers:">
|
||||||
<SelectInput
|
<SelectInput
|
||||||
onChange={(e) => setSpeakersDevice(speakers[e.target.value])}
|
onChange={(e) => setCurrentSpeaker(speakers[e.target.value])}
|
||||||
value={speakers.findIndex(
|
value={speakers.findIndex(
|
||||||
(i) => i.deviceId === currentDevices.speaker.deviceId
|
(i) => i.deviceId === currentSpeaker.deviceId
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{speakers.map(({ deviceId, label }, i) => (
|
{speakers.map(({ deviceId, label }, i) => (
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,10 @@
|
||||||
import React, { useMemo, forwardRef, memo, useEffect, useState } from 'react';
|
import React, { useMemo, forwardRef, memo, useEffect } from 'react';
|
||||||
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
|
||||||
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
|
||||||
import { isScreenId } from '@custom/shared/contexts/participantsState';
|
|
||||||
import Bowser from 'bowser';
|
import Bowser from 'bowser';
|
||||||
import classNames from 'classnames';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { shallowEqualObjects } from 'shallow-equal';
|
import { shallowEqualObjects } from 'shallow-equal';
|
||||||
import { useDeepCompareMemo } from 'use-deep-compare';
|
|
||||||
|
|
||||||
export const Video = memo(
|
export const Video = memo(
|
||||||
forwardRef(({ fit = 'contain', participantId, videoTrack, ...rest }, videoEl) => {
|
forwardRef(({ participantId, videoTrack, ...rest }, videoEl) => {
|
||||||
const { callObject } = useCallState();
|
|
||||||
const { isMobile } = useUIState();
|
|
||||||
|
|
||||||
const isLocalCam = useMemo(() => {
|
|
||||||
const localParticipant = callObject.participants()?.local;
|
|
||||||
return participantId === localParticipant.session_id && !isScreenId(participantId);
|
|
||||||
}, [callObject, participantId]);
|
|
||||||
|
|
||||||
const [isMirrored, setIsMirrored] = useState(isLocalCam);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Considered as playable video:
|
|
||||||
* - local cam feed
|
|
||||||
* - any screen share
|
|
||||||
* - remote cam feed that is subscribed and reported as playable
|
|
||||||
*/
|
|
||||||
const isPlayable = useDeepCompareMemo(
|
|
||||||
() => isLocalCam || isScreenId(participantId),
|
|
||||||
[isLocalCam, isScreenId(participantId)]
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Memo: Chrome >= 92?
|
* Memo: Chrome >= 92?
|
||||||
* See: https://bugs.chromium.org/p/chromium/issues/detail?id=1232649
|
* See: https://bugs.chromium.org/p/chromium/issues/detail?id=1232649
|
||||||
|
|
@ -45,114 +19,33 @@ export const Video = memo(
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if video needs to be mirrored.
|
* Effect: Umount
|
||||||
*/
|
* Note: nullify src to ensure media object is not counted
|
||||||
useEffect(() => {
|
|
||||||
if (!videoTrack) return;
|
|
||||||
|
|
||||||
const videoTrackSettings = videoTrack.getSettings();
|
|
||||||
const isUsersFrontCamera =
|
|
||||||
'facingMode' in videoTrackSettings
|
|
||||||
? isLocalCam && videoTrackSettings.facingMode === 'user'
|
|
||||||
: isLocalCam;
|
|
||||||
// only apply mirror effect to user facing camera
|
|
||||||
if (isMirrored !== isUsersFrontCamera) {
|
|
||||||
setIsMirrored(isUsersFrontCamera);
|
|
||||||
}
|
|
||||||
}, [isMirrored, isLocalCam, videoTrack]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle canplay & picture-in-picture events.
|
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const video = videoEl.current;
|
const video = videoEl.current;
|
||||||
if (!video) return;
|
if (!video) return false;
|
||||||
const handleCanPlay = () => {
|
// clean up when video renders for different participant
|
||||||
if (!video.paused) return;
|
video.srcObject = null;
|
||||||
video.play();
|
|
||||||
};
|
|
||||||
const handleEnterPIP = () => {
|
|
||||||
video.style.transform = 'scale(1)';
|
|
||||||
};
|
|
||||||
const handleLeavePIP = () => {
|
|
||||||
video.style.transform = '';
|
|
||||||
setTimeout(() => {
|
|
||||||
if (video.paused) video.play();
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
video.addEventListener('canplay', handleCanPlay);
|
|
||||||
video.addEventListener('enterpictureinpicture', handleEnterPIP);
|
|
||||||
video.addEventListener('leavepictureinpicture', handleLeavePIP);
|
|
||||||
return () => {
|
|
||||||
video.removeEventListener('canplay', handleCanPlay);
|
|
||||||
video.removeEventListener('enterpictureinpicture', handleEnterPIP);
|
|
||||||
video.removeEventListener('leavepictureinpicture', handleLeavePIP);
|
|
||||||
};
|
|
||||||
}, [isChrome92, videoEl]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update srcObject.
|
|
||||||
*/
|
|
||||||
useEffect(() => {
|
|
||||||
const video = videoEl.current;
|
|
||||||
if (!video || !videoTrack) return;
|
|
||||||
video.srcObject = new MediaStream([videoTrack]);
|
|
||||||
if (isChrome92) video.load();
|
if (isChrome92) video.load();
|
||||||
return () => {
|
return () => {
|
||||||
// clean up when unmounted
|
// clean up when unmounted
|
||||||
video.srcObject = null;
|
video.srcObject = null;
|
||||||
if (isChrome92) video.load();
|
if (isChrome92) video.load();
|
||||||
};
|
};
|
||||||
}, [isChrome92, participantId, videoEl, videoTrack, videoTrack?.id]);
|
}, [videoEl, isChrome92, participantId]);
|
||||||
|
|
||||||
return (
|
/**
|
||||||
<>
|
* Effect: mount source (and force load on Chrome)
|
||||||
<video
|
*/
|
||||||
className={classNames(fit, {
|
useEffect(() => {
|
||||||
isMirrored,
|
const video = videoEl.current;
|
||||||
isMobile,
|
if (!video || !videoTrack) return;
|
||||||
playable: isPlayable && videoTrack?.enabled,
|
video.srcObject = new MediaStream([videoTrack]);
|
||||||
})}
|
if (isChrome92) video.load();
|
||||||
autoPlay
|
}, [videoEl, isChrome92, videoTrack]);
|
||||||
muted
|
|
||||||
playsInline
|
return <video autoPlay muted playsInline ref={videoEl} {...rest} />;
|
||||||
ref={videoEl}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
<style jsx>{`
|
|
||||||
video {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
video.playable {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
video.isMirrored {
|
|
||||||
transform: scale(-1, 1);
|
|
||||||
}
|
|
||||||
video.isMobile {
|
|
||||||
border-radius: 4px;
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
video:not(.isMobile) {
|
|
||||||
height: calc(100% + 4px);
|
|
||||||
left: -2px;
|
|
||||||
object-position: center;
|
|
||||||
position: absolute;
|
|
||||||
top: -2px;
|
|
||||||
width: calc(100% + 4px);
|
|
||||||
}
|
|
||||||
video.contain {
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
video.cover {
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
(p, n) => shallowEqualObjects(p, n)
|
(p, n) => shallowEqualObjects(p, n)
|
||||||
);
|
);
|
||||||
|
|
@ -165,4 +58,4 @@ Video.propTypes = {
|
||||||
participantId: PropTypes.string,
|
participantId: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Video;
|
export default Video;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { createContext, useContext } from 'react';
|
import React, { createContext, useContext, useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useCallState } from './CallProvider';
|
import { useCallState } from './CallProvider';
|
||||||
|
|
@ -12,33 +12,72 @@ export const MediaDeviceProvider = ({ children }) => {
|
||||||
const { localParticipant } = useParticipants();
|
const { localParticipant } = useParticipants();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
cams,
|
|
||||||
mics,
|
|
||||||
speakers,
|
|
||||||
camError,
|
camError,
|
||||||
micError,
|
cams,
|
||||||
currentDevices,
|
currentCam,
|
||||||
|
currentMic,
|
||||||
|
currentSpeaker,
|
||||||
deviceState,
|
deviceState,
|
||||||
setMicDevice,
|
micError,
|
||||||
setCamDevice,
|
mics,
|
||||||
setSpeakersDevice,
|
refreshDevices,
|
||||||
|
setCurrentCam,
|
||||||
|
setCurrentMic,
|
||||||
|
setCurrentSpeaker,
|
||||||
|
speakers,
|
||||||
} = useDevices(callObject);
|
} = useDevices(callObject);
|
||||||
|
|
||||||
|
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 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 selectSpeaker = useCallback(
|
||||||
|
(newSpeaker) => {
|
||||||
|
if (!callObject || newSpeaker.deviceId === currentSpeaker?.deviceId) return;
|
||||||
|
callObject.setOutputDevice({
|
||||||
|
outputDeviceId: newSpeaker.deviceId,
|
||||||
|
});
|
||||||
|
setCurrentSpeaker(newSpeaker);
|
||||||
|
},
|
||||||
|
[callObject, currentSpeaker, setCurrentSpeaker]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MediaDeviceContext.Provider
|
<MediaDeviceContext.Provider
|
||||||
value={{
|
value={{
|
||||||
cams,
|
|
||||||
mics,
|
|
||||||
speakers,
|
|
||||||
camError,
|
camError,
|
||||||
micError,
|
cams,
|
||||||
currentDevices,
|
currentCam,
|
||||||
|
currentMic,
|
||||||
|
currentSpeaker,
|
||||||
deviceState,
|
deviceState,
|
||||||
isCamMuted: localParticipant.isCamMuted,
|
isCamMuted: localParticipant.isCamMuted,
|
||||||
isMicMuted: localParticipant.isMicMuted,
|
isMicMuted: localParticipant.isMicMuted,
|
||||||
setMicDevice,
|
micError,
|
||||||
setCamDevice,
|
mics,
|
||||||
setSpeakersDevice,
|
refreshDevices,
|
||||||
|
setCurrentCam: selectCamera,
|
||||||
|
setCurrentMic: selectMic,
|
||||||
|
setCurrentSpeaker: selectSpeaker,
|
||||||
|
speakers,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,12 @@ import {
|
||||||
export const ParticipantsContext = createContext();
|
export const ParticipantsContext = createContext();
|
||||||
|
|
||||||
export const ParticipantsProvider = ({ children }) => {
|
export const ParticipantsProvider = ({ children }) => {
|
||||||
const { callObject, videoQuality, networkState } = useCallState();
|
const { callObject, videoQuality, networkState, broadcast, broadcastRole, } = useCallState();
|
||||||
const [state, dispatch] = useReducer(
|
const [state, dispatch] = useReducer(
|
||||||
participantsReducer,
|
participantsReducer,
|
||||||
initialParticipantsState
|
initialParticipantsState
|
||||||
);
|
);
|
||||||
const { viewMode } = useUIState();
|
const { isMobile, viewMode, pinnedId } = useUIState();
|
||||||
const [participantMarkedForRemoval, setParticipantMarkedForRemoval] =
|
const [participantMarkedForRemoval, setParticipantMarkedForRemoval] =
|
||||||
useState(null);
|
useState(null);
|
||||||
|
|
||||||
|
|
@ -57,7 +57,12 @@ export const ParticipantsProvider = ({ children }) => {
|
||||||
/**
|
/**
|
||||||
* Only return participants that should be visible in the call
|
* Only return participants that should be visible in the call
|
||||||
*/
|
*/
|
||||||
const participants = useMemo(() => state.participants, [state.participants]);
|
const participants = useMemo(() => {
|
||||||
|
if (broadcast) {
|
||||||
|
return state.participants.filter((p) => p?.isOwner);
|
||||||
|
}
|
||||||
|
return state.participants;
|
||||||
|
}, [broadcast, state.participants]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of participant IDs
|
* Array of participant IDs
|
||||||
|
|
@ -95,11 +100,6 @@ export const ParticipantsProvider = ({ children }) => {
|
||||||
[allParticipants]
|
[allParticipants]
|
||||||
);
|
);
|
||||||
|
|
||||||
const isOwner = useMemo(
|
|
||||||
() => !!localParticipant?.isOwner,
|
|
||||||
[localParticipant]
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The participant who should be rendered prominently right now
|
* The participant who should be rendered prominently right now
|
||||||
*/
|
*/
|
||||||
|
|
@ -110,8 +110,13 @@ export const ParticipantsProvider = ({ children }) => {
|
||||||
* if everyone else is muted when AP leaves, the value will be stale.
|
* if everyone else is muted when AP leaves, the value will be stale.
|
||||||
*/
|
*/
|
||||||
const isPresent = participants.some((p) => p?.id === activeParticipant?.id);
|
const isPresent = participants.some((p) => p?.id === activeParticipant?.id);
|
||||||
|
const pinned = participants.find((p) => p?.id === pinnedId);
|
||||||
|
|
||||||
const displayableParticipants = participants.filter((p) => !p?.isLocal);
|
if (pinned) return pinned;
|
||||||
|
|
||||||
|
const displayableParticipants = participants.filter((p) =>
|
||||||
|
isMobile ? !p?.isLocal && !p?.isScreenshare : !p?.isLocal
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isPresent &&
|
!isPresent &&
|
||||||
|
|
@ -130,22 +135,42 @@ export const ParticipantsProvider = ({ children }) => {
|
||||||
.sort((a, b) => sortByKey(a, b, 'lastActiveDate'))
|
.sort((a, b) => sortByKey(a, b, 'lastActiveDate'))
|
||||||
.reverse();
|
.reverse();
|
||||||
|
|
||||||
return isPresent ? activeParticipant : sorted?.[0] ?? localParticipant;
|
const fallback = broadcastRole === 'attendee' ? null : localParticipant;
|
||||||
}, [activeParticipant, localParticipant, participants]);
|
|
||||||
|
return isPresent ? activeParticipant : sorted?.[0] ?? fallback;
|
||||||
|
}, [
|
||||||
|
activeParticipant,
|
||||||
|
broadcastRole,
|
||||||
|
isMobile,
|
||||||
|
localParticipant,
|
||||||
|
participants,
|
||||||
|
pinnedId,
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Screen shares
|
* Screen shares
|
||||||
*/
|
*/
|
||||||
const screens = useMemo(
|
const screens = useMemo(() => state?.screens, [state?.screens]);
|
||||||
() => allParticipants.filter(({ isScreenshare }) => isScreenshare),
|
|
||||||
[allParticipants]
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The local participant's name
|
* The local participant's name
|
||||||
*/
|
*/
|
||||||
const username = callObject?.participants()?.local?.user_name ?? '';
|
const username = callObject?.participants()?.local?.user_name ?? '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the local participant's name in daily-js
|
||||||
|
* @param name The new username
|
||||||
|
*/
|
||||||
|
const setUsername = (name) => {
|
||||||
|
callObject.setUserName(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const isOwner = useMemo(
|
||||||
|
() => !!localParticipant?.isOwner,
|
||||||
|
[localParticipant]
|
||||||
|
);
|
||||||
|
|
||||||
const [muteNewParticipants, setMuteNewParticipants] = useState(false);
|
const [muteNewParticipants, setMuteNewParticipants] = useState(false);
|
||||||
|
|
||||||
const muteAll = useCallback(
|
const muteAll = useCallback(
|
||||||
|
|
@ -165,22 +190,20 @@ export const ParticipantsProvider = ({ children }) => {
|
||||||
[callObject, localParticipant, participants]
|
[callObject, localParticipant, participants]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
const swapParticipantPosition = useCallback((id1, id2) => {
|
||||||
* Sets the local participant's name in daily-js
|
/**
|
||||||
* @param name The new username
|
* Ignore in the following cases:
|
||||||
*/
|
* - id1 and id2 are equal
|
||||||
const setUsername = (name) => {
|
* - one of both ids is not set
|
||||||
callObject.setUserName(name);
|
* - one of both ids is 'local'
|
||||||
};
|
*/
|
||||||
|
|
||||||
const swapParticipantPosition = (id1, id2) => {
|
|
||||||
if (id1 === id2 || !id1 || !id2 || isLocalId(id1) || isLocalId(id2)) return;
|
if (id1 === id2 || !id1 || !id2 || isLocalId(id1) || isLocalId(id2)) return;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SWAP_POSITION,
|
type: 'SWAP_POSITION',
|
||||||
id1,
|
id1,
|
||||||
id2,
|
id2,
|
||||||
});
|
});
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleNewParticipantsState = useCallback(
|
const handleNewParticipantsState = useCallback(
|
||||||
(event = null) => {
|
(event = null) => {
|
||||||
|
|
@ -241,10 +264,11 @@ export const ParticipantsProvider = ({ children }) => {
|
||||||
/**
|
/**
|
||||||
* Change between the simulcast layers based on view / available bandwidth
|
* Change between the simulcast layers based on view / available bandwidth
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const setBandWidthControls = useCallback(() => {
|
const setBandWidthControls = useCallback(() => {
|
||||||
if (!(callObject && callObject.meetingState() === 'joined-meeting')) return;
|
if (!(callObject && callObject.meetingState() === 'joined-meeting')) return;
|
||||||
|
|
||||||
const ids = participantIds.split(',');
|
const ids = participantIds.split(',').filter(Boolean);
|
||||||
const receiveSettings = {};
|
const receiveSettings = {};
|
||||||
|
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
|
|
@ -252,19 +276,16 @@ export const ParticipantsProvider = ({ children }) => {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
// weak or bad network
|
// weak or bad network
|
||||||
([VIDEO_QUALITY_LOW, VIDEO_QUALITY_VERY_LOW].includes(networkState) &&
|
(['low', 'very-low'].includes(networkState) && videoQuality === 'auto') ||
|
||||||
videoQuality === VIDEO_QUALITY_AUTO) ||
|
|
||||||
// Low quality or Bandwidth saver mode enabled
|
// Low quality or Bandwidth saver mode enabled
|
||||||
[VIDEO_QUALITY_BANDWIDTH_SAVER, VIDEO_QUALITY_LOW].includes(
|
['bandwidth-saver', 'low'].includes(videoQuality)
|
||||||
videoQuality
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
receiveSettings[id] = { video: { layer: 0 } };
|
receiveSettings[id] = { video: { layer: 0 } };
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Speaker view settings based on speaker status or pinned user
|
// Speaker view settings based on speaker status or pinned user
|
||||||
if (viewMode === VIEW_MODE_SPEAKER) {
|
if (viewMode === 'speaker') {
|
||||||
if (currentSpeaker?.id === id) {
|
if (currentSpeaker?.id === id) {
|
||||||
receiveSettings[id] = { video: { layer: 2 } };
|
receiveSettings[id] = { video: { layer: 2 } };
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -273,16 +294,11 @@ export const ParticipantsProvider = ({ children }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grid view settings are handled separately in GridView
|
// Grid view settings are handled separately in GridView
|
||||||
|
// Mobile view settings are handled separately in MobileCall
|
||||||
});
|
});
|
||||||
|
|
||||||
callObject.updateReceiveSettings(receiveSettings);
|
callObject.updateReceiveSettings(receiveSettings);
|
||||||
}, [
|
}, [callObject, participantIds, networkState, videoQuality, viewMode, currentSpeaker?.id]);
|
||||||
currentSpeaker?.id,
|
|
||||||
callObject,
|
|
||||||
networkState,
|
|
||||||
participantIds,
|
|
||||||
videoQuality,
|
|
||||||
viewMode,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBandWidthControls();
|
setBandWidthControls();
|
||||||
|
|
@ -315,12 +331,12 @@ export const ParticipantsProvider = ({ children }) => {
|
||||||
allParticipants,
|
allParticipants,
|
||||||
currentSpeaker,
|
currentSpeaker,
|
||||||
localParticipant,
|
localParticipant,
|
||||||
|
muteAll,
|
||||||
|
muteNewParticipants,
|
||||||
participantCount,
|
participantCount,
|
||||||
participantMarkedForRemoval,
|
participantMarkedForRemoval,
|
||||||
participants,
|
participants,
|
||||||
screens,
|
screens,
|
||||||
muteNewParticipants,
|
|
||||||
muteAll,
|
|
||||||
setParticipantMarkedForRemoval,
|
setParticipantMarkedForRemoval,
|
||||||
setUsername,
|
setUsername,
|
||||||
swapParticipantPosition,
|
swapParticipantPosition,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { useDeepCompareEffect } from 'use-deep-compare';
|
||||||
import { sortByKey } from '../lib/sortByKey';
|
import { sortByKey } from '../lib/sortByKey';
|
||||||
import { useCallState } from './CallProvider';
|
import { useCallState } from './CallProvider';
|
||||||
import { useParticipants } from './ParticipantsProvider';
|
import { useParticipants } from './ParticipantsProvider';
|
||||||
|
import { useUIState } from './UIStateProvider';
|
||||||
import { isLocalId, isScreenId } from './participantsState';
|
import { isLocalId, isScreenId } from './participantsState';
|
||||||
import {
|
import {
|
||||||
initialTracksState,
|
initialTracksState,
|
||||||
|
|
@ -42,6 +43,7 @@ const TracksContext = createContext(null);
|
||||||
export const TracksProvider = ({ children }) => {
|
export const TracksProvider = ({ children }) => {
|
||||||
const { callObject, subscribeToTracksAutomatically } = useCallState();
|
const { callObject, subscribeToTracksAutomatically } = useCallState();
|
||||||
const { participants } = useParticipants();
|
const { participants } = useParticipants();
|
||||||
|
const { viewMode } = useUIState();
|
||||||
const [state, dispatch] = useReducer(tracksReducer, initialTracksState);
|
const [state, dispatch] = useReducer(tracksReducer, initialTracksState);
|
||||||
|
|
||||||
const recentSpeakerIds = useMemo(
|
const recentSpeakerIds = useMemo(
|
||||||
|
|
@ -83,13 +85,19 @@ export const TracksProvider = ({ children }) => {
|
||||||
// stage all remote cams that aren't already marked for subscription.
|
// stage all remote cams that aren't already marked for subscription.
|
||||||
// Otherwise, honor the provided stagedIds, with recent speakers appended
|
// Otherwise, honor the provided stagedIds, with recent speakers appended
|
||||||
// who aren't already marked for subscription.
|
// who aren't already marked for subscription.
|
||||||
const stagedIdsFiltered =
|
if (
|
||||||
remoteParticipantIds.length <= SUBSCRIBE_OR_STAGE_ALL_VIDEO_THRESHOLD
|
remoteParticipantIds.length <= SUBSCRIBE_OR_STAGE_ALL_VIDEO_THRESHOLD
|
||||||
? remoteParticipantIds.filter((id) => !subscribedIds.includes(id))
|
) {
|
||||||
: [
|
stagedIds = remoteParticipantIds.filter(
|
||||||
...stagedIds,
|
(id) => !subscribedIds.includes(id)
|
||||||
...recentSpeakerIds.filter((id) => !subscribedIds.includes(id)),
|
);
|
||||||
];
|
} else {
|
||||||
|
if (viewMode !== 'grid') {
|
||||||
|
stagedIds.push(
|
||||||
|
...recentSpeakerIds.filter((id) => !subscribedIds.includes(id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Assemble updates to get to desired cam subscriptions
|
// Assemble updates to get to desired cam subscriptions
|
||||||
const updates = remoteParticipantIds.reduce((u, id) => {
|
const updates = remoteParticipantIds.reduce((u, id) => {
|
||||||
|
|
@ -104,7 +112,7 @@ export const TracksProvider = ({ children }) => {
|
||||||
// subscribed, staged, or unsubscribed
|
// subscribed, staged, or unsubscribed
|
||||||
if (subscribedIds.includes(id)) {
|
if (subscribedIds.includes(id)) {
|
||||||
desiredSubscription = true;
|
desiredSubscription = true;
|
||||||
} else if (stagedIdsFiltered.includes(id)) {
|
} else if (stagedIds.includes(id)) {
|
||||||
desiredSubscription = 'staged';
|
desiredSubscription = 'staged';
|
||||||
} else {
|
} else {
|
||||||
desiredSubscription = false;
|
desiredSubscription = false;
|
||||||
|
|
@ -116,9 +124,6 @@ export const TracksProvider = ({ children }) => {
|
||||||
|
|
||||||
u[id] = {
|
u[id] = {
|
||||||
setSubscribedTracks: {
|
setSubscribedTracks: {
|
||||||
audio: true,
|
|
||||||
screenAudio: true,
|
|
||||||
screenVideo: true,
|
|
||||||
video: desiredSubscription,
|
video: desiredSubscription,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -128,7 +133,7 @@ export const TracksProvider = ({ children }) => {
|
||||||
if (Object.keys(updates).length === 0) return;
|
if (Object.keys(updates).length === 0) return;
|
||||||
callObject.updateParticipants(updates);
|
callObject.updateParticipants(updates);
|
||||||
},
|
},
|
||||||
[callObject, remoteParticipantIds, recentSpeakerIds]
|
[callObject, remoteParticipantIds, viewMode, recentSpeakerIds]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ export const UIStateProvider = ({
|
||||||
customTrayComponent,
|
customTrayComponent,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
|
||||||
const [pinnedId, setPinnedId] = useState(null);
|
const [pinnedId, setPinnedId] = useState(null);
|
||||||
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
const [preferredViewMode, setPreferredViewMode] = useState(VIEW_MODE_SPEAKER);
|
const [preferredViewMode, setPreferredViewMode] = useState(VIEW_MODE_SPEAKER);
|
||||||
const [viewMode, setViewMode] = useState(preferredViewMode);
|
const [viewMode, setViewMode] = useState(preferredViewMode);
|
||||||
const [isShowingScreenshare, setIsShowingScreenshare] = useState(false);
|
const [isShowingScreenshare, setIsShowingScreenshare] = useState(false);
|
||||||
|
|
@ -31,6 +31,20 @@ export const UIStateProvider = ({
|
||||||
const [customCapsule, setCustomCapsule] = useState();
|
const [customCapsule, setCustomCapsule] = useState();
|
||||||
const [showAutoplayFailedModal, setShowAutoplayFailedModal] = useState(false);
|
const [showAutoplayFailedModal, setShowAutoplayFailedModal] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decide on view mode based on input conditions.
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMobile) {
|
||||||
|
setViewMode(VIEW_MODE_MOBILE);
|
||||||
|
} else if (pinnedId || isShowingScreenshare) {
|
||||||
|
setViewMode(VIEW_MODE_SPEAKER);
|
||||||
|
} else {
|
||||||
|
setViewMode(preferredViewMode);
|
||||||
|
}
|
||||||
|
}, [pinnedId, isMobile, isShowingScreenshare, preferredViewMode]);
|
||||||
|
|
||||||
const openModal = useCallback((modalName) => {
|
const openModal = useCallback((modalName) => {
|
||||||
setActiveModals((prevState) => ({
|
setActiveModals((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ const initialParticipantsState = {
|
||||||
camMutedByHost: false,
|
camMutedByHost: false,
|
||||||
hasNameSet: false,
|
hasNameSet: false,
|
||||||
id: 'local',
|
id: 'local',
|
||||||
user_id: '',
|
|
||||||
isActiveSpeaker: false,
|
isActiveSpeaker: false,
|
||||||
isCamMuted: false,
|
isCamMuted: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|
@ -34,6 +33,7 @@ const initialParticipantsState = {
|
||||||
lastActiveDate: null,
|
lastActiveDate: null,
|
||||||
micMutedByHost: false,
|
micMutedByHost: false,
|
||||||
name: '',
|
name: '',
|
||||||
|
sessionId: '',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
screens: [],
|
screens: [],
|
||||||
|
|
@ -42,7 +42,7 @@ const initialParticipantsState = {
|
||||||
// --- Derived data ---
|
// --- Derived data ---
|
||||||
|
|
||||||
function getId(participant) {
|
function getId(participant) {
|
||||||
return participant.local ? 'local' : participant.user_id;
|
return participant.local ? 'local' : participant.session_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getScreenId(id) {
|
function getScreenId(id) {
|
||||||
|
|
@ -69,28 +69,25 @@ function getNewParticipant(participant) {
|
||||||
camMutedByHost: video?.off?.byRemoteRequest,
|
camMutedByHost: video?.off?.byRemoteRequest,
|
||||||
hasNameSet: !!participant.user_name,
|
hasNameSet: !!participant.user_name,
|
||||||
id,
|
id,
|
||||||
user_id: participant.user_id,
|
|
||||||
isActiveSpeaker: false,
|
isActiveSpeaker: false,
|
||||||
isCamMuted:
|
isCamMuted: video?.state === 'off' || video?.state === 'blocked',
|
||||||
video?.state === DEVICE_STATE_OFF ||
|
isLoading: audio?.state === 'loading' || video?.state === 'loading',
|
||||||
video?.state === DEVICE_STATE_BLOCKED,
|
|
||||||
isLoading:
|
|
||||||
audio?.state === DEVICE_STATE_LOADING ||
|
|
||||||
video?.state === DEVICE_STATE_LOADING,
|
|
||||||
isLocal: local,
|
isLocal: local,
|
||||||
isMicMuted:
|
isMicMuted: audio?.state === 'off' || audio?.state === 'blocked',
|
||||||
audio?.state === DEVICE_STATE_OFF ||
|
|
||||||
audio?.state === DEVICE_STATE_BLOCKED,
|
|
||||||
isOwner: !!participant.owner,
|
isOwner: !!participant.owner,
|
||||||
isRecording: !!participant.record,
|
isRecording: !!participant.record,
|
||||||
isScreenshare: false,
|
isScreenshare: false,
|
||||||
lastActiveDate: null,
|
lastActiveDate: null,
|
||||||
micMutedByHost: audio?.off?.byRemoteRequest,
|
micMutedByHost: audio?.off?.byRemoteRequest,
|
||||||
name: participant.user_name,
|
name: participant.user_name,
|
||||||
|
sessionId: participant.session_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUpdatedParticipant(participant, participants) {
|
function getUpdatedParticipant(
|
||||||
|
participant,
|
||||||
|
participants
|
||||||
|
) {
|
||||||
const id = getId(participant);
|
const id = getId(participant);
|
||||||
const prevItem = participants.find((p) => p.id === id);
|
const prevItem = participants.find((p) => p.id === id);
|
||||||
|
|
||||||
|
|
@ -99,26 +96,21 @@ function getUpdatedParticipant(participant, participants) {
|
||||||
|
|
||||||
const { local } = participant;
|
const { local } = participant;
|
||||||
const { audio, video } = participant.tracks;
|
const { audio, video } = participant.tracks;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...prevItem,
|
...prevItem,
|
||||||
camMutedByHost: video?.off?.byRemoteRequest,
|
camMutedByHost: video?.off?.byRemoteRequest,
|
||||||
hasNameSet: !!participant.user_name,
|
hasNameSet: !!participant.user_name,
|
||||||
id,
|
id,
|
||||||
user_id: participant.user_id,
|
isCamMuted: video?.state === 'off' || video?.state === 'blocked',
|
||||||
isCamMuted:
|
isLoading: audio?.state === 'loading' || video?.state === 'loading',
|
||||||
video?.state === DEVICE_STATE_OFF ||
|
|
||||||
video?.state === DEVICE_STATE_BLOCKED,
|
|
||||||
isLoading:
|
|
||||||
audio?.state === DEVICE_STATE_LOADING ||
|
|
||||||
video?.state === DEVICE_STATE_LOADING,
|
|
||||||
isLocal: local,
|
isLocal: local,
|
||||||
isMicMuted:
|
isMicMuted: audio?.state === 'off' || audio?.state === 'blocked',
|
||||||
audio?.state === DEVICE_STATE_OFF ||
|
|
||||||
audio?.state === DEVICE_STATE_BLOCKED,
|
|
||||||
isOwner: !!participant.owner,
|
isOwner: !!participant.owner,
|
||||||
isRecording: !!participant.record,
|
isRecording: !!participant.record,
|
||||||
micMutedByHost: audio?.off?.byRemoteRequest,
|
micMutedByHost: audio?.off?.byRemoteRequest,
|
||||||
name: participant.user_name,
|
name: participant.user_name,
|
||||||
|
sessionId: participant.session_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,6 +124,7 @@ function getScreenItem(participant) {
|
||||||
isScreenshare: true,
|
isScreenshare: true,
|
||||||
lastActiveDate: null,
|
lastActiveDate: null,
|
||||||
name: participant.user_name,
|
name: participant.user_name,
|
||||||
|
sessionId: participant.session_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ export const CALL_STATE_REDIRECTING = 'redirecting';
|
||||||
export const CALL_STATE_NOT_FOUND = 'not-found';
|
export const CALL_STATE_NOT_FOUND = 'not-found';
|
||||||
export const CALL_STATE_NOT_ALLOWED = 'not-allowed';
|
export const CALL_STATE_NOT_ALLOWED = 'not-allowed';
|
||||||
export const CALL_STATE_AWAITING_ARGS = 'awaiting-args';
|
export const CALL_STATE_AWAITING_ARGS = 'awaiting-args';
|
||||||
|
export const CALL_STATE_NOT_SECURE = 'not-secure';
|
||||||
|
|
||||||
export const useCallMachine = ({
|
export const useCallMachine = ({
|
||||||
domain,
|
domain,
|
||||||
|
|
@ -78,9 +79,10 @@ export const useCallMachine = ({
|
||||||
const join = useCallback(
|
const join = useCallback(
|
||||||
async (callObject) => {
|
async (callObject) => {
|
||||||
setState(CALL_STATE_JOINING);
|
setState(CALL_STATE_JOINING);
|
||||||
|
const dailyRoomInfo = await callObject.room();
|
||||||
|
|
||||||
// Force mute clients when joining a call with experimental_optimize_large_calls enabled.
|
// Force mute clients when joining a call with experimental_optimize_large_calls enabled.
|
||||||
if (room?.config?.experimental_optimize_large_calls) {
|
if (dailyRoomInfo?.config?.experimental_optimize_large_calls) {
|
||||||
callObject.setLocalAudio(false);
|
callObject.setLocalAudio(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,6 +190,15 @@ export const useCallMachine = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (daily || !url || state !== CALL_STATE_READY) return;
|
if (daily || !url || state !== CALL_STATE_READY) return;
|
||||||
|
|
||||||
|
if (
|
||||||
|
location.protocol !== 'https:' &&
|
||||||
|
// We want to still allow local development.
|
||||||
|
!['localhost'].includes(location.hostname)
|
||||||
|
) {
|
||||||
|
setState('not-secure');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('🚀 Creating call object');
|
console.log('🚀 Creating call object');
|
||||||
|
|
||||||
const co = DailyIframe.createCallObject({
|
const co = DailyIframe.createCallObject({
|
||||||
|
|
@ -206,7 +217,7 @@ export const useCallMachine = ({
|
||||||
* Listen for changes in the participant's access state
|
* Listen for changes in the participant's access state
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!daily) return false;
|
if (!daily) return;
|
||||||
|
|
||||||
daily.on('access-state-updated', handleAccessStateUpdated);
|
daily.on('access-state-updated', handleAccessStateUpdated);
|
||||||
return () => daily.off('access-state-updated', handleAccessStateUpdated);
|
return () => daily.off('access-state-updated', handleAccessStateUpdated);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ export const DEVICE_STATE_SENDABLE = 'sendable';
|
||||||
|
|
||||||
export const useDevices = (callObject) => {
|
export const useDevices = (callObject) => {
|
||||||
const [deviceState, setDeviceState] = useState(DEVICE_STATE_LOADING);
|
const [deviceState, setDeviceState] = useState(DEVICE_STATE_LOADING);
|
||||||
const [currentDevices, setCurrentDevices] = useState(null);
|
const [currentCam, setCurrentCam] = useState(null);
|
||||||
|
const [currentMic, setCurrentMic] = useState(null);
|
||||||
|
const [currentSpeaker, setCurrentSpeaker] = useState(null);
|
||||||
|
|
||||||
const [cams, setCams] = useState([]);
|
const [cams, setCams] = useState([]);
|
||||||
const [mics, setMics] = useState([]);
|
const [mics, setMics] = useState([]);
|
||||||
|
|
@ -38,6 +40,10 @@ export const useDevices = (callObject) => {
|
||||||
|
|
||||||
const { camera, mic, speaker } = await callObject.getInputDevices();
|
const { camera, mic, speaker } = await callObject.getInputDevices();
|
||||||
|
|
||||||
|
setCurrentCam(camera ?? null);
|
||||||
|
setCurrentMic(mic ?? null);
|
||||||
|
setCurrentSpeaker(speaker ?? null);
|
||||||
|
|
||||||
const [defaultCam, ...videoDevices] = devices.filter(
|
const [defaultCam, ...videoDevices] = devices.filter(
|
||||||
(d) => d.kind === 'videoinput' && d.deviceId !== ''
|
(d) => d.kind === 'videoinput' && d.deviceId !== ''
|
||||||
);
|
);
|
||||||
|
|
@ -66,12 +72,6 @@ export const useDevices = (callObject) => {
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
);
|
);
|
||||||
|
|
||||||
setCurrentDevices({
|
|
||||||
camera,
|
|
||||||
mic,
|
|
||||||
speaker,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Current cam: ${camera.label}`);
|
console.log(`Current cam: ${camera.label}`);
|
||||||
console.log(`Current mic: ${mic.label}`);
|
console.log(`Current mic: ${mic.label}`);
|
||||||
console.log(`Current speakers: ${speaker.label}`);
|
console.log(`Current speakers: ${speaker.label}`);
|
||||||
|
|
@ -125,31 +125,26 @@ export const useDevices = (callObject) => {
|
||||||
|
|
||||||
const handleParticipantUpdated = useCallback(
|
const handleParticipantUpdated = useCallback(
|
||||||
({ participant }) => {
|
({ participant }) => {
|
||||||
if (!callObject || !participant.local) return;
|
if (!callObject || deviceState === 'not-supported' || !participant.local) return;
|
||||||
|
|
||||||
setDeviceState((prevState) => {
|
switch (participant?.tracks.video.state) {
|
||||||
if (prevState === DEVICE_STATE_NOT_SUPPORTED) return prevState;
|
case DEVICE_STATE_BLOCKED:
|
||||||
switch (participant?.tracks.video.state) {
|
setDeviceState(DEVICE_STATE_ERROR);
|
||||||
case DEVICE_STATE_BLOCKED:
|
break;
|
||||||
updateDeviceErrors();
|
case DEVICE_STATE_OFF:
|
||||||
return DEVICE_STATE_ERROR;
|
case DEVICE_STATE_PLAYABLE:
|
||||||
case DEVICE_STATE_OFF:
|
updateDeviceState();
|
||||||
case DEVICE_STATE_PLAYABLE:
|
setDeviceState(DEVICE_STATE_GRANTED);
|
||||||
if (prevState === DEVICE_STATE_GRANTED) {
|
break;
|
||||||
return prevState;
|
}
|
||||||
}
|
|
||||||
updateDeviceState();
|
updateDeviceErrors();
|
||||||
return DEVICE_STATE_GRANTED;
|
|
||||||
default:
|
|
||||||
return prevState;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[callObject, updateDeviceState, updateDeviceErrors]
|
[callObject, deviceState, updateDeviceErrors, updateDeviceState]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!callObject) return false;
|
if (!callObject) return;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
If the user is slow to allow access, we'll update the device state
|
If the user is slow to allow access, we'll update the device state
|
||||||
|
|
@ -169,6 +164,7 @@ export const useDevices = (callObject) => {
|
||||||
updateDeviceState();
|
updateDeviceState();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateDeviceState();
|
||||||
callObject.on('joining-meeting', handleJoiningMeeting);
|
callObject.on('joining-meeting', handleJoiningMeeting);
|
||||||
callObject.on('joined-meeting', handleJoinedMeeting);
|
callObject.on('joined-meeting', handleJoinedMeeting);
|
||||||
callObject.on('participant-updated', handleParticipantUpdated);
|
callObject.on('participant-updated', handleParticipantUpdated);
|
||||||
|
|
@ -180,74 +176,8 @@ export const useDevices = (callObject) => {
|
||||||
};
|
};
|
||||||
}, [callObject, handleParticipantUpdated, updateDeviceState]);
|
}, [callObject, handleParticipantUpdated, updateDeviceState]);
|
||||||
|
|
||||||
const setCamDevice = useCallback(
|
|
||||||
async (newCam, useLocalStorage = true) => {
|
|
||||||
if (!callObject || newCam.deviceId === currentDevices?.cam?.deviceId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`🔛 Changing camera device to: ${newCam.label}`);
|
|
||||||
|
|
||||||
if (useLocalStorage) {
|
|
||||||
localStorage.setItem('defaultCamId', newCam.deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
await callObject.setInputDevicesAsync({
|
|
||||||
videoDeviceId: newCam.deviceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
setCurrentDevices((prev) => ({ ...prev, camera: newCam }));
|
|
||||||
},
|
|
||||||
[callObject, currentDevices]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setMicDevice = useCallback(
|
|
||||||
async (newMic, useLocalStorage = true) => {
|
|
||||||
if (!callObject || newMic.deviceId === currentDevices?.mic?.deviceId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`🔛 Changing mic device to: ${newMic.label}`);
|
|
||||||
|
|
||||||
if (useLocalStorage) {
|
|
||||||
localStorage.setItem('defaultMicId', newMic.deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
await callObject.setInputDevicesAsync({
|
|
||||||
audioDeviceId: newMic.deviceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
setCurrentDevices((prev) => ({ ...prev, mic: newMic }));
|
|
||||||
},
|
|
||||||
[callObject, currentDevices]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setSpeakersDevice = useCallback(
|
|
||||||
async (newSpeakers, useLocalStorage = true) => {
|
|
||||||
if (
|
|
||||||
!callObject ||
|
|
||||||
newSpeakers.deviceId === currentDevices?.speaker?.deviceId
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Changing speakers device to: ${newSpeakers.label}`);
|
|
||||||
|
|
||||||
if (useLocalStorage) {
|
|
||||||
localStorage.setItem('defaultSpeakersId', newSpeakers.deviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
callObject.setOutputDevice({
|
|
||||||
outputDeviceId: newSpeakers.deviceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
setCurrentDevices((prev) => ({ ...prev, speaker: newSpeakers }));
|
|
||||||
},
|
|
||||||
[callObject, currentDevices]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!callObject) return false;
|
if (!callObject) return;
|
||||||
|
|
||||||
console.log('💻 Device provider events bound');
|
console.log('💻 Device provider events bound');
|
||||||
|
|
||||||
|
|
@ -313,16 +243,19 @@ export const useDevices = (callObject) => {
|
||||||
}, [callObject, updateDeviceErrors]);
|
}, [callObject, updateDeviceErrors]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cams,
|
|
||||||
mics,
|
|
||||||
speakers,
|
|
||||||
camError,
|
camError,
|
||||||
micError,
|
cams,
|
||||||
currentDevices,
|
currentCam,
|
||||||
|
currentMic,
|
||||||
|
currentSpeaker,
|
||||||
deviceState,
|
deviceState,
|
||||||
setCamDevice,
|
micError,
|
||||||
setMicDevice,
|
mics,
|
||||||
setSpeakersDevice,
|
refreshDevices: updateDeviceState,
|
||||||
|
setCurrentCam,
|
||||||
|
setCurrentMic,
|
||||||
|
setCurrentSpeaker,
|
||||||
|
speakers,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
export const sortByKey = (a, b, key, caseSensitive = true) => {
|
export const sortByKey = (a, b, key, caseSensitive = true) => {
|
||||||
const aKey =
|
const aKey =
|
||||||
!caseSensitive && typeof a[key] === 'string'
|
!caseSensitive && typeof a[key] === 'string'
|
||||||
? a[key]?.toLowerCase()
|
? String(a[key])?.toLowerCase()
|
||||||
: a[key];
|
: a[key];
|
||||||
const bKey =
|
const bKey =
|
||||||
!caseSensitive && typeof b[key] === 'string'
|
!caseSensitive && typeof b[key] === 'string'
|
||||||
? b[key]?.toLowerCase()
|
? String(b[key])?.toLowerCase()
|
||||||
: b[key];
|
: b[key];
|
||||||
if (aKey > bKey) return 1;
|
if (aKey > bKey) return 1;
|
||||||
if (aKey < bKey) return -1;
|
if (aKey < bKey) return -1;
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default sortByKey;
|
|
||||||
Loading…
Reference in New Issue