diff --git a/dailyjs/shared/contexts/participantsState.js b/dailyjs/shared/contexts/participantsState.js index 77a1e27..910a440 100644 --- a/dailyjs/shared/contexts/participantsState.js +++ b/dailyjs/shared/contexts/participantsState.js @@ -8,6 +8,7 @@ * - A session id for each remote participant * - "-screen" for each shared screen */ +import fasteq from 'fast-deep-equal'; import { DEVICE_STATE_OFF, DEVICE_STATE_BLOCKED, @@ -15,8 +16,9 @@ import { } from './useDevices'; const initialParticipantsState = { - participants: { - local: { + lastPendingUnknownActiveSpeaker: null, + participants: [ + { camMutedByHost: false, hasNameSet: false, id: 'local', @@ -31,9 +33,9 @@ const initialParticipantsState = { lastActiveDate: null, micMutedByHost: false, name: '', - position: 1, }, - }, + ], + screens: [], }; // --- Derived data --- @@ -63,36 +65,7 @@ function getMaxPosition(participants) { ); } -function getUpdatedParticipant(participant, participants) { - const id = getId(participant); - const prevItem = participants[id]; - - const { local } = participant; - const { audio, video } = participant.tracks; - - return { - ...prevItem, - camMutedByHost: video?.off?.byRemoteRequest, - hasNameSet: !!participant.user_name, - id, - isCamMuted: - video?.state === DEVICE_STATE_OFF || - video?.state === DEVICE_STATE_BLOCKED, - isLoading: - audio?.state === DEVICE_STATE_LOADING || - video?.state === DEVICE_STATE_LOADING, - isLocal: local, - isMicMuted: - audio?.state === DEVICE_STATE_OFF || - audio?.state === DEVICE_STATE_BLOCKED, - isOwner: !!participant.owner, - isRecording: !!participant.record, - micMutedByHost: audio?.off?.byRemoteRequest, - name: participant.user_name, - }; -} - -function getNewParticipant(participant, participants) { +function getNewParticipant(participant) { const id = getId(participant); const { local } = participant; @@ -119,11 +92,41 @@ function getNewParticipant(participant, participants) { lastActiveDate: null, micMutedByHost: audio?.off?.byRemoteRequest, name: participant.user_name, - position: local ? 0 : getMaxPosition(participants) + 1, }; } -function getScreenItem(participant, participants) { +function getUpdatedParticipant(participant, participants) { + const id = getId(participant); + const prevItem = participants.find((p) => p.id === id); + + // In case we haven't set up this participant, yet. + if (!prevItem) return getNewParticipant(participant); + + const { local } = participant; + const { audio, video } = participant.tracks; + return { + ...prevItem, + camMutedByHost: video?.off?.byRemoteRequest, + hasNameSet: !!participant.user_name, + id, + isCamMuted: + video?.state === DEVICE_STATE_OFF || + video?.state === DEVICE_STATE_BLOCKED, + isLoading: + audio?.state === DEVICE_STATE_LOADING || + video?.state === DEVICE_STATE_LOADING, + isLocal: local, + isMicMuted: + audio?.state === DEVICE_STATE_OFF || + audio?.state === DEVICE_STATE_BLOCKED, + isOwner: !!participant.owner, + isRecording: !!participant.record, + micMutedByHost: audio?.off?.byRemoteRequest, + name: participant.user_name, + }; +} + +function getScreenItem(participant) { const id = getId(participant); return { hasNameSet: null, @@ -133,7 +136,6 @@ function getScreenItem(participant, participants) { isScreenshare: true, lastActiveDate: null, name: participant.user_name, - position: getMaxPosition(participants) + 1, }; } @@ -151,49 +153,66 @@ function participantsReducer(prevState, action) { switch (action.type) { case ACTIVE_SPEAKER: { const { participants, ...state } = prevState; - if (!action.id) return prevState; + if (!action.id) + return { + ...prevState, + lastPendingUnknownActiveSpeaker: null, + }; + const date = new Date(); + const isParticipantKnown = participants.some((p) => p.id === action.id); return { ...state, - participants: Object.keys(participants).reduce( - (items, id) => ({ - ...items, - [id]: { - ...participants[id], - isActiveSpeaker: id === action.id, - lastActiveDate: - id === action.id - ? new Date() - : participants[id]?.lastActiveDate, + lastPendingUnknownActiveSpeaker: isParticipantKnown + ? null + : { + date, + id: action.id, }, - }), - {} - ), + participants: participants.map((p) => ({ + ...p, + isActiveSpeaker: p.id === action.id, + lastActiveDate: p.id === action.id ? date : p?.lastActiveDate, + })), }; } case PARTICIPANT_JOINED: { - const item = getNewParticipant( - action.participant, - prevState.participants - ); - const { id } = item; - const screenId = getScreenId(id); + const item = getNewParticipant(action.participant); - const newParticipants = { - ...prevState.participants, - [id]: item, - }; + const participants = [...prevState.participants]; + const screens = [...prevState.screens]; + + const isPendingActiveSpeaker = + item.id === prevState.lastPendingUnknownActiveSpeaker?.id; + if (isPendingActiveSpeaker) { + item.isActiveSpeaker = true; + item.lastActiveDate = prevState.lastPendingUnknownActiveSpeaker?.date; + } + + if (item.isCamMuted) { + participants.push(item); + } else { + const firstInactiveCamOffIndex = prevState.participants.findIndex( + (p) => p.isCamMuted && !p.isLocal && !p.isActiveSpeaker + ); + if (firstInactiveCamOffIndex >= 0) { + participants.splice(firstInactiveCamOffIndex, 0, item); + } else { + participants.push(item); + } + } // Participant is sharing screen if (action.participant.screen) { - newParticipants[screenId] = getScreenItem( - action.participant, - newParticipants - ); + screens.push(getScreenItem(action.participant)); } return { ...prevState, - participants: newParticipants, + lastPendingUnknownActiveSpeaker: isPendingActiveSpeaker + ? null + : prevState.lastPendingUnknownActiveSpeaker, + participants, + screens, }; } case PARTICIPANT_UPDATED: { @@ -204,60 +223,58 @@ function participantsReducer(prevState, action) { const { id } = item; const screenId = getScreenId(id); - const newParticipants = { - ...prevState.participants, - }; - newParticipants[id] = item; + const participants = [...prevState.participants]; + const idx = participants.findIndex((p) => p.id === id); + participants[idx] = item; + + const screens = [...prevState.screens]; + const screenIdx = screens.findIndex((s) => s.id === screenId); if (action.participant.screen) { - newParticipants[screenId] = getScreenItem( - action.participant, - newParticipants - ); - } else { - delete newParticipants[screenId]; + const screenItem = getScreenItem(action.participant); + if (screenIdx >= 0) { + screens[screenIdx] = screenItem; + } else { + screens.push(screenItem); + } + } else if (screenIdx >= 0) { + screens.splice(screenIdx, 1); } - return { + const newState = { ...prevState, - participants: newParticipants, + participants, + screens, }; + + if (fasteq(newState, prevState)) { + return prevState; + } + + return newState; } case PARTICIPANT_LEFT: { const id = getId(action.participant); const screenId = getScreenId(id); - const { ...participants } = prevState.participants; - delete participants[id]; - delete participants[screenId]; + return { ...prevState, - participants, + participants: [...prevState.participants].filter((p) => p.id !== id), + screens: [...prevState.screens].filter((s) => s.id !== screenId), }; } case SWAP_POSITION: { - const { participants, ...state } = prevState; + const participants = [...prevState.participants]; if (!action.id1 || !action.id2) return prevState; - const pos1 = participants[action.id1]?.position; - const pos2 = participants[action.id2]?.position; - if (!pos1 || !pos2) return prevState; + const idx1 = participants.findIndex((p) => p.id === action.id1); + const idx2 = participants.findIndex((p) => p.id === action.id2); + if (idx1 === -1 || idx2 === -1) return prevState; + const tmp = participants[idx1]; + participants[idx1] = participants[idx2]; + participants[idx2] = tmp; return { - ...state, - participants: Object.keys(participants).reduce((items, id) => { - let { position } = participants[id]; - if (action.id1 === id) { - position = pos2; - } - if (action.id2 === id) { - position = pos1; - } - return { - ...items, - [id]: { - ...participants[id], - position, - }, - }; - }, {}), + ...prevState, + participants, }; } default: diff --git a/dailyjs/shared/package.json b/dailyjs/shared/package.json index d9fe3c3..0cb5de2 100644 --- a/dailyjs/shared/package.json +++ b/dailyjs/shared/package.json @@ -7,6 +7,7 @@ "@daily-co/daily-js": "^0.12.0", "classnames": "^2.3.1", "debounce": "^1.2.1", + "fast-deep-equal": "^3.1.3", "nanoid": "^3.1.23", "no-scroll": "^2.1.1", "prop-types": "^15.7.2", diff --git a/yarn.lock b/yarn.lock index 7d557c0..c09f462 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1532,7 +1532,7 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==