From cfc4efb0a1bba1c90522275445cca6ced2e7ea69 Mon Sep 17 00:00:00 2001 From: "titus.moldovan" Date: Fri, 10 Dec 2021 15:24:40 +0200 Subject: [PATCH 1/2] feat(rn) start listening for stats on the Thumbnail --- .../filmstrip/components/native/Thumbnail.js | 80 ++++++++++++++++++- .../participants-pane/actions.native.js | 8 +- .../native/MeetingParticipantItem.js | 4 +- .../native/ConnectionStatusButton.js | 8 +- .../native/ConnectionStatusComponent.js | 20 ++++- .../components/native/RemoteVideoMenu.js | 13 ++- 6 files changed, 120 insertions(+), 13 deletions(-) diff --git a/react/features/filmstrip/components/native/Thumbnail.js b/react/features/filmstrip/components/native/Thumbnail.js index ad13033d94a0..f8947af7218a 100644 --- a/react/features/filmstrip/components/native/Thumbnail.js +++ b/react/features/filmstrip/components/native/Thumbnail.js @@ -21,6 +21,7 @@ import { connect } from '../../../base/redux'; import { StyleType } from '../../../base/styles'; import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks'; import { ConnectionIndicator } from '../../../connection-indicator'; +import statsEmitter from '../../../connection-indicator/statsEmitter'; import { DisplayNameLabel } from '../../../display-name'; import { showConnectionStatus, @@ -144,10 +145,17 @@ type Props = { tileView?: boolean }; +/** + * The type of the React {@code Component} state of {@link Thumbnail}. + */ + type State = { + stats: Object + } + /** * React component for video thumbnail. */ -class Thumbnail extends PureComponent { +class Thumbnail extends PureComponent { /** * Creates new Thumbnail component. @@ -160,6 +168,48 @@ class Thumbnail extends PureComponent { this._onClick = this._onClick.bind(this); this._onThumbnailLongPress = this._onThumbnailLongPress.bind(this); + this._onStatsUpdated = this._onStatsUpdated.bind(this); + + this.state = { + stats: {} + }; + } + + /** + * Starts listening for stat updates. + * + * @inheritdoc + * returns {void} + */ + componentDidMount() { + statsEmitter.subscribeToClientStats( + this.props.participantID, this._onStatsUpdated); + } + + /** + * Updates which user's stats are being listened to. + * + * @inheritdoc + * returns {void} + */ + componentDidUpdate(prevProps: Props) { + if (prevProps.participantID !== this.props.participantID) { + prevProps.participantID && statsEmitter.unsubscribeToClientStats( + prevProps.participantID, this._onStatsUpdated); + this.props.participantID && statsEmitter.subscribeToClientStats( + this.props.participantID, this._onStatsUpdated); + } + } + + /** + * Stop listening for stat updates. + * + * @inheritdoc + * returns {void} + */ + componentWillUnmount() { + this.props.participantID && statsEmitter.unsubscribeToClientStats( + this.props.participantID, this._onStatsUpdated); } _onClick: () => void; @@ -188,6 +238,7 @@ class Thumbnail extends PureComponent { */ _onThumbnailLongPress() { const { _participantId, _local, _isFakeParticipant, _localVideoOwner, dispatch } = this.props; + const { stats } = this.state; if (_isFakeParticipant && _localVideoOwner) { dispatch(showSharedVideoMenu(_participantId)); @@ -195,13 +246,36 @@ class Thumbnail extends PureComponent { if (!_isFakeParticipant) { if (_local) { - dispatch(showConnectionStatus(_participantId)); + dispatch(showConnectionStatus({ + initialStats: stats, + participantId: _participantId + })); } else { - dispatch(showContextMenuDetails(_participantId)); + dispatch(showContextMenuDetails({ + initialStats: stats, + participantId: _participantId + })); } } } + _onStatsUpdated: Object => void; + + /** + * Callback invoked when new connection stats associated with the passed in + * user ID are available. Will update the component's display of current + * statistics. + * + * @param {Object} stats - Connection stats from the library. + * @private + * @returns {void} + */ + _onStatsUpdated(stats = {}) { + this.setState({ + stats + }); + } + /** * Renders the indicators for the thumbnail. * diff --git a/react/features/participants-pane/actions.native.js b/react/features/participants-pane/actions.native.js index 187217d6ad41..361170808313 100644 --- a/react/features/participants-pane/actions.native.js +++ b/react/features/participants-pane/actions.native.js @@ -29,8 +29,8 @@ export function showContextMenuReject(participant: Object) { * @param {string} participantID - The selected meeting participant id. * @returns {Function} */ -export function showConnectionStatus(participantID: string) { - return openDialog(ConnectionStatusComponent, { participantID }); +export function showConnectionStatus(options) { + return openDialog(ConnectionStatusComponent, options); } /** @@ -39,8 +39,8 @@ export function showConnectionStatus(participantID: string) { * @param {string} participantId - The ID of the selected meeting participant. * @returns {Function} */ -export function showContextMenuDetails(participantId: string) { - return openDialog(RemoteVideoMenu, { participantId }); +export function showContextMenuDetails(options) { + return openDialog(RemoteVideoMenu, options); } /** diff --git a/react/features/participants-pane/components/native/MeetingParticipantItem.js b/react/features/participants-pane/components/native/MeetingParticipantItem.js index d100b03ad359..7f36a70f681a 100644 --- a/react/features/participants-pane/components/native/MeetingParticipantItem.js +++ b/react/features/participants-pane/components/native/MeetingParticipantItem.js @@ -121,9 +121,9 @@ class MeetingParticipantItem extends PureComponent { dispatch(showSharedVideoMenu(_participantID)); } else if (!_isFakeParticipant) { if (_local) { - dispatch(showConnectionStatus(_participantID)); + dispatch(showConnectionStatus({ participantId: _participantID })); } else { - dispatch(showContextMenuDetails(_participantID)); + dispatch(showContextMenuDetails({ participantId: _participantID })); } } // else no-op } diff --git a/react/features/video-menu/components/native/ConnectionStatusButton.js b/react/features/video-menu/components/native/ConnectionStatusButton.js index 3614d57935c4..7b936fe22f51 100644 --- a/react/features/video-menu/components/native/ConnectionStatusButton.js +++ b/react/features/video-menu/components/native/ConnectionStatusButton.js @@ -15,6 +15,11 @@ export type Props = AbstractButtonProps & { */ dispatch: Function, + /** + * Connection stats passed from the previous component. + */ + initialStats: Object, + /** * The ID of the participant that this button is supposed to pin. */ @@ -40,9 +45,10 @@ class ConnectionStatusButton extends AbstractButton { * @returns {void} */ _handleClick() { - const { dispatch, participantID } = this.props; + const { dispatch, participantID, initialStats } = this.props; dispatch(openDialog(ConnectionStatusComponent, { + initialStats, participantID })); } diff --git a/react/features/video-menu/components/native/ConnectionStatusComponent.js b/react/features/video-menu/components/native/ConnectionStatusComponent.js index 494950912db9..660c95c9a9ed 100644 --- a/react/features/video-menu/components/native/ConnectionStatusComponent.js +++ b/react/features/video-menu/components/native/ConnectionStatusComponent.js @@ -36,6 +36,11 @@ export type Props = { */ dispatch: Function, + /** + * Connection stats passed from the previous component. + */ + initialStats: Object, + /** * The ID of the participant that this button is supposed to pin. */ @@ -114,6 +119,8 @@ class ConnectionStatusComponent extends Component { codecString: 'N/A', connectionString: 'N/A' }; + + this.state = this._buildState(this.props.initialStats); } /** @@ -228,6 +235,17 @@ class ConnectionStatusComponent extends Component { } } + /** + * Stop listening for stat updates. + * + * @inheritdoc + * returns {void} + */ + componentWillUnmount() { + statsEmitter.unsubscribeToClientStats( + this.props.participantID, this._onStatsUpdated); + } + _onStatsUpdated: Object => void; /** @@ -252,7 +270,7 @@ class ConnectionStatusComponent extends Component { * @private * @returns {State} */ - _buildState(stats) { + _buildState(stats = {}) { const { download: downloadBitrate, upload: uploadBitrate } = this._extractBitrate(stats) ?? {}; const { download: downloadPacketLost, upload: uploadPacketLost } = this._extractPacketLost(stats) ?? {}; diff --git a/react/features/video-menu/components/native/RemoteVideoMenu.js b/react/features/video-menu/components/native/RemoteVideoMenu.js index 08bc6be03ba0..09433b8b2a67 100644 --- a/react/features/video-menu/components/native/RemoteVideoMenu.js +++ b/react/features/video-menu/components/native/RemoteVideoMenu.js @@ -75,6 +75,11 @@ type Props = { */ _disableGrantModerator: Boolean, + /** + * Connection stats passed from the previous component. + */ + _initialStats: Object, + /** * True if the menu is currently open, false otherwise. */ @@ -130,6 +135,7 @@ class RemoteVideoMenu extends PureComponent { _disableKick, _disableRemoteMute, _disableGrantModerator, + _initialStats, _isParticipantAvailable, _rooms, _currentRoomId, @@ -157,7 +163,9 @@ class RemoteVideoMenu extends PureComponent { { !_disableGrantModerator && } - + {_rooms.length > 1 && <> @@ -229,7 +237,7 @@ class RemoteVideoMenu extends PureComponent { */ function _mapStateToProps(state, ownProps) { const kickOutEnabled = getFeatureFlag(state, KICK_OUT_ENABLED, true); - const { participantId } = ownProps; + const { initialStats, participantId } = ownProps; const { remoteVideoMenu = {}, disableRemoteMute } = state['features/base/config']; const isParticipantAvailable = getParticipantById(state, participantId); let { disableKick } = remoteVideoMenu; @@ -243,6 +251,7 @@ function _mapStateToProps(state, ownProps) { _currentRoomId, _disableKick: Boolean(disableKick), _disableRemoteMute: Boolean(disableRemoteMute), + _initialStats: initialStats, _isOpen: isDialogOpen(state, RemoteVideoMenu_), _isParticipantAvailable: Boolean(isParticipantAvailable), _participantDisplayName: getParticipantDisplayName(state, participantId), From 1900d872b2ad119ef1f40671b7fe43c442a37263 Mon Sep 17 00:00:00 2001 From: "titus.moldovan" Date: Tue, 18 Jan 2022 16:40:22 +0200 Subject: [PATCH 2/2] fixes lint --- react/features/participants-pane/actions.native.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/react/features/participants-pane/actions.native.js b/react/features/participants-pane/actions.native.js index 361170808313..fcf2416f5602 100644 --- a/react/features/participants-pane/actions.native.js +++ b/react/features/participants-pane/actions.native.js @@ -26,20 +26,24 @@ export function showContextMenuReject(participant: Object) { /** * Displays the connection status for the local meeting participant. * - * @param {string} participantID - The selected meeting participant id. + * @param {Object} options - Options for displaying the ConnectionStatus dialog. + * @param {boolean} options.participantId - The selected meeting participant id. + * @param {Object} options.initialStats - The initial rtcstats to be displayed. * @returns {Function} */ -export function showConnectionStatus(options) { +export function showConnectionStatus(options: Object) { return openDialog(ConnectionStatusComponent, options); } /** * Displays the context menu for the selected meeting participant. * - * @param {string} participantId - The ID of the selected meeting participant. + * @param {Object} options - Options for displaying the RemoteVideoMenu dialog. + * @param {boolean} options.participantId - The selected meeting participant id. + * @param {Object} options.initialStats - The initial rtcstats to be displayed. * @returns {Function} */ -export function showContextMenuDetails(options) { +export function showContextMenuDetails(options: Object) { return openDialog(RemoteVideoMenu, options); }