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:
Robert Pintilii 2022-04-12 09:57:01 +03:00 committed by GitHub
parent 930852cd88
commit 8bf42e79a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 99 additions and 20 deletions

View File

@ -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';

View File

@ -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
};
}

View File

@ -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,

View File

@ -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;

View File

@ -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;
}
/**

View File

@ -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);

View File

@ -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 ]);