feat(multi-stream-support) Replace participant connection status logic with track streaming status (#10934)
This commit is contained in:
parent
fa65a54f50
commit
05dc018671
|
@ -9,10 +9,8 @@ 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 { i18next } from '../../../react/features/base/i18n';
|
import { i18next } from '../../../react/features/base/i18n';
|
||||||
import {
|
|
||||||
JitsiParticipantConnectionStatus
|
|
||||||
} from '../../../react/features/base/lib-jitsi-meet';
|
|
||||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media';
|
import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media';
|
||||||
import {
|
import {
|
||||||
getParticipantById,
|
getParticipantById,
|
||||||
|
@ -20,6 +18,14 @@ import {
|
||||||
} from '../../../react/features/base/participants';
|
} from '../../../react/features/base/participants';
|
||||||
import { getTrackByMediaTypeAndParticipant } from '../../../react/features/base/tracks';
|
import { getTrackByMediaTypeAndParticipant } from '../../../react/features/base/tracks';
|
||||||
import { CHAT_SIZE } from '../../../react/features/chat';
|
import { CHAT_SIZE } from '../../../react/features/chat';
|
||||||
|
import {
|
||||||
|
isParticipantConnectionStatusActive,
|
||||||
|
isParticipantConnectionStatusInactive,
|
||||||
|
isParticipantConnectionStatusInterrupted,
|
||||||
|
isTrackStreamingStatusActive,
|
||||||
|
isTrackStreamingStatusInactive,
|
||||||
|
isTrackStreamingStatusInterrupted
|
||||||
|
} from '../../../react/features/connection-indicator/functions';
|
||||||
import {
|
import {
|
||||||
updateKnownLargeVideoResolution
|
updateKnownLargeVideoResolution
|
||||||
} from '../../../react/features/large-video/actions';
|
} from '../../../react/features/large-video/actions';
|
||||||
|
@ -226,8 +232,20 @@ export default class LargeVideoManager {
|
||||||
const state = APP.store.getState();
|
const state = APP.store.getState();
|
||||||
const participant = getParticipantById(state, id);
|
const participant = getParticipantById(state, id);
|
||||||
const connectionStatus = participant?.connectionStatus;
|
const connectionStatus = participant?.connectionStatus;
|
||||||
const isVideoRenderable = !isVideoMuted
|
|
||||||
&& (APP.conference.isLocalId(id) || connectionStatus === JitsiParticipantConnectionStatus.ACTIVE);
|
let isVideoRenderable;
|
||||||
|
|
||||||
|
if (getSourceNameSignalingFeatureFlag(state)) {
|
||||||
|
const videoTrack = getTrackByMediaTypeAndParticipant(
|
||||||
|
state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
|
||||||
|
|
||||||
|
isVideoRenderable = !isVideoMuted
|
||||||
|
&& (APP.conference.isLocalId(id) || isTrackStreamingStatusActive(videoTrack));
|
||||||
|
} else {
|
||||||
|
isVideoRenderable = !isVideoMuted
|
||||||
|
&& (APP.conference.isLocalId(id) || isParticipantConnectionStatusActive(participant));
|
||||||
|
}
|
||||||
|
|
||||||
const isAudioOnly = APP.conference.isAudioOnly();
|
const isAudioOnly = APP.conference.isAudioOnly();
|
||||||
const showAvatar
|
const showAvatar
|
||||||
= isVideoContainer
|
= isVideoContainer
|
||||||
|
@ -278,8 +296,16 @@ export default class LargeVideoManager {
|
||||||
this.updateLargeVideoAudioLevel(0);
|
this.updateLargeVideoAudioLevel(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageKey
|
let messageKey;
|
||||||
= connectionStatus === JitsiParticipantConnectionStatus.INACTIVE ? 'connection.LOW_BANDWIDTH' : null;
|
|
||||||
|
if (getSourceNameSignalingFeatureFlag(state)) {
|
||||||
|
const videoTrack = getTrackByMediaTypeAndParticipant(
|
||||||
|
state['features/base/tracks'], MEDIA_TYPE.VIDEO, id);
|
||||||
|
|
||||||
|
messageKey = isTrackStreamingStatusInactive(videoTrack) ? 'connection.LOW_BANDWIDTH' : null;
|
||||||
|
} else {
|
||||||
|
messageKey = isParticipantConnectionStatusInactive(participant) ? 'connection.LOW_BANDWIDTH' : null;
|
||||||
|
}
|
||||||
|
|
||||||
// Do not show connection status message in the audio only mode,
|
// Do not show connection status message in the audio only mode,
|
||||||
// because it's based on the video playback status.
|
// because it's based on the video playback status.
|
||||||
|
@ -505,13 +531,22 @@ export default class LargeVideoManager {
|
||||||
showRemoteConnectionMessage(show) {
|
showRemoteConnectionMessage(show) {
|
||||||
if (typeof show !== 'boolean') {
|
if (typeof show !== 'boolean') {
|
||||||
const participant = getParticipantById(APP.store.getState(), this.id);
|
const participant = getParticipantById(APP.store.getState(), this.id);
|
||||||
const connStatus = participant?.connectionStatus;
|
const state = APP.store.getState();
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
if (getSourceNameSignalingFeatureFlag(state)) {
|
||||||
show = !APP.conference.isLocalId(this.id)
|
const videoTrack = getTrackByMediaTypeAndParticipant(
|
||||||
&& (connStatus === JitsiParticipantConnectionStatus.INTERRUPTED
|
state['features/base/tracks'], MEDIA_TYPE.VIDEO, this.id);
|
||||||
|| connStatus
|
|
||||||
=== JitsiParticipantConnectionStatus.INACTIVE);
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
show = !APP.conference.isLocalId(this.id)
|
||||||
|
&& (isTrackStreamingStatusInterrupted(videoTrack)
|
||||||
|
|| isTrackStreamingStatusInactive(videoTrack));
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
show = !APP.conference.isLocalId(this.id)
|
||||||
|
&& (isParticipantConnectionStatusInterrupted(participant)
|
||||||
|
|| isParticipantConnectionStatusInactive(participant));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ export const JitsiE2ePingEvents = JitsiMeetJS.events.e2eping;
|
||||||
export const JitsiMediaDevicesEvents = JitsiMeetJS.events.mediaDevices;
|
export const JitsiMediaDevicesEvents = JitsiMeetJS.events.mediaDevices;
|
||||||
export const JitsiParticipantConnectionStatus
|
export const JitsiParticipantConnectionStatus
|
||||||
= JitsiMeetJS.constants.participantConnectionStatus;
|
= JitsiMeetJS.constants.participantConnectionStatus;
|
||||||
|
export const JitsiTrackStreamingStatus = JitsiMeetJS.constants.trackStreamingStatus;
|
||||||
export const JitsiRecordingConstants = JitsiMeetJS.constants.recording;
|
export const JitsiRecordingConstants = JitsiMeetJS.constants.recording;
|
||||||
export const JitsiSIPVideoGWStatus = JitsiMeetJS.constants.sipVideoGW;
|
export const JitsiSIPVideoGWStatus = JitsiMeetJS.constants.sipVideoGW;
|
||||||
export const JitsiTrackErrors = JitsiMeetJS.errors.track;
|
export const JitsiTrackErrors = JitsiMeetJS.errors.track;
|
||||||
|
|
|
@ -553,6 +553,26 @@ export function trackVideoTypeChanged(track, videoType) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an action for when track streaming status changes.
|
||||||
|
*
|
||||||
|
* @param {(JitsiRemoteTrack)} track - JitsiTrack instance.
|
||||||
|
* @param {string} streamingStatus - The new streaming status of the track.
|
||||||
|
* @returns {{
|
||||||
|
* type: TRACK_UPDATED,
|
||||||
|
* track: Track
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function trackStreamingStatusChanged(track, streamingStatus) {
|
||||||
|
return {
|
||||||
|
type: TRACK_UPDATED,
|
||||||
|
track: {
|
||||||
|
jitsiTrack: track,
|
||||||
|
streamingStatus
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals passed tracks to be added.
|
* Signals passed tracks to be added.
|
||||||
*
|
*
|
||||||
|
|
|
@ -5,12 +5,19 @@ import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
|
import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
|
||||||
import { translate } from '../../../base/i18n';
|
import { translate } from '../../../base/i18n';
|
||||||
import { Icon, IconConnectionActive, IconConnectionInactive } from '../../../base/icons';
|
import { MEDIA_TYPE } from '../../../base/media';
|
||||||
import { JitsiParticipantConnectionStatus } from '../../../base/lib-jitsi-meet';
|
|
||||||
import { getLocalParticipant, getParticipantById } from '../../../base/participants';
|
import { getLocalParticipant, getParticipantById } from '../../../base/participants';
|
||||||
import { Popover } from '../../../base/popover';
|
import { Popover } from '../../../base/popover';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
|
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
||||||
|
import {
|
||||||
|
isParticipantConnectionStatusInactive,
|
||||||
|
isParticipantConnectionStatusInterrupted,
|
||||||
|
isTrackStreamingStatusInactive,
|
||||||
|
isTrackStreamingStatusInterrupted
|
||||||
|
} from '../../functions';
|
||||||
import AbstractConnectionIndicator, {
|
import AbstractConnectionIndicator, {
|
||||||
INDICATOR_DISPLAY_THRESHOLD,
|
INDICATOR_DISPLAY_THRESHOLD,
|
||||||
type Props as AbstractProps,
|
type Props as AbstractProps,
|
||||||
|
@ -18,6 +25,7 @@ import AbstractConnectionIndicator, {
|
||||||
} from '../AbstractConnectionIndicator';
|
} from '../AbstractConnectionIndicator';
|
||||||
|
|
||||||
import ConnectionIndicatorContent from './ConnectionIndicatorContent';
|
import ConnectionIndicatorContent from './ConnectionIndicatorContent';
|
||||||
|
import { ConnectionIndicatorIcon } from './ConnectionIndicatorIcon';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of display configurations for the connection indicator and its bars.
|
* An array of display configurations for the connection indicator and its bars.
|
||||||
|
@ -237,17 +245,22 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
_getConnectionColorClass() {
|
_getConnectionColorClass() {
|
||||||
const { _connectionStatus } = this.props;
|
// TODO We currently do not have logic to emit and handle stats changes for tracks.
|
||||||
const { percent } = this.state.stats;
|
const { percent } = this.state.stats;
|
||||||
const { INACTIVE, INTERRUPTED } = JitsiParticipantConnectionStatus;
|
|
||||||
|
|
||||||
if (_connectionStatus === INACTIVE) {
|
const {
|
||||||
if (this.props._connectionIndicatorInactiveDisabled) {
|
_isConnectionStatusInactive,
|
||||||
|
_isConnectionStatusInterrupted,
|
||||||
|
_connectionIndicatorInactiveDisabled
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (_isConnectionStatusInactive) {
|
||||||
|
if (_connectionIndicatorInactiveDisabled) {
|
||||||
return 'status-disabled';
|
return 'status-disabled';
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'status-other';
|
return 'status-other';
|
||||||
} else if (_connectionStatus === INTERRUPTED) {
|
} else if (_isConnectionStatusInterrupted) {
|
||||||
return 'status-lost';
|
return 'status-lost';
|
||||||
} else if (typeof percent === 'undefined') {
|
} else if (typeof percent === 'undefined') {
|
||||||
return 'status-high';
|
return 'status-high';
|
||||||
|
@ -279,12 +292,12 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
_getVisibilityClass() {
|
_getVisibilityClass() {
|
||||||
const { _connectionStatus, classes } = this.props;
|
const { _isConnectionStatusInactive, _isConnectionStatusInterrupted, classes } = this.props;
|
||||||
|
|
||||||
return this.state.showIndicator
|
return this.state.showIndicator
|
||||||
|| this.props.alwaysVisible
|
|| this.props.alwaysVisible
|
||||||
|| _connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED
|
|| _isConnectionStatusInterrupted
|
||||||
|| _connectionStatus === JitsiParticipantConnectionStatus.INACTIVE
|
|| _isConnectionStatusInactive
|
||||||
? '' : classes.hidden;
|
? '' : classes.hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,49 +313,6 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||||
this.setState({ popoverVisible: false });
|
this.setState({ popoverVisible: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a ReactElement for displaying an icon that represents the current
|
|
||||||
* connection quality.
|
|
||||||
*
|
|
||||||
* @returns {ReactElement}
|
|
||||||
*/
|
|
||||||
_renderIcon() {
|
|
||||||
const colorClass = this._getConnectionColorClass();
|
|
||||||
|
|
||||||
if (this.props._connectionStatus === JitsiParticipantConnectionStatus.INACTIVE) {
|
|
||||||
if (this.props._connectionIndicatorInactiveDisabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className = 'connection_ninja'>
|
|
||||||
<Icon
|
|
||||||
className = { clsx(this.props.classes.icon, this.props.classes.inactiveIcon, colorClass) }
|
|
||||||
size = { 24 }
|
|
||||||
src = { IconConnectionInactive } />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let emptyIconWrapperClassName = 'connection_empty';
|
|
||||||
|
|
||||||
if (this.props._connectionStatus
|
|
||||||
=== JitsiParticipantConnectionStatus.INTERRUPTED) {
|
|
||||||
|
|
||||||
// emptyIconWrapperClassName is used by the torture tests to
|
|
||||||
// identify lost connection status handling.
|
|
||||||
emptyIconWrapperClassName = 'connection_lost';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className = { emptyIconWrapperClassName }>
|
|
||||||
<Icon
|
|
||||||
className = { clsx(this.props.classes.icon, colorClass) }
|
|
||||||
size = { 12 }
|
|
||||||
src = { IconConnectionActive } />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onShowPopover: () => void;
|
_onShowPopover: () => void;
|
||||||
|
|
||||||
|
@ -363,10 +333,25 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
_renderIndicator() {
|
_renderIndicator() {
|
||||||
|
const {
|
||||||
|
_isConnectionStatusInactive,
|
||||||
|
_isConnectionStatusInterrupted,
|
||||||
|
_connectionIndicatorInactiveDisabled,
|
||||||
|
_videoTrack,
|
||||||
|
classes,
|
||||||
|
iconSize
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style = {{ fontSize: this.props.iconSize }}>
|
style = {{ fontSize: iconSize }}>
|
||||||
{this._renderIcon()}
|
<ConnectionIndicatorIcon
|
||||||
|
classes = { classes }
|
||||||
|
colorClass = { this._getConnectionColorClass() }
|
||||||
|
connectionIndicatorInactiveDisabled = { _connectionIndicatorInactiveDisabled }
|
||||||
|
isConnectionStatusInactive = { _isConnectionStatusInactive }
|
||||||
|
isConnectionStatusInterrupted = { _isConnectionStatusInterrupted }
|
||||||
|
track = { _videoTrack } />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -381,14 +366,27 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, State> {
|
||||||
*/
|
*/
|
||||||
export function _mapStateToProps(state: Object, ownProps: Props) {
|
export function _mapStateToProps(state: Object, ownProps: Props) {
|
||||||
const { participantId } = ownProps;
|
const { participantId } = ownProps;
|
||||||
const participant
|
const sourceNameSignalingEnabled = getSourceNameSignalingFeatureFlag(state);
|
||||||
= participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
|
const firstVideoTrack = getTrackByMediaTypeAndParticipant(
|
||||||
|
state['features/base/tracks'], MEDIA_TYPE.VIDEO, participantId);
|
||||||
|
|
||||||
|
const participant = participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
|
||||||
|
|
||||||
|
const _isConnectionStatusInactive = sourceNameSignalingEnabled
|
||||||
|
? isTrackStreamingStatusInactive(firstVideoTrack)
|
||||||
|
: isParticipantConnectionStatusInactive(participant);
|
||||||
|
|
||||||
|
const _isConnectionStatusInterrupted = sourceNameSignalingEnabled
|
||||||
|
? isTrackStreamingStatusInterrupted(firstVideoTrack)
|
||||||
|
: isParticipantConnectionStatusInterrupted(participant);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_connectionIndicatorInactiveDisabled:
|
_connectionIndicatorInactiveDisabled:
|
||||||
Boolean(state['features/base/config'].connectionIndicators?.inactiveDisabled),
|
Boolean(state['features/base/config'].connectionIndicators?.inactiveDisabled),
|
||||||
_popoverDisabled: state['features/base/config'].connectionIndicators?.disableDetails,
|
_popoverDisabled: state['features/base/config'].connectionIndicators?.disableDetails,
|
||||||
_connectionStatus: participant?.connectionStatus
|
_videoTrack: firstVideoTrack,
|
||||||
|
_isConnectionStatusInactive,
|
||||||
|
_isConnectionStatusInterrupted
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export default translate(connect(_mapStateToProps)(
|
export default translate(connect(_mapStateToProps)(
|
||||||
|
|
|
@ -3,14 +3,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
|
import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
|
||||||
import { translate } from '../../../base/i18n';
|
import { translate } from '../../../base/i18n';
|
||||||
import { JitsiParticipantConnectionStatus } from '../../../base/lib-jitsi-meet';
|
|
||||||
import { MEDIA_TYPE } from '../../../base/media';
|
import { MEDIA_TYPE } from '../../../base/media';
|
||||||
import { getLocalParticipant, getParticipantById } from '../../../base/participants';
|
import { getLocalParticipant, getParticipantById } from '../../../base/participants';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
|
||||||
import { ConnectionStatsTable } from '../../../connection-stats';
|
import { ConnectionStatsTable } from '../../../connection-stats';
|
||||||
import { saveLogs } from '../../actions';
|
import { saveLogs } from '../../actions';
|
||||||
|
import {
|
||||||
|
isParticipantConnectionStatusInactive,
|
||||||
|
isParticipantConnectionStatusInterrupted,
|
||||||
|
isTrackStreamingStatusInactive,
|
||||||
|
isTrackStreamingStatusInterrupted
|
||||||
|
} from '../../functions';
|
||||||
import AbstractConnectionIndicator, {
|
import AbstractConnectionIndicator, {
|
||||||
INDICATOR_DISPLAY_THRESHOLD,
|
INDICATOR_DISPLAY_THRESHOLD,
|
||||||
type Props as AbstractProps,
|
type Props as AbstractProps,
|
||||||
|
@ -217,12 +223,14 @@ class ConnectionIndicatorContent extends AbstractConnectionIndicator<Props, Stat
|
||||||
_getConnectionStatusTip() {
|
_getConnectionStatusTip() {
|
||||||
let tipKey;
|
let tipKey;
|
||||||
|
|
||||||
switch (this.props._connectionStatus) {
|
const { _isConnectionStatusInactive, _isConnectionStatusInterrupted } = this.props;
|
||||||
case JitsiParticipantConnectionStatus.INTERRUPTED:
|
|
||||||
|
switch (true) {
|
||||||
|
case _isConnectionStatusInterrupted:
|
||||||
tipKey = 'connectionindicator.quality.lost';
|
tipKey = 'connectionindicator.quality.lost';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case JitsiParticipantConnectionStatus.INACTIVE:
|
case _isConnectionStatusInactive:
|
||||||
tipKey = 'connectionindicator.quality.inactive';
|
tipKey = 'connectionindicator.quality.inactive';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -310,17 +318,29 @@ 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 _isConnectionStatusInactive = sourceNameSignalingEnabled
|
||||||
|
? isTrackStreamingStatusInactive(firstVideoTrack)
|
||||||
|
: isParticipantConnectionStatusInactive(participant);
|
||||||
|
|
||||||
|
const _isConnectionStatusInterrupted = sourceNameSignalingEnabled
|
||||||
|
? isTrackStreamingStatusInterrupted(firstVideoTrack)
|
||||||
|
: isParticipantConnectionStatusInterrupted(participant);
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
_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,
|
||||||
_isLocalVideo: participant?.local,
|
_isLocalVideo: participant?.local,
|
||||||
_region: participant?.region
|
_region: participant?.region,
|
||||||
|
_isConnectionStatusInactive,
|
||||||
|
_isConnectionStatusInterrupted
|
||||||
};
|
};
|
||||||
|
|
||||||
if (conference) {
|
if (conference) {
|
||||||
const firstVideoTrack = getTrackByMediaTypeAndParticipant(
|
|
||||||
state['features/base/tracks'], MEDIA_TYPE.VIDEO, participantId);
|
|
||||||
const firstAudioTrack = getTrackByMediaTypeAndParticipant(
|
const firstAudioTrack = getTrackByMediaTypeAndParticipant(
|
||||||
state['features/base/tracks'], MEDIA_TYPE.AUDIO, participantId);
|
state['features/base/tracks'], MEDIA_TYPE.AUDIO, participantId);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { getSourceNameSignalingFeatureFlag } from '../../../base/config';
|
||||||
|
import { Icon, IconConnectionActive, IconConnectionInactive } from '../../../base/icons';
|
||||||
|
import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet';
|
||||||
|
import { trackStreamingStatusChanged } from '../../../base/tracks';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object containing the CSS classes.
|
||||||
|
*/
|
||||||
|
classes: Object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A CSS class that interprets the current connection status as a color.
|
||||||
|
*/
|
||||||
|
colorClass: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable/enable inactive indicator.
|
||||||
|
*/
|
||||||
|
connectionIndicatorInactiveDisabled: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JitsiTrack instance.
|
||||||
|
*/
|
||||||
|
track: Object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the connection status is inactive.
|
||||||
|
*/
|
||||||
|
isConnectionStatusInactive: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the connection status is interrupted.
|
||||||
|
*/
|
||||||
|
isConnectionStatusInterrupted: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConnectionIndicatorIcon = ({
|
||||||
|
classes,
|
||||||
|
colorClass,
|
||||||
|
connectionIndicatorInactiveDisabled,
|
||||||
|
isConnectionStatusInactive,
|
||||||
|
isConnectionStatusInterrupted,
|
||||||
|
track
|
||||||
|
}: Props) => {
|
||||||
|
const sourceNameSignalingEnabled = useSelector(state => getSourceNameSignalingFeatureFlag(state));
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const sourceName = track?.jitsiTrack?.getSourceName?.();
|
||||||
|
|
||||||
|
const handleTrackStreamingStatusChanged = streamingStatus => {
|
||||||
|
dispatch(trackStreamingStatusChanged(track.jitsiTrack, streamingStatus));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (track && !track.local && sourceNameSignalingEnabled) {
|
||||||
|
track.jitsiTrack.on(JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED, handleTrackStreamingStatusChanged);
|
||||||
|
|
||||||
|
dispatch(trackStreamingStatusChanged(track.jitsiTrack, track.jitsiTrack.getTrackStreamingStatus?.()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (track && !track.local && sourceNameSignalingEnabled) {
|
||||||
|
track.jitsiTrack.off(
|
||||||
|
JitsiTrackEvents.TRACK_STREAMING_STATUS_CHANGED,
|
||||||
|
handleTrackStreamingStatusChanged
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch(trackStreamingStatusChanged(track.jitsiTrack, track.jitsiTrack.getTrackStreamingStatus?.()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [ sourceName ]);
|
||||||
|
|
||||||
|
if (isConnectionStatusInactive) {
|
||||||
|
if (connectionIndicatorInactiveDisabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className = 'connection_ninja'>
|
||||||
|
<Icon
|
||||||
|
className = { clsx(classes.icon, classes.inactiveIcon, colorClass) }
|
||||||
|
size = { 24 }
|
||||||
|
src = { IconConnectionInactive } />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let emptyIconWrapperClassName = 'connection_empty';
|
||||||
|
|
||||||
|
if (isConnectionStatusInterrupted) {
|
||||||
|
// emptyIconWrapperClassName is used by the torture tests to identify lost connection status handling.
|
||||||
|
emptyIconWrapperClassName = 'connection_lost';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className = { emptyIconWrapperClassName }>
|
||||||
|
<Icon
|
||||||
|
className = { clsx(classes.icon, colorClass) }
|
||||||
|
size = { 12 }
|
||||||
|
src = { IconConnectionActive } />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { JitsiParticipantConnectionStatus, JitsiTrackStreamingStatus } from '../base/lib-jitsi-meet';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the passed track's streaming status is active.
|
||||||
|
*
|
||||||
|
* @param {Object} videoTrack - Track reference.
|
||||||
|
* @returns {boolean} - Is streaming status active.
|
||||||
|
*/
|
||||||
|
export function isTrackStreamingStatusActive(videoTrack) {
|
||||||
|
const streamingStatus = videoTrack?.streamingStatus;
|
||||||
|
|
||||||
|
return streamingStatus === JitsiTrackStreamingStatus.ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the passed track's streaming status is inactive.
|
||||||
|
*
|
||||||
|
* @param {Object} videoTrack - Track reference.
|
||||||
|
* @returns {boolean} - Is streaming status inactive.
|
||||||
|
*/
|
||||||
|
export function isTrackStreamingStatusInactive(videoTrack) {
|
||||||
|
const streamingStatus = videoTrack?.streamingStatus;
|
||||||
|
|
||||||
|
return streamingStatus === JitsiTrackStreamingStatus.INACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the passed track's streaming status is interrupted.
|
||||||
|
*
|
||||||
|
* @param {Object} videoTrack - Track reference.
|
||||||
|
* @returns {boolean} - Is streaming status interrupted.
|
||||||
|
*/
|
||||||
|
export function isTrackStreamingStatusInterrupted(videoTrack) {
|
||||||
|
const streamingStatus = videoTrack?.streamingStatus;
|
||||||
|
|
||||||
|
return streamingStatus === JitsiTrackStreamingStatus.INTERRUPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the passed participant's connecton status is active.
|
||||||
|
*
|
||||||
|
* @param {Object} participant - Participant reference.
|
||||||
|
* @returns {boolean} - Is connection status active.
|
||||||
|
*/
|
||||||
|
export function isParticipantConnectionStatusActive(participant) {
|
||||||
|
const connectionStatus = participant?.connectionStatus;
|
||||||
|
|
||||||
|
return connectionStatus === JitsiParticipantConnectionStatus.ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the passed participant's connecton status is inactive.
|
||||||
|
*
|
||||||
|
* @param {Object} participant - Participant reference.
|
||||||
|
* @returns {boolean} - Is connection status inactive.
|
||||||
|
*/
|
||||||
|
export function isParticipantConnectionStatusInactive(participant) {
|
||||||
|
const connectionStatus = participant?.connectionStatus;
|
||||||
|
|
||||||
|
return connectionStatus === JitsiParticipantConnectionStatus.INACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the passed participant's connecton status is interrupted.
|
||||||
|
*
|
||||||
|
* @param {Object} participant - Participant reference.
|
||||||
|
* @returns {boolean} - Is connection status interrupted.
|
||||||
|
*/
|
||||||
|
export function isParticipantConnectionStatusInterrupted(participant) {
|
||||||
|
const connectionStatus = participant?.connectionStatus;
|
||||||
|
|
||||||
|
return connectionStatus === JitsiParticipantConnectionStatus.INTERRUPTED;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { JitsiParticipantConnectionStatus } from '../base/lib-jitsi-meet';
|
import { getSourceNameSignalingFeatureFlag } from '../base/config';
|
||||||
import { MEDIA_TYPE } from '../base/media';
|
import { MEDIA_TYPE } from '../base/media';
|
||||||
import {
|
import {
|
||||||
getLocalParticipant,
|
getLocalParticipant,
|
||||||
|
@ -15,6 +15,7 @@ import {
|
||||||
isLocalTrackMuted,
|
isLocalTrackMuted,
|
||||||
isRemoteTrackMuted
|
isRemoteTrackMuted
|
||||||
} from '../base/tracks/functions';
|
} from '../base/tracks/functions';
|
||||||
|
import { isTrackStreamingStatusActive, isParticipantConnectionStatusActive } from '../connection-indicator/functions';
|
||||||
import { LAYOUTS } from '../video-layout';
|
import { LAYOUTS } from '../video-layout';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -105,7 +106,7 @@ export function isVideoPlayable(stateful: Object | Function, id: String) {
|
||||||
const tracks = state['features/base/tracks'];
|
const tracks = state['features/base/tracks'];
|
||||||
const participant = id ? getParticipantById(state, id) : getLocalParticipant(state);
|
const participant = id ? getParticipantById(state, id) : getLocalParticipant(state);
|
||||||
const isLocal = participant?.local ?? true;
|
const isLocal = participant?.local ?? true;
|
||||||
const { connectionStatus } = participant || {};
|
|
||||||
const videoTrack
|
const videoTrack
|
||||||
= isLocal ? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
|
= isLocal ? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
|
||||||
const isAudioOnly = Boolean(state['features/base/audio-only'].enabled);
|
const isAudioOnly = Boolean(state['features/base/audio-only'].enabled);
|
||||||
|
@ -118,8 +119,13 @@ export function isVideoPlayable(stateful: Object | Function, id: String) {
|
||||||
} else if (!participant?.isFakeParticipant) { // remote participants excluding shared video
|
} else if (!participant?.isFakeParticipant) { // remote participants excluding shared video
|
||||||
const isVideoMuted = isRemoteTrackMuted(tracks, MEDIA_TYPE.VIDEO, id);
|
const isVideoMuted = isRemoteTrackMuted(tracks, MEDIA_TYPE.VIDEO, id);
|
||||||
|
|
||||||
isPlayable = Boolean(videoTrack) && !isVideoMuted && !isAudioOnly
|
if (getSourceNameSignalingFeatureFlag(state)) {
|
||||||
&& connectionStatus === JitsiParticipantConnectionStatus.ACTIVE;
|
isPlayable = Boolean(videoTrack) && !isVideoMuted && !isAudioOnly
|
||||||
|
&& isTrackStreamingStatusActive(videoTrack);
|
||||||
|
} else {
|
||||||
|
isPlayable = Boolean(videoTrack) && !isVideoMuted && !isAudioOnly
|
||||||
|
&& isParticipantConnectionStatusActive(participant);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return isPlayable;
|
return isPlayable;
|
||||||
|
|
Loading…
Reference in New Issue