diff --git a/custom/fitness-demo/components/Call/Container.js b/custom/fitness-demo/components/Call/Container.js
index 22e0d08..5e9cb68 100644
--- a/custom/fitness-demo/components/Call/Container.js
+++ b/custom/fitness-demo/components/Call/Container.js
@@ -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';
diff --git a/custom/shared/components/DeviceSelect/DeviceSelect.js b/custom/shared/components/DeviceSelect/DeviceSelect.js
index f0b0153..b47c63e 100644
--- a/custom/shared/components/DeviceSelect/DeviceSelect.js
+++ b/custom/shared/components/DeviceSelect/DeviceSelect.js
@@ -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
Loading devices...
;
}
@@ -22,9 +24,9 @@ export const DeviceSelect = () => {
<>
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 = () => {
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 && (
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) => (
diff --git a/custom/shared/components/Tile/Video.js b/custom/shared/components/Tile/Video.js
index 5e975be..c9541cc 100644
--- a/custom/shared/components/Tile/Video.js
+++ b/custom/shared/components/Tile/Video.js
@@ -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 (
- <>
-
-
- >
- );
+ /**
+ * 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 ;
}),
(p, n) => shallowEqualObjects(p, n)
);
@@ -165,4 +58,4 @@ Video.propTypes = {
participantId: PropTypes.string,
};
-export default Video;
+export default Video;
\ No newline at end of file
diff --git a/custom/shared/contexts/MediaDeviceProvider.js b/custom/shared/contexts/MediaDeviceProvider.js
index 2e5e6e7..b0b4349 100644
--- a/custom/shared/contexts/MediaDeviceProvider.js
+++ b/custom/shared/contexts/MediaDeviceProvider.js
@@ -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 (
{children}
diff --git a/custom/shared/contexts/ParticipantsProvider.js b/custom/shared/contexts/ParticipantsProvider.js
index f6c9428..2e41e96 100644
--- a/custom/shared/contexts/ParticipantsProvider.js
+++ b/custom/shared/contexts/ParticipantsProvider.js
@@ -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,
diff --git a/custom/shared/contexts/TracksProvider.js b/custom/shared/contexts/TracksProvider.js
index f61ffea..06931ba 100644
--- a/custom/shared/contexts/TracksProvider.js
+++ b/custom/shared/contexts/TracksProvider.js
@@ -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(() => {
diff --git a/custom/shared/contexts/UIStateProvider.js b/custom/shared/contexts/UIStateProvider.js
index 0a376fb..cf283d5 100644
--- a/custom/shared/contexts/UIStateProvider.js
+++ b/custom/shared/contexts/UIStateProvider.js
@@ -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,
diff --git a/custom/shared/contexts/participantsState.js b/custom/shared/contexts/participantsState.js
index 1b2a47c..081b8a5 100644
--- a/custom/shared/contexts/participantsState.js
+++ b/custom/shared/contexts/participantsState.js
@@ -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,
};
}
diff --git a/custom/shared/contexts/useCallMachine.js b/custom/shared/contexts/useCallMachine.js
index 2d693c9..979691b 100644
--- a/custom/shared/contexts/useCallMachine.js
+++ b/custom/shared/contexts/useCallMachine.js
@@ -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);
diff --git a/custom/shared/contexts/useDevices.js b/custom/shared/contexts/useDevices.js
index b2e677e..ee4362a 100644
--- a/custom/shared/contexts/useDevices.js
+++ b/custom/shared/contexts/useDevices.js
@@ -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,
};
};
diff --git a/custom/shared/lib/sortByKey.js b/custom/shared/lib/sortByKey.js
index e13de49..de09b11 100644
--- a/custom/shared/lib/sortByKey.js
+++ b/custom/shared/lib/sortByKey.js
@@ -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;
+};
\ No newline at end of file