fix(connection-indicator) use SSRCs to match tiles to stats
This commit is contained in:
parent
73160de3b7
commit
9b1e662a93
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
Loading…
Reference in New Issue