fix(stats) split stats for camera and screenshare in multi-stream mode (#11475)
* no ssrc when sourceNameSignalingEnabled * conditionally use source name for stats * update doc * always subscribe to participant id
This commit is contained in:
parent
6068a30488
commit
3fc3a217eb
|
@ -7,7 +7,7 @@ import {
|
|||
import { isMobileBrowser } from '../environment/utils';
|
||||
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
|
||||
import { getVirtualScreenshareParticipantOwnerId } from '../participants';
|
||||
import { getParticipantByIdOrUndefined, getVirtualScreenshareParticipantOwnerId } from '../participants';
|
||||
import { toState } from '../redux';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
|
@ -436,6 +436,21 @@ export function getVideoTrackByParticipant(
|
|||
return getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, participant.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns source name for specified participant id.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {string} participantId - Participant ID.
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
export function getSourceNameByParticipantId(state, participantId) {
|
||||
const participant = getParticipantByIdOrUndefined(state, participantId);
|
||||
const tracks = state['features/base/tracks'];
|
||||
const track = getVideoTrackByParticipant(tracks, participant);
|
||||
|
||||
return track?.jitsiTrack?.getSourceName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns track of specified media type for specified participant id.
|
||||
*
|
||||
|
|
|
@ -30,7 +30,17 @@ export type Props = {
|
|||
* The ID of the participant associated with the displayed connection indication and
|
||||
* stats.
|
||||
*/
|
||||
participantId: string
|
||||
participantId: string,
|
||||
|
||||
/**
|
||||
* The source name of the track.
|
||||
*/
|
||||
_sourceName: string,
|
||||
|
||||
/**
|
||||
* The flag whether source name signaling is enabled.
|
||||
*/
|
||||
_sourceNameSignalingEnabled: string
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -87,6 +97,11 @@ class AbstractConnectionIndicator<P: Props, S: State> extends Component<P, S> {
|
|||
componentDidMount() {
|
||||
statsEmitter.subscribeToClientStats(
|
||||
this.props.participantId, this._onStatsUpdated);
|
||||
|
||||
if (this.props._sourceNameSignalingEnabled) {
|
||||
statsEmitter.subscribeToClientStats(
|
||||
this.props._sourceName, this._onStatsUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,6 +117,15 @@ class AbstractConnectionIndicator<P: Props, S: State> extends Component<P, S> {
|
|||
statsEmitter.subscribeToClientStats(
|
||||
this.props.participantId, this._onStatsUpdated);
|
||||
}
|
||||
|
||||
if (this.props._sourceNameSignalingEnabled) {
|
||||
if (prevProps._sourceName !== this.props._sourceName) {
|
||||
statsEmitter.unsubscribeToClientStats(
|
||||
prevProps._sourceName, this._onStatsUpdated);
|
||||
statsEmitter.subscribeToClientStats(
|
||||
this.props._sourceName, this._onStatsUpdated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,6 +139,11 @@ class AbstractConnectionIndicator<P: Props, S: State> extends Component<P, S> {
|
|||
statsEmitter.unsubscribeToClientStats(
|
||||
this.props.participantId, this._onStatsUpdated);
|
||||
|
||||
if (this.props._sourceNameSignalingEnabled) {
|
||||
statsEmitter.unsubscribeToClientStats(
|
||||
this.props._sourceName, this._onStatsUpdated);
|
||||
}
|
||||
|
||||
clearTimeout(this.autoHideTimeout);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Popover } from '../../../base/popover';
|
|||
import { connect } from '../../../base/redux';
|
||||
import {
|
||||
getVirtualScreenshareParticipantTrack,
|
||||
getSourceNameByParticipantId,
|
||||
getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
||||
import {
|
||||
isParticipantConnectionStatusInactive,
|
||||
|
@ -126,6 +127,16 @@ type Props = AbstractProps & {
|
|||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* The source name of the track.
|
||||
*/
|
||||
_sourceName: string,
|
||||
|
||||
/**
|
||||
* Whether source name signaling is enabled.
|
||||
*/
|
||||
_sourceNameSignalingEnabled: boolean
|
||||
};
|
||||
|
||||
type State = AbstractState & {
|
||||
|
@ -394,7 +405,9 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
|||
_popoverDisabled: state['features/base/config'].connectionIndicators?.disableDetails,
|
||||
_videoTrack: firstVideoTrack,
|
||||
_isConnectionStatusInactive,
|
||||
_isConnectionStatusInterrupted
|
||||
_isConnectionStatusInterrupted,
|
||||
_sourceName: getSourceNameByParticipantId(state, participantId),
|
||||
_sourceNameSignalingEnabled: sourceNameSignalingEnabled
|
||||
};
|
||||
}
|
||||
export default translate(connect(_mapStateToProps)(
|
||||
|
|
|
@ -8,7 +8,7 @@ import { translate } from '../../../base/i18n';
|
|||
import { MEDIA_TYPE } from '../../../base/media';
|
||||
import { getLocalParticipant, getParticipantById } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
||||
import { getSourceNameByParticipantId, getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
||||
import { ConnectionStatsTable } from '../../../connection-stats';
|
||||
import { saveLogs } from '../../actions';
|
||||
import {
|
||||
|
@ -131,7 +131,17 @@ type Props = AbstractProps & {
|
|||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
t: Function,
|
||||
|
||||
/**
|
||||
* The source name of the track.
|
||||
*/
|
||||
_sourceName: string,
|
||||
|
||||
/**
|
||||
* Whether source name signaling is enabled.
|
||||
*/
|
||||
_sourceNameSignalingEnabled: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -216,6 +226,7 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<Props, Stat
|
|||
resolution = { resolution }
|
||||
serverRegion = { serverRegion }
|
||||
shouldShowMore = { this.state.showMoreStats }
|
||||
sourceNameSignalingEnabled = { this.props._sourceNameSignalingEnabled }
|
||||
transport = { transport }
|
||||
videoSsrc = { this.props._videoSsrc } />
|
||||
);
|
||||
|
@ -345,7 +356,9 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
|
|||
_isConnectionStatusInterrupted,
|
||||
_isVirtualScreenshareParticipant: sourceNameSignalingEnabled && participant?.isVirtualScreenshareParticipant,
|
||||
_isLocalVideo: participant?.local,
|
||||
_region: participant?.region
|
||||
_region: participant?.region,
|
||||
_sourceName: getSourceNameByParticipantId(state, participantId),
|
||||
_sourceNameSignalingEnabled: sourceNameSignalingEnabled
|
||||
};
|
||||
|
||||
if (conference) {
|
||||
|
|
|
@ -161,7 +161,12 @@ type Props = {
|
|||
/**
|
||||
* Statistics related to transports.
|
||||
*/
|
||||
transport: Array<Object>
|
||||
transport: Array<Object>,
|
||||
|
||||
/**
|
||||
* Whether source name signaling is enabled.
|
||||
*/
|
||||
sourceNameSignalingEnabled: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -450,7 +455,7 @@ class ConnectionStatsTable extends Component<Props> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderCodecs() {
|
||||
const { codec, t } = this.props;
|
||||
const { codec, t, sourceNameSignalingEnabled } = this.props;
|
||||
|
||||
if (!codec) {
|
||||
return;
|
||||
|
@ -458,13 +463,17 @@ class ConnectionStatsTable extends Component<Props> {
|
|||
|
||||
let codecString;
|
||||
|
||||
// Only report one codec, in case there are multiple for a user.
|
||||
Object.keys(codec || {})
|
||||
.forEach(ssrc => {
|
||||
const { audio, video } = codec[ssrc];
|
||||
if (sourceNameSignalingEnabled) {
|
||||
codecString = `${codec.audio}, ${codec.video}`;
|
||||
} else {
|
||||
// Only report one codec, in case there are multiple for a user.
|
||||
Object.keys(codec || {})
|
||||
.forEach(ssrc => {
|
||||
const { audio, video } = codec[ssrc];
|
||||
|
||||
codecString = `${audio}, ${video}`;
|
||||
});
|
||||
codecString = `${audio}, ${video}`;
|
||||
});
|
||||
}
|
||||
|
||||
if (!codecString) {
|
||||
codecString = 'N/A';
|
||||
|
@ -585,10 +594,17 @@ class ConnectionStatsTable extends Component<Props> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderFrameRate() {
|
||||
const { framerate, t } = this.props;
|
||||
const frameRateString = Object.keys(framerate || {})
|
||||
.map(ssrc => framerate[ssrc])
|
||||
.join(', ') || 'N/A';
|
||||
const { framerate, t, sourceNameSignalingEnabled } = this.props;
|
||||
|
||||
let frameRateString;
|
||||
|
||||
if (sourceNameSignalingEnabled) {
|
||||
frameRateString = framerate || 'N/A';
|
||||
} else {
|
||||
frameRateString = Object.keys(framerate || {})
|
||||
.map(ssrc => framerate[ssrc])
|
||||
.join(', ') || 'N/A';
|
||||
}
|
||||
|
||||
return (
|
||||
<tr>
|
||||
|
@ -650,14 +666,20 @@ class ConnectionStatsTable extends Component<Props> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderResolution() {
|
||||
const { resolution, maxEnabledResolution, t } = this.props;
|
||||
let resolutionString = Object.keys(resolution || {})
|
||||
.map(ssrc => {
|
||||
const { width, height } = resolution[ssrc];
|
||||
const { resolution, maxEnabledResolution, t, sourceNameSignalingEnabled } = this.props;
|
||||
let resolutionString;
|
||||
|
||||
return `${width}x${height}`;
|
||||
})
|
||||
.join(', ') || 'N/A';
|
||||
if (sourceNameSignalingEnabled) {
|
||||
resolutionString = resolution ? `${resolution.width}x${resolution.height}` : 'N/A';
|
||||
} else {
|
||||
resolutionString = Object.keys(resolution || {})
|
||||
.map(ssrc => {
|
||||
const { width, height } = resolution[ssrc];
|
||||
|
||||
return `${width}x${height}`;
|
||||
})
|
||||
.join(', ') || 'N/A';
|
||||
}
|
||||
|
||||
if (maxEnabledResolution && maxEnabledResolution < 720) {
|
||||
const maxEnabledResolutionTitle = t('connectionindicator.maxEnabledResolution');
|
||||
|
|
|
@ -7,6 +7,7 @@ import { withTheme } from 'react-native-paper';
|
|||
|
||||
import { Avatar } from '../../../base/avatar';
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
|
||||
import { BottomSheet, isDialogOpen, hideDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconArrowDownLarge, IconArrowUpLarge } from '../../../base/icons';
|
||||
|
@ -15,6 +16,7 @@ import { BaseIndicator } from '../../../base/react';
|
|||
import { connect } from '../../../base/redux';
|
||||
import { StyleType } from '../../../base/styles';
|
||||
import statsEmitter from '../../../connection-indicator/statsEmitter';
|
||||
import { getSourceNameByParticipantId } from '../../base/tracks';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
@ -64,7 +66,17 @@ export type Props = {
|
|||
/**
|
||||
* Theme used for styles.
|
||||
*/
|
||||
theme: Object
|
||||
theme: Object,
|
||||
|
||||
/**
|
||||
* The source name of the track.
|
||||
*/
|
||||
_sourceName: string,
|
||||
|
||||
/**
|
||||
* Whether source name signaling is enabled.
|
||||
*/
|
||||
_sourceNameSignalingEnabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -211,6 +223,11 @@ class ConnectionStatusComponent extends Component<Props, State> {
|
|||
componentDidMount() {
|
||||
statsEmitter.subscribeToClientStats(
|
||||
this.props.participantID, this._onStatsUpdated);
|
||||
|
||||
if (this.props._sourceNameSignalingEnabled) {
|
||||
statsEmitter.subscribeToClientStats(
|
||||
this.props._sourceName, this._onStatsUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -226,6 +243,15 @@ class ConnectionStatusComponent extends Component<Props, State> {
|
|||
statsEmitter.subscribeToClientStats(
|
||||
this.props.participantID, this._onStatsUpdated);
|
||||
}
|
||||
|
||||
if (this.props._sourceNameSignalingEnabled) {
|
||||
if (prevProps._sourceName !== this.props._sourceName) {
|
||||
statsEmitter.unsubscribeToClientStats(
|
||||
prevProps._sourceName, this._onStatsUpdated);
|
||||
statsEmitter.subscribeToClientStats(
|
||||
this.props._sourceName, this._onStatsUpdated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onStatsUpdated: Object => void;
|
||||
|
@ -280,18 +306,24 @@ class ConnectionStatusComponent extends Component<Props, State> {
|
|||
*/
|
||||
_extractResolutionString(stats) {
|
||||
const { framerate, resolution } = stats;
|
||||
let frameRateString, resolutionString;
|
||||
|
||||
const resolutionString = Object.keys(resolution || {})
|
||||
.map(ssrc => {
|
||||
const { width, height } = resolution[ssrc];
|
||||
if (this.props._sourceNameSignalingEnabled) {
|
||||
resolutionString = resolution ? `${resolution.width}x${resolution.height}` : null;
|
||||
frameRateString = framerate || null;
|
||||
} else {
|
||||
resolutionString = Object.keys(resolution || {})
|
||||
.map(ssrc => {
|
||||
const { width, height } = resolution[ssrc];
|
||||
|
||||
return `${width}x${height}`;
|
||||
})
|
||||
.join(', ') || null;
|
||||
return `${width}x${height}`;
|
||||
})
|
||||
.join(', ') || null;
|
||||
|
||||
const frameRateString = Object.keys(framerate || {})
|
||||
.map(ssrc => framerate[ssrc])
|
||||
.join(', ') || null;
|
||||
frameRateString = Object.keys(framerate || {})
|
||||
.map(ssrc => framerate[ssrc])
|
||||
.join(', ') || null;
|
||||
}
|
||||
|
||||
return resolutionString && frameRateString ? `${resolutionString}@${frameRateString}fps` : undefined;
|
||||
}
|
||||
|
@ -341,13 +373,20 @@ class ConnectionStatusComponent extends Component<Props, State> {
|
|||
|
||||
let codecString;
|
||||
|
||||
// Only report one codec, in case there are multiple for a user.
|
||||
Object.keys(codec || {})
|
||||
.forEach(ssrc => {
|
||||
const { audio, video } = codec[ssrc];
|
||||
if (this.props._sourceNameSignalingEnabled) {
|
||||
if (codec) {
|
||||
codecString = `${codec.audio}, ${codec.video}`;
|
||||
}
|
||||
} else {
|
||||
// Only report one codec, in case there are multiple for a user.
|
||||
Object.keys(codec || {})
|
||||
.forEach(ssrc => {
|
||||
const { audio, video } = codec[ssrc];
|
||||
|
||||
codecString = `${audio}, ${video}`;
|
||||
});
|
||||
}
|
||||
|
||||
codecString = `${audio}, ${video}`;
|
||||
});
|
||||
|
||||
return codecString;
|
||||
}
|
||||
|
@ -381,6 +420,11 @@ class ConnectionStatusComponent extends Component<Props, State> {
|
|||
statsEmitter.unsubscribeToClientStats(
|
||||
this.props.participantID, this._onStatsUpdated);
|
||||
|
||||
if (this.props._sourceNameSignalingEnabled) {
|
||||
statsEmitter.unsubscribeToClientStats(
|
||||
this.props._sourceName, this._onStatsUpdated);
|
||||
}
|
||||
|
||||
if (this.props._isOpen) {
|
||||
this.props.dispatch(hideDialog(ConnectionStatusComponent_));
|
||||
|
||||
|
@ -430,7 +474,9 @@ function _mapStateToProps(state, ownProps) {
|
|||
return {
|
||||
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
|
||||
_isOpen: isDialogOpen(state, ConnectionStatusComponent_),
|
||||
_participantDisplayName: getParticipantDisplayName(state, participantID)
|
||||
_participantDisplayName: getParticipantDisplayName(state, participantID),
|
||||
_sourceNameSignalingEnabled: getSourceNameSignalingFeatureFlag(state),
|
||||
_sourceName: getSourceNameByParticipantId(state, ownProps.participantId)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue