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 { BasicTray } from '@custom/shared/components/Tray';
|
||||
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 { WaitingRoom } from './WaitingRoom';
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,15 @@ export const DeviceSelect = () => {
|
|||
cams,
|
||||
mics,
|
||||
speakers,
|
||||
currentDevices,
|
||||
setCamDevice,
|
||||
setMicDevice,
|
||||
setSpeakersDevice,
|
||||
currentCam,
|
||||
setCurrentCam,
|
||||
currentMic,
|
||||
setCurrentMic,
|
||||
currentSpeaker,
|
||||
setCurrentSpeaker,
|
||||
} = useMediaDevices();
|
||||
|
||||
if (!currentDevices) {
|
||||
if (!currentCam && !currentMic && !currentSpeaker) {
|
||||
return <div>Loading devices...</div>;
|
||||
}
|
||||
|
||||
|
|
@ -22,9 +24,9 @@ export const DeviceSelect = () => {
|
|||
<>
|
||||
<Field label="Select camera:">
|
||||
<SelectInput
|
||||
onChange={(e) => setCamDevice(cams[e.target.value])}
|
||||
onChange={(e) => setCurrentCam(cams[e.target.value])}
|
||||
value={cams.findIndex(
|
||||
(i) => i.deviceId === currentDevices.camera.deviceId
|
||||
(i) => i.deviceId === currentCam.deviceId
|
||||
)}
|
||||
>
|
||||
{cams.map(({ deviceId, label }, i) => (
|
||||
|
|
@ -37,9 +39,9 @@ export const DeviceSelect = () => {
|
|||
|
||||
<Field label="Select microphone:">
|
||||
<SelectInput
|
||||
onChange={(e) => setMicDevice(mics[e.target.value])}
|
||||
onChange={(e) => setCurrentMic(mics[e.target.value])}
|
||||
value={mics.findIndex(
|
||||
(i) => i.deviceId === currentDevices.mic.deviceId
|
||||
(i) => i.deviceId === currentMic.deviceId
|
||||
)}
|
||||
>
|
||||
{mics.map(({ deviceId, label }, i) => (
|
||||
|
|
@ -56,9 +58,9 @@ export const DeviceSelect = () => {
|
|||
{speakers.length > 0 && (
|
||||
<Field label="Select speakers:">
|
||||
<SelectInput
|
||||
onChange={(e) => setSpeakersDevice(speakers[e.target.value])}
|
||||
onChange={(e) => setCurrentSpeaker(speakers[e.target.value])}
|
||||
value={speakers.findIndex(
|
||||
(i) => i.deviceId === currentDevices.speaker.deviceId
|
||||
(i) => i.deviceId === currentSpeaker.deviceId
|
||||
)}
|
||||
>
|
||||
{speakers.map(({ deviceId, label }, i) => (
|
||||
|
|
|
|||
|
|
@ -1,36 +1,10 @@
|
|||
import React, { useMemo, forwardRef, memo, useEffect, useState } from 'react';
|
||||
import { useCallState } from '@custom/shared/contexts/CallProvider';
|
||||
import { useUIState } from '@custom/shared/contexts/UIStateProvider';
|
||||
import { isScreenId } from '@custom/shared/contexts/participantsState';
|
||||
import React, { useMemo, forwardRef, memo, useEffect } from 'react';
|
||||
import Bowser from 'bowser';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { shallowEqualObjects } from 'shallow-equal';
|
||||
import { useDeepCompareMemo } from 'use-deep-compare';
|
||||
|
||||
export const Video = memo(
|
||||
forwardRef(({ fit = 'contain', 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)]
|
||||
);
|
||||
|
||||
forwardRef(({ participantId, videoTrack, ...rest }, videoEl) => {
|
||||
/**
|
||||
* Memo: Chrome >= 92?
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
* Effect: Umount
|
||||
* Note: nullify src to ensure media object is not counted
|
||||
*/
|
||||
useEffect(() => {
|
||||
const video = videoEl.current;
|
||||
if (!video) return;
|
||||
const handleCanPlay = () => {
|
||||
if (!video.paused) return;
|
||||
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 (!video) return false;
|
||||
// clean up when video renders for different participant
|
||||
video.srcObject = null;
|
||||
if (isChrome92) video.load();
|
||||
return () => {
|
||||
// clean up when unmounted
|
||||
video.srcObject = null;
|
||||
if (isChrome92) video.load();
|
||||
};
|
||||
}, [isChrome92, participantId, videoEl, videoTrack, videoTrack?.id]);
|
||||
}, [videoEl, isChrome92, participantId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<video
|
||||
className={classNames(fit, {
|
||||
isMirrored,
|
||||
isMobile,
|
||||
playable: isPlayable && videoTrack?.enabled,
|
||||
})}
|
||||
autoPlay
|
||||
muted
|
||||
playsInline
|
||||
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>
|
||||
</>
|
||||
);
|
||||
/**
|
||||
* Effect: mount source (and force load on Chrome)
|
||||
*/
|
||||
useEffect(() => {
|
||||
const video = videoEl.current;
|
||||
if (!video || !videoTrack) return;
|
||||
video.srcObject = new MediaStream([videoTrack]);
|
||||
if (isChrome92) video.load();
|
||||
}, [videoEl, isChrome92, videoTrack]);
|
||||
|
||||
return <video autoPlay muted playsInline ref={videoEl} {...rest} />;
|
||||
}),
|
||||
(p, n) => shallowEqualObjects(p, n)
|
||||
);
|
||||
|
|
@ -165,4 +58,4 @@ Video.propTypes = {
|
|||
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 { useCallState } from './CallProvider';
|
||||
|
|
@ -12,33 +12,72 @@ export const MediaDeviceProvider = ({ children }) => {
|
|||
const { localParticipant } = useParticipants();
|
||||
|
||||
const {
|
||||
cams,
|
||||
mics,
|
||||
speakers,
|
||||
camError,
|
||||
micError,
|
||||
currentDevices,
|
||||
cams,
|
||||
currentCam,
|
||||
currentMic,
|
||||
currentSpeaker,
|
||||
deviceState,
|
||||
setMicDevice,
|
||||
setCamDevice,
|
||||
setSpeakersDevice,
|
||||
micError,
|
||||
mics,
|
||||
refreshDevices,
|
||||
setCurrentCam,
|
||||
setCurrentMic,
|
||||
setCurrentSpeaker,
|
||||
speakers,
|
||||
} = 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 (
|
||||
<MediaDeviceContext.Provider
|
||||
value={{
|
||||
cams,
|
||||
mics,
|
||||
speakers,
|
||||
camError,
|
||||
micError,
|
||||
currentDevices,
|
||||
cams,
|
||||
currentCam,
|
||||
currentMic,
|
||||
currentSpeaker,
|
||||
deviceState,
|
||||
isCamMuted: localParticipant.isCamMuted,
|
||||
isMicMuted: localParticipant.isMicMuted,
|
||||
setMicDevice,
|
||||
setCamDevice,
|
||||
setSpeakersDevice,
|
||||
micError,
|
||||
mics,
|
||||
refreshDevices,
|
||||
setCurrentCam: selectCamera,
|
||||
setCurrentMic: selectMic,
|
||||
setCurrentSpeaker: selectSpeaker,
|
||||
speakers,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -37,12 +37,12 @@ import {
|
|||
export const ParticipantsContext = createContext();
|
||||
|
||||
export const ParticipantsProvider = ({ children }) => {
|
||||
const { callObject, videoQuality, networkState } = useCallState();
|
||||
const { callObject, videoQuality, networkState, broadcast, broadcastRole, } = useCallState();
|
||||
const [state, dispatch] = useReducer(
|
||||
participantsReducer,
|
||||
initialParticipantsState
|
||||
);
|
||||
const { viewMode } = useUIState();
|
||||
const { isMobile, viewMode, pinnedId } = useUIState();
|
||||
const [participantMarkedForRemoval, setParticipantMarkedForRemoval] =
|
||||
useState(null);
|
||||
|
||||
|
|
@ -57,7 +57,12 @@ export const ParticipantsProvider = ({ children }) => {
|
|||
/**
|
||||
* 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
|
||||
|
|
@ -95,11 +100,6 @@ export const ParticipantsProvider = ({ children }) => {
|
|||
[allParticipants]
|
||||
);
|
||||
|
||||
const isOwner = useMemo(
|
||||
() => !!localParticipant?.isOwner,
|
||||
[localParticipant]
|
||||
);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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 (
|
||||
!isPresent &&
|
||||
|
|
@ -130,22 +135,42 @@ export const ParticipantsProvider = ({ children }) => {
|
|||
.sort((a, b) => sortByKey(a, b, 'lastActiveDate'))
|
||||
.reverse();
|
||||
|
||||
return isPresent ? activeParticipant : sorted?.[0] ?? localParticipant;
|
||||
}, [activeParticipant, localParticipant, participants]);
|
||||
const fallback = broadcastRole === 'attendee' ? null : localParticipant;
|
||||
|
||||
return isPresent ? activeParticipant : sorted?.[0] ?? fallback;
|
||||
}, [
|
||||
activeParticipant,
|
||||
broadcastRole,
|
||||
isMobile,
|
||||
localParticipant,
|
||||
participants,
|
||||
pinnedId,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Screen shares
|
||||
*/
|
||||
const screens = useMemo(
|
||||
() => allParticipants.filter(({ isScreenshare }) => isScreenshare),
|
||||
[allParticipants]
|
||||
);
|
||||
const screens = useMemo(() => state?.screens, [state?.screens]);
|
||||
|
||||
/**
|
||||
* The local participant's 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 muteAll = useCallback(
|
||||
|
|
@ -165,22 +190,20 @@ export const ParticipantsProvider = ({ children }) => {
|
|||
[callObject, localParticipant, participants]
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the local participant's name in daily-js
|
||||
* @param name The new username
|
||||
*/
|
||||
const setUsername = (name) => {
|
||||
callObject.setUserName(name);
|
||||
};
|
||||
|
||||
const swapParticipantPosition = (id1, id2) => {
|
||||
const swapParticipantPosition = useCallback((id1, id2) => {
|
||||
/**
|
||||
* Ignore in the following cases:
|
||||
* - id1 and id2 are equal
|
||||
* - one of both ids is not set
|
||||
* - one of both ids is 'local'
|
||||
*/
|
||||
if (id1 === id2 || !id1 || !id2 || isLocalId(id1) || isLocalId(id2)) return;
|
||||
dispatch({
|
||||
type: SWAP_POSITION,
|
||||
type: 'SWAP_POSITION',
|
||||
id1,
|
||||
id2,
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleNewParticipantsState = useCallback(
|
||||
(event = null) => {
|
||||
|
|
@ -241,10 +264,11 @@ export const ParticipantsProvider = ({ children }) => {
|
|||
/**
|
||||
* Change between the simulcast layers based on view / available bandwidth
|
||||
*/
|
||||
|
||||
const setBandWidthControls = useCallback(() => {
|
||||
if (!(callObject && callObject.meetingState() === 'joined-meeting')) return;
|
||||
|
||||
const ids = participantIds.split(',');
|
||||
const ids = participantIds.split(',').filter(Boolean);
|
||||
const receiveSettings = {};
|
||||
|
||||
ids.forEach((id) => {
|
||||
|
|
@ -252,19 +276,16 @@ export const ParticipantsProvider = ({ children }) => {
|
|||
|
||||
if (
|
||||
// weak or bad network
|
||||
([VIDEO_QUALITY_LOW, VIDEO_QUALITY_VERY_LOW].includes(networkState) &&
|
||||
videoQuality === VIDEO_QUALITY_AUTO) ||
|
||||
(['low', 'very-low'].includes(networkState) && videoQuality === 'auto') ||
|
||||
// Low quality or Bandwidth saver mode enabled
|
||||
[VIDEO_QUALITY_BANDWIDTH_SAVER, VIDEO_QUALITY_LOW].includes(
|
||||
videoQuality
|
||||
)
|
||||
['bandwidth-saver', 'low'].includes(videoQuality)
|
||||
) {
|
||||
receiveSettings[id] = { video: { layer: 0 } };
|
||||
return;
|
||||
}
|
||||
|
||||
// Speaker view settings based on speaker status or pinned user
|
||||
if (viewMode === VIEW_MODE_SPEAKER) {
|
||||
if (viewMode === 'speaker') {
|
||||
if (currentSpeaker?.id === id) {
|
||||
receiveSettings[id] = { video: { layer: 2 } };
|
||||
} else {
|
||||
|
|
@ -273,16 +294,11 @@ export const ParticipantsProvider = ({ children }) => {
|
|||
}
|
||||
|
||||
// Grid view settings are handled separately in GridView
|
||||
// Mobile view settings are handled separately in MobileCall
|
||||
});
|
||||
|
||||
callObject.updateReceiveSettings(receiveSettings);
|
||||
}, [
|
||||
currentSpeaker?.id,
|
||||
callObject,
|
||||
networkState,
|
||||
participantIds,
|
||||
videoQuality,
|
||||
viewMode,
|
||||
]);
|
||||
}, [callObject, participantIds, networkState, videoQuality, viewMode, currentSpeaker?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
setBandWidthControls();
|
||||
|
|
@ -315,12 +331,12 @@ export const ParticipantsProvider = ({ children }) => {
|
|||
allParticipants,
|
||||
currentSpeaker,
|
||||
localParticipant,
|
||||
muteAll,
|
||||
muteNewParticipants,
|
||||
participantCount,
|
||||
participantMarkedForRemoval,
|
||||
participants,
|
||||
screens,
|
||||
muteNewParticipants,
|
||||
muteAll,
|
||||
setParticipantMarkedForRemoval,
|
||||
setUsername,
|
||||
swapParticipantPosition,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { useDeepCompareEffect } from 'use-deep-compare';
|
|||
import { sortByKey } from '../lib/sortByKey';
|
||||
import { useCallState } from './CallProvider';
|
||||
import { useParticipants } from './ParticipantsProvider';
|
||||
import { useUIState } from './UIStateProvider';
|
||||
import { isLocalId, isScreenId } from './participantsState';
|
||||
import {
|
||||
initialTracksState,
|
||||
|
|
@ -42,6 +43,7 @@ const TracksContext = createContext(null);
|
|||
export const TracksProvider = ({ children }) => {
|
||||
const { callObject, subscribeToTracksAutomatically } = useCallState();
|
||||
const { participants } = useParticipants();
|
||||
const { viewMode } = useUIState();
|
||||
const [state, dispatch] = useReducer(tracksReducer, initialTracksState);
|
||||
|
||||
const recentSpeakerIds = useMemo(
|
||||
|
|
@ -83,13 +85,19 @@ export const TracksProvider = ({ children }) => {
|
|||
// stage all remote cams that aren't already marked for subscription.
|
||||
// Otherwise, honor the provided stagedIds, with recent speakers appended
|
||||
// who aren't already marked for subscription.
|
||||
const stagedIdsFiltered =
|
||||
if (
|
||||
remoteParticipantIds.length <= SUBSCRIBE_OR_STAGE_ALL_VIDEO_THRESHOLD
|
||||
? remoteParticipantIds.filter((id) => !subscribedIds.includes(id))
|
||||
: [
|
||||
...stagedIds,
|
||||
...recentSpeakerIds.filter((id) => !subscribedIds.includes(id)),
|
||||
];
|
||||
) {
|
||||
stagedIds = remoteParticipantIds.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
|
||||
const updates = remoteParticipantIds.reduce((u, id) => {
|
||||
|
|
@ -104,7 +112,7 @@ export const TracksProvider = ({ children }) => {
|
|||
// subscribed, staged, or unsubscribed
|
||||
if (subscribedIds.includes(id)) {
|
||||
desiredSubscription = true;
|
||||
} else if (stagedIdsFiltered.includes(id)) {
|
||||
} else if (stagedIds.includes(id)) {
|
||||
desiredSubscription = 'staged';
|
||||
} else {
|
||||
desiredSubscription = false;
|
||||
|
|
@ -116,9 +124,6 @@ export const TracksProvider = ({ children }) => {
|
|||
|
||||
u[id] = {
|
||||
setSubscribedTracks: {
|
||||
audio: true,
|
||||
screenAudio: true,
|
||||
screenVideo: true,
|
||||
video: desiredSubscription,
|
||||
},
|
||||
};
|
||||
|
|
@ -128,7 +133,7 @@ export const TracksProvider = ({ children }) => {
|
|||
if (Object.keys(updates).length === 0) return;
|
||||
callObject.updateParticipants(updates);
|
||||
},
|
||||
[callObject, remoteParticipantIds, recentSpeakerIds]
|
||||
[callObject, remoteParticipantIds, viewMode, recentSpeakerIds]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ export const UIStateProvider = ({
|
|||
customTrayComponent,
|
||||
children,
|
||||
}) => {
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [pinnedId, setPinnedId] = useState(null);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [preferredViewMode, setPreferredViewMode] = useState(VIEW_MODE_SPEAKER);
|
||||
const [viewMode, setViewMode] = useState(preferredViewMode);
|
||||
const [isShowingScreenshare, setIsShowingScreenshare] = useState(false);
|
||||
|
|
@ -31,6 +31,20 @@ export const UIStateProvider = ({
|
|||
const [customCapsule, setCustomCapsule] = useState();
|
||||
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) => {
|
||||
setActiveModals((prevState) => ({
|
||||
...prevState,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ const initialParticipantsState = {
|
|||
camMutedByHost: false,
|
||||
hasNameSet: false,
|
||||
id: 'local',
|
||||
user_id: '',
|
||||
isActiveSpeaker: false,
|
||||
isCamMuted: false,
|
||||
isLoading: true,
|
||||
|
|
@ -34,6 +33,7 @@ const initialParticipantsState = {
|
|||
lastActiveDate: null,
|
||||
micMutedByHost: false,
|
||||
name: '',
|
||||
sessionId: '',
|
||||
},
|
||||
],
|
||||
screens: [],
|
||||
|
|
@ -42,7 +42,7 @@ const initialParticipantsState = {
|
|||
// --- Derived data ---
|
||||
|
||||
function getId(participant) {
|
||||
return participant.local ? 'local' : participant.user_id;
|
||||
return participant.local ? 'local' : participant.session_id;
|
||||
}
|
||||
|
||||
function getScreenId(id) {
|
||||
|
|
@ -69,28 +69,25 @@ function getNewParticipant(participant) {
|
|||
camMutedByHost: video?.off?.byRemoteRequest,
|
||||
hasNameSet: !!participant.user_name,
|
||||
id,
|
||||
user_id: participant.user_id,
|
||||
isActiveSpeaker: false,
|
||||
isCamMuted:
|
||||
video?.state === DEVICE_STATE_OFF ||
|
||||
video?.state === DEVICE_STATE_BLOCKED,
|
||||
isLoading:
|
||||
audio?.state === DEVICE_STATE_LOADING ||
|
||||
video?.state === DEVICE_STATE_LOADING,
|
||||
isCamMuted: video?.state === 'off' || video?.state === 'blocked',
|
||||
isLoading: audio?.state === 'loading' || video?.state === 'loading',
|
||||
isLocal: local,
|
||||
isMicMuted:
|
||||
audio?.state === DEVICE_STATE_OFF ||
|
||||
audio?.state === DEVICE_STATE_BLOCKED,
|
||||
isMicMuted: audio?.state === 'off' || audio?.state === 'blocked',
|
||||
isOwner: !!participant.owner,
|
||||
isRecording: !!participant.record,
|
||||
isScreenshare: false,
|
||||
lastActiveDate: null,
|
||||
micMutedByHost: audio?.off?.byRemoteRequest,
|
||||
name: participant.user_name,
|
||||
sessionId: participant.session_id,
|
||||
};
|
||||
}
|
||||
|
||||
function getUpdatedParticipant(participant, participants) {
|
||||
function getUpdatedParticipant(
|
||||
participant,
|
||||
participants
|
||||
) {
|
||||
const id = getId(participant);
|
||||
const prevItem = participants.find((p) => p.id === id);
|
||||
|
||||
|
|
@ -99,26 +96,21 @@ function getUpdatedParticipant(participant, participants) {
|
|||
|
||||
const { local } = participant;
|
||||
const { audio, video } = participant.tracks;
|
||||
|
||||
return {
|
||||
...prevItem,
|
||||
camMutedByHost: video?.off?.byRemoteRequest,
|
||||
hasNameSet: !!participant.user_name,
|
||||
id,
|
||||
user_id: participant.user_id,
|
||||
isCamMuted:
|
||||
video?.state === DEVICE_STATE_OFF ||
|
||||
video?.state === DEVICE_STATE_BLOCKED,
|
||||
isLoading:
|
||||
audio?.state === DEVICE_STATE_LOADING ||
|
||||
video?.state === DEVICE_STATE_LOADING,
|
||||
isCamMuted: video?.state === 'off' || video?.state === 'blocked',
|
||||
isLoading: audio?.state === 'loading' || video?.state === 'loading',
|
||||
isLocal: local,
|
||||
isMicMuted:
|
||||
audio?.state === DEVICE_STATE_OFF ||
|
||||
audio?.state === DEVICE_STATE_BLOCKED,
|
||||
isMicMuted: audio?.state === 'off' || audio?.state === 'blocked',
|
||||
isOwner: !!participant.owner,
|
||||
isRecording: !!participant.record,
|
||||
micMutedByHost: audio?.off?.byRemoteRequest,
|
||||
name: participant.user_name,
|
||||
sessionId: participant.session_id,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +124,7 @@ function getScreenItem(participant) {
|
|||
isScreenshare: true,
|
||||
lastActiveDate: null,
|
||||
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_ALLOWED = 'not-allowed';
|
||||
export const CALL_STATE_AWAITING_ARGS = 'awaiting-args';
|
||||
export const CALL_STATE_NOT_SECURE = 'not-secure';
|
||||
|
||||
export const useCallMachine = ({
|
||||
domain,
|
||||
|
|
@ -78,9 +79,10 @@ export const useCallMachine = ({
|
|||
const join = useCallback(
|
||||
async (callObject) => {
|
||||
setState(CALL_STATE_JOINING);
|
||||
const dailyRoomInfo = await callObject.room();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
@ -188,6 +190,15 @@ export const useCallMachine = ({
|
|||
useEffect(() => {
|
||||
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');
|
||||
|
||||
const co = DailyIframe.createCallObject({
|
||||
|
|
@ -206,7 +217,7 @@ export const useCallMachine = ({
|
|||
* Listen for changes in the participant's access state
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!daily) return false;
|
||||
if (!daily) return;
|
||||
|
||||
daily.on('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) => {
|
||||
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 [mics, setMics] = useState([]);
|
||||
|
|
@ -38,6 +40,10 @@ export const useDevices = (callObject) => {
|
|||
|
||||
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 !== ''
|
||||
);
|
||||
|
|
@ -66,12 +72,6 @@ export const useDevices = (callObject) => {
|
|||
].filter(Boolean)
|
||||
);
|
||||
|
||||
setCurrentDevices({
|
||||
camera,
|
||||
mic,
|
||||
speaker,
|
||||
});
|
||||
|
||||
console.log(`Current cam: ${camera.label}`);
|
||||
console.log(`Current mic: ${mic.label}`);
|
||||
console.log(`Current speakers: ${speaker.label}`);
|
||||
|
|
@ -125,31 +125,26 @@ export const useDevices = (callObject) => {
|
|||
|
||||
const handleParticipantUpdated = useCallback(
|
||||
({ participant }) => {
|
||||
if (!callObject || !participant.local) return;
|
||||
if (!callObject || deviceState === 'not-supported' || !participant.local) return;
|
||||
|
||||
setDeviceState((prevState) => {
|
||||
if (prevState === DEVICE_STATE_NOT_SUPPORTED) return prevState;
|
||||
switch (participant?.tracks.video.state) {
|
||||
case DEVICE_STATE_BLOCKED:
|
||||
updateDeviceErrors();
|
||||
return DEVICE_STATE_ERROR;
|
||||
case DEVICE_STATE_OFF:
|
||||
case DEVICE_STATE_PLAYABLE:
|
||||
if (prevState === DEVICE_STATE_GRANTED) {
|
||||
return prevState;
|
||||
}
|
||||
updateDeviceState();
|
||||
return DEVICE_STATE_GRANTED;
|
||||
default:
|
||||
return prevState;
|
||||
}
|
||||
});
|
||||
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, updateDeviceState, updateDeviceErrors]
|
||||
[callObject, deviceState, updateDeviceErrors, updateDeviceState]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!callObject) return false;
|
||||
if (!callObject) return;
|
||||
|
||||
/**
|
||||
If the user is slow to allow access, we'll update the device state
|
||||
|
|
@ -169,6 +164,7 @@ export const useDevices = (callObject) => {
|
|||
updateDeviceState();
|
||||
};
|
||||
|
||||
updateDeviceState();
|
||||
callObject.on('joining-meeting', handleJoiningMeeting);
|
||||
callObject.on('joined-meeting', handleJoinedMeeting);
|
||||
callObject.on('participant-updated', handleParticipantUpdated);
|
||||
|
|
@ -180,74 +176,8 @@ export const useDevices = (callObject) => {
|
|||
};
|
||||
}, [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(() => {
|
||||
if (!callObject) return false;
|
||||
if (!callObject) return;
|
||||
|
||||
console.log('💻 Device provider events bound');
|
||||
|
||||
|
|
@ -313,16 +243,19 @@ export const useDevices = (callObject) => {
|
|||
}, [callObject, updateDeviceErrors]);
|
||||
|
||||
return {
|
||||
cams,
|
||||
mics,
|
||||
speakers,
|
||||
camError,
|
||||
micError,
|
||||
currentDevices,
|
||||
cams,
|
||||
currentCam,
|
||||
currentMic,
|
||||
currentSpeaker,
|
||||
deviceState,
|
||||
setCamDevice,
|
||||
setMicDevice,
|
||||
setSpeakersDevice,
|
||||
micError,
|
||||
mics,
|
||||
refreshDevices: updateDeviceState,
|
||||
setCurrentCam,
|
||||
setCurrentMic,
|
||||
setCurrentSpeaker,
|
||||
speakers,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
export const sortByKey = (a, b, key, caseSensitive = true) => {
|
||||
const aKey =
|
||||
!caseSensitive && typeof a[key] === 'string'
|
||||
? a[key]?.toLowerCase()
|
||||
? String(a[key])?.toLowerCase()
|
||||
: a[key];
|
||||
const bKey =
|
||||
!caseSensitive && typeof b[key] === 'string'
|
||||
? b[key]?.toLowerCase()
|
||||
? String(b[key])?.toLowerCase()
|
||||
: b[key];
|
||||
if (aKey > bKey) return 1;
|
||||
if (aKey < bKey) return -1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
export default sortByKey;
|
||||
};
|
||||
Loading…
Reference in New Issue