fix(stage-filmstrip) Fix issues (#11360)
Fix dominant speaker not removed on leave Fix video not shown in vertical filmstrip when a remote screensharing was on Refactor pin/ unpin. Add click to unpin Remove from stage on unpin, except dominant (just change pin state) Fix local shows video on both stage and vertical filmstrip Don't reorder on stage base on queue (sort all by id)
This commit is contained in:
parent
930852cd88
commit
8bf42e79a0
|
@ -170,3 +170,13 @@ export const SET_STAGE_PARTICIPANTS = 'SET_STAGE_PARTICIPANTS';
|
|||
* }
|
||||
*/
|
||||
export const SET_MAX_STAGE_PARTICIPANTS = 'SET_MAX_STAGE_PARTICIPANTS';
|
||||
|
||||
|
||||
/**
|
||||
* The type of Redux action which toggles the pin state of stage participants.
|
||||
* {
|
||||
* type: TOGGLE_PIN_STAGE_PARTICIPANT,
|
||||
* participantId: String
|
||||
* }
|
||||
*/
|
||||
export const TOGGLE_PIN_STAGE_PARTICIPANT = 'TOGGLE_PIN_STAGE_PARTICIPANT';
|
||||
|
|
|
@ -23,7 +23,8 @@ import {
|
|||
SET_USER_IS_RESIZING,
|
||||
SET_VERTICAL_VIEW_DIMENSIONS,
|
||||
SET_VOLUME,
|
||||
SET_MAX_STAGE_PARTICIPANTS
|
||||
SET_MAX_STAGE_PARTICIPANTS,
|
||||
TOGGLE_PIN_STAGE_PARTICIPANT
|
||||
} from './actionTypes';
|
||||
import {
|
||||
HORIZONTAL_FILMSTRIP_MARGIN,
|
||||
|
@ -449,3 +450,16 @@ export function setMaxStageParticipants(maxParticipants) {
|
|||
maxParticipants
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the pin state of the given participant.
|
||||
*
|
||||
* @param {string} participantId - The id of the participant to be toggled.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function togglePinStageParticipant(participantId) {
|
||||
return {
|
||||
type: TOGGLE_PIN_STAGE_PARTICIPANT,
|
||||
participantId
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
|
|||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantByIdOrUndefined,
|
||||
hasRaisedHand,
|
||||
pinParticipant
|
||||
|
@ -30,7 +31,7 @@ import { hideGif, showGif } from '../../../gifs/actions';
|
|||
import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions';
|
||||
import { PresenceLabel } from '../../../presence-status';
|
||||
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
|
||||
import { addStageParticipant } from '../../actions.web';
|
||||
import { togglePinStageParticipant } from '../../actions';
|
||||
import {
|
||||
DISPLAY_MODE_TO_CLASS_NAME,
|
||||
DISPLAY_VIDEO,
|
||||
|
@ -42,9 +43,10 @@ import {
|
|||
getActiveParticipantsIds,
|
||||
getDisplayModeInput,
|
||||
isVideoPlayable,
|
||||
showGridInVerticalView
|
||||
showGridInVerticalView,
|
||||
isStageFilmstripEnabled,
|
||||
shouldDisplayStageFilmstrip
|
||||
} from '../../functions';
|
||||
import { isStageFilmstripEnabled } from '../../functions.web';
|
||||
|
||||
import FakeScreenShareParticipant from './FakeScreenShareParticipant';
|
||||
import ThumbnailAudioIndicator from './ThumbnailAudioIndicator';
|
||||
|
@ -191,6 +193,13 @@ export type Props = {|
|
|||
*/
|
||||
_stageFilmstripDisabled: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the participants are displayed on stage.
|
||||
* (and not screensharing or shared video; used to determine
|
||||
* whether or not the display the participant video in the vertical filmstrip).
|
||||
*/
|
||||
_stageParticipantsVisible: boolean,
|
||||
|
||||
/**
|
||||
* The video object position for the participant.
|
||||
*/
|
||||
|
@ -633,7 +642,7 @@ class Thumbnail extends Component<Props, State> {
|
|||
if (_stageFilmstripDisabled) {
|
||||
dispatch(pinParticipant(pinned ? null : id));
|
||||
} else {
|
||||
dispatch(addStageParticipant(id, true));
|
||||
dispatch(togglePinStageParticipant(id));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1173,6 +1182,7 @@ function _mapStateToProps(state, ownProps): Object {
|
|||
|
||||
const { gifUrl: gifSrc } = getGifForParticipant(state, id);
|
||||
const mode = getGifDisplayMode(state);
|
||||
const participantId = isLocal ? getLocalParticipant(state).id : participantID;
|
||||
|
||||
return {
|
||||
_audioTrack,
|
||||
|
@ -1180,7 +1190,7 @@ function _mapStateToProps(state, ownProps): Object {
|
|||
_defaultLocalDisplayName: defaultLocalDisplayName,
|
||||
_disableLocalVideoFlip: Boolean(disableLocalVideoFlip),
|
||||
_disableTileEnlargement: Boolean(disableTileEnlargement),
|
||||
_isActiveParticipant: activeParticipants.find(pId => pId === participantID),
|
||||
_isActiveParticipant: activeParticipants.find(pId => pId === participantId),
|
||||
_isHidden: isLocal && iAmRecorder && !iAmSipGateway,
|
||||
_isAudioOnly: Boolean(state['features/base/audio-only'].enabled),
|
||||
_isCurrentlyOnLargeVideo: state['features/large-video']?.participantId === id,
|
||||
|
@ -1195,6 +1205,7 @@ function _mapStateToProps(state, ownProps): Object {
|
|||
_participant: participant,
|
||||
_raisedHand: hasRaisedHand(participant),
|
||||
_stageFilmstripDisabled: !isStageFilmstripEnabled(state),
|
||||
_stageParticipantsVisible: shouldDisplayStageFilmstrip(state, 1),
|
||||
_videoObjectPosition: getVideoObjectPosition(state, participant?.id),
|
||||
_videoTrack,
|
||||
...size,
|
||||
|
|
|
@ -148,7 +148,8 @@ function _mapStateToProps(state, ownProps) {
|
|||
const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
|
||||
const _verticalViewGrid = showGridInVerticalView(state);
|
||||
const stageFilmstrip = ownProps.data?.stageFilmstrip;
|
||||
const remoteParticipants = stageFilmstrip ? activeParticipants : remote;
|
||||
const sortedActiveParticipants = activeParticipants.sort();
|
||||
const remoteParticipants = stageFilmstrip ? sortedActiveParticipants : remote;
|
||||
const remoteParticipantsLength = remoteParticipants.length;
|
||||
const localId = getLocalParticipant(state).id;
|
||||
|
||||
|
|
|
@ -498,6 +498,7 @@ export function computeDisplayModeFromInput(input: Object) {
|
|||
isScreenSharing,
|
||||
canPlayEventReceived,
|
||||
isRemoteParticipant,
|
||||
stageParticipantsVisible,
|
||||
tileViewActive
|
||||
} = input;
|
||||
const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
|
||||
|
@ -506,7 +507,8 @@ export function computeDisplayModeFromInput(input: Object) {
|
|||
return DISPLAY_VIDEO;
|
||||
}
|
||||
|
||||
if (!tileViewActive && ((isScreenSharing && isRemoteParticipant) || isActiveParticipant)) {
|
||||
if (!tileViewActive && ((isScreenSharing && isRemoteParticipant)
|
||||
|| (stageParticipantsVisible && isActiveParticipant))) {
|
||||
return DISPLAY_AVATAR;
|
||||
} else if (isCurrentlyOnLargeVideo && !tileViewActive) {
|
||||
// Display name is always and only displayed when user is on the stage
|
||||
|
@ -537,6 +539,7 @@ export function getDisplayModeInput(props: Object, state: Object) {
|
|||
_isScreenSharing,
|
||||
_isVideoPlayable,
|
||||
_participant,
|
||||
_stageParticipantsVisible,
|
||||
_videoTrack
|
||||
} = props;
|
||||
const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW;
|
||||
|
@ -554,6 +557,7 @@ export function getDisplayModeInput(props: Object, state: Object) {
|
|||
isRemoteParticipant: !_participant?.isFakeParticipant && !_participant?.local,
|
||||
isScreenSharing: _isScreenSharing,
|
||||
isFakeScreenShareParticipant: _isFakeScreenShareParticipant,
|
||||
stageParticipantsVisible: _stageParticipantsVisible,
|
||||
videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'
|
||||
};
|
||||
}
|
||||
|
@ -674,7 +678,7 @@ export function getActiveParticipantsIds(state) {
|
|||
* Gets the ids of the active participants.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {Array<string>}
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
export function getPinnedActiveParticipants(state) {
|
||||
const { activeParticipants } = state['features/filmstrip'];
|
||||
|
@ -686,16 +690,18 @@ export function getPinnedActiveParticipants(state) {
|
|||
* Get whether or not the stage filmstrip should be displayed.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @param {number} minParticipantCount - The min number of participants for the stage filmstrip
|
||||
* to be displayed.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldDisplayStageFilmstrip(state) {
|
||||
export function shouldDisplayStageFilmstrip(state, minParticipantCount = 2) {
|
||||
const { activeParticipants } = state['features/filmstrip'];
|
||||
const { remoteScreenShares } = state['features/video-layout'];
|
||||
const currentLayout = getCurrentLayout(state);
|
||||
const sharedVideo = isSharingStatus(state['features/shared-video']?.status);
|
||||
|
||||
return isStageFilmstripEnabled(state) && remoteScreenShares.length === 0 && !sharedVideo
|
||||
&& activeParticipants.length > 1 && currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW;
|
||||
&& activeParticipants.length >= minParticipantCount && currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,7 +24,8 @@ import {
|
|||
ADD_STAGE_PARTICIPANT,
|
||||
REMOVE_STAGE_PARTICIPANT,
|
||||
SET_MAX_STAGE_PARTICIPANTS,
|
||||
SET_USER_FILMSTRIP_WIDTH
|
||||
SET_USER_FILMSTRIP_WIDTH,
|
||||
TOGGLE_PIN_STAGE_PARTICIPANT
|
||||
} from './actionTypes';
|
||||
import {
|
||||
addStageParticipant,
|
||||
|
@ -141,6 +142,7 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
const tid = timers.get(participantId);
|
||||
|
||||
clearTimeout(tid);
|
||||
timers.delete(participantId);
|
||||
} else if (activeParticipants.length < maxStageParticipants) {
|
||||
queue = [ ...activeParticipants, {
|
||||
participantId,
|
||||
|
@ -216,11 +218,17 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
break;
|
||||
}
|
||||
case PARTICIPANT_LEFT: {
|
||||
const state = store.getState();
|
||||
const { id } = action.participant;
|
||||
const activeParticipantsIds = getActiveParticipantsIds(store.getState());
|
||||
const activeParticipantsIds = getActiveParticipantsIds(state);
|
||||
|
||||
if (activeParticipantsIds.find(pId => pId === id)) {
|
||||
store.dispatch(removeStageParticipant(id));
|
||||
const tid = timers.get(id);
|
||||
const { activeParticipants } = state['features/filmstrip'];
|
||||
|
||||
clearTimeout(tid);
|
||||
timers.delete(id);
|
||||
store.dispatch(setStageParticipants(activeParticipants.filter(p => p.participantId !== id)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -240,6 +248,36 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case TOGGLE_PIN_STAGE_PARTICIPANT: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const { participantId } = action;
|
||||
const pinnedParticipants = getPinnedActiveParticipants(state);
|
||||
const dominant = getDominantSpeakerParticipant(state);
|
||||
|
||||
if (pinnedParticipants.find(p => p.participantId === participantId)) {
|
||||
if (dominant?.id === participantId) {
|
||||
const { activeParticipants } = state['features/filmstrip'];
|
||||
const queue = activeParticipants.map(p => {
|
||||
if (p.participantId === participantId) {
|
||||
return {
|
||||
participantId,
|
||||
pinned: false
|
||||
};
|
||||
}
|
||||
|
||||
return p;
|
||||
});
|
||||
|
||||
dispatch(setStageParticipants(queue));
|
||||
} else {
|
||||
dispatch(removeStageParticipant(participantId));
|
||||
}
|
||||
} else {
|
||||
dispatch(addStageParticipant(participantId, true));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return result ?? next(action);
|
||||
|
|
|
@ -6,8 +6,8 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||
|
||||
import ContextMenuItem from '../../../base/components/context-menu/ContextMenuItem';
|
||||
import { IconPinParticipant, IconUnpin } from '../../../base/icons';
|
||||
import { addStageParticipant, removeStageParticipant } from '../../../filmstrip/actions.web';
|
||||
import { getActiveParticipantsIds } from '../../../filmstrip/functions';
|
||||
import { togglePinStageParticipant } from '../../../filmstrip/actions.web';
|
||||
import { getPinnedActiveParticipants } from '../../../filmstrip/functions.web';
|
||||
|
||||
type Props = {
|
||||
|
||||
|
@ -35,11 +35,10 @@ type Props = {
|
|||
const TogglePinToStageButton = ({ className, noIcon = false, onClick, participantID }: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const isActive = Boolean(useSelector(getActiveParticipantsIds).find(p => p === participantID));
|
||||
const isActive = Boolean(useSelector(getPinnedActiveParticipants)
|
||||
.find(p => p.participantId === participantID));
|
||||
const _onClick = useCallback(() => {
|
||||
dispatch(isActive
|
||||
? removeStageParticipant(participantID)
|
||||
: addStageParticipant(participantID, true));
|
||||
dispatch(togglePinStageParticipant(participantID));
|
||||
onClick && onClick();
|
||||
}, [ participantID, isActive ]);
|
||||
|
||||
|
|
Loading…
Reference in New Issue