Feat ssrc rewriting (#12408)
Use a fixed set of remote tracks for a call based on the ssrc-limit set in jvb config. When this feature is enabled, JVB will signal the audio and video SSRCs and their owner/endpoint info using a bridge channel message. For audio, the mappings are determined based on the energy rankings on the audio sources present in the call and for video, the mappings are calculated based on the video sources the client requests through the video receiver constraints. Remote tracks are then created/remapped by the client based on these mappings. * Added track_owner_changed action * Skip track-based large-video selection in rewriting mode. * Register OWNER_CHANGED handler at track level. * feat(participants) Add source info to participants in redux. With ssrc-rewriting, the receiver constraints need to be generated using the source info received in presence. Currently they are generated from the track info in redux but with ssrc-rewriting, remote sources are not signaled and therefore created until they are being requested through receiver constraints. Co-authored-by: James A <jqdrqgnq@users.noreply.github.com>
This commit is contained in:
parent
cbae997eda
commit
5e90e72562
|
@ -103,6 +103,7 @@ import {
|
||||||
participantMutedUs,
|
participantMutedUs,
|
||||||
participantPresenceChanged,
|
participantPresenceChanged,
|
||||||
participantRoleChanged,
|
participantRoleChanged,
|
||||||
|
participantSourcesUpdated,
|
||||||
participantUpdated,
|
participantUpdated,
|
||||||
screenshareParticipantDisplayNameChanged,
|
screenshareParticipantDisplayNameChanged,
|
||||||
updateRemoteParticipantFeatures
|
updateRemoteParticipantFeatures
|
||||||
|
@ -1987,6 +1988,11 @@ export default {
|
||||||
APP.store.dispatch(participantKicked(kicker, kicked));
|
APP.store.dispatch(participantKicked(kicker, kicked));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
room.on(JitsiConferenceEvents.PARTICIPANT_SOURCE_UPDATED,
|
||||||
|
jitsiParticipant => {
|
||||||
|
APP.store.dispatch(participantSourcesUpdated(jitsiParticipant));
|
||||||
|
});
|
||||||
|
|
||||||
room.on(JitsiConferenceEvents.SUSPEND_DETECTED, () => {
|
room.on(JitsiConferenceEvents.SUSPEND_DETECTED, () => {
|
||||||
APP.store.dispatch(suspendDetected());
|
APP.store.dispatch(suspendDetected());
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,9 +15,11 @@ import {
|
||||||
participantMutedUs,
|
participantMutedUs,
|
||||||
participantPresenceChanged,
|
participantPresenceChanged,
|
||||||
participantRoleChanged,
|
participantRoleChanged,
|
||||||
|
participantSourcesUpdated,
|
||||||
participantUpdated
|
participantUpdated
|
||||||
} from '../participants/actions';
|
} from '../participants/actions';
|
||||||
import { getNormalizedDisplayName } from '../participants/functions';
|
import { getNormalizedDisplayName } from '../participants/functions';
|
||||||
|
import { IJitsiParticipant } from '../participants/types';
|
||||||
import { toState } from '../redux/functions';
|
import { toState } from '../redux/functions';
|
||||||
import {
|
import {
|
||||||
destroyLocalTracks,
|
destroyLocalTracks,
|
||||||
|
@ -128,6 +130,10 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
|
||||||
JitsiConferenceEvents.PARTICIPANT_KICKED,
|
JitsiConferenceEvents.PARTICIPANT_KICKED,
|
||||||
(kicker: any, kicked: any) => dispatch(participantKicked(kicker, kicked)));
|
(kicker: any, kicked: any) => dispatch(participantKicked(kicker, kicked)));
|
||||||
|
|
||||||
|
conference.on(
|
||||||
|
JitsiConferenceEvents.PARTICIPANT_SOURCE_UPDATED,
|
||||||
|
(jitsiParticipant: IJitsiParticipant) => dispatch(participantSourcesUpdated(jitsiParticipant)));
|
||||||
|
|
||||||
conference.on(
|
conference.on(
|
||||||
JitsiConferenceEvents.LOCK_STATE_CHANGED, // @ts-ignore
|
JitsiConferenceEvents.LOCK_STATE_CHANGED, // @ts-ignore
|
||||||
(...args: any[]) => dispatch(lockStateChanged(conference, ...args)));
|
(...args: any[]) => dispatch(lockStateChanged(conference, ...args)));
|
||||||
|
|
|
@ -105,7 +105,8 @@ export function commonUserJoinedHandling(
|
||||||
name: displayName,
|
name: displayName,
|
||||||
presence: user.getStatus(),
|
presence: user.getStatus(),
|
||||||
role: user.getRole(),
|
role: user.getRole(),
|
||||||
isReplacing
|
isReplacing,
|
||||||
|
sources: user.getSources()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -336,6 +336,7 @@ export interface IConfig {
|
||||||
};
|
};
|
||||||
firefox_fake_device?: string;
|
firefox_fake_device?: string;
|
||||||
flags?: {
|
flags?: {
|
||||||
|
ssrcRewritingEnabled: boolean;
|
||||||
};
|
};
|
||||||
focusUserJid?: string;
|
focusUserJid?: string;
|
||||||
gatherStats?: boolean;
|
gatherStats?: boolean;
|
||||||
|
|
|
@ -60,3 +60,13 @@ export const PREMEETING_BUTTONS = [ 'microphone', 'camera', 'select-background',
|
||||||
* The toolbar buttons to show on 3rdParty prejoin screen.
|
* The toolbar buttons to show on 3rdParty prejoin screen.
|
||||||
*/
|
*/
|
||||||
export const THIRD_PARTY_PREJOIN_BUTTONS = [ 'microphone', 'camera', 'select-background' ];
|
export const THIRD_PARTY_PREJOIN_BUTTONS = [ 'microphone', 'camera', 'select-background' ];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of feature flags.
|
||||||
|
*
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const FEATURE_FLAGS = {
|
||||||
|
SSRC_REWRITING: 'ssrcRewritingEnabled'
|
||||||
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { parseURLParams } from '../util/parseURLParams';
|
||||||
|
|
||||||
import { IConfig } from './configType';
|
import { IConfig } from './configType';
|
||||||
import CONFIG_WHITELIST from './configWhitelist';
|
import CONFIG_WHITELIST from './configWhitelist';
|
||||||
import { _CONFIG_STORE_PREFIX } from './constants';
|
import { FEATURE_FLAGS, _CONFIG_STORE_PREFIX } from './constants';
|
||||||
import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist';
|
import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
|
||||||
|
@ -63,6 +63,16 @@ export function getMultipleVideoSendingSupportFeatureFlag(state: IReduxState) {
|
||||||
return isUnifiedPlanEnabled(state);
|
return isUnifiedPlanEnabled(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selector used to get the SSRC-rewriting feature flag.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The global state.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function getSsrcRewritingFeatureFlag(state: IReduxState) {
|
||||||
|
return getFeatureFlag(state, FEATURE_FLAGS.SSRC_REWRITING);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector used to get a feature flag.
|
* Selector used to get a feature flag.
|
||||||
*
|
*
|
||||||
|
|
|
@ -117,6 +117,18 @@ export const PARTICIPANT_KICKED = 'PARTICIPANT_KICKED';
|
||||||
*/
|
*/
|
||||||
export const PARTICIPANT_LEFT = 'PARTICIPANT_LEFT';
|
export const PARTICIPANT_LEFT = 'PARTICIPANT_LEFT';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to handle case when the sources attached to a participant are updated.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: PARTICIPANT_SOURCES_UPDATED,
|
||||||
|
* participant: {
|
||||||
|
* id: string
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const PARTICIPANT_SOURCES_UPDATED = 'PARTICIPANT_SOURCES_UPDATED';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action to handle case when info about participant changes.
|
* Action to handle case when info about participant changes.
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
PARTICIPANT_JOINED,
|
PARTICIPANT_JOINED,
|
||||||
PARTICIPANT_KICKED,
|
PARTICIPANT_KICKED,
|
||||||
PARTICIPANT_LEFT,
|
PARTICIPANT_LEFT,
|
||||||
|
PARTICIPANT_SOURCES_UPDATED,
|
||||||
PARTICIPANT_UPDATED,
|
PARTICIPANT_UPDATED,
|
||||||
PIN_PARTICIPANT,
|
PIN_PARTICIPANT,
|
||||||
RAISE_HAND_UPDATED,
|
RAISE_HAND_UPDATED,
|
||||||
|
@ -36,7 +37,7 @@ import {
|
||||||
getVirtualScreenshareParticipantOwnerId
|
getVirtualScreenshareParticipantOwnerId
|
||||||
} from './functions';
|
} from './functions';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import { FakeParticipant, IParticipant } from './types';
|
import { FakeParticipant, IJitsiParticipant, IParticipant } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an action for when dominant speaker changes.
|
* Create an action for when dominant speaker changes.
|
||||||
|
@ -253,6 +254,39 @@ export function participantJoined(participant: IParticipant) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the sources of a remote participant.
|
||||||
|
*
|
||||||
|
* @param {IJitsiParticipant} jitsiParticipant - The IJitsiParticipant instance.
|
||||||
|
* @returns {{
|
||||||
|
* type: PARTICIPANT_SOURCES_UPDATED,
|
||||||
|
* participant: IParticipant
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function participantSourcesUpdated(jitsiParticipant: IJitsiParticipant) {
|
||||||
|
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
|
const id = jitsiParticipant.getId();
|
||||||
|
const participant = getParticipantById(getState(), id);
|
||||||
|
|
||||||
|
if (participant?.local) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sources = jitsiParticipant.getSources();
|
||||||
|
|
||||||
|
if (!sources?.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dispatch({
|
||||||
|
type: PARTICIPANT_SOURCES_UPDATED,
|
||||||
|
participant: {
|
||||||
|
id,
|
||||||
|
sources
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the features of a remote participant.
|
* Updates the features of a remote participant.
|
||||||
*
|
*
|
||||||
|
|
|
@ -6,8 +6,9 @@ import { isStageFilmstripAvailable } from '../../filmstrip/functions';
|
||||||
import { IStateful } from '../app/types';
|
import { IStateful } from '../app/types';
|
||||||
import { GRAVATAR_BASE_URL } from '../avatar/constants';
|
import { GRAVATAR_BASE_URL } from '../avatar/constants';
|
||||||
import { isCORSAvatarURL } from '../avatar/functions';
|
import { isCORSAvatarURL } from '../avatar/functions';
|
||||||
|
import { getCurrentConference } from '../conference/functions';
|
||||||
import i18next from '../i18n/i18next';
|
import i18next from '../i18n/i18next';
|
||||||
import { VIDEO_TYPE } from '../media/constants';
|
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||||
import { toState } from '../redux/functions';
|
import { toState } from '../redux/functions';
|
||||||
import { getScreenShareTrack } from '../tracks/functions';
|
import { getScreenShareTrack } from '../tracks/functions';
|
||||||
import { createDeferred } from '../util/helpers';
|
import { createDeferred } from '../util/helpers';
|
||||||
|
@ -21,7 +22,7 @@ import {
|
||||||
// eslint-disable-next-line lines-around-comment
|
// eslint-disable-next-line lines-around-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { preloadImage } from './preloadImage';
|
import { preloadImage } from './preloadImage';
|
||||||
import { FakeParticipant, IParticipant } from './types';
|
import { FakeParticipant, IJitsiParticipant, IParticipant, ISourceInfo } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temp structures for avatar urls to be checked/preloaded.
|
* Temp structures for avatar urls to be checked/preloaded.
|
||||||
|
@ -407,6 +408,35 @@ export function getParticipantDisplayName(stateful: IStateful, id: string): stri
|
||||||
return defaultRemoteDisplayName ?? '';
|
return defaultRemoteDisplayName ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the source names of the screenshare sources in the conference based on the presence shared by the remote
|
||||||
|
* endpoints. This should be only used for creating/removing virtual screenshare participant tiles when ssrc-rewriting
|
||||||
|
* is enabled. Once the tile is created, the source-name gets added to the receiver constraints based on which the
|
||||||
|
* JVB will add the source to the video sources map and signal it to the local endpoint. Only then, a remote track is
|
||||||
|
* created/remapped and the tracks in redux will be updated. Once the track is updated in redux, the client will
|
||||||
|
* will continue to use the other track based getter functions for other operations related to screenshare.
|
||||||
|
*
|
||||||
|
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||||
|
* retrieve the state.
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
export function getRemoteScreensharesBasedOnPresence(stateful: IStateful): string[] {
|
||||||
|
const conference = getCurrentConference(stateful);
|
||||||
|
|
||||||
|
return conference?.getParticipants()?.reduce((screenshares: string[], participant: IJitsiParticipant) => {
|
||||||
|
const sources: Map<string, Map<string, ISourceInfo>> = participant.getSources();
|
||||||
|
const videoSources = sources.get(MEDIA_TYPE.VIDEO);
|
||||||
|
const screenshareSources = Array.from(videoSources ?? new Map())
|
||||||
|
.filter(source => source[1].videoType === VIDEO_TYPE.DESKTOP && !source[1].muted)
|
||||||
|
.map(source => source[0]);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
screenshares = [ ...screenshares, ...screenshareSources ];
|
||||||
|
|
||||||
|
return screenshares;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns screenshare participant's display name.
|
* Returns screenshare participant's display name.
|
||||||
*
|
*
|
||||||
|
@ -434,6 +464,36 @@ export function getScreenshareParticipantIds(stateful: IStateful): Array<string>
|
||||||
.map(t => t.participantId);
|
.map(t => t.participantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list source name associated with a given remote participant and for the given media type.
|
||||||
|
*
|
||||||
|
* @param {(Function|Object)} stateful - The (whole) redux state, or redux's {@code getState} function to be used to
|
||||||
|
* retrieve the state.
|
||||||
|
* @param {string} id - The id of the participant whose source names are to be retrieved.
|
||||||
|
* @param {string} mediaType - The type of source, audio or video.
|
||||||
|
* @returns {Array<string>|undefined}
|
||||||
|
*/
|
||||||
|
export function getSourceNamesByMediaType(
|
||||||
|
stateful: IStateful,
|
||||||
|
id: string,
|
||||||
|
mediaType: string): Array<string> | undefined {
|
||||||
|
const participant: IParticipant | undefined = getParticipantById(stateful, id);
|
||||||
|
|
||||||
|
if (!participant) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sources = participant.sources;
|
||||||
|
|
||||||
|
if (!sources) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(sources.get(mediaType) ?? new Map())
|
||||||
|
.filter(source => source[1].videoType !== VIDEO_TYPE.DESKTOP || !source[1].muted)
|
||||||
|
.map(s => s[0]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the presence status of a participant associated with the passed id.
|
* Returns the presence status of a participant associated with the passed id.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { MEDIA_TYPE } from '../media/constants';
|
||||||
import ReducerRegistry from '../redux/ReducerRegistry';
|
import ReducerRegistry from '../redux/ReducerRegistry';
|
||||||
import { set } from '../redux/functions';
|
import { set } from '../redux/functions';
|
||||||
|
|
||||||
|
@ -7,6 +8,7 @@ import {
|
||||||
PARTICIPANT_ID_CHANGED,
|
PARTICIPANT_ID_CHANGED,
|
||||||
PARTICIPANT_JOINED,
|
PARTICIPANT_JOINED,
|
||||||
PARTICIPANT_LEFT,
|
PARTICIPANT_LEFT,
|
||||||
|
PARTICIPANT_SOURCES_UPDATED,
|
||||||
PARTICIPANT_UPDATED,
|
PARTICIPANT_UPDATED,
|
||||||
PIN_PARTICIPANT,
|
PIN_PARTICIPANT,
|
||||||
RAISE_HAND_UPDATED,
|
RAISE_HAND_UPDATED,
|
||||||
|
@ -20,7 +22,7 @@ import {
|
||||||
isRemoteScreenshareParticipant,
|
isRemoteScreenshareParticipant,
|
||||||
isScreenShareParticipant
|
isScreenShareParticipant
|
||||||
} from './functions';
|
} from './functions';
|
||||||
import { ILocalParticipant, IParticipant } from './types';
|
import { FakeParticipant, ILocalParticipant, IParticipant, ISourceInfo } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Participant object.
|
* Participant object.
|
||||||
|
@ -69,6 +71,7 @@ const DEFAULT_STATE = {
|
||||||
pinnedParticipant: undefined,
|
pinnedParticipant: undefined,
|
||||||
raisedHandsQueue: [],
|
raisedHandsQueue: [],
|
||||||
remote: new Map(),
|
remote: new Map(),
|
||||||
|
remoteVideoSources: new Set<string>(),
|
||||||
sortedRemoteVirtualScreenshareParticipants: new Map(),
|
sortedRemoteVirtualScreenshareParticipants: new Map(),
|
||||||
sortedRemoteParticipants: new Map(),
|
sortedRemoteParticipants: new Map(),
|
||||||
speakersList: new Map()
|
speakersList: new Map()
|
||||||
|
@ -84,6 +87,7 @@ export interface IParticipantsState {
|
||||||
pinnedParticipant?: string;
|
pinnedParticipant?: string;
|
||||||
raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number; }>;
|
raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number; }>;
|
||||||
remote: Map<string, IParticipant>;
|
remote: Map<string, IParticipant>;
|
||||||
|
remoteVideoSources: Set<string>;
|
||||||
sortedRemoteParticipants: Map<string, string>;
|
sortedRemoteParticipants: Map<string, string>;
|
||||||
sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
|
sortedRemoteVirtualScreenshareParticipants: Map<string, string>;
|
||||||
speakersList: Map<string, string>;
|
speakersList: Map<string, string>;
|
||||||
|
@ -243,7 +247,8 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||||
fakeParticipant,
|
fakeParticipant,
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
pinned
|
pinned,
|
||||||
|
sources
|
||||||
} = participant;
|
} = participant;
|
||||||
const { pinnedParticipant, dominantSpeaker } = state;
|
const { pinnedParticipant, dominantSpeaker } = state;
|
||||||
|
|
||||||
|
@ -287,6 +292,19 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||||
|
|
||||||
state.remote.set(id, participant);
|
state.remote.set(id, participant);
|
||||||
|
|
||||||
|
if (sources?.size) {
|
||||||
|
const videoSources: Map<string, ISourceInfo> | undefined = sources.get(MEDIA_TYPE.VIDEO);
|
||||||
|
|
||||||
|
if (videoSources?.size) {
|
||||||
|
const newRemoteVideoSources = new Set(state.remoteVideoSources);
|
||||||
|
|
||||||
|
for (const source of videoSources.keys()) {
|
||||||
|
newRemoteVideoSources.add(source);
|
||||||
|
}
|
||||||
|
state.remoteVideoSources = newRemoteVideoSources;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Insert the new participant.
|
// Insert the new participant.
|
||||||
const displayName = _getDisplayName(state, name);
|
const displayName = _getDisplayName(state, name);
|
||||||
const sortedRemoteParticipants = Array.from(state.sortedRemoteParticipants);
|
const sortedRemoteParticipants = Array.from(state.sortedRemoteParticipants);
|
||||||
|
@ -332,6 +350,23 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||||
} = state;
|
} = state;
|
||||||
let oldParticipant = remote.get(id);
|
let oldParticipant = remote.get(id);
|
||||||
|
|
||||||
|
if (oldParticipant?.sources?.size) {
|
||||||
|
const videoSources: Map<string, ISourceInfo> | undefined = oldParticipant.sources.get(MEDIA_TYPE.VIDEO);
|
||||||
|
const newRemoteVideoSources = new Set(state.remoteVideoSources);
|
||||||
|
|
||||||
|
if (videoSources?.size) {
|
||||||
|
for (const source of videoSources.keys()) {
|
||||||
|
newRemoteVideoSources.delete(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.remoteVideoSources = newRemoteVideoSources;
|
||||||
|
} else if (oldParticipant?.fakeParticipant === FakeParticipant.RemoteScreenShare) {
|
||||||
|
const newRemoteVideoSources = new Set(state.remoteVideoSources);
|
||||||
|
|
||||||
|
newRemoteVideoSources.delete(id);
|
||||||
|
state.remoteVideoSources = newRemoteVideoSources;
|
||||||
|
}
|
||||||
|
|
||||||
if (oldParticipant && oldParticipant.conference === conference) {
|
if (oldParticipant && oldParticipant.conference === conference) {
|
||||||
remote.delete(id);
|
remote.delete(id);
|
||||||
} else if (local?.id === id) {
|
} else if (local?.id === id) {
|
||||||
|
@ -374,6 +409,26 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||||
|
|
||||||
return { ...state };
|
return { ...state };
|
||||||
}
|
}
|
||||||
|
case PARTICIPANT_SOURCES_UPDATED: {
|
||||||
|
const { id, sources } = action.participant;
|
||||||
|
const participant = state.remote.get(id);
|
||||||
|
|
||||||
|
if (participant) {
|
||||||
|
participant.sources = sources;
|
||||||
|
const videoSources: Map<string, ISourceInfo> = sources.get(MEDIA_TYPE.VIDEO);
|
||||||
|
|
||||||
|
if (videoSources?.size) {
|
||||||
|
const newRemoteVideoSources = new Set(state.remoteVideoSources);
|
||||||
|
|
||||||
|
for (const source of videoSources.keys()) {
|
||||||
|
newRemoteVideoSources.add(source);
|
||||||
|
}
|
||||||
|
state.remoteVideoSources = newRemoteVideoSources;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...state };
|
||||||
|
}
|
||||||
case RAISE_HAND_UPDATED: {
|
case RAISE_HAND_UPDATED: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -493,7 +548,8 @@ function _participantJoined({ participant }: { participant: IParticipant; }) {
|
||||||
name,
|
name,
|
||||||
pinned,
|
pinned,
|
||||||
presence,
|
presence,
|
||||||
role
|
role,
|
||||||
|
sources
|
||||||
} = participant;
|
} = participant;
|
||||||
let { conference, id } = participant;
|
let { conference, id } = participant;
|
||||||
|
|
||||||
|
@ -523,7 +579,8 @@ function _participantJoined({ participant }: { participant: IParticipant; }) {
|
||||||
name,
|
name,
|
||||||
pinned: pinned || false,
|
pinned: pinned || false,
|
||||||
presence,
|
presence,
|
||||||
role: role || PARTICIPANT_ROLE.NONE
|
role: role || PARTICIPANT_ROLE.NONE,
|
||||||
|
sources
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,14 @@ import _ from 'lodash';
|
||||||
import { IStore } from '../../app/types';
|
import { IStore } from '../../app/types';
|
||||||
import { getCurrentConference } from '../conference/functions';
|
import { getCurrentConference } from '../conference/functions';
|
||||||
import {
|
import {
|
||||||
getMultipleVideoSendingSupportFeatureFlag
|
getMultipleVideoSendingSupportFeatureFlag,
|
||||||
|
getSsrcRewritingFeatureFlag
|
||||||
} from '../config/functions.any';
|
} from '../config/functions.any';
|
||||||
|
import { VIDEO_TYPE } from '../media/constants';
|
||||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||||
|
|
||||||
import { createVirtualScreenshareParticipant, participantLeft } from './actions';
|
import { createVirtualScreenshareParticipant, participantLeft } from './actions';
|
||||||
|
import { getRemoteScreensharesBasedOnPresence } from './functions';
|
||||||
import { FakeParticipant } from './types';
|
import { FakeParticipant } from './types';
|
||||||
|
|
||||||
StateListenerRegistry.register(
|
StateListenerRegistry.register(
|
||||||
|
@ -15,13 +18,50 @@ StateListenerRegistry.register(
|
||||||
/* listener */(tracks, store) => _updateScreenshareParticipants(store)
|
/* listener */(tracks, store) => _updateScreenshareParticipants(store)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
StateListenerRegistry.register(
|
||||||
|
/* selector */ state => state['features/base/participants'].remoteVideoSources,
|
||||||
|
/* listener */(remoteVideoSources, store) => getSsrcRewritingFeatureFlag(store.getState())
|
||||||
|
&& _updateScreenshareParticipantsBasedOnPresence(store)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares the old and new screenshare lists provided and creates/removes the virtual screenshare participant
|
||||||
|
* tiles accodingly.
|
||||||
|
*
|
||||||
|
* @param {Array<string>} oldScreenshareSourceNames - List of old screenshare source names.
|
||||||
|
* @param {Array<string>} newScreenshareSourceNames - Current list of screenshare source names.
|
||||||
|
* @param {Object} store - The redux store.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function _createOrRemoveVirtualParticipants(
|
||||||
|
oldScreenshareSourceNames: string[],
|
||||||
|
newScreenshareSourceNames: string[],
|
||||||
|
store: IStore): void {
|
||||||
|
const { dispatch, getState } = store;
|
||||||
|
const conference = getCurrentConference(getState());
|
||||||
|
const removedScreenshareSourceNames = _.difference(oldScreenshareSourceNames, newScreenshareSourceNames);
|
||||||
|
const addedScreenshareSourceNames = _.difference(newScreenshareSourceNames, oldScreenshareSourceNames);
|
||||||
|
|
||||||
|
if (removedScreenshareSourceNames.length) {
|
||||||
|
removedScreenshareSourceNames.forEach(id => dispatch(participantLeft(id, conference, {
|
||||||
|
fakeParticipant: FakeParticipant.RemoteScreenShare
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedScreenshareSourceNames.length) {
|
||||||
|
addedScreenshareSourceNames.forEach(id => dispatch(
|
||||||
|
createVirtualScreenshareParticipant(id, false, conference)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles creating and removing virtual screenshare participants.
|
* Handles creating and removing virtual screenshare participants.
|
||||||
*
|
*
|
||||||
* @param {*} store - The redux store.
|
* @param {*} store - The redux store.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
function _updateScreenshareParticipants({ getState, dispatch }: IStore) {
|
function _updateScreenshareParticipants(store: IStore): void {
|
||||||
|
const { dispatch, getState } = store;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const conference = getCurrentConference(state);
|
const conference = getCurrentConference(state);
|
||||||
const tracks = state['features/base/tracks'];
|
const tracks = state['features/base/tracks'];
|
||||||
|
@ -31,7 +71,7 @@ function _updateScreenshareParticipants({ getState, dispatch }: IStore) {
|
||||||
let newLocalSceenshareSourceName;
|
let newLocalSceenshareSourceName;
|
||||||
|
|
||||||
const currentScreenshareSourceNames = tracks.reduce((acc: string[], track) => {
|
const currentScreenshareSourceNames = tracks.reduce((acc: string[], track) => {
|
||||||
if (track.videoType === 'desktop' && !track.jitsiTrack.isMuted()) {
|
if (track.videoType === VIDEO_TYPE.DESKTOP && !track.jitsiTrack.isMuted()) {
|
||||||
const sourceName: string = track.jitsiTrack.getSourceName();
|
const sourceName: string = track.jitsiTrack.getSourceName();
|
||||||
|
|
||||||
if (track.local) {
|
if (track.local) {
|
||||||
|
@ -51,24 +91,30 @@ function _updateScreenshareParticipants({ getState, dispatch }: IStore) {
|
||||||
|
|
||||||
if (localScreenShare && !newLocalSceenshareSourceName) {
|
if (localScreenShare && !newLocalSceenshareSourceName) {
|
||||||
dispatch(participantLeft(localScreenShare.id, conference, {
|
dispatch(participantLeft(localScreenShare.id, conference, {
|
||||||
fakeParticipant: FakeParticipant.LocalScreenShare,
|
fakeParticipant: FakeParticipant.LocalScreenShare
|
||||||
isReplaced: undefined
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const removedScreenshareSourceNames = _.difference(previousScreenshareSourceNames, currentScreenshareSourceNames);
|
if (getSsrcRewritingFeatureFlag(state)) {
|
||||||
const addedScreenshareSourceNames = _.difference(currentScreenshareSourceNames, previousScreenshareSourceNames);
|
return;
|
||||||
|
|
||||||
if (removedScreenshareSourceNames.length) {
|
|
||||||
removedScreenshareSourceNames.forEach(id => dispatch(participantLeft(id, conference, {
|
|
||||||
fakeParticipant: FakeParticipant.RemoteScreenShare,
|
|
||||||
isReplaced: undefined
|
|
||||||
})));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addedScreenshareSourceNames.length) {
|
_createOrRemoveVirtualParticipants(previousScreenshareSourceNames, currentScreenshareSourceNames, store);
|
||||||
addedScreenshareSourceNames.forEach(id => dispatch(
|
}
|
||||||
createVirtualScreenshareParticipant(id, false, conference)));
|
|
||||||
}
|
/**
|
||||||
|
* Handles the creation and removal of remote virtual screenshare participants when ssrc-rewriting is enabled.
|
||||||
|
*
|
||||||
|
* @param {Object} store - The redux store.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function _updateScreenshareParticipantsBasedOnPresence(store: IStore): void {
|
||||||
|
const { getState } = store;
|
||||||
|
const state = getState();
|
||||||
|
const { sortedRemoteVirtualScreenshareParticipants } = state['features/base/participants'];
|
||||||
|
const previousScreenshareSourceNames = [ ...sortedRemoteVirtualScreenshareParticipants.keys() ];
|
||||||
|
const currentScreenshareSourceNames = getRemoteScreensharesBasedOnPresence(state);
|
||||||
|
|
||||||
|
_createOrRemoveVirtualParticipants(previousScreenshareSourceNames, currentScreenshareSourceNames, store);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ export interface IParticipant {
|
||||||
region?: string;
|
region?: string;
|
||||||
remoteControlSessionStatus?: boolean;
|
remoteControlSessionStatus?: boolean;
|
||||||
role?: string;
|
role?: string;
|
||||||
|
sources?: Map<string, Map<string, ISourceInfo>>;
|
||||||
supportsRemoteControl?: boolean;
|
supportsRemoteControl?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,10 +52,16 @@ export interface ILocalParticipant extends IParticipant {
|
||||||
userSelectedMicDeviceLabel?: string;
|
userSelectedMicDeviceLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISourceInfo {
|
||||||
|
muted: boolean;
|
||||||
|
videoType: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IJitsiParticipant {
|
export interface IJitsiParticipant {
|
||||||
getDisplayName: () => string;
|
getDisplayName: () => string;
|
||||||
getId: () => string;
|
getId: () => string;
|
||||||
getJid: () => string;
|
getJid: () => string;
|
||||||
getRole: () => string;
|
getRole: () => string;
|
||||||
|
getSources: () => Map<string, Map<string, ISourceInfo>>;
|
||||||
isHidden: () => boolean;
|
isHidden: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,16 @@ export const TRACK_MUTE_UNMUTE_FAILED = 'TRACK_MUTE_UNMUTE_FAILED';
|
||||||
*/
|
*/
|
||||||
export const TRACK_NO_DATA_FROM_SOURCE = 'TRACK_NO_DATA_FROM_SOURCE';
|
export const TRACK_NO_DATA_FROM_SOURCE = 'TRACK_NO_DATA_FROM_SOURCE';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of redux action dispatched when the owner of a track changes due to ssrc remapping.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: TRACK_OWNER_CHANGED,
|
||||||
|
* track: Track
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const TRACK_OWNER_CHANGED = 'TRACK_OWNER_CHANGED';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of redux action dispatched when a track has been (locally or
|
* The type of redux action dispatched when a track has been (locally or
|
||||||
* remotely) removed from the conference.
|
* remotely) removed from the conference.
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {
|
||||||
TRACK_CREATE_ERROR,
|
TRACK_CREATE_ERROR,
|
||||||
TRACK_MUTE_UNMUTE_FAILED,
|
TRACK_MUTE_UNMUTE_FAILED,
|
||||||
TRACK_NO_DATA_FROM_SOURCE,
|
TRACK_NO_DATA_FROM_SOURCE,
|
||||||
|
TRACK_OWNER_CHANGED,
|
||||||
TRACK_REMOVED,
|
TRACK_REMOVED,
|
||||||
TRACK_STOPPED,
|
TRACK_STOPPED,
|
||||||
TRACK_UPDATED,
|
TRACK_UPDATED,
|
||||||
|
@ -377,7 +378,9 @@ export function trackAdded(track: any) {
|
||||||
track.on(
|
track.on(
|
||||||
JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
|
JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED,
|
||||||
(type: VideoType) => dispatch(trackVideoTypeChanged(track, type)));
|
(type: VideoType) => dispatch(trackVideoTypeChanged(track, type)));
|
||||||
|
track.on(
|
||||||
|
JitsiTrackEvents.TRACK_OWNER_CHANGED,
|
||||||
|
(owner: string) => dispatch(trackOwnerChanged(track, owner)));
|
||||||
const local = track.isLocal();
|
const local = track.isLocal();
|
||||||
const isVirtualScreenshareParticipantCreated = !local || getMultipleVideoSendingSupportFeatureFlag(getState());
|
const isVirtualScreenshareParticipantCreated = !local || getMultipleVideoSendingSupportFeatureFlag(getState());
|
||||||
const mediaType = track.getVideoType() === VIDEO_TYPE.DESKTOP && isVirtualScreenshareParticipantCreated
|
const mediaType = track.getVideoType() === VIDEO_TYPE.DESKTOP && isVirtualScreenshareParticipantCreated
|
||||||
|
@ -582,11 +585,14 @@ export function trackVideoStarted(track: any): {
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function trackVideoTypeChanged(track: any, videoType: VideoType) {
|
export function trackVideoTypeChanged(track: any, videoType: VideoType) {
|
||||||
|
const mediaType = videoType === VIDEO_TYPE.CAMERA ? MEDIA_TYPE.VIDEO : MEDIA_TYPE.SCREENSHARE;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: TRACK_UPDATED,
|
type: TRACK_UPDATED,
|
||||||
track: {
|
track: {
|
||||||
jitsiTrack: track,
|
jitsiTrack: track,
|
||||||
videoType
|
videoType,
|
||||||
|
mediaType
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -617,6 +623,32 @@ export function trackStreamingStatusChanged(track: any, streamingStatus: string)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an action for when the owner of the track changes due to ssrc remapping.
|
||||||
|
*
|
||||||
|
* @param {(JitsiRemoteTrack)} track - JitsiTrack instance.
|
||||||
|
* @param {string} participantId - New owner's participant ID.
|
||||||
|
* @returns {{
|
||||||
|
* type: TRACK_OWNER_CHANGED,
|
||||||
|
* track: Track
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function trackOwnerChanged(track: any, participantId: string): {
|
||||||
|
track: {
|
||||||
|
jitsiTrack: any;
|
||||||
|
participantId: string;
|
||||||
|
};
|
||||||
|
type: 'TRACK_OWNER_CHANGED';
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
type: TRACK_OWNER_CHANGED,
|
||||||
|
track: {
|
||||||
|
jitsiTrack: track,
|
||||||
|
participantId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals passed tracks to be added.
|
* Signals passed tracks to be added.
|
||||||
*
|
*
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
TRACK_CREATE_CANCELED,
|
TRACK_CREATE_CANCELED,
|
||||||
TRACK_CREATE_ERROR,
|
TRACK_CREATE_ERROR,
|
||||||
TRACK_NO_DATA_FROM_SOURCE,
|
TRACK_NO_DATA_FROM_SOURCE,
|
||||||
|
TRACK_OWNER_CHANGED,
|
||||||
TRACK_REMOVED,
|
TRACK_REMOVED,
|
||||||
TRACK_UPDATED,
|
TRACK_UPDATED,
|
||||||
TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
|
TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
|
||||||
|
@ -41,6 +42,18 @@ function track(state: ITrack, action: any) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TRACK_OWNER_CHANGED: {
|
||||||
|
const t = action.track;
|
||||||
|
|
||||||
|
if (state.jitsiTrack === t.jitsiTrack) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
participantId: t.participantId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case TRACK_UPDATED: {
|
case TRACK_UPDATED: {
|
||||||
const t = action.track;
|
const t = action.track;
|
||||||
|
|
||||||
|
@ -103,10 +116,10 @@ ReducerRegistry.register<ITracksState>('features/base/tracks', (state = [], acti
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case PARTICIPANT_ID_CHANGED:
|
case PARTICIPANT_ID_CHANGED:
|
||||||
case TRACK_NO_DATA_FROM_SOURCE:
|
case TRACK_NO_DATA_FROM_SOURCE:
|
||||||
|
case TRACK_OWNER_CHANGED:
|
||||||
case TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT:
|
case TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT:
|
||||||
case TRACK_UPDATED:
|
case TRACK_UPDATED:
|
||||||
return state.map((t: ITrack) => track(t, action));
|
return state.map((t: ITrack) => track(t, action));
|
||||||
|
|
||||||
case TRACK_ADDED: {
|
case TRACK_ADDED: {
|
||||||
let withoutTrackStub = state;
|
let withoutTrackStub = state;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { IReduxState, IStore } from '../app/types';
|
import { IReduxState, IStore } from '../app/types';
|
||||||
|
import { getSsrcRewritingFeatureFlag } from '../base/config/functions.any';
|
||||||
import { MEDIA_TYPE } from '../base/media/constants';
|
import { MEDIA_TYPE } from '../base/media/constants';
|
||||||
import {
|
import {
|
||||||
getDominantSpeakerParticipant,
|
getDominantSpeakerParticipant,
|
||||||
|
@ -170,11 +171,14 @@ function _electParticipantInLargeVideo(state: IReduxState) {
|
||||||
participant = undefined;
|
participant = undefined;
|
||||||
|
|
||||||
// Next, pick the most recent participant with video.
|
// Next, pick the most recent participant with video.
|
||||||
const tracks = state['features/base/tracks'];
|
// (Skip this if rewriting, tracks may be detached from any owner.)
|
||||||
const videoTrack = _electLastVisibleRemoteVideo(tracks);
|
if (!getSsrcRewritingFeatureFlag(state)) {
|
||||||
|
const tracks = state['features/base/tracks'];
|
||||||
|
const videoTrack = _electLastVisibleRemoteVideo(tracks);
|
||||||
|
|
||||||
if (videoTrack) {
|
if (videoTrack) {
|
||||||
return videoTrack.participantId;
|
return videoTrack.participantId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last, select the participant that joined last (other than poltergist or other bot type participants).
|
// Last, select the participant that joined last (other than poltergist or other bot type participants).
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
import { IStore } from '../app/types';
|
import { IReduxState, IStore } from '../app/types';
|
||||||
import { _handleParticipantError } from '../base/conference/functions';
|
import { _handleParticipantError } from '../base/conference/functions';
|
||||||
|
import { getSsrcRewritingFeatureFlag } from '../base/config/functions.any';
|
||||||
import { MEDIA_TYPE } from '../base/media/constants';
|
import { MEDIA_TYPE } from '../base/media/constants';
|
||||||
import { getLocalParticipant } from '../base/participants/functions';
|
import {
|
||||||
|
getLocalParticipant,
|
||||||
|
getSourceNamesByMediaType
|
||||||
|
} from '../base/participants/functions';
|
||||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||||
import { getTrackSourceNameByMediaTypeAndParticipant } from '../base/tracks/functions';
|
import { getTrackSourceNameByMediaTypeAndParticipant } from '../base/tracks/functions';
|
||||||
import { reportError } from '../base/util/helpers';
|
import { reportError } from '../base/util/helpers';
|
||||||
|
@ -94,6 +98,15 @@ StateListenerRegistry.register(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the receiver constraints when new video sources are added to the conference.
|
||||||
|
*/
|
||||||
|
StateListenerRegistry.register(
|
||||||
|
/* selector */ state => state['features/base/participants'].remoteVideoSources,
|
||||||
|
/* listener */ (remoteVideoSources, store) => {
|
||||||
|
getSsrcRewritingFeatureFlag(store.getState()) && _updateReceiverVideoConstraints(store);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StateListenerRegistry provides a reliable way of detecting changes to
|
* StateListenerRegistry provides a reliable way of detecting changes to
|
||||||
* maxReceiverVideoQuality* and preferredVideoQuality state and dispatching additional actions.
|
* maxReceiverVideoQuality* and preferredVideoQuality state and dispatching additional actions.
|
||||||
|
@ -294,6 +307,42 @@ StateListenerRegistry.register(
|
||||||
deepEquals: true
|
deepEquals: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the source names asociated with the given participants list.
|
||||||
|
*
|
||||||
|
* @param {Array<string>} participantList - The list of participants.
|
||||||
|
* @param {Object} state - The redux state.
|
||||||
|
* @returns {Array<string>}
|
||||||
|
*/
|
||||||
|
function _getSourceNames(participantList: Array<string>, state: IReduxState): Array<string> {
|
||||||
|
const { remoteScreenShares } = state['features/video-layout'];
|
||||||
|
const tracks = state['features/base/tracks'];
|
||||||
|
const sourceNamesList: string[] = [];
|
||||||
|
|
||||||
|
participantList.forEach(participantId => {
|
||||||
|
if (getSsrcRewritingFeatureFlag(state)) {
|
||||||
|
const sourceNames: string[] | undefined
|
||||||
|
= getSourceNamesByMediaType(state, participantId, MEDIA_TYPE.VIDEO);
|
||||||
|
|
||||||
|
sourceNames?.length && sourceNamesList.push(...sourceNames);
|
||||||
|
} else {
|
||||||
|
let sourceName: string;
|
||||||
|
|
||||||
|
if (remoteScreenShares.includes(participantId)) {
|
||||||
|
sourceName = participantId;
|
||||||
|
} else {
|
||||||
|
sourceName = getTrackSourceNameByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceName) {
|
||||||
|
sourceNamesList.push(sourceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sourceNamesList;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function for updating the preferred sender video constraint, based on the user preference.
|
* Helper function for updating the preferred sender video constraint, based on the user preference.
|
||||||
*
|
*
|
||||||
|
@ -360,53 +409,29 @@ function _updateReceiverVideoConstraints({ getState }: IStore) {
|
||||||
lastN
|
lastN
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeParticipantsSources: string[] = [];
|
let activeParticipantsSources: string[] = [];
|
||||||
const visibleRemoteTrackSourceNames: string[] = [];
|
let visibleRemoteTrackSourceNames: string[] = [];
|
||||||
let largeVideoSourceName: string | undefined;
|
let largeVideoSourceName: string | undefined;
|
||||||
|
|
||||||
receiverConstraints.onStageSources = [];
|
receiverConstraints.onStageSources = [];
|
||||||
receiverConstraints.selectedSources = [];
|
receiverConstraints.selectedSources = [];
|
||||||
|
|
||||||
if (visibleRemoteParticipants?.size) {
|
if (visibleRemoteParticipants?.size) {
|
||||||
visibleRemoteParticipants.forEach(participantId => {
|
visibleRemoteTrackSourceNames = _getSourceNames(Array.from(visibleRemoteParticipants), state);
|
||||||
let sourceName;
|
|
||||||
|
|
||||||
if (remoteScreenShares.includes(participantId)) {
|
|
||||||
sourceName = participantId;
|
|
||||||
} else {
|
|
||||||
sourceName = getTrackSourceNameByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceName) {
|
|
||||||
visibleRemoteTrackSourceNames.push(sourceName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeParticipantsIds?.length > 0) {
|
if (activeParticipantsIds?.length > 0) {
|
||||||
activeParticipantsIds.forEach((participantId: string) => {
|
activeParticipantsSources = _getSourceNames(activeParticipantsIds, state);
|
||||||
let sourceName;
|
|
||||||
|
|
||||||
if (remoteScreenShares.includes(participantId)) {
|
|
||||||
sourceName = participantId;
|
|
||||||
} else {
|
|
||||||
sourceName = getTrackSourceNameByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceName) {
|
|
||||||
activeParticipantsSources.push(sourceName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localParticipantId !== largeVideoParticipantId) {
|
if (localParticipantId !== largeVideoParticipantId) {
|
||||||
if (remoteScreenShares.includes(largeVideoParticipantId)) {
|
if (remoteScreenShares.includes(largeVideoParticipantId)) {
|
||||||
largeVideoSourceName = largeVideoParticipantId;
|
largeVideoSourceName = largeVideoParticipantId;
|
||||||
} else {
|
} else {
|
||||||
largeVideoSourceName = getTrackSourceNameByMediaTypeAndParticipant(
|
largeVideoSourceName = getSsrcRewritingFeatureFlag(state)
|
||||||
tracks, MEDIA_TYPE.VIDEO, largeVideoParticipantId
|
? getSourceNamesByMediaType(state, largeVideoParticipantId, MEDIA_TYPE.VIDEO)?.[0]
|
||||||
);
|
: getTrackSourceNameByMediaTypeAndParticipant(
|
||||||
|
tracks, MEDIA_TYPE.VIDEO, largeVideoParticipantId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue