fix(connection-indicator) use SSRCs to match tiles to stats

This commit is contained in:
Saúl Ibarra Corretgé 2022-10-31 22:17:05 +01:00 committed by Saúl Ibarra Corretgé
parent 73160de3b7
commit 9b1e662a93
4 changed files with 75 additions and 62 deletions

View File

@ -2,10 +2,9 @@
import { Component } from 'react'; import { Component } from 'react';
import { getVirtualScreenshareParticipantOwnerId } from '../../base/participants/functions';
import statsEmitter from '../statsEmitter'; import statsEmitter from '../statsEmitter';
declare var interfaceConfig: Object;
const defaultAutoHideTimeout = 5000; const defaultAutoHideTimeout = 5000;
/** /**
@ -26,6 +25,11 @@ export type Props = {
*/ */
_autoHideTimeout: number, _autoHideTimeout: number,
/**
* Whether or not the statistics are for screen share.
*/
_isVirtualScreenshareParticipant: boolean,
/** /**
* The ID of the participant associated with the displayed connection indication and * The ID of the participant associated with the displayed connection indication and
* stats. * stats.
@ -90,7 +94,7 @@ class AbstractConnectionIndicator<P: Props, S: State> extends Component<P, S> {
* returns {void} * returns {void}
*/ */
componentDidMount() { componentDidMount() {
statsEmitter.subscribeToClientStats(this.props.participantId, this._onStatsUpdated); statsEmitter.subscribeToClientStats(this._getRealParticipantId(this.props), this._onStatsUpdated);
} }
/** /**
@ -100,11 +104,12 @@ class AbstractConnectionIndicator<P: Props, S: State> extends Component<P, S> {
* returns {void} * returns {void}
*/ */
componentDidUpdate(prevProps: Props) { componentDidUpdate(prevProps: Props) {
if (prevProps.participantId !== this.props.participantId) { const prevParticipantId = this._getRealParticipantId(prevProps);
statsEmitter.unsubscribeToClientStats( const participantId = this._getRealParticipantId(this.props);
prevProps.participantId, this._onStatsUpdated);
statsEmitter.subscribeToClientStats( if (prevParticipantId !== participantId) {
this.props.participantId, this._onStatsUpdated); statsEmitter.unsubscribeToClientStats(prevParticipantId, this._onStatsUpdated);
statsEmitter.subscribeToClientStats(participantId, this._onStatsUpdated);
} }
} }
@ -116,11 +121,25 @@ class AbstractConnectionIndicator<P: Props, S: State> extends Component<P, S> {
* @returns {void} * @returns {void}
*/ */
componentWillUnmount() { componentWillUnmount() {
statsEmitter.unsubscribeToClientStats(this.props.participantId, this._onStatsUpdated); statsEmitter.unsubscribeToClientStats(this._getRealParticipantId(this.props), this._onStatsUpdated);
clearTimeout(this.autoHideTimeout); clearTimeout(this.autoHideTimeout);
} }
/**
* Gets the "real" participant ID. FOr a virtual screenshare participant, that is its "owner".
*
* @param {Props} props - The props where to extract the data from.
* @returns {string | undefined } The resolved participant ID.
*/
_getRealParticipantId(props: Props) {
if (props._isVirtualScreenshareParticipant) {
return getVirtualScreenshareParticipantOwnerId(props.participantId);
}
return props.participantId;
}
_onStatsUpdated: (Object) => void; _onStatsUpdated: (Object) => void;
/** /**

View File

@ -415,13 +415,15 @@ export function _mapStateToProps(state: IReduxState, ownProps: Props) {
return { return {
_connectionIndicatorInactiveDisabled: _connectionIndicatorInactiveDisabled:
Boolean(state['features/base/config'].connectionIndicators?.inactiveDisabled), Boolean(state['features/base/config'].connectionIndicators?.inactiveDisabled),
_isVirtualScreenshareParticipant: sourceNameSignalingEnabled && isScreenShareParticipant(participant),
_popoverDisabled: state['features/base/config'].connectionIndicators?.disableDetails, _popoverDisabled: state['features/base/config'].connectionIndicators?.disableDetails,
_videoTrack: firstVideoTrack, _videoTrack: firstVideoTrack,
_isConnectionStatusInactive, _isConnectionStatusInactive,
_isConnectionStatusInterrupted _isConnectionStatusInterrupted
}; };
} }
export default translate(connect(_mapStateToProps)( export default translate(connect(_mapStateToProps)(
// @ts-ignore // @ts-ignore
withStyles(styles)(ConnectionIndicator))); withStyles(styles)(ConnectionIndicator)));

View File

@ -8,7 +8,10 @@ import { translate } from '../../../base/i18n';
import { MEDIA_TYPE } from '../../../base/media'; import { MEDIA_TYPE } from '../../../base/media';
import { getLocalParticipant, getParticipantById, isScreenShareParticipant } from '../../../base/participants'; import { getLocalParticipant, getParticipantById, isScreenShareParticipant } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks'; import {
getTrackByMediaTypeAndParticipant,
getVirtualScreenshareParticipantTrack
} from '../../../base/tracks/functions';
import { ConnectionStatsTable } from '../../../connection-stats'; import { ConnectionStatsTable } from '../../../connection-stats';
import { saveLogs } from '../../actions'; import { saveLogs } from '../../actions';
import { import {
@ -67,7 +70,7 @@ type Props = AbstractProps & {
/** /**
* The audio SSRC of this client. * The audio SSRC of this client.
*/ */
_audioSsrc: number, _audioSsrc: number,
/** /**
* The current condition of the user's connection, matching one of the * The current condition of the user's connection, matching one of the
@ -87,12 +90,6 @@ type Props = AbstractProps & {
*/ */
_enableSaveLogs: boolean, _enableSaveLogs: boolean,
/**
* Whether or not the displays stats are for screen share. This prop is behind the sourceNameSignaling feature
* flag.
*/
_isVirtualScreenshareParticipant: Boolean,
/** /**
* Whether or not the displays stats are for local video. * Whether or not the displays stats are for local video.
*/ */
@ -323,19 +320,26 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
const conference = state['features/base/conference'].conference; const conference = state['features/base/conference'].conference;
const participant const participant
= participantId ? getParticipantById(state, participantId) : getLocalParticipant(state); = participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
const firstVideoTrack = getTrackByMediaTypeAndParticipant(
state['features/base/tracks'], MEDIA_TYPE.VIDEO, participantId);
const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state); const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
const tracks = state['features/base/tracks'];
const audioTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, participantId);
let videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participantId);
if (sourceNameSignalingEnabled && isScreenShareParticipant(participant)) {
videoTrack = getVirtualScreenshareParticipantTrack(tracks, participant?.id);
}
const _isConnectionStatusInactive = sourceNameSignalingEnabled const _isConnectionStatusInactive = sourceNameSignalingEnabled
? isTrackStreamingStatusInactive(firstVideoTrack) ? isTrackStreamingStatusInactive(videoTrack)
: isParticipantConnectionStatusInactive(participant); : isParticipantConnectionStatusInactive(participant);
const _isConnectionStatusInterrupted = sourceNameSignalingEnabled const _isConnectionStatusInterrupted = sourceNameSignalingEnabled
? isTrackStreamingStatusInterrupted(firstVideoTrack) ? isTrackStreamingStatusInterrupted(videoTrack)
: isParticipantConnectionStatusInterrupted(participant); : isParticipantConnectionStatusInterrupted(participant);
const props = { return {
_audioSsrc: audioTrack ? conference?.getSsrcByTrack(audioTrack.jitsiTrack) : undefined,
_connectionStatus: participant?.connectionStatus, _connectionStatus: participant?.connectionStatus,
_enableSaveLogs: state['features/base/config'].enableSaveLogs, _enableSaveLogs: state['features/base/config'].enableSaveLogs,
_disableShowMoreStats: state['features/base/config'].disableShowMoreStats, _disableShowMoreStats: state['features/base/config'].disableShowMoreStats,
@ -343,20 +347,9 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
_isConnectionStatusInterrupted, _isConnectionStatusInterrupted,
_isVirtualScreenshareParticipant: sourceNameSignalingEnabled && isScreenShareParticipant(participant), _isVirtualScreenshareParticipant: sourceNameSignalingEnabled && isScreenShareParticipant(participant),
_isLocalVideo: participant?.local, _isLocalVideo: participant?.local,
_region: participant?.region _region: participant?.region,
_videoSsrc: videoTrack ? conference?.getSsrcByTrack(videoTrack.jitsiTrack) : undefined
}; };
if (conference) {
const firstAudioTrack = getTrackByMediaTypeAndParticipant(
state['features/base/tracks'], MEDIA_TYPE.AUDIO, participantId);
return {
...props,
_audioSsrc: firstAudioTrack ? conference.getSsrcByTrack(firstAudioTrack.jitsiTrack) : undefined,
_videoSsrc: firstVideoTrack ? conference.getSsrcByTrack(firstVideoTrack.jitsiTrack) : undefined
};
}
return props;
} }
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ConnectionIndicatorContent)); export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ConnectionIndicatorContent));

