refactor(multi-stream) refactor virtual screenshare creation and support plan-b clients (#11445)

* fix(multi-stream) update selector to find ss track by videoType or mediaType

* ref(multi-stream) move fake ss creation logic and support video type changed

* refactor(multi-stream) decouple sending and receiving multiple screenshare streams

* fix(multi-stream) fix receiver constraints with signaling and without multi-stream

* fix(mutli-stream) ensure plan b original SS thumbnail displays avatar

* fix(multi-stream) show fake SS for plan b sender

* refactor(multi-stream) poc for moving SS creation to state listener

* remove reference to fake SS creation

* fix lint errors

* rename to virtual screenshare participants

* fix minor bugs

* rename participant subscriber to specify web support only
This commit is contained in:
William Liang 2022-04-29 10:32:16 -04:00 committed by GitHub
parent b9c4d28dac
commit d3fe246f61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 365 additions and 301 deletions

View File

@ -56,8 +56,7 @@ import {
} from './react/features/base/conference';
import {
getReplaceParticipant,
getMultipleVideoSupportFeatureFlag,
getSourceNameSignalingFeatureFlag
getMultipleVideoSendingSupportFeatureFlag
} from './react/features/base/config/functions';
import {
checkAndNotifyForNewDevice,
@ -97,7 +96,7 @@ import {
dominantSpeakerChanged,
getLocalParticipant,
getNormalizedDisplayName,
getScreenshareParticipantByOwnerId,
getVirtualScreenshareParticipantByOwnerId,
localParticipantAudioLevelChanged,
localParticipantConnectionStatusChanged,
localParticipantRoleChanged,
@ -1472,7 +1471,7 @@ export default {
// In the multi-stream mode, add the track to the conference if there is no existing track, replace it
// otherwise.
if (getMultipleVideoSupportFeatureFlag(state)) {
if (getMultipleVideoSendingSupportFeatureFlag(state)) {
const trackAction = oldTrack
? replaceLocalTrack(oldTrack, newTrack, room)
: addLocalTrack(newTrack);
@ -2265,14 +2264,12 @@ export default {
name: formattedDisplayName
}));
if (getSourceNameSignalingFeatureFlag(state)) {
const screenshareParticipantId = getScreenshareParticipantByOwnerId(state, id)?.id;
const virtualScreenshareParticipantId = getVirtualScreenshareParticipantByOwnerId(state, id)?.id;
if (screenshareParticipantId) {
APP.store.dispatch(
screenshareParticipantDisplayNameChanged(screenshareParticipantId, formattedDisplayName)
);
}
if (virtualScreenshareParticipantId) {
APP.store.dispatch(
screenshareParticipantDisplayNameChanged(virtualScreenshareParticipantId, formattedDisplayName)
);
}
APP.API.notifyDisplayNameChanged(id, {

View File

@ -9,7 +9,10 @@ import { Provider } from 'react-redux';
import { createScreenSharingIssueEvent, sendAnalytics } from '../../../react/features/analytics';
import { Avatar } from '../../../react/features/base/avatar';
import theme from '../../../react/features/base/components/themes/participantsPaneTheme.json';
import { getSourceNameSignalingFeatureFlag } from '../../../react/features/base/config';
import {
getMultipleVideoSupportFeatureFlag,
getSourceNameSignalingFeatureFlag
} from '../../../react/features/base/config';
import { i18next } from '../../../react/features/base/i18n';
import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
import { VIDEO_TYPE } from '../../../react/features/base/media';
@ -283,9 +286,18 @@ export default class LargeVideoManager {
}
const isAudioOnly = APP.conference.isAudioOnly();
// Multi-stream is not supported on plan-b endpoints even if its is enabled via config.js. A virtual
// screenshare tile is still created when a remote endpoint starts screenshare to keep the behavior
// consistent and an avatar is displayed on the original participant thumbnail as long as screenshare is in
// progress.
const legacyScreenshare = getMultipleVideoSupportFeatureFlag(state)
&& videoType === VIDEO_TYPE.DESKTOP
&& !participant.isVirtualScreenshareParticipant;
const showAvatar
= isVideoContainer
&& ((isAudioOnly && videoType !== VIDEO_TYPE.DESKTOP) || !isVideoRenderable);
&& ((isAudioOnly && videoType !== VIDEO_TYPE.DESKTOP) || !isVideoRenderable || legacyScreenshare);
let promise;

View File

@ -10,7 +10,7 @@ import {
} from '../../../react/features/base/participants';
import {
getTrackByMediaTypeAndParticipant,
getFakeScreenshareParticipantTrack
getVirtualScreenshareParticipantTrack
} from '../../../react/features/base/tracks';
import LargeVideoManager from './LargeVideoManager';
@ -95,7 +95,7 @@ const VideoLayout = {
return VIDEO_TYPE.CAMERA;
}
if (getSourceNameSignalingFeatureFlag(state) && participant?.isFakeScreenShareParticipant) {
if (getSourceNameSignalingFeatureFlag(state) && participant?.isVirtualScreenshareParticipant) {
return VIDEO_TYPE.DESKTOP;
}
@ -190,8 +190,8 @@ const VideoLayout = {
let videoTrack;
if (getSourceNameSignalingFeatureFlag(state) && participant?.isFakeScreenShareParticipant) {
videoTrack = getFakeScreenshareParticipantTrack(tracks, id);
if (getSourceNameSignalingFeatureFlag(state) && participant?.isVirtualScreenshareParticipant) {
videoTrack = getVirtualScreenshareParticipantTrack(tracks, id);
} else {
videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
}

View File

@ -9,7 +9,7 @@ import {
import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen-share';
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
import { setAudioOnly } from '../audio-only';
import { getMultipleVideoSupportFeatureFlag } from '../config/functions.any';
import { getMultipleVideoSendingSupportFeatureFlag } from '../config/functions.any';
import { JitsiConferenceErrors, JitsiTrackErrors } from '../lib-jitsi-meet';
import { MEDIA_TYPE, setScreenshareMuted, VIDEO_TYPE } from '../media';
import { MiddlewareRegistry } from '../redux';
@ -51,7 +51,7 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case TOGGLE_SCREENSHARING: {
getMultipleVideoSupportFeatureFlag(getState()) && _toggleScreenSharing(action, store);
getMultipleVideoSendingSupportFeatureFlag(getState()) && _toggleScreenSharing(action, store);
break;
}

View File

@ -51,15 +51,24 @@ export function getMeetingRegion(state: Object) {
}
/**
* Selector used to get the sendMultipleVideoStreams feature flag.
* Selector for determining if receiving multiple stream support is enabled.
*
* @param {Object} state - The global state.
* @returns {boolean}
*/
export function getMultipleVideoSupportFeatureFlag(state: Object) {
return getFeatureFlag(state, FEATURE_FLAGS.MULTIPLE_VIDEO_STREAMS_SUPPORT)
&& getSourceNameSignalingFeatureFlag(state)
&& isUnifiedPlanEnabled(state);
&& getSourceNameSignalingFeatureFlag(state);
}
/**
* Selector for determining if sending multiple stream support is enabled.
*
* @param {Object} state - The global state.
* @returns {boolean}
*/
export function getMultipleVideoSendingSupportFeatureFlag(state: Object) {
return getMultipleVideoSupportFeatureFlag(state) && isUnifiedPlanEnabled(state);
}
/**

View File

@ -6,9 +6,9 @@ import { SET_FILMSTRIP_ENABLED } from '../../filmstrip/actionTypes';
import { SELECT_LARGE_VIDEO_PARTICIPANT } from '../../large-video/actionTypes';
import { APP_STATE_CHANGED } from '../../mobile/background/actionTypes';
import {
FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
SET_TILE_VIEW
SET_TILE_VIEW,
VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED
} from '../../video-layout/actionTypes';
import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
import { CONFERENCE_JOINED } from '../conference/actionTypes';
@ -95,7 +95,6 @@ MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case APP_STATE_CHANGED:
case CONFERENCE_JOINED:
case FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
case PARTICIPANT_JOINED:
case PARTICIPANT_KICKED:
case PARTICIPANT_LEFT:
@ -104,6 +103,7 @@ MiddlewareRegistry.register(store => next => action => {
case SET_AUDIO_ONLY:
case SET_FILMSTRIP_ENABLED:
case SET_TILE_VIEW:
case VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED:
_updateLastN(store);
break;
}

View File

@ -16,7 +16,7 @@ import { isForceMuted } from '../../participants-pane/functions';
import { isScreenMediaShared } from '../../screen-share/functions';
import { SET_AUDIO_ONLY, setAudioOnly } from '../audio-only';
import { isRoomValid, SET_ROOM } from '../conference';
import { getMultipleVideoSupportFeatureFlag } from '../config';
import { getMultipleVideoSendingSupportFeatureFlag } from '../config';
import { getLocalParticipant } from '../participants';
import { MiddlewareRegistry } from '../redux';
import { getPropertyValue } from '../settings';
@ -192,7 +192,7 @@ function _setAudioOnly({ dispatch, getState }, next, action) {
// Make sure we mute both the desktop and video tracks.
dispatch(setVideoMuted(audioOnly, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY, ensureVideoTrack));
if (getMultipleVideoSupportFeatureFlag(state)) {
if (getMultipleVideoSendingSupportFeatureFlag(state)) {
dispatch(setScreenshareMuted(audioOnly, MEDIA_TYPE.SCREENSHARE, SCREENSHARE_MUTISM_AUTHORITY.AUDIO_ONLY));
} else if (navigator.product !== 'ReactNative') {
dispatch(setVideoMuted(audioOnly, MEDIA_TYPE.PRESENTER, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY, ensureVideoTrack));

View File

@ -25,6 +25,7 @@ import {
} from './constants';
import {
getLocalParticipant,
getVirtualScreenshareParticipantOwnerId,
getNormalizedDisplayName,
getParticipantDisplayName,
getParticipantById
@ -503,6 +504,36 @@ export function participantMutedUs(participant, track) {
};
}
/**
* Action to create a virtual screenshare participant.
*
* @param {(string)} sourceName - JitsiTrack instance.
* @param {(boolean)} local - JitsiTrack instance.
* @returns {Function}
*/
export function createVirtualScreenshareParticipant(sourceName, local) {
return (dispatch, getState) => {
const state = getState();
const ownerId = getVirtualScreenshareParticipantOwnerId(sourceName);
const owner = getParticipantById(state, ownerId);
const ownerName = owner.name;
if (!ownerName) {
logger.error(`Failed to create a screenshare participant for sourceName: ${sourceName}`);
return;
}
dispatch(participantJoined({
conference: state['features/base/conference'].conference,
id: sourceName,
isVirtualScreenshareParticipant: true,
isLocalScreenShare: local,
name: ownerName
}));
};
}
/**
* Action to signal that a participant had been kicked.
*

View File

@ -6,21 +6,16 @@ import type { Store } from 'redux';
import { i18next } from '../../base/i18n';
import { isStageFilmstripAvailable } from '../../filmstrip/functions';
import { GRAVATAR_BASE_URL, isCORSAvatarURL } from '../avatar';
import { getSourceNameSignalingFeatureFlag } from '../config';
import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../config';
import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
import { toState } from '../redux';
import { getTrackByMediaTypeAndParticipant } from '../tracks';
import { getScreenShareTrack, getTrackByMediaTypeAndParticipant } from '../tracks';
import { createDeferred } from '../util';
import {
JIGASI_PARTICIPANT_ICON,
MAX_DISPLAY_NAME_LENGTH,
PARTICIPANT_ROLE
} from './constants';
import { JIGASI_PARTICIPANT_ICON, MAX_DISPLAY_NAME_LENGTH, PARTICIPANT_ROLE } from './constants';
import { preloadImage } from './preloadImage';
/**
* Temp structures for avatar urls to be checked/preloaded.
*/
@ -114,12 +109,16 @@ export function getLocalScreenShareParticipant(stateful: Object | Function) {
* @param {string} id - The owner ID of the screenshare participant to retrieve.
* @returns {(Participant|undefined)}
*/
export function getScreenshareParticipantByOwnerId(stateful: Object | Function, id: string) {
const track = getTrackByMediaTypeAndParticipant(
toState(stateful)['features/base/tracks'], MEDIA_TYPE.SCREENSHARE, id
);
export function getVirtualScreenshareParticipantByOwnerId(stateful: Object | Function, id: string) {
const state = toState(stateful);
return getParticipantById(stateful, track?.jitsiTrack.getSourceName());
if (getMultipleVideoSupportFeatureFlag(state)) {
const track = getScreenShareTrack(state['features/base/tracks'], id);
return getParticipantById(stateful, track?.jitsiTrack.getSourceName());
}
return;
}
/**
@ -186,11 +185,11 @@ export function getParticipantCount(stateful: Object | Function) {
local,
remote,
fakeParticipants,
sortedRemoteFakeScreenShareParticipants
sortedRemoteVirtualScreenshareParticipants
} = state['features/base/participants'];
if (getSourceNameSignalingFeatureFlag(state)) {
return remote.size - fakeParticipants.size - sortedRemoteFakeScreenShareParticipants.size + (local ? 1 : 0);
return remote.size - fakeParticipants.size - sortedRemoteVirtualScreenshareParticipants.size + (local ? 1 : 0);
}
return remote.size - fakeParticipants.size + (local ? 1 : 0);
@ -198,13 +197,13 @@ export function getParticipantCount(stateful: Object | Function) {
}
/**
* Returns participant ID of the owner of a fake screenshare participant.
* Returns participant ID of the owner of a virtual screenshare participant.
*
* @param {string} id - The ID of the fake screenshare participant.
* @param {string} id - The ID of the virtual screenshare participant.
* @private
* @returns {(string|undefined)}
*/
export function getFakeScreenShareParticipantOwnerId(id: string) {
export function getVirtualScreenshareParticipantOwnerId(id: string) {
return id.split('-')[0];
}
@ -232,7 +231,7 @@ export function getRemoteParticipantCount(stateful: Object | Function) {
const state = toState(stateful)['features/base/participants'];
if (getSourceNameSignalingFeatureFlag(state)) {
return state.remote.size - state.sortedRemoteFakeScreenShareParticipants.size;
return state.remote.size - state.sortedRemoteVirtualScreenshareParticipants.size;
}
return state.remote.size;
@ -274,7 +273,7 @@ export function getParticipantDisplayName(stateful: Object | Function, id: strin
} = toState(stateful)['features/base/config'];
if (participant) {
if (participant.isFakeScreenShareParticipant) {
if (participant.isVirtualScreenshareParticipant) {
return getScreenshareParticipantDisplayName(stateful, id);
}
@ -299,7 +298,7 @@ export function getParticipantDisplayName(stateful: Object | Function, id: strin
* @returns {string}
*/
export function getScreenshareParticipantDisplayName(stateful: Object | Function, id: string) {
const owner = getParticipantById(stateful, getFakeScreenShareParticipantOwnerId(id));
const owner = getParticipantById(stateful, getVirtualScreenshareParticipantOwnerId(id));
const name = owner.name;
return i18next.t('screenshareDisplayName', { name });

View File

@ -69,6 +69,7 @@ import {
isLocalParticipantModerator
} from './functions';
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
import './subscriber';
declare var APP: Object;
@ -210,19 +211,19 @@ MiddlewareRegistry.register(store => next => action => {
}
case PARTICIPANT_JOINED: {
const { isFakeScreenShareParticipant } = action.participant;
const { isVirtualScreenshareParticipant } = action.participant;
// Do not play sounds when a fake participant tile is created for screenshare.
!isFakeScreenShareParticipant && _maybePlaySounds(store, action);
// Do not play sounds when a virtual participant tile is created for screenshare.
!isVirtualScreenshareParticipant && _maybePlaySounds(store, action);
return _participantJoinedOrUpdated(store, next, action);
}
case PARTICIPANT_LEFT: {
const { isFakeScreenShareParticipant } = action.participant;
const { isVirtualScreenshareParticipant } = action.participant;
// Do not play sounds when a tile for screenshare is removed.
!isFakeScreenShareParticipant && _maybePlaySounds(store, action);
!isVirtualScreenshareParticipant && _maybePlaySounds(store, action);
break;
}

View File

@ -66,7 +66,7 @@ const DEFAULT_STATE = {
pinnedParticipant: undefined,
raisedHandsQueue: [],
remote: new Map(),
sortedRemoteFakeScreenShareParticipants: new Map(),
sortedRemoteVirtualScreenshareParticipants: new Map(),
sortedRemoteParticipants: new Map(),
sortedRemoteScreenshares: new Map(),
speakersList: new Map()
@ -213,15 +213,15 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
case SCREENSHARE_PARTICIPANT_NAME_CHANGED: {
const { id, name } = action;
if (state.sortedRemoteFakeScreenShareParticipants.has(id)) {
state.sortedRemoteFakeScreenShareParticipants.delete(id);
if (state.sortedRemoteVirtualScreenshareParticipants.has(id)) {
state.sortedRemoteVirtualScreenshareParticipants.delete(id);
const sortedRemoteFakeScreenShareParticipants = [ ...state.sortedRemoteFakeScreenShareParticipants ];
const sortedRemoteVirtualScreenshareParticipants = [ ...state.sortedRemoteVirtualScreenshareParticipants ];
sortedRemoteFakeScreenShareParticipants.push([ id, name ]);
sortedRemoteFakeScreenShareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
sortedRemoteVirtualScreenshareParticipants.push([ id, name ]);
sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
state.sortedRemoteFakeScreenShareParticipants = new Map(sortedRemoteFakeScreenShareParticipants);
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
}
return { ...state };
@ -229,7 +229,14 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
case PARTICIPANT_JOINED: {
const participant = _participantJoined(action);
const { id, isFakeParticipant, isFakeScreenShareParticipant, isLocalScreenShare, name, pinned } = participant;
const {
id,
isFakeParticipant,
isLocalScreenShare,
isVirtualScreenshareParticipant,
name,
pinned
} = participant;
const { pinnedParticipant, dominantSpeaker } = state;
if (pinned) {
@ -282,13 +289,13 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
// The sort order of participants is preserved since Map remembers the original insertion order of the keys.
state.sortedRemoteParticipants = new Map(sortedRemoteParticipants);
if (isFakeScreenShareParticipant) {
const sortedRemoteFakeScreenShareParticipants = [ ...state.sortedRemoteFakeScreenShareParticipants ];
if (isVirtualScreenshareParticipant) {
const sortedRemoteVirtualScreenshareParticipants = [ ...state.sortedRemoteVirtualScreenshareParticipants ];
sortedRemoteFakeScreenShareParticipants.push([ id, name ]);
sortedRemoteFakeScreenShareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
sortedRemoteVirtualScreenshareParticipants.push([ id, name ]);
sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
state.sortedRemoteFakeScreenShareParticipants = new Map(sortedRemoteFakeScreenShareParticipants);
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
}
if (isFakeParticipant) {
state.fakeParticipants.set(id, participant);
@ -306,7 +313,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
const { conference, id } = action.participant;
const {
fakeParticipants,
sortedRemoteFakeScreenShareParticipants,
sortedRemoteVirtualScreenshareParticipants,
remote,
local,
localScreenShare,
@ -372,9 +379,9 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
fakeParticipants.delete(id);
}
if (sortedRemoteFakeScreenShareParticipants.has(id)) {
sortedRemoteFakeScreenShareParticipants.delete(id);
state.sortedRemoteFakeScreenShareParticipants = new Map(sortedRemoteFakeScreenShareParticipants);
if (sortedRemoteVirtualScreenshareParticipants.has(id)) {
sortedRemoteVirtualScreenshareParticipants.delete(id);
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
}
return { ...state };
@ -500,7 +507,7 @@ function _participantJoined({ participant }) {
dominantSpeaker,
email,
isFakeParticipant,
isFakeScreenShareParticipant,
isVirtualScreenshareParticipant,
isLocalScreenShare,
isReplacing,
isJigasi,
@ -534,7 +541,7 @@ function _participantJoined({ participant }) {
email,
id,
isFakeParticipant,
isFakeScreenShareParticipant,
isVirtualScreenshareParticipant,
isLocalScreenShare,
isReplacing,
isJigasi,

View File

@ -0,0 +1,69 @@
// @flow
import _ from 'lodash';
import { getCurrentConference } from '../conference';
import { getMultipleVideoSupportFeatureFlag } from '../config';
import { StateListenerRegistry } from '../redux';
import { createVirtualScreenshareParticipant, participantLeft } from './actions';
StateListenerRegistry.register(
/* selector */ state => state['features/base/tracks'],
/* listener */(tracks, store) => _updateScreenshareParticipants(store)
);
/**
* Handles creating and removing virtual screenshare participants.
*
* @param {*} store - The redux store.
* @returns {void}
*/
function _updateScreenshareParticipants({ getState, dispatch }) {
const state = getState();
if (!getMultipleVideoSupportFeatureFlag(state)) {
return;
}
const conference = getCurrentConference(state);
const tracks = state['features/base/tracks'];
const { sortedRemoteVirtualScreenshareParticipants, localScreenShare } = state['features/base/participants'];
const previousScreenshareSourceNames = [ ...sortedRemoteVirtualScreenshareParticipants.keys() ];
let newLocalSceenshareSourceName;
const currentScreenshareSourceNames = tracks.reduce((acc, track) => {
if (track.videoType === 'desktop' && !track.jitsiTrack.isMuted()) {
const sourceName = track.jitsiTrack.getSourceName();
if (track.local) {
newLocalSceenshareSourceName = sourceName;
} else {
acc.push(sourceName);
}
}
return acc;
}, []);
if (!localScreenShare && newLocalSceenshareSourceName) {
dispatch(createVirtualScreenshareParticipant(newLocalSceenshareSourceName, true));
}
if (localScreenShare && !newLocalSceenshareSourceName) {
dispatch(participantLeft(localScreenShare.id, conference));
}
const removedScreenshareSourceNames = _.difference(previousScreenshareSourceNames, currentScreenshareSourceNames);
const addedScreenshareSourceNames = _.difference(currentScreenshareSourceNames, previousScreenshareSourceNames);
if (removedScreenshareSourceNames.length) {
removedScreenshareSourceNames.forEach(id => dispatch(participantLeft(id, conference)));
}
if (addedScreenshareSourceNames.length) {
addedScreenshareSourceNames.forEach(id => dispatch(createVirtualScreenshareParticipant(id, false)));
}
}

View File

@ -107,18 +107,6 @@ export const TRACK_STOPPED = 'TRACK_STOPPED';
*/
export const TRACK_UPDATED = 'TRACK_UPDATED';
/**
* The type of redux action dispatched when a screenshare track's muted property were updated.
*
* {
* type: SCREENSHARE_TRACK_MUTED_UPDATED,
* track: Track,
* muted: Boolean
*
* }
*/
export const SCREENSHARE_TRACK_MUTED_UPDATED = 'SCREENSHARE_TRACK_MUTED_UPDATED';
/**
* The type of redux action dispatched when a local track starts being created
* via a WebRTC {@code getUserMedia} call. The action's payload includes an

View File

@ -6,7 +6,7 @@ import {
} from '../../analytics';
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification, showNotification } from '../../notifications';
import { getCurrentConference } from '../conference';
import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../config';
import { getMultipleVideoSendingSupportFeatureFlag } from '../config';
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
import { createLocalTrack } from '../lib-jitsi-meet/functions';
import {
@ -22,7 +22,6 @@ import { getLocalParticipant } from '../participants';
import { updateSettings } from '../settings';
import {
SCREENSHARE_TRACK_MUTED_UPDATED,
SET_NO_SRC_DATA_NOTIFICATION_UID,
TOGGLE_SCREENSHARING,
TRACK_ADDED,
@ -60,7 +59,7 @@ export function addLocalTrack(newTrack) {
}
const setMuted = newTrack.isVideoTrack()
? getMultipleVideoSupportFeatureFlag(getState())
? getMultipleVideoSendingSupportFeatureFlag(getState())
&& newTrack.getVideoType() === VIDEO_TYPE.DESKTOP
? setScreenshareMuted
: setVideoMuted
@ -371,7 +370,8 @@ function replaceStoredTracks(oldTrack, newTrack) {
// state. If this is not done, the current mute state of the app will be reflected on the track,
// not vice-versa.
const setMuted = newTrack.isVideoTrack()
? getMultipleVideoSupportFeatureFlag(getState()) && newTrack.getVideoType() === VIDEO_TYPE.DESKTOP
? getMultipleVideoSendingSupportFeatureFlag(getState())
&& newTrack.getVideoType() === VIDEO_TYPE.DESKTOP
? setScreenshareMuted
: setVideoMuted
: setAudioMuted;
@ -397,19 +397,15 @@ export function trackAdded(track) {
return async (dispatch, getState) => {
track.on(
JitsiTrackEvents.TRACK_MUTE_CHANGED,
() => {
if (getSourceNameSignalingFeatureFlag(getState()) && track.getVideoType() === VIDEO_TYPE.DESKTOP) {
dispatch(screenshareTrackMutedChanged(track));
}
dispatch(trackMutedChanged(track));
});
() => dispatch(trackMutedChanged(track)));
track.on(
JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
type => dispatch(trackVideoTypeChanged(track, type)));
// participantId
const local = track.isLocal();
const mediaType = getMultipleVideoSupportFeatureFlag(getState()) && track.getVideoType() === VIDEO_TYPE.DESKTOP
const mediaType = getMultipleVideoSendingSupportFeatureFlag(getState())
&& track.getVideoType() === VIDEO_TYPE.DESKTOP
? MEDIA_TYPE.SCREENSHARE
: track.getType();
let isReceivingData, noDataFromSourceNotificationInfo, participantId;
@ -498,24 +494,6 @@ export function trackMutedChanged(track) {
};
}
/**
* Create an action for when a screenshare track's muted state has been signaled to be changed.
*
* @param {(JitsiLocalTrack|JitsiRemoteTrack)} track - JitsiTrack instance.
* @returns {{
* type: TRACK_UPDATED,
* track: Track,
* muted: boolean
* }}
*/
export function screenshareTrackMutedChanged(track) {
return {
type: SCREENSHARE_TRACK_MUTED_UPDATED,
track: { jitsiTrack: track },
muted: track.isMuted()
};
}
/**
* Create an action for when a track's muted state change action has failed. This could happen because of
* {@code getUserMedia} errors during unmute or replace track errors at the peerconnection level.

View File

@ -1,10 +1,13 @@
/* global APP */
import { getMultipleVideoSupportFeatureFlag } from '../config/functions.any';
import {
getMultipleVideoSendingSupportFeatureFlag,
getMultipleVideoSupportFeatureFlag
} from '../config/functions.any';
import { isMobileBrowser } from '../environment/utils';
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
import { getFakeScreenShareParticipantOwnerId } from '../participants';
import { getVirtualScreenshareParticipantOwnerId } from '../participants';
import { toState } from '../redux';
import {
getUserSelectedCameraDeviceId,
@ -426,18 +429,11 @@ export function getVideoTrackByParticipant(
return;
}
let participantId;
let mediaType;
if (participant?.isFakeScreenShareParticipant) {
participantId = getFakeScreenShareParticipantOwnerId(participant.id);
mediaType = MEDIA_TYPE.SCREENSHARE;
} else {
participantId = participant.id;
mediaType = MEDIA_TYPE.VIDEO;
if (participant?.isVirtualScreenshareParticipant) {
return getVirtualScreenshareParticipantTrack(tracks, participant.id);
}
return getTrackByMediaTypeAndParticipant(tracks, mediaType, participantId);
return getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participant.id);
}
/**
@ -458,16 +454,54 @@ export function getTrackByMediaTypeAndParticipant(
}
/**
* Returns track of given fakeScreenshareParticipantId.
* Returns screenshare track of given virtualScreenshareParticipantId.
*
* @param {Track[]} tracks - List of all tracks.
* @param {string} fakeScreenshareParticipantId - Fake Screenshare Participant ID.
* @param {string} virtualScreenshareParticipantId - Virtual Screenshare Participant ID.
* @returns {(Track|undefined)}
*/
export function getFakeScreenshareParticipantTrack(tracks, fakeScreenshareParticipantId) {
const participantId = getFakeScreenShareParticipantOwnerId(fakeScreenshareParticipantId);
export function getVirtualScreenshareParticipantTrack(tracks, virtualScreenshareParticipantId) {
const ownderId = getVirtualScreenshareParticipantOwnerId(virtualScreenshareParticipantId);
return getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.SCREENSHARE, participantId);
return getScreenShareTrack(tracks, ownderId);
}
/**
* Returns track source names of given screen share participant ids.
*
* @param {Object} state - The entire redux state.
* @param {string[]} screenShareParticipantIds - Participant ID.
* @returns {(string[])}
*/
export function getRemoteScreenSharesSourceNames(state, screenShareParticipantIds = []) {
const tracks = state['features/base/tracks'];
return getMultipleVideoSupportFeatureFlag(state)
? screenShareParticipantIds
: screenShareParticipantIds.reduce((acc, id) => {
const sourceName = getScreenShareTrack(tracks, id)?.jitsiTrack.getSourceName();
if (sourceName) {
acc.push(sourceName);
}
return acc;
}, []);
}
/**
* Returns screenshare track of given owner ID.
*
* @param {Track[]} tracks - List of all tracks.
* @param {string} ownerId - Screenshare track owner ID.
* @returns {(Track|undefined)}
*/
export function getScreenShareTrack(tracks, ownerId) {
return tracks.find(
t => Boolean(t.jitsiTrack)
&& t.participantId === ownerId
&& (t.mediaType === MEDIA_TYPE.SCREENSHARE || t.videoType === VIDEO_TYPE.DESKTOP)
);
}
/**
@ -610,7 +644,7 @@ export function setTrackMuted(track, muted, state) {
// browser's 'Stop sharing' button, the local stream is stopped before the inactive stream handler is fired.
// We still need to proceed here and remove the track from the peerconnection.
if (track.isMuted() === muted
&& !(track.getVideoType() === VIDEO_TYPE.DESKTOP && getMultipleVideoSupportFeatureFlag(state))) {
&& !(track.getVideoType() === VIDEO_TYPE.DESKTOP && getMultipleVideoSendingSupportFeatureFlag(state))) {
return Promise.resolve();
}

View File

@ -9,7 +9,7 @@ import { _RESET_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
import { hideNotification, isModerationNotificationDisplayed } from '../../notifications';
import { isPrejoinPageVisible } from '../../prejoin/functions';
import { getCurrentConference } from '../conference/functions';
import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../config';
import { getMultipleVideoSendingSupportFeatureFlag } from '../config';
import { getAvailableDevices } from '../devices/actions';
import {
CAMERA_FACING_MODE,
@ -25,11 +25,9 @@ import {
setScreenshareMuted,
SCREENSHARE_MUTISM_AUTHORITY
} from '../media';
import { participantLeft, participantJoined, getParticipantById } from '../participants';
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
import {
SCREENSHARE_TRACK_MUTED_UPDATED,
TOGGLE_SCREENSHARING,
TRACK_ADDED,
TRACK_MUTE_UNMUTE_FAILED,
@ -53,7 +51,6 @@ import {
isUserInteractionRequiredForUnmute,
setTrackMuted
} from './functions';
import logger from './logger';
import './subscriber';
@ -70,8 +67,7 @@ declare var APP: Object;
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case TRACK_ADDED: {
const state = store.getState();
const { jitsiTrack, local } = action.track;
const { local } = action.track;
// The devices list needs to be refreshed when no initial video permissions
// were granted and a local video track is added by umuting the video.
@ -79,23 +75,7 @@ MiddlewareRegistry.register(store => next => action => {
store.dispatch(getAvailableDevices());
}
// Call next before the creation of a fake screenshare participant to ensure a video track is available when
// the participant is auto pinned.
const result = next(action);
// The TRACK_ADDED action is dispatched when a presenter starts a screenshare. Do not create a local fake
// screenshare participant when multiple stream is not enabled.
const skipCreateFakeScreenShareParticipant = local && !getMultipleVideoSupportFeatureFlag(state);
if (getSourceNameSignalingFeatureFlag(state)
&& jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP
&& !jitsiTrack.isMuted()
&& !skipCreateFakeScreenShareParticipant
) {
createFakeScreenShareParticipant(store, action);
}
return result;
break;
}
case TRACK_NO_DATA_FROM_SOURCE: {
const result = next(action);
@ -105,39 +85,7 @@ MiddlewareRegistry.register(store => next => action => {
return result;
}
case SCREENSHARE_TRACK_MUTED_UPDATED: {
const state = store.getState();
if (!getSourceNameSignalingFeatureFlag(state)) {
return;
}
const { track, muted } = action;
if (muted) {
const conference = getCurrentConference(state);
const participantId = track?.jitsiTrack.getSourceName();
store.dispatch(participantLeft(participantId, conference));
}
if (!muted) {
createFakeScreenShareParticipant(store, action);
}
break;
}
case TRACK_REMOVED: {
const state = store.getState();
if (getSourceNameSignalingFeatureFlag(state) && action.track.jitsiTrack.videoType === VIDEO_TYPE.DESKTOP) {
const conference = getCurrentConference(state);
const participantId = action.track.jitsiTrack.getSourceName();
store.dispatch(participantLeft(participantId, conference));
}
_removeNoDataFromSourceNotification(store, action.track);
break;
}
@ -223,7 +171,7 @@ MiddlewareRegistry.register(store => next => action => {
const { enabled, audioOnly, ignoreDidHaveVideo } = action;
if (!getMultipleVideoSupportFeatureFlag(store.getState())) {
if (!getMultipleVideoSendingSupportFeatureFlag(store.getState())) {
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING,
{
enabled,
@ -241,7 +189,7 @@ MiddlewareRegistry.register(store => next => action => {
if (typeof APP !== 'undefined') {
if (isVideoTrack && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP
&& getMultipleVideoSupportFeatureFlag(store.getState())) {
&& getMultipleVideoSendingSupportFeatureFlag(store.getState())) {
store.dispatch(setScreenshareMuted(!muted));
} else if (isVideoTrack) {
APP.conference.setVideoMuteStatus();
@ -256,7 +204,7 @@ MiddlewareRegistry.register(store => next => action => {
const { jitsiTrack } = action.track;
if (typeof APP !== 'undefined'
&& getMultipleVideoSupportFeatureFlag(store.getState())
&& getMultipleVideoSendingSupportFeatureFlag(store.getState())
&& jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
store.dispatch(toggleScreensharing(false));
}
@ -286,7 +234,7 @@ MiddlewareRegistry.register(store => next => action => {
} else if (jitsiTrack.isLocal() && !(jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP)) {
APP.conference.setVideoMuteStatus();
} else if (jitsiTrack.isLocal() && muted && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
!getMultipleVideoSupportFeatureFlag(state)
!getMultipleVideoSendingSupportFeatureFlag(state)
&& store.dispatch(toggleScreensharing(false, false, true));
} else {
APP.UI.setVideoMuted(participantID);
@ -384,32 +332,6 @@ function _handleNoDataFromSourceErrors(store, action) {
}
}
/**
* Creates a fake participant for screen share using the track's source name as the participant id.
*
* @param {Store} store - The redux store in which the specified action is dispatched.
* @param {Action} action - The redux action dispatched in the specified store.
* @private
* @returns {void}
*/
function createFakeScreenShareParticipant({ dispatch, getState }, { track }) {
const state = getState();
const participantId = track.jitsiTrack?.getParticipantId?.();
const participant = getParticipantById(state, participantId);
if (participant.name) {
dispatch(participantJoined({
conference: state['features/base/conference'].conference,
id: track.jitsiTrack.getSourceName(),
isFakeScreenShareParticipant: true,
isLocalScreenShare: track?.jitsiTrack.isLocal(),
name: participant.name
}));
} else {
logger.error(`Failed to create a screenshare participant for participantId: ${participantId}`);
}
}
/**
* Gets the local track associated with a specific {@code MEDIA_TYPE} in a
* specific redux store.
@ -472,7 +394,7 @@ async function _setMuted(store, { ensureTrack, authority, muted }, mediaType: ME
const state = getState();
if (mediaType === MEDIA_TYPE.SCREENSHARE
&& getMultipleVideoSupportFeatureFlag(state)
&& getMultipleVideoSendingSupportFeatureFlag(state)
&& !muted) {
return;
}
@ -487,8 +409,9 @@ async function _setMuted(store, { ensureTrack, authority, muted }, mediaType: ME
// Screenshare cannot be unmuted using the video mute button unless it is muted by audioOnly in the legacy
// screensharing mode.
if (jitsiTrack
&& (jitsiTrack.videoType !== 'desktop' || isAudioOnly || getMultipleVideoSupportFeatureFlag(state))) {
if (jitsiTrack && (
jitsiTrack.videoType !== 'desktop' || isAudioOnly || getMultipleVideoSendingSupportFeatureFlag(state))
) {
setTrackMuted(jitsiTrack, muted, state).catch(() => dispatch(trackMuteUnmuteFailed(localTrack, muted)));
}
} else if (!muted && ensureTrack && (typeof APP === 'undefined' || isPrejoinPageVisible(state))) {

View File

@ -12,7 +12,7 @@ import { getLocalParticipant, getParticipantById } from '../../../base/participa
import { Popover } from '../../../base/popover';
import { connect } from '../../../base/redux';
import {
getFakeScreenshareParticipantTrack,
getVirtualScreenshareParticipantTrack,
getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
import {
isParticipantConnectionStatusInactive,
@ -374,8 +374,8 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
let firstVideoTrack;
if (sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant) {
firstVideoTrack = getFakeScreenshareParticipantTrack(tracks, participantId);
if (sourceNameSignalingEnabled && participant?.isVirtualScreenshareParticipant) {
firstVideoTrack = getVirtualScreenshareParticipantTrack(tracks, participantId);
} else {
firstVideoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
}

View File

@ -91,7 +91,7 @@ type Props = AbstractProps & {
* Whether or not the displays stats are for screen share. This prop is behind the sourceNameSignaling feature
* flag.
*/
_isFakeScreenShareParticipant: Boolean,
_isVirtualScreenshareParticipant: Boolean,
/**
* Whether or not the displays stats are for local video.
@ -205,8 +205,8 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<Props, Stat
e2eRtt = { e2eRtt }
enableSaveLogs = { this.props._enableSaveLogs }
framerate = { framerate }
isFakeScreenShareParticipant = { this.props._isFakeScreenShareParticipant }
isLocalVideo = { this.props._isLocalVideo }
isVirtualScreenshareParticipant = { this.props._isVirtualScreenshareParticipant }
maxEnabledResolution = { maxEnabledResolution }
onSaveLogs = { this.props._onSaveLogs }
onShowMore = { this._onToggleShowMore }
@ -343,7 +343,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
_disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
_isConnectionStatusInactive,
_isConnectionStatusInterrupted,
_isFakeScreenShareParticipant: sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant,
_isVirtualScreenshareParticipant: sourceNameSignalingEnabled && participant?.isVirtualScreenshareParticipant,
_isLocalVideo: participant?.local,
_region: participant?.region
};

View File

@ -94,7 +94,7 @@ type Props = {
/**
* Whether or not the statistics are for screen share.
*/
isFakeScreenShareParticipant: boolean,
isVirtualScreenshareParticipant: boolean,
/**
* The send-side max enabled resolution (aka the highest layer that is not
@ -240,12 +240,12 @@ class ConnectionStatsTable extends Component<Props> {
classes,
disableShowMoreStats,
enableSaveLogs,
isFakeScreenShareParticipant,
isVirtualScreenshareParticipant,
isLocalVideo
} = this.props;
const className = clsx(classes.connectionStatsTable, { [classes.mobile]: isMobileBrowser() });
if (isFakeScreenShareParticipant) {
if (isVirtualScreenshareParticipant) {
return this._renderScreenShareStatus();
}

View File

@ -7,7 +7,7 @@ import React, { Component } from 'react';
import { createScreenSharingIssueEvent, sendAnalytics } from '../../../analytics';
import { Avatar } from '../../../base/avatar';
import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../../../base/config';
import { isMobileBrowser } from '../../../base/environment/utils';
import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet';
import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
@ -24,7 +24,7 @@ import {
getLocalAudioTrack,
getLocalVideoTrack,
getTrackByMediaTypeAndParticipant,
getFakeScreenshareParticipantTrack,
getVirtualScreenshareParticipantTrack,
updateLastTrackVideoMediaEvent,
trackStreamingStatusChanged
} from '../../../base/tracks';
@ -51,10 +51,10 @@ import {
showGridInVerticalView
} from '../../functions';
import FakeScreenShareParticipant from './FakeScreenShareParticipant';
import ThumbnailAudioIndicator from './ThumbnailAudioIndicator';
import ThumbnailBottomIndicators from './ThumbnailBottomIndicators';
import ThumbnailTopIndicators from './ThumbnailTopIndicators';
import VirtualScreenshareParticipant from './VirtualScreenshareParticipant';
declare var interfaceConfig: Object;
@ -137,10 +137,10 @@ export type Props = {|
_isCurrentlyOnLargeVideo: boolean,
/**
* Indicates whether the participant is a fake screen share participant. This prop is behind the
* Indicates whether the participant is a virtual screen share participant. This prop is behind the
* sourceNameSignaling feature flag.
*/
_isFakeScreenShareParticipant: boolean,
_isVirtualScreenshareParticipant: boolean,
/**
* Whether we are currently running in a mobile browser.
@ -626,7 +626,7 @@ class Thumbnail extends Component<Props, State> {
const {
_disableTileEnlargement,
_height,
_isFakeScreenShareParticipant,
_isVirtualScreenshareParticipant,
_isHidden,
_isScreenSharing,
_participant,
@ -665,7 +665,7 @@ class Thumbnail extends Component<Props, State> {
|| _disableTileEnlargement
|| _isScreenSharing;
if (canPlayEventReceived || _participant.local || _isFakeScreenShareParticipant) {
if (canPlayEventReceived || _participant.local || _isVirtualScreenshareParticipant) {
videoStyles = {
objectFit: doNotStretchVideo ? 'contain' : 'cover'
};
@ -1107,7 +1107,7 @@ class Thumbnail extends Component<Props, State> {
* @returns {ReactElement}
*/
render() {
const { _participant, _isFakeScreenShareParticipant } = this.props;
const { _participant, _isVirtualScreenshareParticipant } = this.props;
if (!_participant) {
return null;
@ -1123,12 +1123,12 @@ class Thumbnail extends Component<Props, State> {
return this._renderFakeParticipant();
}
if (_isFakeScreenShareParticipant) {
if (_isVirtualScreenshareParticipant) {
const { isHovered } = this.state;
const { _videoTrack, _isMobile, classes, _thumbnailType } = this.props;
return (
<FakeScreenShareParticipant
<VirtualScreenshareParticipant
classes = { classes }
containerClassName = { this._getContainerClassName() }
isHovered = { isHovered }
@ -1170,8 +1170,8 @@ function _mapStateToProps(state, ownProps): Object {
let _videoTrack;
if (sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant) {
_videoTrack = getFakeScreenshareParticipantTrack(tracks, id);
if (sourceNameSignalingEnabled && participant?.isVirtualScreenshareParticipant) {
_videoTrack = getVirtualScreenshareParticipantTrack(tracks, id);
} else {
_videoTrack = isLocal
? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
@ -1193,7 +1193,6 @@ function _mapStateToProps(state, ownProps): Object {
const activeParticipants = getActiveParticipantsIds(state);
const tileType = getThumbnailTypeFromLayout(_currentLayout, stageFilmstrip);
switch (tileType) {
case THUMBNAIL_TYPE.VERTICAL:
case THUMBNAIL_TYPE.HORIZONTAL: {
@ -1263,6 +1262,7 @@ function _mapStateToProps(state, ownProps): Object {
return {
_audioTrack,
_currentLayout,
_defaultLocalDisplayName: defaultLocalDisplayName,
_disableLocalVideoFlip: Boolean(disableLocalVideoFlip),
_disableTileEnlargement: Boolean(disableTileEnlargement),
@ -1271,13 +1271,14 @@ function _mapStateToProps(state, ownProps): Object {
_isAudioOnly: Boolean(state['features/base/audio-only'].enabled),
_isCurrentlyOnLargeVideo: state['features/large-video']?.participantId === id,
_isDominantSpeakerDisabled: interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR,
_isFakeScreenShareParticipant: sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant,
_isMobile,
_isMobilePortrait,
_isScreenSharing: _videoTrack?.videoType === 'desktop',
_isTestModeEnabled: isTestModeEnabled(state),
_isVideoPlayable: id && isVideoPlayable(state, id),
_isVirtualScreenshareParticipant: sourceNameSignalingEnabled && participant?.isVirtualScreenshareParticipant,
_localFlipX: Boolean(localFlipX),
_multipleVideoSupport: getMultipleVideoSupportFeatureFlag(state),
_participant: participant,
_raisedHand: hasRaisedHand(participant),
_stageFilmstripLayout: isStageFilmstripAvailable(state),

View File

@ -36,9 +36,9 @@ type Props = {
isHovered: boolean,
/**
* Whether or not the thumbnail is a fake screen share participant.
* Whether or not the thumbnail is a virtual screen share participant.
*/
isFakeScreenShareParticipant: boolean,
isVirtualScreenshareParticipant: boolean,
/**
* Whether or not the indicators are for the local participant.
@ -81,7 +81,7 @@ const useStyles = makeStyles(() => {
const ThumbnailTopIndicators = ({
hidePopover,
indicatorsClassName,
isFakeScreenShareParticipant,
isVirtualScreenshareParticipant,
isHovered,
local,
participantId,
@ -101,7 +101,7 @@ const ThumbnailTopIndicators = ({
const sourceNameSignalingEnabled = useSelector(getSourceNameSignalingFeatureFlag);
const showConnectionIndicator = isHovered || !_connectionIndicatorAutoHideEnabled;
if (sourceNameSignalingEnabled && isFakeScreenShareParticipant) {
if (sourceNameSignalingEnabled && isVirtualScreenshareParticipant) {
return (
<div className = { styles.container }>
{!_connectionIndicatorDisabled

View File

@ -68,7 +68,7 @@ type Props = {
onTouchStart: Function,
/**
* The ID of the fake screen share participant.
* The ID of the virtual screen share participant.
*/
participantId: string,
@ -88,7 +88,7 @@ type Props = {
videoTrack: Object
}
const FakeScreenShareParticipant = ({
const VirtualScreenshareParticipant = ({
classes,
containerClassName,
isHovered,
@ -141,8 +141,8 @@ const FakeScreenShareParticipant = ({
) }>
<ThumbnailTopIndicators
currentLayout = { currentLayout }
isFakeScreenShareParticipant = { true }
isHovered = { isHovered }
isVirtualScreenshareParticipant = { true }
participantId = { participantId }
thumbnailType = { thumbnailType } />
</div>
@ -161,4 +161,4 @@ const FakeScreenShareParticipant = ({
</span>);
};
export default FakeScreenShareParticipant;
export default VirtualScreenshareParticipant;

View File

@ -1,7 +1,7 @@
// @flow
import { getSourceNameSignalingFeatureFlag } from '../base/config';
import { getFakeScreenShareParticipantOwnerId } from '../base/participants';
import { getVirtualScreenshareParticipantOwnerId } from '../base/participants';
import { setRemoteParticipants } from './actions';
import { isReorderingEnabled } from './functions';
@ -18,9 +18,9 @@ export function updateRemoteParticipants(store: Object, participantId: ?number)
const state = store.getState();
let reorderedParticipants = [];
const { sortedRemoteFakeScreenShareParticipants } = state['features/base/participants'];
const { sortedRemoteVirtualScreenshareParticipants } = state['features/base/participants'];
if (!isReorderingEnabled(state) && !sortedRemoteFakeScreenShareParticipants.size) {
if (!isReorderingEnabled(state) && !sortedRemoteVirtualScreenshareParticipants.size) {
if (participantId) {
const { remoteParticipants } = state['features/filmstrip'];
@ -39,14 +39,14 @@ export function updateRemoteParticipants(store: Object, participantId: ?number)
} = state['features/base/participants'];
const remoteParticipants = new Map(sortedRemoteParticipants);
const screenShares = new Map(sortedRemoteScreenshares);
const screenShareParticipants = sortedRemoteFakeScreenShareParticipants
? [ ...sortedRemoteFakeScreenShareParticipants.keys() ] : [];
const screenShareParticipants = sortedRemoteVirtualScreenshareParticipants
? [ ...sortedRemoteVirtualScreenshareParticipants.keys() ] : [];
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
const speakers = new Map(speakersList);
if (getSourceNameSignalingFeatureFlag(state)) {
for (const screenshare of screenShareParticipants) {
const ownerId = getFakeScreenShareParticipantOwnerId(screenshare);
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
remoteParticipants.delete(ownerId);
remoteParticipants.delete(screenshare);
@ -72,7 +72,7 @@ export function updateRemoteParticipants(store: Object, participantId: ?number)
if (getSourceNameSignalingFeatureFlag(state)) {
// Always update the order of the thumnails.
const participantsWithScreenShare = screenShareParticipants.reduce((acc, screenshare) => {
const ownerId = getFakeScreenShareParticipantOwnerId(screenshare);
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
acc.push(ownerId);
acc.push(screenshare);

View File

@ -509,18 +509,29 @@ export function computeDisplayModeFromInput(input: Object) {
isActiveParticipant,
isAudioOnly,
isCurrentlyOnLargeVideo,
isFakeScreenShareParticipant,
isVirtualScreenshareParticipant,
isScreenSharing,
canPlayEventReceived,
isRemoteParticipant,
multipleVideoSupport,
stageParticipantsVisible,
stageFilmstrip,
tileViewActive
} = input;
const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
if (isFakeScreenShareParticipant) {
return DISPLAY_VIDEO;
if (multipleVideoSupport) {
// Display video for virtual screen share participants in all layouts.
if (isVirtualScreenshareParticipant) {
return DISPLAY_VIDEO;
}
// Multi-stream is not supported on plan-b endpoints even if its is enabled via config.js. A virtual
// screenshare tile is still created when a remote endpoint starts screenshare to keep the behavior consistent
// and an avatar is displayed on the original participant thumbnail as long as screenshare is in progress.
if (isScreenSharing) {
return DISPLAY_AVATAR;
}
}
if (!tileViewActive && ((isScreenSharing && isRemoteParticipant)
@ -551,9 +562,10 @@ export function getDisplayModeInput(props: Object, state: Object) {
_isActiveParticipant,
_isAudioOnly,
_isCurrentlyOnLargeVideo,
_isFakeScreenShareParticipant,
_isVirtualScreenshareParticipant,
_isScreenSharing,
_isVideoPlayable,
_multipleVideoSupport,
_participant,
_stageParticipantsVisible,
_videoTrack,
@ -573,7 +585,8 @@ export function getDisplayModeInput(props: Object, state: Object) {
videoStream: Boolean(_videoTrack),
isRemoteParticipant: !_participant?.isFakeParticipant && !_participant?.local,
isScreenSharing: _isScreenSharing,
isFakeScreenShareParticipant: _isFakeScreenShareParticipant,
isVirtualScreenshareParticipant: _isVirtualScreenshareParticipant,
multipleVideoSupport: _multipleVideoSupport,
stageParticipantsVisible: _stageParticipantsVisible,
stageFilmstrip,
videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'

View File

@ -16,8 +16,8 @@ StateListenerRegistry.register(
* Listens for changes to the remote screenshare participants to recompute the reordered list of the remote endpoints.
*/
StateListenerRegistry.register(
/* selector */ state => state['features/base/participants'].sortedRemoteFakeScreenShareParticipants,
/* listener */ (sortedRemoteFakeScreenShareParticipants, store) => updateRemoteParticipants(store));
/* selector */ state => state['features/base/participants'].sortedRemoteVirtualScreenshareParticipants,
/* listener */ (sortedRemoteVirtualScreenshareParticipants, store) => updateRemoteParticipants(store));
/**
* Listens for changes to the dominant speaker to recompute the reordered list of the remote endpoints.

View File

@ -128,10 +128,10 @@ MiddlewareRegistry.register(store => next => action => {
const { participant: p } = action;
const { conference } = state['features/base/conference'];
// Do not display notifications for the fake screenshare tiles.
// Do not display notifications for the virtual screenshare tiles.
if (conference
&& !p.local
&& !p.isFakeScreenShareParticipant
&& !p.isVirtualScreenshareParticipant
&& !joinLeaveNotificationsDisabled()
&& !p.isReplacing) {
dispatch(showParticipantJoinedNotification(
@ -148,10 +148,10 @@ MiddlewareRegistry.register(store => next => action => {
action.participant.id
);
// Do not display notifications for the fake screenshare tiles.
// Do not display notifications for the virtual screenshare tiles.
if (participant
&& !participant.local
&& !participant.isFakeScreenShareParticipant
&& !participant.isVirtualScreenshareParticipant
&& !action.participant.isReplaced) {
dispatch(showParticipantLeftNotification(
getParticipantDisplayName(state, participant.id)

View File

@ -1,6 +1,6 @@
// @flow
import { getMultipleVideoSupportFeatureFlag } from '../base/config/functions.any';
import { getMultipleVideoSendingSupportFeatureFlag } from '../base/config/functions.any';
import { openDialog } from '../base/dialog/actions';
import { browser } from '../base/lib-jitsi-meet';
import { shouldHideShareAudioHelper } from '../base/settings';
@ -86,7 +86,7 @@ export function startAudioScreenShareFlow() {
// available for audio screen sharing, namely full window audio.
// If we're already sharing audio, toggle off.
if (shouldHideShareAudioHelper(state) || browser.isElectron() || audioOnlySharing) {
if (getMultipleVideoSupportFeatureFlag(state)) {
if (getMultipleVideoSendingSupportFeatureFlag(state)) {
dispatch(toggleScreensharing(!audioOnlySharing, true));
return;

View File

@ -11,14 +11,14 @@ export const SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED
= 'SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED';
/**
* The type of the action which sets the list of known remote fake screen share participant IDs.
* The type of the action which sets the list of known remote virtual screen share participant IDs.
*
* @returns {{
* type: FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
* type: VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED,
* participantIds: Array<string>
* }}
*/
export const FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED = 'FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED';
export const VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED = 'VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED';
/**
* The type of the action which enables or disables the feature for showing

View File

@ -3,9 +3,9 @@
import type { Dispatch } from 'redux';
import {
FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
SET_TILE_VIEW
SET_TILE_VIEW,
VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED
} from './actionTypes';
import { shouldDisplayTileView } from './functions';
@ -28,17 +28,18 @@ export function setRemoteParticipantsWithScreenShare(participantIds: Array<strin
}
/**
* Creates a (redux) action which signals that the list of known remote fake screen share participant ids has changed.
* Creates a (redux) action which signals that the list of known remote virtual screen share participant ids has
* changed.
*
* @param {string} participantIds - The remote fake screen share participants.
* @param {string} participantIds - The remote virtual screen share participants.
* @returns {{
* type: FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
* type: VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED,
* participantIds: Array<string>
* }}
*/
export function fakeScreenshareParticipantsUpdated(participantIds: Array<string>) {
export function virtualScreenshareParticipantsUpdated(participantIds: Array<string>) {
return {
type: FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
type: VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED,
participantIds
};
}

View File

@ -3,9 +3,9 @@
import { ReducerRegistry } from '../base/redux';
import {
FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
SET_TILE_VIEW
SET_TILE_VIEW,
VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED
} from './actionTypes';
const DEFAULT_STATE = {
@ -28,8 +28,8 @@ const STORE_NAME = 'features/video-layout';
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
switch (action.type) {
case FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED: {
case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
case VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED: {
return {
...state,
remoteScreenShares: action.participantIds

View File

@ -2,22 +2,22 @@
import debounce from 'lodash/debounce';
import { getSourceNameSignalingFeatureFlag } from '../base/config';
import { getMultipleVideoSupportFeatureFlag } from '../base/config';
import { StateListenerRegistry, equals } from '../base/redux';
import { isFollowMeActive } from '../follow-me';
import { setRemoteParticipantsWithScreenShare, fakeScreenshareParticipantsUpdated } from './actions';
import { setRemoteParticipantsWithScreenShare, virtualScreenshareParticipantsUpdated } from './actions';
import { getAutoPinSetting, updateAutoPinnedParticipant } from './functions';
StateListenerRegistry.register(
/* selector */ state => state['features/base/participants'].sortedRemoteFakeScreenShareParticipants,
/* listener */ (sortedRemoteFakeScreenShareParticipants, store) => {
if (!getAutoPinSetting() || isFollowMeActive(store) || !getSourceNameSignalingFeatureFlag(store.getState())) {
/* selector */ state => state['features/base/participants'].sortedRemoteVirtualScreenshareParticipants,
/* listener */ (sortedRemoteVirtualScreenshareParticipants, store) => {
if (!getAutoPinSetting() || isFollowMeActive(store) || !getMultipleVideoSupportFeatureFlag(store.getState())) {
return;
}
const oldScreenSharesOrder = store.getState()['features/video-layout'].remoteScreenShares || [];
const knownSharingParticipantIds = [ ...sortedRemoteFakeScreenShareParticipants.keys() ];
const knownSharingParticipantIds = [ ...sortedRemoteVirtualScreenshareParticipants.keys() ];
// Filter out any participants which are no longer screen sharing
// by looping through the known sharing participants and removing any
@ -34,7 +34,7 @@ StateListenerRegistry.register(
});
if (!equals(oldScreenSharesOrder, newScreenSharesOrder)) {
store.dispatch(fakeScreenshareParticipantsUpdated(newScreenSharesOrder));
store.dispatch(virtualScreenshareParticipantsUpdated(newScreenSharesOrder));
updateAutoPinnedParticipant(oldScreenSharesOrder, store);
}
@ -53,7 +53,7 @@ StateListenerRegistry.register(
// possible to have screen sharing participant that has already left in the remoteScreenShares array.
// This can lead to rendering a thumbnails for already left participants since the remoteScreenShares
// array is used for building the ordered list of remote participants.
if (!getAutoPinSetting() || isFollowMeActive(store) || getSourceNameSignalingFeatureFlag(store.getState())) {
if (!getAutoPinSetting() || isFollowMeActive(store) || getMultipleVideoSupportFeatureFlag(store.getState())) {
return;
}

View File

@ -7,7 +7,7 @@ import { getSourceNameSignalingFeatureFlag } from '../base/config';
import { MEDIA_TYPE } from '../base/media';
import { getLocalParticipant, getParticipantCount } from '../base/participants';
import { StateListenerRegistry } from '../base/redux';
import { getTrackSourceNameByMediaTypeAndParticipant } from '../base/tracks';
import { getRemoteScreenSharesSourceNames, getTrackSourceNameByMediaTypeAndParticipant } from '../base/tracks';
import { reportError } from '../base/util';
import { getActiveParticipantsIds } from '../filmstrip/functions.web';
import {
@ -238,6 +238,8 @@ function _updateReceiverVideoConstraints({ getState }) {
let receiverConstraints;
if (sourceNameSignaling) {
const remoteScreenSharesSourceNames = getRemoteScreenSharesSourceNames(state, remoteScreenShares);
receiverConstraints = {
constraints: {},
defaultConstraints: { 'maxHeight': VIDEO_QUALITY_LEVELS.NONE },
@ -253,7 +255,7 @@ function _updateReceiverVideoConstraints({ getState }) {
visibleRemoteParticipants.forEach(participantId => {
let sourceName;
if (remoteScreenShares.includes(participantId)) {
if (remoteScreenSharesSourceNames.includes(participantId)) {
sourceName = participantId;
} else {
sourceName = getTrackSourceNameByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
@ -269,12 +271,11 @@ function _updateReceiverVideoConstraints({ getState }) {
}
if (localParticipantId !== largeVideoParticipantId) {
if (remoteScreenShares.includes(largeVideoParticipantId)) {
if (remoteScreenSharesSourceNames.includes(largeVideoParticipantId)) {
largeVideoSourceName = largeVideoParticipantId;
} else {
largeVideoSourceName = getTrackSourceNameByMediaTypeAndParticipant(
tracks, MEDIA_TYPE.VIDEO,
largeVideoParticipantId
tracks, MEDIA_TYPE.VIDEO, largeVideoParticipantId
);
}
}
@ -290,8 +291,8 @@ function _updateReceiverVideoConstraints({ getState }) {
});
// Prioritize screenshare in tile view.
if (remoteScreenShares?.length) {
receiverConstraints.selectedSources = remoteScreenShares;
if (remoteScreenSharesSourceNames?.length) {
receiverConstraints.selectedSources = remoteScreenSharesSourceNames;
}
// Stage view.
@ -325,8 +326,8 @@ function _updateReceiverVideoConstraints({ getState }) {
}
}
if (remoteScreenShares?.length) {
remoteScreenShares.forEach(sourceName => {
if (remoteScreenSharesSourceNames?.length) {
remoteScreenSharesSourceNames.forEach(sourceName => {
receiverConstraints.constraints[sourceName] = { 'maxHeight': VIDEO_QUALITY_LEVELS.ULTRA };
});
}