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:
parent
b9c4d28dac
commit
d3fe246f61
|
@ -56,8 +56,7 @@ import {
|
||||||
} from './react/features/base/conference';
|
} from './react/features/base/conference';
|
||||||
import {
|
import {
|
||||||
getReplaceParticipant,
|
getReplaceParticipant,
|
||||||
getMultipleVideoSupportFeatureFlag,
|
getMultipleVideoSendingSupportFeatureFlag
|
||||||
getSourceNameSignalingFeatureFlag
|
|
||||||
} from './react/features/base/config/functions';
|
} from './react/features/base/config/functions';
|
||||||
import {
|
import {
|
||||||
checkAndNotifyForNewDevice,
|
checkAndNotifyForNewDevice,
|
||||||
|
@ -97,7 +96,7 @@ import {
|
||||||
dominantSpeakerChanged,
|
dominantSpeakerChanged,
|
||||||
getLocalParticipant,
|
getLocalParticipant,
|
||||||
getNormalizedDisplayName,
|
getNormalizedDisplayName,
|
||||||
getScreenshareParticipantByOwnerId,
|
getVirtualScreenshareParticipantByOwnerId,
|
||||||
localParticipantAudioLevelChanged,
|
localParticipantAudioLevelChanged,
|
||||||
localParticipantConnectionStatusChanged,
|
localParticipantConnectionStatusChanged,
|
||||||
localParticipantRoleChanged,
|
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
|
// In the multi-stream mode, add the track to the conference if there is no existing track, replace it
|
||||||
// otherwise.
|
// otherwise.
|
||||||
if (getMultipleVideoSupportFeatureFlag(state)) {
|
if (getMultipleVideoSendingSupportFeatureFlag(state)) {
|
||||||
const trackAction = oldTrack
|
const trackAction = oldTrack
|
||||||
? replaceLocalTrack(oldTrack, newTrack, room)
|
? replaceLocalTrack(oldTrack, newTrack, room)
|
||||||
: addLocalTrack(newTrack);
|
: addLocalTrack(newTrack);
|
||||||
|
@ -2265,14 +2264,12 @@ export default {
|
||||||
name: formattedDisplayName
|
name: formattedDisplayName
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (getSourceNameSignalingFeatureFlag(state)) {
|
const virtualScreenshareParticipantId = getVirtualScreenshareParticipantByOwnerId(state, id)?.id;
|
||||||
const screenshareParticipantId = getScreenshareParticipantByOwnerId(state, id)?.id;
|
|
||||||
|
|
||||||
if (screenshareParticipantId) {
|
if (virtualScreenshareParticipantId) {
|
||||||
APP.store.dispatch(
|
APP.store.dispatch(
|
||||||
screenshareParticipantDisplayNameChanged(screenshareParticipantId, formattedDisplayName)
|
screenshareParticipantDisplayNameChanged(virtualScreenshareParticipantId, formattedDisplayName)
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
APP.API.notifyDisplayNameChanged(id, {
|
APP.API.notifyDisplayNameChanged(id, {
|
||||||
|
|
|
@ -9,7 +9,10 @@ import { Provider } from 'react-redux';
|
||||||
import { createScreenSharingIssueEvent, sendAnalytics } from '../../../react/features/analytics';
|
import { createScreenSharingIssueEvent, sendAnalytics } from '../../../react/features/analytics';
|
||||||
import { Avatar } from '../../../react/features/base/avatar';
|
import { Avatar } from '../../../react/features/base/avatar';
|
||||||
import theme from '../../../react/features/base/components/themes/participantsPaneTheme.json';
|
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 { i18next } from '../../../react/features/base/i18n';
|
||||||
import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
|
import { JitsiTrackEvents } from '../../../react/features/base/lib-jitsi-meet';
|
||||||
import { VIDEO_TYPE } from '../../../react/features/base/media';
|
import { VIDEO_TYPE } from '../../../react/features/base/media';
|
||||||
|
@ -283,9 +286,18 @@ export default class LargeVideoManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAudioOnly = APP.conference.isAudioOnly();
|
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
|
const showAvatar
|
||||||
= isVideoContainer
|
= isVideoContainer
|
||||||
&& ((isAudioOnly && videoType !== VIDEO_TYPE.DESKTOP) || !isVideoRenderable);
|
&& ((isAudioOnly && videoType !== VIDEO_TYPE.DESKTOP) || !isVideoRenderable || legacyScreenshare);
|
||||||
|
|
||||||
let promise;
|
let promise;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
} from '../../../react/features/base/participants';
|
} from '../../../react/features/base/participants';
|
||||||
import {
|
import {
|
||||||
getTrackByMediaTypeAndParticipant,
|
getTrackByMediaTypeAndParticipant,
|
||||||
getFakeScreenshareParticipantTrack
|
getVirtualScreenshareParticipantTrack
|
||||||
} from '../../../react/features/base/tracks';
|
} from '../../../react/features/base/tracks';
|
||||||
|
|
||||||
import LargeVideoManager from './LargeVideoManager';
|
import LargeVideoManager from './LargeVideoManager';
|
||||||
|
@ -95,7 +95,7 @@ const VideoLayout = {
|
||||||
return VIDEO_TYPE.CAMERA;
|
return VIDEO_TYPE.CAMERA;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getSourceNameSignalingFeatureFlag(state) && participant?.isFakeScreenShareParticipant) {
|
if (getSourceNameSignalingFeatureFlag(state) && participant?.isVirtualScreenshareParticipant) {
|
||||||
return VIDEO_TYPE.DESKTOP;
|
return VIDEO_TYPE.DESKTOP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,8 +190,8 @@ const VideoLayout = {
|
||||||
|
|
||||||
let videoTrack;
|
let videoTrack;
|
||||||
|
|
||||||
if (getSourceNameSignalingFeatureFlag(state) && participant?.isFakeScreenShareParticipant) {
|
if (getSourceNameSignalingFeatureFlag(state) && participant?.isVirtualScreenshareParticipant) {
|
||||||
videoTrack = getFakeScreenshareParticipantTrack(tracks, id);
|
videoTrack = getVirtualScreenshareParticipantTrack(tracks, id);
|
||||||
} else {
|
} else {
|
||||||
videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
|
videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen-share';
|
import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen-share';
|
||||||
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
|
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
|
||||||
import { setAudioOnly } from '../audio-only';
|
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 { JitsiConferenceErrors, JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||||
import { MEDIA_TYPE, setScreenshareMuted, VIDEO_TYPE } from '../media';
|
import { MEDIA_TYPE, setScreenshareMuted, VIDEO_TYPE } from '../media';
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
|
@ -51,7 +51,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TOGGLE_SCREENSHARING: {
|
case TOGGLE_SCREENSHARING: {
|
||||||
getMultipleVideoSupportFeatureFlag(getState()) && _toggleScreenSharing(action, store);
|
getMultipleVideoSendingSupportFeatureFlag(getState()) && _toggleScreenSharing(action, store);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
* @param {Object} state - The global state.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function getMultipleVideoSupportFeatureFlag(state: Object) {
|
export function getMultipleVideoSupportFeatureFlag(state: Object) {
|
||||||
return getFeatureFlag(state, FEATURE_FLAGS.MULTIPLE_VIDEO_STREAMS_SUPPORT)
|
return getFeatureFlag(state, FEATURE_FLAGS.MULTIPLE_VIDEO_STREAMS_SUPPORT)
|
||||||
&& getSourceNameSignalingFeatureFlag(state)
|
&& getSourceNameSignalingFeatureFlag(state);
|
||||||
&& isUnifiedPlanEnabled(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,9 +6,9 @@ import { SET_FILMSTRIP_ENABLED } from '../../filmstrip/actionTypes';
|
||||||
import { SELECT_LARGE_VIDEO_PARTICIPANT } from '../../large-video/actionTypes';
|
import { SELECT_LARGE_VIDEO_PARTICIPANT } from '../../large-video/actionTypes';
|
||||||
import { APP_STATE_CHANGED } from '../../mobile/background/actionTypes';
|
import { APP_STATE_CHANGED } from '../../mobile/background/actionTypes';
|
||||||
import {
|
import {
|
||||||
FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
|
||||||
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';
|
} from '../../video-layout/actionTypes';
|
||||||
import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
|
import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
|
||||||
import { CONFERENCE_JOINED } from '../conference/actionTypes';
|
import { CONFERENCE_JOINED } from '../conference/actionTypes';
|
||||||
|
@ -95,7 +95,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case APP_STATE_CHANGED:
|
case APP_STATE_CHANGED:
|
||||||
case CONFERENCE_JOINED:
|
case CONFERENCE_JOINED:
|
||||||
case FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED:
|
|
||||||
case PARTICIPANT_JOINED:
|
case PARTICIPANT_JOINED:
|
||||||
case PARTICIPANT_KICKED:
|
case PARTICIPANT_KICKED:
|
||||||
case PARTICIPANT_LEFT:
|
case PARTICIPANT_LEFT:
|
||||||
|
@ -104,6 +103,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
case SET_AUDIO_ONLY:
|
case SET_AUDIO_ONLY:
|
||||||
case SET_FILMSTRIP_ENABLED:
|
case SET_FILMSTRIP_ENABLED:
|
||||||
case SET_TILE_VIEW:
|
case SET_TILE_VIEW:
|
||||||
|
case VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED:
|
||||||
_updateLastN(store);
|
_updateLastN(store);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { isForceMuted } from '../../participants-pane/functions';
|
||||||
import { isScreenMediaShared } from '../../screen-share/functions';
|
import { isScreenMediaShared } from '../../screen-share/functions';
|
||||||
import { SET_AUDIO_ONLY, setAudioOnly } from '../audio-only';
|
import { SET_AUDIO_ONLY, setAudioOnly } from '../audio-only';
|
||||||
import { isRoomValid, SET_ROOM } from '../conference';
|
import { isRoomValid, SET_ROOM } from '../conference';
|
||||||
import { getMultipleVideoSupportFeatureFlag } from '../config';
|
import { getMultipleVideoSendingSupportFeatureFlag } from '../config';
|
||||||
import { getLocalParticipant } from '../participants';
|
import { getLocalParticipant } from '../participants';
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
import { getPropertyValue } from '../settings';
|
import { getPropertyValue } from '../settings';
|
||||||
|
@ -192,7 +192,7 @@ function _setAudioOnly({ dispatch, getState }, next, action) {
|
||||||
|
|
||||||
// Make sure we mute both the desktop and video tracks.
|
// Make sure we mute both the desktop and video tracks.
|
||||||
dispatch(setVideoMuted(audioOnly, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY, ensureVideoTrack));
|
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));
|
dispatch(setScreenshareMuted(audioOnly, MEDIA_TYPE.SCREENSHARE, SCREENSHARE_MUTISM_AUTHORITY.AUDIO_ONLY));
|
||||||
} else if (navigator.product !== 'ReactNative') {
|
} else if (navigator.product !== 'ReactNative') {
|
||||||
dispatch(setVideoMuted(audioOnly, MEDIA_TYPE.PRESENTER, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY, ensureVideoTrack));
|
dispatch(setVideoMuted(audioOnly, MEDIA_TYPE.PRESENTER, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY, ensureVideoTrack));
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import {
|
import {
|
||||||
getLocalParticipant,
|
getLocalParticipant,
|
||||||
|
getVirtualScreenshareParticipantOwnerId,
|
||||||
getNormalizedDisplayName,
|
getNormalizedDisplayName,
|
||||||
getParticipantDisplayName,
|
getParticipantDisplayName,
|
||||||
getParticipantById
|
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.
|
* Action to signal that a participant had been kicked.
|
||||||
*
|
*
|
||||||
|
|
|
@ -6,21 +6,16 @@ import type { Store } from 'redux';
|
||||||
import { i18next } from '../../base/i18n';
|
import { i18next } from '../../base/i18n';
|
||||||
import { isStageFilmstripAvailable } from '../../filmstrip/functions';
|
import { isStageFilmstripAvailable } from '../../filmstrip/functions';
|
||||||
import { GRAVATAR_BASE_URL, isCORSAvatarURL } from '../avatar';
|
import { GRAVATAR_BASE_URL, isCORSAvatarURL } from '../avatar';
|
||||||
import { getSourceNameSignalingFeatureFlag } from '../config';
|
import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../config';
|
||||||
import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
|
import { JitsiParticipantConnectionStatus } from '../lib-jitsi-meet';
|
||||||
import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
|
import { MEDIA_TYPE, shouldRenderVideoTrack } from '../media';
|
||||||
import { toState } from '../redux';
|
import { toState } from '../redux';
|
||||||
import { getTrackByMediaTypeAndParticipant } from '../tracks';
|
import { getScreenShareTrack, getTrackByMediaTypeAndParticipant } from '../tracks';
|
||||||
import { createDeferred } from '../util';
|
import { createDeferred } from '../util';
|
||||||
|
|
||||||
import {
|
import { JIGASI_PARTICIPANT_ICON, MAX_DISPLAY_NAME_LENGTH, PARTICIPANT_ROLE } from './constants';
|
||||||
JIGASI_PARTICIPANT_ICON,
|
|
||||||
MAX_DISPLAY_NAME_LENGTH,
|
|
||||||
PARTICIPANT_ROLE
|
|
||||||
} from './constants';
|
|
||||||
import { preloadImage } from './preloadImage';
|
import { preloadImage } from './preloadImage';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temp structures for avatar urls to be checked/preloaded.
|
* 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.
|
* @param {string} id - The owner ID of the screenshare participant to retrieve.
|
||||||
* @returns {(Participant|undefined)}
|
* @returns {(Participant|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getScreenshareParticipantByOwnerId(stateful: Object | Function, id: string) {
|
export function getVirtualScreenshareParticipantByOwnerId(stateful: Object | Function, id: string) {
|
||||||
const track = getTrackByMediaTypeAndParticipant(
|
const state = toState(stateful);
|
||||||
toState(stateful)['features/base/tracks'], MEDIA_TYPE.SCREENSHARE, id
|
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
local,
|
||||||
remote,
|
remote,
|
||||||
fakeParticipants,
|
fakeParticipants,
|
||||||
sortedRemoteFakeScreenShareParticipants
|
sortedRemoteVirtualScreenshareParticipants
|
||||||
} = state['features/base/participants'];
|
} = state['features/base/participants'];
|
||||||
|
|
||||||
if (getSourceNameSignalingFeatureFlag(state)) {
|
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);
|
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
|
* @private
|
||||||
* @returns {(string|undefined)}
|
* @returns {(string|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getFakeScreenShareParticipantOwnerId(id: string) {
|
export function getVirtualScreenshareParticipantOwnerId(id: string) {
|
||||||
return id.split('-')[0];
|
return id.split('-')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,7 +231,7 @@ export function getRemoteParticipantCount(stateful: Object | Function) {
|
||||||
const state = toState(stateful)['features/base/participants'];
|
const state = toState(stateful)['features/base/participants'];
|
||||||
|
|
||||||
if (getSourceNameSignalingFeatureFlag(state)) {
|
if (getSourceNameSignalingFeatureFlag(state)) {
|
||||||
return state.remote.size - state.sortedRemoteFakeScreenShareParticipants.size;
|
return state.remote.size - state.sortedRemoteVirtualScreenshareParticipants.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.remote.size;
|
return state.remote.size;
|
||||||
|
@ -274,7 +273,7 @@ export function getParticipantDisplayName(stateful: Object | Function, id: strin
|
||||||
} = toState(stateful)['features/base/config'];
|
} = toState(stateful)['features/base/config'];
|
||||||
|
|
||||||
if (participant) {
|
if (participant) {
|
||||||
if (participant.isFakeScreenShareParticipant) {
|
if (participant.isVirtualScreenshareParticipant) {
|
||||||
return getScreenshareParticipantDisplayName(stateful, id);
|
return getScreenshareParticipantDisplayName(stateful, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,7 +298,7 @@ export function getParticipantDisplayName(stateful: Object | Function, id: strin
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getScreenshareParticipantDisplayName(stateful: Object | Function, id: 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;
|
const name = owner.name;
|
||||||
|
|
||||||
return i18next.t('screenshareDisplayName', { name });
|
return i18next.t('screenshareDisplayName', { name });
|
||||||
|
|
|
@ -69,6 +69,7 @@ import {
|
||||||
isLocalParticipantModerator
|
isLocalParticipantModerator
|
||||||
} from './functions';
|
} from './functions';
|
||||||
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
|
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
|
||||||
|
import './subscriber';
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
|
|
||||||
|
@ -210,19 +211,19 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
}
|
}
|
||||||
|
|
||||||
case PARTICIPANT_JOINED: {
|
case PARTICIPANT_JOINED: {
|
||||||
const { isFakeScreenShareParticipant } = action.participant;
|
const { isVirtualScreenshareParticipant } = action.participant;
|
||||||
|
|
||||||
// Do not play sounds when a fake participant tile is created for screenshare.
|
// Do not play sounds when a virtual participant tile is created for screenshare.
|
||||||
!isFakeScreenShareParticipant && _maybePlaySounds(store, action);
|
!isVirtualScreenshareParticipant && _maybePlaySounds(store, action);
|
||||||
|
|
||||||
return _participantJoinedOrUpdated(store, next, action);
|
return _participantJoinedOrUpdated(store, next, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
case PARTICIPANT_LEFT: {
|
case PARTICIPANT_LEFT: {
|
||||||
const { isFakeScreenShareParticipant } = action.participant;
|
const { isVirtualScreenshareParticipant } = action.participant;
|
||||||
|
|
||||||
// Do not play sounds when a tile for screenshare is removed.
|
// Do not play sounds when a tile for screenshare is removed.
|
||||||
!isFakeScreenShareParticipant && _maybePlaySounds(store, action);
|
!isVirtualScreenshareParticipant && _maybePlaySounds(store, action);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ const DEFAULT_STATE = {
|
||||||
pinnedParticipant: undefined,
|
pinnedParticipant: undefined,
|
||||||
raisedHandsQueue: [],
|
raisedHandsQueue: [],
|
||||||
remote: new Map(),
|
remote: new Map(),
|
||||||
sortedRemoteFakeScreenShareParticipants: new Map(),
|
sortedRemoteVirtualScreenshareParticipants: new Map(),
|
||||||
sortedRemoteParticipants: new Map(),
|
sortedRemoteParticipants: new Map(),
|
||||||
sortedRemoteScreenshares: new Map(),
|
sortedRemoteScreenshares: new Map(),
|
||||||
speakersList: new Map()
|
speakersList: new Map()
|
||||||
|
@ -213,15 +213,15 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
|
||||||
case SCREENSHARE_PARTICIPANT_NAME_CHANGED: {
|
case SCREENSHARE_PARTICIPANT_NAME_CHANGED: {
|
||||||
const { id, name } = action;
|
const { id, name } = action;
|
||||||
|
|
||||||
if (state.sortedRemoteFakeScreenShareParticipants.has(id)) {
|
if (state.sortedRemoteVirtualScreenshareParticipants.has(id)) {
|
||||||
state.sortedRemoteFakeScreenShareParticipants.delete(id);
|
state.sortedRemoteVirtualScreenshareParticipants.delete(id);
|
||||||
|
|
||||||
const sortedRemoteFakeScreenShareParticipants = [ ...state.sortedRemoteFakeScreenShareParticipants ];
|
const sortedRemoteVirtualScreenshareParticipants = [ ...state.sortedRemoteVirtualScreenshareParticipants ];
|
||||||
|
|
||||||
sortedRemoteFakeScreenShareParticipants.push([ id, name ]);
|
sortedRemoteVirtualScreenshareParticipants.push([ id, name ]);
|
||||||
sortedRemoteFakeScreenShareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
|
sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
|
||||||
|
|
||||||
state.sortedRemoteFakeScreenShareParticipants = new Map(sortedRemoteFakeScreenShareParticipants);
|
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...state };
|
return { ...state };
|
||||||
|
@ -229,7 +229,14 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
|
||||||
|
|
||||||
case PARTICIPANT_JOINED: {
|
case PARTICIPANT_JOINED: {
|
||||||
const participant = _participantJoined(action);
|
const participant = _participantJoined(action);
|
||||||
const { id, isFakeParticipant, isFakeScreenShareParticipant, isLocalScreenShare, name, pinned } = participant;
|
const {
|
||||||
|
id,
|
||||||
|
isFakeParticipant,
|
||||||
|
isLocalScreenShare,
|
||||||
|
isVirtualScreenshareParticipant,
|
||||||
|
name,
|
||||||
|
pinned
|
||||||
|
} = participant;
|
||||||
const { pinnedParticipant, dominantSpeaker } = state;
|
const { pinnedParticipant, dominantSpeaker } = state;
|
||||||
|
|
||||||
if (pinned) {
|
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.
|
// The sort order of participants is preserved since Map remembers the original insertion order of the keys.
|
||||||
state.sortedRemoteParticipants = new Map(sortedRemoteParticipants);
|
state.sortedRemoteParticipants = new Map(sortedRemoteParticipants);
|
||||||
|
|
||||||
if (isFakeScreenShareParticipant) {
|
if (isVirtualScreenshareParticipant) {
|
||||||
const sortedRemoteFakeScreenShareParticipants = [ ...state.sortedRemoteFakeScreenShareParticipants ];
|
const sortedRemoteVirtualScreenshareParticipants = [ ...state.sortedRemoteVirtualScreenshareParticipants ];
|
||||||
|
|
||||||
sortedRemoteFakeScreenShareParticipants.push([ id, name ]);
|
sortedRemoteVirtualScreenshareParticipants.push([ id, name ]);
|
||||||
sortedRemoteFakeScreenShareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
|
sortedRemoteVirtualScreenshareParticipants.sort((a, b) => a[1].localeCompare(b[1]));
|
||||||
|
|
||||||
state.sortedRemoteFakeScreenShareParticipants = new Map(sortedRemoteFakeScreenShareParticipants);
|
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
|
||||||
}
|
}
|
||||||
if (isFakeParticipant) {
|
if (isFakeParticipant) {
|
||||||
state.fakeParticipants.set(id, participant);
|
state.fakeParticipants.set(id, participant);
|
||||||
|
@ -306,7 +313,7 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
|
||||||
const { conference, id } = action.participant;
|
const { conference, id } = action.participant;
|
||||||
const {
|
const {
|
||||||
fakeParticipants,
|
fakeParticipants,
|
||||||
sortedRemoteFakeScreenShareParticipants,
|
sortedRemoteVirtualScreenshareParticipants,
|
||||||
remote,
|
remote,
|
||||||
local,
|
local,
|
||||||
localScreenShare,
|
localScreenShare,
|
||||||
|
@ -372,9 +379,9 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
|
||||||
fakeParticipants.delete(id);
|
fakeParticipants.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortedRemoteFakeScreenShareParticipants.has(id)) {
|
if (sortedRemoteVirtualScreenshareParticipants.has(id)) {
|
||||||
sortedRemoteFakeScreenShareParticipants.delete(id);
|
sortedRemoteVirtualScreenshareParticipants.delete(id);
|
||||||
state.sortedRemoteFakeScreenShareParticipants = new Map(sortedRemoteFakeScreenShareParticipants);
|
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...state };
|
return { ...state };
|
||||||
|
@ -500,7 +507,7 @@ function _participantJoined({ participant }) {
|
||||||
dominantSpeaker,
|
dominantSpeaker,
|
||||||
email,
|
email,
|
||||||
isFakeParticipant,
|
isFakeParticipant,
|
||||||
isFakeScreenShareParticipant,
|
isVirtualScreenshareParticipant,
|
||||||
isLocalScreenShare,
|
isLocalScreenShare,
|
||||||
isReplacing,
|
isReplacing,
|
||||||
isJigasi,
|
isJigasi,
|
||||||
|
@ -534,7 +541,7 @@ function _participantJoined({ participant }) {
|
||||||
email,
|
email,
|
||||||
id,
|
id,
|
||||||
isFakeParticipant,
|
isFakeParticipant,
|
||||||
isFakeScreenShareParticipant,
|
isVirtualScreenshareParticipant,
|
||||||
isLocalScreenShare,
|
isLocalScreenShare,
|
||||||
isReplacing,
|
isReplacing,
|
||||||
isJigasi,
|
isJigasi,
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,18 +107,6 @@ export const TRACK_STOPPED = 'TRACK_STOPPED';
|
||||||
*/
|
*/
|
||||||
export const TRACK_UPDATED = 'TRACK_UPDATED';
|
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
|
* 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
|
* via a WebRTC {@code getUserMedia} call. The action's payload includes an
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
} from '../../analytics';
|
} from '../../analytics';
|
||||||
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification, showNotification } from '../../notifications';
|
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification, showNotification } from '../../notifications';
|
||||||
import { getCurrentConference } from '../conference';
|
import { getCurrentConference } from '../conference';
|
||||||
import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../config';
|
import { getMultipleVideoSendingSupportFeatureFlag } from '../config';
|
||||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||||
import { createLocalTrack } from '../lib-jitsi-meet/functions';
|
import { createLocalTrack } from '../lib-jitsi-meet/functions';
|
||||||
import {
|
import {
|
||||||
|
@ -22,7 +22,6 @@ import { getLocalParticipant } from '../participants';
|
||||||
import { updateSettings } from '../settings';
|
import { updateSettings } from '../settings';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SCREENSHARE_TRACK_MUTED_UPDATED,
|
|
||||||
SET_NO_SRC_DATA_NOTIFICATION_UID,
|
SET_NO_SRC_DATA_NOTIFICATION_UID,
|
||||||
TOGGLE_SCREENSHARING,
|
TOGGLE_SCREENSHARING,
|
||||||
TRACK_ADDED,
|
TRACK_ADDED,
|
||||||
|
@ -60,7 +59,7 @@ export function addLocalTrack(newTrack) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const setMuted = newTrack.isVideoTrack()
|
const setMuted = newTrack.isVideoTrack()
|
||||||
? getMultipleVideoSupportFeatureFlag(getState())
|
? getMultipleVideoSendingSupportFeatureFlag(getState())
|
||||||
&& newTrack.getVideoType() === VIDEO_TYPE.DESKTOP
|
&& newTrack.getVideoType() === VIDEO_TYPE.DESKTOP
|
||||||
? setScreenshareMuted
|
? setScreenshareMuted
|
||||||
: setVideoMuted
|
: 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,
|
// state. If this is not done, the current mute state of the app will be reflected on the track,
|
||||||
// not vice-versa.
|
// not vice-versa.
|
||||||
const setMuted = newTrack.isVideoTrack()
|
const setMuted = newTrack.isVideoTrack()
|
||||||
? getMultipleVideoSupportFeatureFlag(getState()) && newTrack.getVideoType() === VIDEO_TYPE.DESKTOP
|
? getMultipleVideoSendingSupportFeatureFlag(getState())
|
||||||
|
&& newTrack.getVideoType() === VIDEO_TYPE.DESKTOP
|
||||||
? setScreenshareMuted
|
? setScreenshareMuted
|
||||||
: setVideoMuted
|
: setVideoMuted
|
||||||
: setAudioMuted;
|
: setAudioMuted;
|
||||||
|
@ -397,19 +397,15 @@ export function trackAdded(track) {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
track.on(
|
track.on(
|
||||||
JitsiTrackEvents.TRACK_MUTE_CHANGED,
|
JitsiTrackEvents.TRACK_MUTE_CHANGED,
|
||||||
() => {
|
() => dispatch(trackMutedChanged(track)));
|
||||||
if (getSourceNameSignalingFeatureFlag(getState()) && track.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
|
||||||
dispatch(screenshareTrackMutedChanged(track));
|
|
||||||
}
|
|
||||||
dispatch(trackMutedChanged(track));
|
|
||||||
});
|
|
||||||
track.on(
|
track.on(
|
||||||
JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
|
JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
|
||||||
type => dispatch(trackVideoTypeChanged(track, type)));
|
type => dispatch(trackVideoTypeChanged(track, type)));
|
||||||
|
|
||||||
// participantId
|
// participantId
|
||||||
const local = track.isLocal();
|
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
|
? MEDIA_TYPE.SCREENSHARE
|
||||||
: track.getType();
|
: track.getType();
|
||||||
let isReceivingData, noDataFromSourceNotificationInfo, participantId;
|
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
|
* 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.
|
* {@code getUserMedia} errors during unmute or replace track errors at the peerconnection level.
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
/* global APP */
|
/* global APP */
|
||||||
|
|
||||||
import { getMultipleVideoSupportFeatureFlag } from '../config/functions.any';
|
import {
|
||||||
|
getMultipleVideoSendingSupportFeatureFlag,
|
||||||
|
getMultipleVideoSupportFeatureFlag
|
||||||
|
} from '../config/functions.any';
|
||||||
import { isMobileBrowser } from '../environment/utils';
|
import { isMobileBrowser } from '../environment/utils';
|
||||||
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||||
import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
|
import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
|
||||||
import { getFakeScreenShareParticipantOwnerId } from '../participants';
|
import { getVirtualScreenshareParticipantOwnerId } from '../participants';
|
||||||
import { toState } from '../redux';
|
import { toState } from '../redux';
|
||||||
import {
|
import {
|
||||||
getUserSelectedCameraDeviceId,
|
getUserSelectedCameraDeviceId,
|
||||||
|
@ -426,18 +429,11 @@ export function getVideoTrackByParticipant(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let participantId;
|
if (participant?.isVirtualScreenshareParticipant) {
|
||||||
let mediaType;
|
return getVirtualScreenshareParticipantTrack(tracks, participant.id);
|
||||||
|
|
||||||
if (participant?.isFakeScreenShareParticipant) {
|
|
||||||
participantId = getFakeScreenShareParticipantOwnerId(participant.id);
|
|
||||||
mediaType = MEDIA_TYPE.SCREENSHARE;
|
|
||||||
} else {
|
|
||||||
participantId = participant.id;
|
|
||||||
mediaType = MEDIA_TYPE.VIDEO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {Track[]} tracks - List of all tracks.
|
||||||
* @param {string} fakeScreenshareParticipantId - Fake Screenshare Participant ID.
|
* @param {string} virtualScreenshareParticipantId - Virtual Screenshare Participant ID.
|
||||||
* @returns {(Track|undefined)}
|
* @returns {(Track|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getFakeScreenshareParticipantTrack(tracks, fakeScreenshareParticipantId) {
|
export function getVirtualScreenshareParticipantTrack(tracks, virtualScreenshareParticipantId) {
|
||||||
const participantId = getFakeScreenShareParticipantOwnerId(fakeScreenshareParticipantId);
|
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.
|
// 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.
|
// We still need to proceed here and remove the track from the peerconnection.
|
||||||
if (track.isMuted() === muted
|
if (track.isMuted() === muted
|
||||||
&& !(track.getVideoType() === VIDEO_TYPE.DESKTOP && getMultipleVideoSupportFeatureFlag(state))) {
|
&& !(track.getVideoType() === VIDEO_TYPE.DESKTOP && getMultipleVideoSendingSupportFeatureFlag(state))) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { _RESET_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
|
||||||
import { hideNotification, isModerationNotificationDisplayed } from '../../notifications';
|
import { hideNotification, isModerationNotificationDisplayed } from '../../notifications';
|
||||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||||
import { getCurrentConference } from '../conference/functions';
|
import { getCurrentConference } from '../conference/functions';
|
||||||
import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../config';
|
import { getMultipleVideoSendingSupportFeatureFlag } from '../config';
|
||||||
import { getAvailableDevices } from '../devices/actions';
|
import { getAvailableDevices } from '../devices/actions';
|
||||||
import {
|
import {
|
||||||
CAMERA_FACING_MODE,
|
CAMERA_FACING_MODE,
|
||||||
|
@ -25,11 +25,9 @@ import {
|
||||||
setScreenshareMuted,
|
setScreenshareMuted,
|
||||||
SCREENSHARE_MUTISM_AUTHORITY
|
SCREENSHARE_MUTISM_AUTHORITY
|
||||||
} from '../media';
|
} from '../media';
|
||||||
import { participantLeft, participantJoined, getParticipantById } from '../participants';
|
|
||||||
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
|
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SCREENSHARE_TRACK_MUTED_UPDATED,
|
|
||||||
TOGGLE_SCREENSHARING,
|
TOGGLE_SCREENSHARING,
|
||||||
TRACK_ADDED,
|
TRACK_ADDED,
|
||||||
TRACK_MUTE_UNMUTE_FAILED,
|
TRACK_MUTE_UNMUTE_FAILED,
|
||||||
|
@ -53,7 +51,6 @@ import {
|
||||||
isUserInteractionRequiredForUnmute,
|
isUserInteractionRequiredForUnmute,
|
||||||
setTrackMuted
|
setTrackMuted
|
||||||
} from './functions';
|
} from './functions';
|
||||||
import logger from './logger';
|
|
||||||
|
|
||||||
import './subscriber';
|
import './subscriber';
|
||||||
|
|
||||||
|
@ -70,8 +67,7 @@ declare var APP: Object;
|
||||||
MiddlewareRegistry.register(store => next => action => {
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case TRACK_ADDED: {
|
case TRACK_ADDED: {
|
||||||
const state = store.getState();
|
const { local } = action.track;
|
||||||
const { jitsiTrack, local } = action.track;
|
|
||||||
|
|
||||||
// The devices list needs to be refreshed when no initial video permissions
|
// 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.
|
// 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());
|
store.dispatch(getAvailableDevices());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call next before the creation of a fake screenshare participant to ensure a video track is available when
|
break;
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
case TRACK_NO_DATA_FROM_SOURCE: {
|
case TRACK_NO_DATA_FROM_SOURCE: {
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
|
@ -105,39 +85,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
return result;
|
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: {
|
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);
|
_removeNoDataFromSourceNotification(store, action.track);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -223,7 +171,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
|
|
||||||
const { enabled, audioOnly, ignoreDidHaveVideo } = action;
|
const { enabled, audioOnly, ignoreDidHaveVideo } = action;
|
||||||
|
|
||||||
if (!getMultipleVideoSupportFeatureFlag(store.getState())) {
|
if (!getMultipleVideoSendingSupportFeatureFlag(store.getState())) {
|
||||||
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING,
|
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING,
|
||||||
{
|
{
|
||||||
enabled,
|
enabled,
|
||||||
|
@ -241,7 +189,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
|
|
||||||
if (typeof APP !== 'undefined') {
|
if (typeof APP !== 'undefined') {
|
||||||
if (isVideoTrack && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP
|
if (isVideoTrack && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP
|
||||||
&& getMultipleVideoSupportFeatureFlag(store.getState())) {
|
&& getMultipleVideoSendingSupportFeatureFlag(store.getState())) {
|
||||||
store.dispatch(setScreenshareMuted(!muted));
|
store.dispatch(setScreenshareMuted(!muted));
|
||||||
} else if (isVideoTrack) {
|
} else if (isVideoTrack) {
|
||||||
APP.conference.setVideoMuteStatus();
|
APP.conference.setVideoMuteStatus();
|
||||||
|
@ -256,7 +204,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
const { jitsiTrack } = action.track;
|
const { jitsiTrack } = action.track;
|
||||||
|
|
||||||
if (typeof APP !== 'undefined'
|
if (typeof APP !== 'undefined'
|
||||||
&& getMultipleVideoSupportFeatureFlag(store.getState())
|
&& getMultipleVideoSendingSupportFeatureFlag(store.getState())
|
||||||
&& jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
&& jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||||
store.dispatch(toggleScreensharing(false));
|
store.dispatch(toggleScreensharing(false));
|
||||||
}
|
}
|
||||||
|
@ -286,7 +234,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
} else if (jitsiTrack.isLocal() && !(jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP)) {
|
} else if (jitsiTrack.isLocal() && !(jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP)) {
|
||||||
APP.conference.setVideoMuteStatus();
|
APP.conference.setVideoMuteStatus();
|
||||||
} else if (jitsiTrack.isLocal() && muted && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
} else if (jitsiTrack.isLocal() && muted && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
|
||||||
!getMultipleVideoSupportFeatureFlag(state)
|
!getMultipleVideoSendingSupportFeatureFlag(state)
|
||||||
&& store.dispatch(toggleScreensharing(false, false, true));
|
&& store.dispatch(toggleScreensharing(false, false, true));
|
||||||
} else {
|
} else {
|
||||||
APP.UI.setVideoMuted(participantID);
|
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
|
* Gets the local track associated with a specific {@code MEDIA_TYPE} in a
|
||||||
* specific redux store.
|
* specific redux store.
|
||||||
|
@ -472,7 +394,7 @@ async function _setMuted(store, { ensureTrack, authority, muted }, mediaType: ME
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
if (mediaType === MEDIA_TYPE.SCREENSHARE
|
if (mediaType === MEDIA_TYPE.SCREENSHARE
|
||||||
&& getMultipleVideoSupportFeatureFlag(state)
|
&& getMultipleVideoSendingSupportFeatureFlag(state)
|
||||||
&& !muted) {
|
&& !muted) {
|
||||||
return;
|
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
|
// Screenshare cannot be unmuted using the video mute button unless it is muted by audioOnly in the legacy
|
||||||
// screensharing mode.
|
// screensharing mode.
|
||||||
if (jitsiTrack
|
if (jitsiTrack && (
|
||||||
&& (jitsiTrack.videoType !== 'desktop' || isAudioOnly || getMultipleVideoSupportFeatureFlag(state))) {
|
jitsiTrack.videoType !== 'desktop' || isAudioOnly || getMultipleVideoSendingSupportFeatureFlag(state))
|
||||||
|
) {
|
||||||
setTrackMuted(jitsiTrack, muted, state).catch(() => dispatch(trackMuteUnmuteFailed(localTrack, muted)));
|
setTrackMuted(jitsiTrack, muted, state).catch(() => dispatch(trackMuteUnmuteFailed(localTrack, muted)));
|
||||||
}
|
}
|
||||||
} else if (!muted && ensureTrack && (typeof APP === 'undefined' || isPrejoinPageVisible(state))) {
|
} else if (!muted && ensureTrack && (typeof APP === 'undefined' || isPrejoinPageVisible(state))) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { getLocalParticipant, getParticipantById } from '../../../base/participa
|
||||||
import { Popover } from '../../../base/popover';
|
import { Popover } from '../../../base/popover';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import {
|
import {
|
||||||
getFakeScreenshareParticipantTrack,
|
getVirtualScreenshareParticipantTrack,
|
||||||
getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
||||||
import {
|
import {
|
||||||
isParticipantConnectionStatusInactive,
|
isParticipantConnectionStatusInactive,
|
||||||
|
@ -374,8 +374,8 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||||
|
|
||||||
let firstVideoTrack;
|
let firstVideoTrack;
|
||||||
|
|
||||||
if (sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant) {
|
if (sourceNameSignalingEnabled && participant?.isVirtualScreenshareParticipant) {
|
||||||
firstVideoTrack = getFakeScreenshareParticipantTrack(tracks, participantId);
|
firstVideoTrack = getVirtualScreenshareParticipantTrack(tracks, participantId);
|
||||||
} else {
|
} else {
|
||||||
firstVideoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
|
firstVideoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ type Props = AbstractProps & {
|
||||||
* Whether or not the displays stats are for screen share. This prop is behind the sourceNameSignaling feature
|
* Whether or not the displays stats are for screen share. This prop is behind the sourceNameSignaling feature
|
||||||
* flag.
|
* flag.
|
||||||
*/
|
*/
|
||||||
_isFakeScreenShareParticipant: Boolean,
|
_isVirtualScreenshareParticipant: Boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the displays stats are for local video.
|
* Whether or not the displays stats are for local video.
|
||||||
|
@ -205,8 +205,8 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<Props, Stat
|
||||||
e2eRtt = { e2eRtt }
|
e2eRtt = { e2eRtt }
|
||||||
enableSaveLogs = { this.props._enableSaveLogs }
|
enableSaveLogs = { this.props._enableSaveLogs }
|
||||||
framerate = { framerate }
|
framerate = { framerate }
|
||||||
isFakeScreenShareParticipant = { this.props._isFakeScreenShareParticipant }
|
|
||||||
isLocalVideo = { this.props._isLocalVideo }
|
isLocalVideo = { this.props._isLocalVideo }
|
||||||
|
isVirtualScreenshareParticipant = { this.props._isVirtualScreenshareParticipant }
|
||||||
maxEnabledResolution = { maxEnabledResolution }
|
maxEnabledResolution = { maxEnabledResolution }
|
||||||
onSaveLogs = { this.props._onSaveLogs }
|
onSaveLogs = { this.props._onSaveLogs }
|
||||||
onShowMore = { this._onToggleShowMore }
|
onShowMore = { this._onToggleShowMore }
|
||||||
|
@ -343,7 +343,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||||
_disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
|
_disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
|
||||||
_isConnectionStatusInactive,
|
_isConnectionStatusInactive,
|
||||||
_isConnectionStatusInterrupted,
|
_isConnectionStatusInterrupted,
|
||||||
_isFakeScreenShareParticipant: sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant,
|
_isVirtualScreenshareParticipant: sourceNameSignalingEnabled && participant?.isVirtualScreenshareParticipant,
|
||||||
_isLocalVideo: participant?.local,
|
_isLocalVideo: participant?.local,
|
||||||
_region: participant?.region
|
_region: participant?.region
|
||||||
};
|
};
|
||||||
|
|
|
@ -94,7 +94,7 @@ type Props = {
|
||||||
/**
|
/**
|
||||||
* Whether or not the statistics are for screen share.
|
* 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
|
* The send-side max enabled resolution (aka the highest layer that is not
|
||||||
|
@ -240,12 +240,12 @@ class ConnectionStatsTable extends Component<Props> {
|
||||||
classes,
|
classes,
|
||||||
disableShowMoreStats,
|
disableShowMoreStats,
|
||||||
enableSaveLogs,
|
enableSaveLogs,
|
||||||
isFakeScreenShareParticipant,
|
isVirtualScreenshareParticipant,
|
||||||
isLocalVideo
|
isLocalVideo
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const className = clsx(classes.connectionStatsTable, { [classes.mobile]: isMobileBrowser() });
|
const className = clsx(classes.connectionStatsTable, { [classes.mobile]: isMobileBrowser() });
|
||||||
|
|
||||||
if (isFakeScreenShareParticipant) {
|
if (isVirtualScreenshareParticipant) {
|
||||||
return this._renderScreenShareStatus();
|
return this._renderScreenShareStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import React, { Component } from 'react';
|
||||||
|
|
||||||
import { createScreenSharingIssueEvent, sendAnalytics } from '../../../analytics';
|
import { createScreenSharingIssueEvent, sendAnalytics } from '../../../analytics';
|
||||||
import { Avatar } from '../../../base/avatar';
|
import { Avatar } from '../../../base/avatar';
|
||||||
import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
|
import { getMultipleVideoSupportFeatureFlag, getSourceNameSignalingFeatureFlag } from '../../../base/config';
|
||||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||||
import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet';
|
import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet';
|
||||||
import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
|
import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
|
||||||
|
@ -24,7 +24,7 @@ import {
|
||||||
getLocalAudioTrack,
|
getLocalAudioTrack,
|
||||||
getLocalVideoTrack,
|
getLocalVideoTrack,
|
||||||
getTrackByMediaTypeAndParticipant,
|
getTrackByMediaTypeAndParticipant,
|
||||||
getFakeScreenshareParticipantTrack,
|
getVirtualScreenshareParticipantTrack,
|
||||||
updateLastTrackVideoMediaEvent,
|
updateLastTrackVideoMediaEvent,
|
||||||
trackStreamingStatusChanged
|
trackStreamingStatusChanged
|
||||||
} from '../../../base/tracks';
|
} from '../../../base/tracks';
|
||||||
|
@ -51,10 +51,10 @@ import {
|
||||||
showGridInVerticalView
|
showGridInVerticalView
|
||||||
} from '../../functions';
|
} from '../../functions';
|
||||||
|
|
||||||
import FakeScreenShareParticipant from './FakeScreenShareParticipant';
|
|
||||||
import ThumbnailAudioIndicator from './ThumbnailAudioIndicator';
|
import ThumbnailAudioIndicator from './ThumbnailAudioIndicator';
|
||||||
import ThumbnailBottomIndicators from './ThumbnailBottomIndicators';
|
import ThumbnailBottomIndicators from './ThumbnailBottomIndicators';
|
||||||
import ThumbnailTopIndicators from './ThumbnailTopIndicators';
|
import ThumbnailTopIndicators from './ThumbnailTopIndicators';
|
||||||
|
import VirtualScreenshareParticipant from './VirtualScreenshareParticipant';
|
||||||
|
|
||||||
|
|
||||||
declare var interfaceConfig: Object;
|
declare var interfaceConfig: Object;
|
||||||
|
@ -137,10 +137,10 @@ export type Props = {|
|
||||||
_isCurrentlyOnLargeVideo: boolean,
|
_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.
|
* sourceNameSignaling feature flag.
|
||||||
*/
|
*/
|
||||||
_isFakeScreenShareParticipant: boolean,
|
_isVirtualScreenshareParticipant: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether we are currently running in a mobile browser.
|
* Whether we are currently running in a mobile browser.
|
||||||
|
@ -626,7 +626,7 @@ class Thumbnail extends Component<Props, State> {
|
||||||
const {
|
const {
|
||||||
_disableTileEnlargement,
|
_disableTileEnlargement,
|
||||||
_height,
|
_height,
|
||||||
_isFakeScreenShareParticipant,
|
_isVirtualScreenshareParticipant,
|
||||||
_isHidden,
|
_isHidden,
|
||||||
_isScreenSharing,
|
_isScreenSharing,
|
||||||
_participant,
|
_participant,
|
||||||
|
@ -665,7 +665,7 @@ class Thumbnail extends Component<Props, State> {
|
||||||
|| _disableTileEnlargement
|
|| _disableTileEnlargement
|
||||||
|| _isScreenSharing;
|
|| _isScreenSharing;
|
||||||
|
|
||||||
if (canPlayEventReceived || _participant.local || _isFakeScreenShareParticipant) {
|
if (canPlayEventReceived || _participant.local || _isVirtualScreenshareParticipant) {
|
||||||
videoStyles = {
|
videoStyles = {
|
||||||
objectFit: doNotStretchVideo ? 'contain' : 'cover'
|
objectFit: doNotStretchVideo ? 'contain' : 'cover'
|
||||||
};
|
};
|
||||||
|
@ -1107,7 +1107,7 @@ class Thumbnail extends Component<Props, State> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { _participant, _isFakeScreenShareParticipant } = this.props;
|
const { _participant, _isVirtualScreenshareParticipant } = this.props;
|
||||||
|
|
||||||
if (!_participant) {
|
if (!_participant) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1123,12 +1123,12 @@ class Thumbnail extends Component<Props, State> {
|
||||||
return this._renderFakeParticipant();
|
return this._renderFakeParticipant();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isFakeScreenShareParticipant) {
|
if (_isVirtualScreenshareParticipant) {
|
||||||
const { isHovered } = this.state;
|
const { isHovered } = this.state;
|
||||||
const { _videoTrack, _isMobile, classes, _thumbnailType } = this.props;
|
const { _videoTrack, _isMobile, classes, _thumbnailType } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FakeScreenShareParticipant
|
<VirtualScreenshareParticipant
|
||||||
classes = { classes }
|
classes = { classes }
|
||||||
containerClassName = { this._getContainerClassName() }
|
containerClassName = { this._getContainerClassName() }
|
||||||
isHovered = { isHovered }
|
isHovered = { isHovered }
|
||||||
|
@ -1170,8 +1170,8 @@ function _mapStateToProps(state, ownProps): Object {
|
||||||
|
|
||||||
let _videoTrack;
|
let _videoTrack;
|
||||||
|
|
||||||
if (sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant) {
|
if (sourceNameSignalingEnabled && participant?.isVirtualScreenshareParticipant) {
|
||||||
_videoTrack = getFakeScreenshareParticipantTrack(tracks, id);
|
_videoTrack = getVirtualScreenshareParticipantTrack(tracks, id);
|
||||||
} else {
|
} else {
|
||||||
_videoTrack = isLocal
|
_videoTrack = isLocal
|
||||||
? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
|
? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantID);
|
||||||
|
@ -1193,7 +1193,6 @@ function _mapStateToProps(state, ownProps): Object {
|
||||||
const activeParticipants = getActiveParticipantsIds(state);
|
const activeParticipants = getActiveParticipantsIds(state);
|
||||||
const tileType = getThumbnailTypeFromLayout(_currentLayout, stageFilmstrip);
|
const tileType = getThumbnailTypeFromLayout(_currentLayout, stageFilmstrip);
|
||||||
|
|
||||||
|
|
||||||
switch (tileType) {
|
switch (tileType) {
|
||||||
case THUMBNAIL_TYPE.VERTICAL:
|
case THUMBNAIL_TYPE.VERTICAL:
|
||||||
case THUMBNAIL_TYPE.HORIZONTAL: {
|
case THUMBNAIL_TYPE.HORIZONTAL: {
|
||||||
|
@ -1263,6 +1262,7 @@ function _mapStateToProps(state, ownProps): Object {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_audioTrack,
|
_audioTrack,
|
||||||
|
_currentLayout,
|
||||||
_defaultLocalDisplayName: defaultLocalDisplayName,
|
_defaultLocalDisplayName: defaultLocalDisplayName,
|
||||||
_disableLocalVideoFlip: Boolean(disableLocalVideoFlip),
|
_disableLocalVideoFlip: Boolean(disableLocalVideoFlip),
|
||||||
_disableTileEnlargement: Boolean(disableTileEnlargement),
|
_disableTileEnlargement: Boolean(disableTileEnlargement),
|
||||||
|
@ -1271,13 +1271,14 @@ function _mapStateToProps(state, ownProps): Object {
|
||||||
_isAudioOnly: Boolean(state['features/base/audio-only'].enabled),
|
_isAudioOnly: Boolean(state['features/base/audio-only'].enabled),
|
||||||
_isCurrentlyOnLargeVideo: state['features/large-video']?.participantId === id,
|
_isCurrentlyOnLargeVideo: state['features/large-video']?.participantId === id,
|
||||||
_isDominantSpeakerDisabled: interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR,
|
_isDominantSpeakerDisabled: interfaceConfig.DISABLE_DOMINANT_SPEAKER_INDICATOR,
|
||||||
_isFakeScreenShareParticipant: sourceNameSignalingEnabled && participant?.isFakeScreenShareParticipant,
|
|
||||||
_isMobile,
|
_isMobile,
|
||||||
_isMobilePortrait,
|
_isMobilePortrait,
|
||||||
_isScreenSharing: _videoTrack?.videoType === 'desktop',
|
_isScreenSharing: _videoTrack?.videoType === 'desktop',
|
||||||
_isTestModeEnabled: isTestModeEnabled(state),
|
_isTestModeEnabled: isTestModeEnabled(state),
|
||||||
_isVideoPlayable: id && isVideoPlayable(state, id),
|
_isVideoPlayable: id && isVideoPlayable(state, id),
|
||||||
|
_isVirtualScreenshareParticipant: sourceNameSignalingEnabled && participant?.isVirtualScreenshareParticipant,
|
||||||
_localFlipX: Boolean(localFlipX),
|
_localFlipX: Boolean(localFlipX),
|
||||||
|
_multipleVideoSupport: getMultipleVideoSupportFeatureFlag(state),
|
||||||
_participant: participant,
|
_participant: participant,
|
||||||
_raisedHand: hasRaisedHand(participant),
|
_raisedHand: hasRaisedHand(participant),
|
||||||
_stageFilmstripLayout: isStageFilmstripAvailable(state),
|
_stageFilmstripLayout: isStageFilmstripAvailable(state),
|
||||||
|
|
|
@ -36,9 +36,9 @@ type Props = {
|
||||||
isHovered: boolean,
|
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.
|
* Whether or not the indicators are for the local participant.
|
||||||
|
@ -81,7 +81,7 @@ const useStyles = makeStyles(() => {
|
||||||
const ThumbnailTopIndicators = ({
|
const ThumbnailTopIndicators = ({
|
||||||
hidePopover,
|
hidePopover,
|
||||||
indicatorsClassName,
|
indicatorsClassName,
|
||||||
isFakeScreenShareParticipant,
|
isVirtualScreenshareParticipant,
|
||||||
isHovered,
|
isHovered,
|
||||||
local,
|
local,
|
||||||
participantId,
|
participantId,
|
||||||
|
@ -101,7 +101,7 @@ const ThumbnailTopIndicators = ({
|
||||||
const sourceNameSignalingEnabled = useSelector(getSourceNameSignalingFeatureFlag);
|
const sourceNameSignalingEnabled = useSelector(getSourceNameSignalingFeatureFlag);
|
||||||
const showConnectionIndicator = isHovered || !_connectionIndicatorAutoHideEnabled;
|
const showConnectionIndicator = isHovered || !_connectionIndicatorAutoHideEnabled;
|
||||||
|
|
||||||
if (sourceNameSignalingEnabled && isFakeScreenShareParticipant) {
|
if (sourceNameSignalingEnabled && isVirtualScreenshareParticipant) {
|
||||||
return (
|
return (
|
||||||
<div className = { styles.container }>
|
<div className = { styles.container }>
|
||||||
{!_connectionIndicatorDisabled
|
{!_connectionIndicatorDisabled
|
||||||
|
|
|
@ -68,7 +68,7 @@ type Props = {
|
||||||
onTouchStart: Function,
|
onTouchStart: Function,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the fake screen share participant.
|
* The ID of the virtual screen share participant.
|
||||||
*/
|
*/
|
||||||
participantId: string,
|
participantId: string,
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ type Props = {
|
||||||
videoTrack: Object
|
videoTrack: Object
|
||||||
}
|
}
|
||||||
|
|
||||||
const FakeScreenShareParticipant = ({
|
const VirtualScreenshareParticipant = ({
|
||||||
classes,
|
classes,
|
||||||
containerClassName,
|
containerClassName,
|
||||||
isHovered,
|
isHovered,
|
||||||
|
@ -141,8 +141,8 @@ const FakeScreenShareParticipant = ({
|
||||||
) }>
|
) }>
|
||||||
<ThumbnailTopIndicators
|
<ThumbnailTopIndicators
|
||||||
currentLayout = { currentLayout }
|
currentLayout = { currentLayout }
|
||||||
isFakeScreenShareParticipant = { true }
|
|
||||||
isHovered = { isHovered }
|
isHovered = { isHovered }
|
||||||
|
isVirtualScreenshareParticipant = { true }
|
||||||
participantId = { participantId }
|
participantId = { participantId }
|
||||||
thumbnailType = { thumbnailType } />
|
thumbnailType = { thumbnailType } />
|
||||||
</div>
|
</div>
|
||||||
|
@ -161,4 +161,4 @@ const FakeScreenShareParticipant = ({
|
||||||
</span>);
|
</span>);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FakeScreenShareParticipant;
|
export default VirtualScreenshareParticipant;
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { getSourceNameSignalingFeatureFlag } from '../base/config';
|
import { getSourceNameSignalingFeatureFlag } from '../base/config';
|
||||||
import { getFakeScreenShareParticipantOwnerId } from '../base/participants';
|
import { getVirtualScreenshareParticipantOwnerId } from '../base/participants';
|
||||||
|
|
||||||
import { setRemoteParticipants } from './actions';
|
import { setRemoteParticipants } from './actions';
|
||||||
import { isReorderingEnabled } from './functions';
|
import { isReorderingEnabled } from './functions';
|
||||||
|
@ -18,9 +18,9 @@ export function updateRemoteParticipants(store: Object, participantId: ?number)
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
let reorderedParticipants = [];
|
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) {
|
if (participantId) {
|
||||||
const { remoteParticipants } = state['features/filmstrip'];
|
const { remoteParticipants } = state['features/filmstrip'];
|
||||||
|
|
||||||
|
@ -39,14 +39,14 @@ export function updateRemoteParticipants(store: Object, participantId: ?number)
|
||||||
} = state['features/base/participants'];
|
} = state['features/base/participants'];
|
||||||
const remoteParticipants = new Map(sortedRemoteParticipants);
|
const remoteParticipants = new Map(sortedRemoteParticipants);
|
||||||
const screenShares = new Map(sortedRemoteScreenshares);
|
const screenShares = new Map(sortedRemoteScreenshares);
|
||||||
const screenShareParticipants = sortedRemoteFakeScreenShareParticipants
|
const screenShareParticipants = sortedRemoteVirtualScreenshareParticipants
|
||||||
? [ ...sortedRemoteFakeScreenShareParticipants.keys() ] : [];
|
? [ ...sortedRemoteVirtualScreenshareParticipants.keys() ] : [];
|
||||||
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
|
const sharedVideos = fakeParticipants ? Array.from(fakeParticipants.keys()) : [];
|
||||||
const speakers = new Map(speakersList);
|
const speakers = new Map(speakersList);
|
||||||
|
|
||||||
if (getSourceNameSignalingFeatureFlag(state)) {
|
if (getSourceNameSignalingFeatureFlag(state)) {
|
||||||
for (const screenshare of screenShareParticipants) {
|
for (const screenshare of screenShareParticipants) {
|
||||||
const ownerId = getFakeScreenShareParticipantOwnerId(screenshare);
|
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
|
||||||
|
|
||||||
remoteParticipants.delete(ownerId);
|
remoteParticipants.delete(ownerId);
|
||||||
remoteParticipants.delete(screenshare);
|
remoteParticipants.delete(screenshare);
|
||||||
|
@ -72,7 +72,7 @@ export function updateRemoteParticipants(store: Object, participantId: ?number)
|
||||||
if (getSourceNameSignalingFeatureFlag(state)) {
|
if (getSourceNameSignalingFeatureFlag(state)) {
|
||||||
// Always update the order of the thumnails.
|
// Always update the order of the thumnails.
|
||||||
const participantsWithScreenShare = screenShareParticipants.reduce((acc, screenshare) => {
|
const participantsWithScreenShare = screenShareParticipants.reduce((acc, screenshare) => {
|
||||||
const ownerId = getFakeScreenShareParticipantOwnerId(screenshare);
|
const ownerId = getVirtualScreenshareParticipantOwnerId(screenshare);
|
||||||
|
|
||||||
acc.push(ownerId);
|
acc.push(ownerId);
|
||||||
acc.push(screenshare);
|
acc.push(screenshare);
|
||||||
|
|
|
@ -509,18 +509,29 @@ export function computeDisplayModeFromInput(input: Object) {
|
||||||
isActiveParticipant,
|
isActiveParticipant,
|
||||||
isAudioOnly,
|
isAudioOnly,
|
||||||
isCurrentlyOnLargeVideo,
|
isCurrentlyOnLargeVideo,
|
||||||
isFakeScreenShareParticipant,
|
isVirtualScreenshareParticipant,
|
||||||
isScreenSharing,
|
isScreenSharing,
|
||||||
canPlayEventReceived,
|
canPlayEventReceived,
|
||||||
isRemoteParticipant,
|
isRemoteParticipant,
|
||||||
|
multipleVideoSupport,
|
||||||
stageParticipantsVisible,
|
stageParticipantsVisible,
|
||||||
stageFilmstrip,
|
stageFilmstrip,
|
||||||
tileViewActive
|
tileViewActive
|
||||||
} = input;
|
} = input;
|
||||||
const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
|
const adjustedIsVideoPlayable = input.isVideoPlayable && (!isRemoteParticipant || canPlayEventReceived);
|
||||||
|
|
||||||
if (isFakeScreenShareParticipant) {
|
if (multipleVideoSupport) {
|
||||||
return DISPLAY_VIDEO;
|
// 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)
|
if (!tileViewActive && ((isScreenSharing && isRemoteParticipant)
|
||||||
|
@ -551,9 +562,10 @@ export function getDisplayModeInput(props: Object, state: Object) {
|
||||||
_isActiveParticipant,
|
_isActiveParticipant,
|
||||||
_isAudioOnly,
|
_isAudioOnly,
|
||||||
_isCurrentlyOnLargeVideo,
|
_isCurrentlyOnLargeVideo,
|
||||||
_isFakeScreenShareParticipant,
|
_isVirtualScreenshareParticipant,
|
||||||
_isScreenSharing,
|
_isScreenSharing,
|
||||||
_isVideoPlayable,
|
_isVideoPlayable,
|
||||||
|
_multipleVideoSupport,
|
||||||
_participant,
|
_participant,
|
||||||
_stageParticipantsVisible,
|
_stageParticipantsVisible,
|
||||||
_videoTrack,
|
_videoTrack,
|
||||||
|
@ -573,7 +585,8 @@ export function getDisplayModeInput(props: Object, state: Object) {
|
||||||
videoStream: Boolean(_videoTrack),
|
videoStream: Boolean(_videoTrack),
|
||||||
isRemoteParticipant: !_participant?.isFakeParticipant && !_participant?.local,
|
isRemoteParticipant: !_participant?.isFakeParticipant && !_participant?.local,
|
||||||
isScreenSharing: _isScreenSharing,
|
isScreenSharing: _isScreenSharing,
|
||||||
isFakeScreenShareParticipant: _isFakeScreenShareParticipant,
|
isVirtualScreenshareParticipant: _isVirtualScreenshareParticipant,
|
||||||
|
multipleVideoSupport: _multipleVideoSupport,
|
||||||
stageParticipantsVisible: _stageParticipantsVisible,
|
stageParticipantsVisible: _stageParticipantsVisible,
|
||||||
stageFilmstrip,
|
stageFilmstrip,
|
||||||
videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'
|
videoStreamMuted: _videoTrack ? _videoTrack.muted : 'no stream'
|
||||||
|
|
|
@ -16,8 +16,8 @@ StateListenerRegistry.register(
|
||||||
* Listens for changes to the remote screenshare participants to recompute the reordered list of the remote endpoints.
|
* Listens for changes to the remote screenshare participants to recompute the reordered list of the remote endpoints.
|
||||||
*/
|
*/
|
||||||
StateListenerRegistry.register(
|
StateListenerRegistry.register(
|
||||||
/* selector */ state => state['features/base/participants'].sortedRemoteFakeScreenShareParticipants,
|
/* selector */ state => state['features/base/participants'].sortedRemoteVirtualScreenshareParticipants,
|
||||||
/* listener */ (sortedRemoteFakeScreenShareParticipants, store) => updateRemoteParticipants(store));
|
/* listener */ (sortedRemoteVirtualScreenshareParticipants, store) => updateRemoteParticipants(store));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens for changes to the dominant speaker to recompute the reordered list of the remote endpoints.
|
* Listens for changes to the dominant speaker to recompute the reordered list of the remote endpoints.
|
||||||
|
|
|
@ -128,10 +128,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
const { participant: p } = action;
|
const { participant: p } = action;
|
||||||
const { conference } = state['features/base/conference'];
|
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
|
if (conference
|
||||||
&& !p.local
|
&& !p.local
|
||||||
&& !p.isFakeScreenShareParticipant
|
&& !p.isVirtualScreenshareParticipant
|
||||||
&& !joinLeaveNotificationsDisabled()
|
&& !joinLeaveNotificationsDisabled()
|
||||||
&& !p.isReplacing) {
|
&& !p.isReplacing) {
|
||||||
dispatch(showParticipantJoinedNotification(
|
dispatch(showParticipantJoinedNotification(
|
||||||
|
@ -148,10 +148,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
action.participant.id
|
action.participant.id
|
||||||
);
|
);
|
||||||
|
|
||||||
// Do not display notifications for the fake screenshare tiles.
|
// Do not display notifications for the virtual screenshare tiles.
|
||||||
if (participant
|
if (participant
|
||||||
&& !participant.local
|
&& !participant.local
|
||||||
&& !participant.isFakeScreenShareParticipant
|
&& !participant.isVirtualScreenshareParticipant
|
||||||
&& !action.participant.isReplaced) {
|
&& !action.participant.isReplaced) {
|
||||||
dispatch(showParticipantLeftNotification(
|
dispatch(showParticipantLeftNotification(
|
||||||
getParticipantDisplayName(state, participant.id)
|
getParticipantDisplayName(state, participant.id)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { getMultipleVideoSupportFeatureFlag } from '../base/config/functions.any';
|
import { getMultipleVideoSendingSupportFeatureFlag } from '../base/config/functions.any';
|
||||||
import { openDialog } from '../base/dialog/actions';
|
import { openDialog } from '../base/dialog/actions';
|
||||||
import { browser } from '../base/lib-jitsi-meet';
|
import { browser } from '../base/lib-jitsi-meet';
|
||||||
import { shouldHideShareAudioHelper } from '../base/settings';
|
import { shouldHideShareAudioHelper } from '../base/settings';
|
||||||
|
@ -86,7 +86,7 @@ export function startAudioScreenShareFlow() {
|
||||||
// available for audio screen sharing, namely full window audio.
|
// available for audio screen sharing, namely full window audio.
|
||||||
// If we're already sharing audio, toggle off.
|
// If we're already sharing audio, toggle off.
|
||||||
if (shouldHideShareAudioHelper(state) || browser.isElectron() || audioOnlySharing) {
|
if (shouldHideShareAudioHelper(state) || browser.isElectron() || audioOnlySharing) {
|
||||||
if (getMultipleVideoSupportFeatureFlag(state)) {
|
if (getMultipleVideoSendingSupportFeatureFlag(state)) {
|
||||||
dispatch(toggleScreensharing(!audioOnlySharing, true));
|
dispatch(toggleScreensharing(!audioOnlySharing, true));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -11,14 +11,14 @@ export const SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED
|
||||||
= '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 {{
|
* @returns {{
|
||||||
* type: FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
* type: VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED,
|
||||||
* participantIds: Array<string>
|
* 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
|
* The type of the action which enables or disables the feature for showing
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
|
||||||
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
||||||
SET_TILE_VIEW
|
SET_TILE_VIEW,
|
||||||
|
VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import { shouldDisplayTileView } from './functions';
|
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 {{
|
* @returns {{
|
||||||
* type: FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
* type: VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED,
|
||||||
* participantIds: Array<string>
|
* participantIds: Array<string>
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function fakeScreenshareParticipantsUpdated(participantIds: Array<string>) {
|
export function virtualScreenshareParticipantsUpdated(participantIds: Array<string>) {
|
||||||
return {
|
return {
|
||||||
type: FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
type: VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED,
|
||||||
participantIds
|
participantIds
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
import { ReducerRegistry } from '../base/redux';
|
import { ReducerRegistry } from '../base/redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FAKE_SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
|
||||||
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED,
|
||||||
SET_TILE_VIEW
|
SET_TILE_VIEW,
|
||||||
|
VIRTUAL_SCREENSHARE_REMOTE_PARTICIPANTS_UPDATED
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
|
|
||||||
const DEFAULT_STATE = {
|
const DEFAULT_STATE = {
|
||||||
|
@ -28,8 +28,8 @@ const STORE_NAME = 'features/video-layout';
|
||||||
|
|
||||||
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||||
switch (action.type) {
|
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 {
|
return {
|
||||||
...state,
|
...state,
|
||||||
remoteScreenShares: action.participantIds
|
remoteScreenShares: action.participantIds
|
||||||
|
|
|
@ -2,22 +2,22 @@
|
||||||
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
import { getSourceNameSignalingFeatureFlag } from '../base/config';
|
import { getMultipleVideoSupportFeatureFlag } from '../base/config';
|
||||||
import { StateListenerRegistry, equals } from '../base/redux';
|
import { StateListenerRegistry, equals } from '../base/redux';
|
||||||
import { isFollowMeActive } from '../follow-me';
|
import { isFollowMeActive } from '../follow-me';
|
||||||
|
|
||||||
import { setRemoteParticipantsWithScreenShare, fakeScreenshareParticipantsUpdated } from './actions';
|
import { setRemoteParticipantsWithScreenShare, virtualScreenshareParticipantsUpdated } from './actions';
|
||||||
import { getAutoPinSetting, updateAutoPinnedParticipant } from './functions';
|
import { getAutoPinSetting, updateAutoPinnedParticipant } from './functions';
|
||||||
|
|
||||||
StateListenerRegistry.register(
|
StateListenerRegistry.register(
|
||||||
/* selector */ state => state['features/base/participants'].sortedRemoteFakeScreenShareParticipants,
|
/* selector */ state => state['features/base/participants'].sortedRemoteVirtualScreenshareParticipants,
|
||||||
/* listener */ (sortedRemoteFakeScreenShareParticipants, store) => {
|
/* listener */ (sortedRemoteVirtualScreenshareParticipants, store) => {
|
||||||
if (!getAutoPinSetting() || isFollowMeActive(store) || !getSourceNameSignalingFeatureFlag(store.getState())) {
|
if (!getAutoPinSetting() || isFollowMeActive(store) || !getMultipleVideoSupportFeatureFlag(store.getState())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldScreenSharesOrder = store.getState()['features/video-layout'].remoteScreenShares || [];
|
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
|
// Filter out any participants which are no longer screen sharing
|
||||||
// by looping through the known sharing participants and removing any
|
// by looping through the known sharing participants and removing any
|
||||||
|
@ -34,7 +34,7 @@ StateListenerRegistry.register(
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!equals(oldScreenSharesOrder, newScreenSharesOrder)) {
|
if (!equals(oldScreenSharesOrder, newScreenSharesOrder)) {
|
||||||
store.dispatch(fakeScreenshareParticipantsUpdated(newScreenSharesOrder));
|
store.dispatch(virtualScreenshareParticipantsUpdated(newScreenSharesOrder));
|
||||||
|
|
||||||
updateAutoPinnedParticipant(oldScreenSharesOrder, store);
|
updateAutoPinnedParticipant(oldScreenSharesOrder, store);
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ StateListenerRegistry.register(
|
||||||
// possible to have screen sharing participant that has already left in the remoteScreenShares array.
|
// 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
|
// 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.
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { getSourceNameSignalingFeatureFlag } from '../base/config';
|
||||||
import { MEDIA_TYPE } from '../base/media';
|
import { MEDIA_TYPE } from '../base/media';
|
||||||
import { getLocalParticipant, getParticipantCount } from '../base/participants';
|
import { getLocalParticipant, getParticipantCount } from '../base/participants';
|
||||||
import { StateListenerRegistry } from '../base/redux';
|
import { StateListenerRegistry } from '../base/redux';
|
||||||
import { getTrackSourceNameByMediaTypeAndParticipant } from '../base/tracks';
|
import { getRemoteScreenSharesSourceNames, getTrackSourceNameByMediaTypeAndParticipant } from '../base/tracks';
|
||||||
import { reportError } from '../base/util';
|
import { reportError } from '../base/util';
|
||||||
import { getActiveParticipantsIds } from '../filmstrip/functions.web';
|
import { getActiveParticipantsIds } from '../filmstrip/functions.web';
|
||||||
import {
|
import {
|
||||||
|
@ -238,6 +238,8 @@ function _updateReceiverVideoConstraints({ getState }) {
|
||||||
let receiverConstraints;
|
let receiverConstraints;
|
||||||
|
|
||||||
if (sourceNameSignaling) {
|
if (sourceNameSignaling) {
|
||||||
|
const remoteScreenSharesSourceNames = getRemoteScreenSharesSourceNames(state, remoteScreenShares);
|
||||||
|
|
||||||
receiverConstraints = {
|
receiverConstraints = {
|
||||||
constraints: {},
|
constraints: {},
|
||||||
defaultConstraints: { 'maxHeight': VIDEO_QUALITY_LEVELS.NONE },
|
defaultConstraints: { 'maxHeight': VIDEO_QUALITY_LEVELS.NONE },
|
||||||
|
@ -253,7 +255,7 @@ function _updateReceiverVideoConstraints({ getState }) {
|
||||||
visibleRemoteParticipants.forEach(participantId => {
|
visibleRemoteParticipants.forEach(participantId => {
|
||||||
let sourceName;
|
let sourceName;
|
||||||
|
|
||||||
if (remoteScreenShares.includes(participantId)) {
|
if (remoteScreenSharesSourceNames.includes(participantId)) {
|
||||||
sourceName = participantId;
|
sourceName = participantId;
|
||||||
} else {
|
} else {
|
||||||
sourceName = getTrackSourceNameByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
|
sourceName = getTrackSourceNameByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
|
||||||
|
@ -269,12 +271,11 @@ function _updateReceiverVideoConstraints({ getState }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localParticipantId !== largeVideoParticipantId) {
|
if (localParticipantId !== largeVideoParticipantId) {
|
||||||
if (remoteScreenShares.includes(largeVideoParticipantId)) {
|
if (remoteScreenSharesSourceNames.includes(largeVideoParticipantId)) {
|
||||||
largeVideoSourceName = largeVideoParticipantId;
|
largeVideoSourceName = largeVideoParticipantId;
|
||||||
} else {
|
} else {
|
||||||
largeVideoSourceName = getTrackSourceNameByMediaTypeAndParticipant(
|
largeVideoSourceName = getTrackSourceNameByMediaTypeAndParticipant(
|
||||||
tracks, MEDIA_TYPE.VIDEO,
|
tracks, MEDIA_TYPE.VIDEO, largeVideoParticipantId
|
||||||
largeVideoParticipantId
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,8 +291,8 @@ function _updateReceiverVideoConstraints({ getState }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prioritize screenshare in tile view.
|
// Prioritize screenshare in tile view.
|
||||||
if (remoteScreenShares?.length) {
|
if (remoteScreenSharesSourceNames?.length) {
|
||||||
receiverConstraints.selectedSources = remoteScreenShares;
|
receiverConstraints.selectedSources = remoteScreenSharesSourceNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stage view.
|
// Stage view.
|
||||||
|
@ -325,8 +326,8 @@ function _updateReceiverVideoConstraints({ getState }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remoteScreenShares?.length) {
|
if (remoteScreenSharesSourceNames?.length) {
|
||||||
remoteScreenShares.forEach(sourceName => {
|
remoteScreenSharesSourceNames.forEach(sourceName => {
|
||||||
receiverConstraints.constraints[sourceName] = { 'maxHeight': VIDEO_QUALITY_LEVELS.ULTRA };
|
receiverConstraints.constraints[sourceName] = { 'maxHeight': VIDEO_QUALITY_LEVELS.ULTRA };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue