diff --git a/react/features/filmstrip/actions.any.js b/react/features/filmstrip/actions.any.js new file mode 100644 index 000000000..f3c3d9cf7 --- /dev/null +++ b/react/features/filmstrip/actions.any.js @@ -0,0 +1,52 @@ +// @flow + +import { SET_FILMSTRIP_ENABLED, SET_FILMSTRIP_VISIBLE, SET_REMOTE_PARTICIPANTS } from './actionTypes'; + +/** + * Sets whether the filmstrip is enabled. + * + * @param {boolean} enabled - Whether the filmstrip is enabled. + * @returns {{ + * type: SET_FILMSTRIP_ENABLED, + * enabled: boolean + * }} + */ +export function setFilmstripEnabled(enabled: boolean) { + return { + type: SET_FILMSTRIP_ENABLED, + enabled + }; +} + +/** + * Sets whether the filmstrip is visible. + * + * @param {boolean} visible - Whether the filmstrip is visible. + * @returns {{ + * type: SET_FILMSTRIP_VISIBLE, + * visible: boolean + * }} + */ +export function setFilmstripVisible(visible: boolean) { + return { + type: SET_FILMSTRIP_VISIBLE, + visible + }; +} + +/** + * Sets the list of the reordered remote participants based on which the visible participants in the filmstrip will be + * determined. + * + * @param {Array} participants - The list of the remote participant endpoint IDs. + * @returns {{ + type: SET_REMOTE_PARTICIPANTS, + participants: Array + }} + */ +export function setRemoteParticipants(participants: Array) { + return { + type: SET_REMOTE_PARTICIPANTS, + participants + }; +} diff --git a/react/features/filmstrip/actions.native.js b/react/features/filmstrip/actions.native.js index 419df8f27..a70d6d951 100644 --- a/react/features/filmstrip/actions.native.js +++ b/react/features/filmstrip/actions.native.js @@ -1,42 +1,8 @@ // @flow -import { - SET_FILMSTRIP_ENABLED, - SET_FILMSTRIP_VISIBLE, - SET_TILE_VIEW_DIMENSIONS -} from './actionTypes'; +import { SET_TILE_VIEW_DIMENSIONS } from './actionTypes'; -/** - * Sets whether the filmstrip is enabled. - * - * @param {boolean} enabled - Whether the filmstrip is enabled. - * @returns {{ - * type: SET_FILMSTRIP_ENABLED, - * enabled: boolean - * }} - */ -export function setFilmstripEnabled(enabled: boolean) { - return { - type: SET_FILMSTRIP_ENABLED, - enabled - }; -} - -/** - * Sets whether the filmstrip is visible. - * - * @param {boolean} visible - Whether the filmstrip is visible. - * @returns {{ - * type: SET_FILMSTRIP_VISIBLE, - * visible: boolean - * }} - */ -export function setFilmstripVisible(visible: boolean) { - return { - type: SET_FILMSTRIP_VISIBLE, - visible - }; -} +export * from './actions.any'; /** * Sets the dimensions of the tile view grid. The action is only partially implemented on native as not all diff --git a/react/features/filmstrip/actions.web.js b/react/features/filmstrip/actions.web.js index 7911eea54..2c7661923 100644 --- a/react/features/filmstrip/actions.web.js +++ b/react/features/filmstrip/actions.web.js @@ -5,7 +5,6 @@ import { getLocalParticipant, getRemoteParticipants, pinParticipant } from '../b import { SET_HORIZONTAL_VIEW_DIMENSIONS, - SET_REMOTE_PARTICIPANTS, SET_TILE_VIEW_DIMENSIONS, SET_VERTICAL_VIEW_DIMENSIONS, SET_VISIBLE_REMOTE_PARTICIPANTS, @@ -26,22 +25,7 @@ import { calculateThumbnailSizeForVerticalView } from './functions'; -/** - * Sets the list of the reordered remote participants based on which the visible participants in the filmstrip will be - * determined. - * - * @param {Array} participants - The list of the remote participant endpoint IDs. - * @returns {{ - type: SET_REMOTE_PARTICIPANTS, - participants: Array - }} - */ -export function setRemoteParticipants(participants: Array) { - return { - type: SET_REMOTE_PARTICIPANTS, - participants - }; -} +export * from './actions.any'; /** * Sets the dimensions of the tile view grid. @@ -192,5 +176,3 @@ export function setVisibleRemoteParticipants(startIndex: number, endIndex: numbe endIndex }; } - -export * from './actions.native'; diff --git a/react/features/filmstrip/components/native/Thumbnail.js b/react/features/filmstrip/components/native/Thumbnail.js index 09920e3a9..f7c2f075d 100644 --- a/react/features/filmstrip/components/native/Thumbnail.js +++ b/react/features/filmstrip/components/native/Thumbnail.js @@ -136,6 +136,13 @@ function Thumbnail(props: Props) { tileView } = props; + // It seems that on leave the Thumbnail for the left participant can be re-rendered. + // This will happen when mapStateToProps is executed before the remoteParticipants list in redux is updated. + if (typeof participant === 'undefined') { + + return null; + } + const participantId = participant.id; const participantInLargeVideo = participantId === largeVideo.participantId; diff --git a/react/features/filmstrip/functions.any.js b/react/features/filmstrip/functions.any.js new file mode 100644 index 000000000..973e6b8b5 --- /dev/null +++ b/react/features/filmstrip/functions.any.js @@ -0,0 +1,56 @@ +// @flow + +import { setRemoteParticipants } from './actions'; + +/** + * Computes the reorderd list of the remote participants. + * + * @param {*} store - The redux store. + * @returns {void} + * @private + */ +export function updateRemoteParticipants(store: Object) { + const state = store.getState(); + const { fakeParticipants, sortedRemoteParticipants, speakersList } = state['features/base/participants']; + const { remoteScreenShares } = state['features/video-layout']; + const screenShares = (remoteScreenShares || []).slice(); + let speakers = (speakersList || []).slice(); + const remoteParticipants = new Map(sortedRemoteParticipants); + const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : []; + + for (const screenshare of screenShares) { + remoteParticipants.delete(screenshare); + speakers = speakers.filter(speaker => speaker !== screenshare); + } + for (const sharedVideo of sharedVideos) { + remoteParticipants.delete(sharedVideo); + speakers = speakers.filter(speaker => speaker !== sharedVideo); + } + for (const speaker of speakers) { + remoteParticipants.delete(speaker); + } + const reorderedParticipants + = [ ...screenShares.reverse(), ...sharedVideos, ...speakers, ...Array.from(remoteParticipants.keys()) ]; + + store.dispatch(setRemoteParticipants(reorderedParticipants)); +} + +/** + * Private helper to calculate the reordered list of remote participants when a participant leaves. + * + * @param {*} store - The redux store. + * @param {string} participantId - The endpoint id of the participant leaving the call. + * @returns {void} + * @private + */ +export function updateRemoteParticipantsOnLeave(store: Object, participantId: ?string = null) { + if (!participantId) { + return; + } + const state = store.getState(); + const { remoteParticipants } = state['features/filmstrip']; + const reorderedParticipants = new Set(remoteParticipants); + + reorderedParticipants.delete(participantId) + && store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants))); +} diff --git a/react/features/filmstrip/functions.native.js b/react/features/filmstrip/functions.native.js index eb9a784ce..c6df2e714 100644 --- a/react/features/filmstrip/functions.native.js +++ b/react/features/filmstrip/functions.native.js @@ -4,6 +4,8 @@ import { getFeatureFlag, FILMSTRIP_ENABLED } from '../base/flags'; import { getParticipantCountWithFake, getPinnedParticipant } from '../base/participants'; import { toState } from '../base/redux'; +export * from './functions.any'; + /** * Returns true if the filmstrip on mobile is visible, false otherwise. * diff --git a/react/features/filmstrip/functions.web.js b/react/features/filmstrip/functions.web.js index a687b3192..7b73c6587 100644 --- a/react/features/filmstrip/functions.web.js +++ b/react/features/filmstrip/functions.web.js @@ -16,7 +16,6 @@ import { isRemoteTrackMuted } from '../base/tracks/functions'; -import { setRemoteParticipants } from './actions.web'; import { ASPECT_RATIO_BREAKPOINT, DISPLAY_AVATAR, @@ -33,6 +32,8 @@ import { VERTICAL_FILMSTRIP_MIN_HORIZONTAL_MARGIN } from './constants'; +export * from './functions.any'; + declare var interfaceConfig: Object; /** @@ -266,36 +267,3 @@ export function computeDisplayMode(input: Object) { // check hovering and change state to avatar with name return isHovered ? DISPLAY_AVATAR_WITH_NAME : DISPLAY_AVATAR; } - -/** - * Computes the reorderd list of the remote participants. - * - * @param {*} store - The redux store. - * @returns {void} - * @private - */ -export function updateRemoteParticipants(store: Object) { - const state = store.getState(); - const { fakeParticipants, sortedRemoteParticipants, speakersList } = state['features/base/participants']; - const { remoteScreenShares } = state['features/video-layout']; - const screenShares = (remoteScreenShares || []).slice(); - let speakers = (speakersList || []).slice(); - const remoteParticipants = new Map(sortedRemoteParticipants); - const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : []; - - for (const screenshare of screenShares) { - remoteParticipants.delete(screenshare); - speakers = speakers.filter(speaker => speaker !== screenshare); - } - for (const sharedVideo of sharedVideos) { - remoteParticipants.delete(sharedVideo); - speakers = speakers.filter(speaker => speaker !== sharedVideo); - } - for (const speaker of speakers) { - remoteParticipants.delete(speaker); - } - const reorderedParticipants - = [ ...screenShares.reverse(), ...sharedVideos, ...speakers, ...Array.from(remoteParticipants.keys()) ]; - - store.dispatch(setRemoteParticipants(reorderedParticipants)); -} diff --git a/react/features/filmstrip/middleware.native.js b/react/features/filmstrip/middleware.native.js index e69de29bb..71dc82f5c 100644 --- a/react/features/filmstrip/middleware.native.js +++ b/react/features/filmstrip/middleware.native.js @@ -0,0 +1,28 @@ +// @flow + +import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants'; +import { MiddlewareRegistry } from '../base/redux'; + +import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions'; +import './subscriber'; + +/** + * The middleware of the feature Filmstrip. + */ +MiddlewareRegistry.register(store => next => action => { + const result = next(action); + + switch (action.type) { + case PARTICIPANT_JOINED: { + updateRemoteParticipants(store); + break; + } + case PARTICIPANT_LEFT: { + updateRemoteParticipantsOnLeave(store, action.participant?.id); + break; + } + } + + return result; +}); + diff --git a/react/features/filmstrip/middleware.web.js b/react/features/filmstrip/middleware.web.js index 69d521d52..c476583d0 100644 --- a/react/features/filmstrip/middleware.web.js +++ b/react/features/filmstrip/middleware.web.js @@ -12,12 +12,11 @@ import { import { setHorizontalViewDimensions, - setRemoteParticipants, setTileViewDimensions, setVerticalViewDimensions -} from './actions.web'; -import { updateRemoteParticipants } from './functions.web'; -import './subscriber.web'; +} from './actions'; +import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions'; +import './subscriber'; /** * The middleware of the feature Filmstrip. @@ -52,7 +51,7 @@ MiddlewareRegistry.register(store => next => action => { break; } case PARTICIPANT_LEFT: { - _updateRemoteParticipantsOnLeave(store, action.participant?.id); + updateRemoteParticipantsOnLeave(store, action.participant?.id); break; } case SETTINGS_UPDATED: { @@ -66,23 +65,3 @@ MiddlewareRegistry.register(store => next => action => { return result; }); - -/** - * Private helper to calculate the reordered list of remote participants when a participant leaves. - * - * @param {*} store - The redux store. - * @param {string} participantId - The endpoint id of the participant leaving the call. - * @returns {void} - * @private - */ -function _updateRemoteParticipantsOnLeave(store, participantId = null) { - if (!participantId) { - return; - } - const state = store.getState(); - const { remoteParticipants } = state['features/filmstrip']; - const reorderedParticipants = new Set(remoteParticipants); - - reorderedParticipants.delete(participantId) - && store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants))); -} diff --git a/react/features/filmstrip/reducer.js b/react/features/filmstrip/reducer.js index 06a76d9cb..377e85612 100644 --- a/react/features/filmstrip/reducer.js +++ b/react/features/filmstrip/reducer.js @@ -117,10 +117,14 @@ ReducerRegistry.register( horizontalViewDimensions: action.dimensions }; case SET_REMOTE_PARTICIPANTS: { - const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state; - state.remoteParticipants = action.participants; - state.visibleRemoteParticipants = new Set(state.remoteParticipants.slice(startIndex, endIndex)); + + // TODO: implement this on mobile. + if (navigator.product !== 'ReactNative') { + const { visibleParticipantsStartIndex: startIndex, visibleParticipantsEndIndex: endIndex } = state; + + state.visibleRemoteParticipants = new Set(state.remoteParticipants.slice(startIndex, endIndex)); + } return { ...state }; } diff --git a/react/features/filmstrip/subscriber.any.js b/react/features/filmstrip/subscriber.any.js new file mode 100644 index 000000000..c131b38f8 --- /dev/null +++ b/react/features/filmstrip/subscriber.any.js @@ -0,0 +1,38 @@ +// @flow + +import { StateListenerRegistry } from '../base/redux'; + +import { updateRemoteParticipants } from './functions'; + +/** + * Listens for changes to the screensharing status of the remote participants to recompute the reordered list of the + * remote endpoints. + */ +StateListenerRegistry.register( + /* selector */ state => state['features/video-layout'].remoteScreenShares, + /* listener */ (remoteScreenShares, store) => updateRemoteParticipants(store)); + +/** + * Listens for changes to the dominant speaker to recompute the reordered list of the remote endpoints. + */ +StateListenerRegistry.register( + /* selector */ state => state['features/base/participants'].dominantSpeaker, + /* listener */ (dominantSpeaker, store) => _reorderDominantSpeakers(store)); + +/** + * Private helper function that reorders the remote participants based on dominant speaker changes. + * + * @param {*} store - The redux store. + * @returns {void} + * @private + */ +function _reorderDominantSpeakers(store) { + const state = store.getState(); + const { dominantSpeaker, local } = state['features/base/participants']; + const { visibleRemoteParticipants } = state['features/filmstrip']; + + // Reorder the participants if the new dominant speaker is currently not visible. + if (dominantSpeaker !== local?.id && !visibleRemoteParticipants.has(dominantSpeaker)) { + updateRemoteParticipants(store); + } +} diff --git a/react/features/filmstrip/subscriber.native.js b/react/features/filmstrip/subscriber.native.js new file mode 100644 index 000000000..2c1f69b2d --- /dev/null +++ b/react/features/filmstrip/subscriber.native.js @@ -0,0 +1,3 @@ +// @flow + +import './subscriber.any'; diff --git a/react/features/filmstrip/subscriber.web.js b/react/features/filmstrip/subscriber.web.js index c30ac8d1f..242426a70 100644 --- a/react/features/filmstrip/subscriber.web.js +++ b/react/features/filmstrip/subscriber.web.js @@ -12,14 +12,15 @@ import { setHorizontalViewDimensions, setTileViewDimensions, setVerticalViewDimensions -} from './actions.web'; +} from './actions'; import { ASPECT_RATIO_BREAKPOINT, DISPLAY_DRAWER_THRESHOLD, SINGLE_COLUMN_BREAKPOINT, TWO_COLUMN_BREAKPOINT } from './constants'; -import { updateRemoteParticipants } from './functions.web'; +import './subscriber.any'; + /** * Listens for changes in the number of participants to calculate the dimensions of the tile view grid and the tiles. @@ -158,36 +159,3 @@ StateListenerRegistry.register( store.dispatch(setTileViewDimensions(gridDimensions)); } }); - -/** - * Listens for changes to the screensharing status of the remote participants to recompute the reordered list of the - * remote endpoints. - */ -StateListenerRegistry.register( - /* selector */ state => state['features/video-layout'].remoteScreenShares, - /* listener */ (remoteScreenShares, store) => updateRemoteParticipants(store)); - -/** - * Listens for changes to the dominant speaker to recompute the reordered list of the remote endpoints. - */ -StateListenerRegistry.register( - /* selector */ state => state['features/base/participants'].dominantSpeaker, - /* listener */ (dominantSpeaker, store) => _reorderDominantSpeakers(store)); - -/** - * Private helper function that reorders the remote participants based on dominant speaker changes. - * - * @param {*} store - The redux store. - * @returns {void} - * @private - */ -function _reorderDominantSpeakers(store) { - const state = store.getState(); - const { dominantSpeaker, local } = state['features/base/participants']; - const { visibleRemoteParticipants } = state['features/filmstrip']; - - // Reorder the participants if the new dominant speaker is currently not visible. - if (dominantSpeaker !== local?.id && !visibleRemoteParticipants.has(dominantSpeaker)) { - updateRemoteParticipants(store); - } -}