View File

@ -84,7 +84,9 @@ interface IProps extends WithTranslation {
* [ ssrc ]: Number * [ ssrc ]: Number
* }}. * }}.
*/ */
framerate: Object; framerate: {
[ssrc: string]: number;
};
/** /**
* Whether or not the statistics are for local video. * Whether or not the statistics are for local video.
@ -461,21 +463,16 @@ class ConnectionStatsTable extends Component<IProps> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderCodecs() { _renderCodecs() {
const { codec, t } = this.props; const { audioSsrc, codec, t, videoSsrc } = this.props;
let codecString = 'N/A'; let codecString = 'N/A';
if (codec) { if (codec) {
const audioCodecs = Object.values(codec) const audioCodec = codec[audioSsrc]?.audio;
.map(c => c.audio) const videoCodec = codec[videoSsrc]?.video;
.filter(Boolean);
const videoCodecs = Object.values(codec)
.map(c => c.video)
.filter(Boolean);
if (audioCodecs.length || videoCodecs.length) { if (audioCodec || videoCodec) {
// Use a Set to eliminate duplicates. codecString = [ audioCodec, videoCodec ].filter(Boolean).join(', ');
codecString = Array.from(new Set([ ...audioCodecs, ...videoCodecs ])).join(', ');
} }
} }
@ -573,11 +570,12 @@ class ConnectionStatsTable extends Component<IProps> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderFrameRate() { _renderFrameRate() {
const { framerate, t } = this.props; const { framerate, t, videoSsrc } = this.props;
let frameRateString = 'N/A';
const frameRateString = Object.keys(framerate || {}) if (framerate) {
.map(ssrc => framerate[ssrc as keyof typeof framerate]) frameRateString = String(framerate[videoSsrc] ?? 'N/A');
.join(', ') || 'N/A'; }
return ( return (
<tr> <tr>
@ -639,20 +637,21 @@ class ConnectionStatsTable extends Component<IProps> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
_renderResolution() { _renderResolution() {
const { resolution, maxEnabledResolution, t } = this.props; const { resolution, maxEnabledResolution, t, videoSsrc } = this.props;
let resolutionString = 'N/A';
let resolutionString = Object.keys(resolution || {}) if (resolution && videoSsrc) {
.map(ssrc => { const { width, height } = resolution[videoSsrc] ?? { };
const { width, height } = resolution[ssrc];
return `${width}x${height}`; if (width && height) {
}) resolutionString = `${width}x${height}`;
.join(', ') || 'N/A';
if (maxEnabledResolution && maxEnabledResolution < 720) { if (maxEnabledResolution && maxEnabledResolution < 720) {
const maxEnabledResolutionTitle = t('connectionindicator.maxEnabledResolution'); const maxEnabledResolutionTitle = t('connectionindicator.maxEnabledResolution');
resolutionString += ` (${maxEnabledResolutionTitle} ${maxEnabledResolution}p)`; resolutionString += ` (${maxEnabledResolutionTitle} ${maxEnabledResolution}p)`;
}
}
} }
return ( return (