Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rn) start listening for stats on the Thumbnail #10583

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 77 additions & 3 deletions react/features/filmstrip/components/native/Thumbnail.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Props> {
class Thumbnail extends PureComponent<Props, State> {

/**
* Creates new Thumbnail component.
Expand All @@ -160,6 +168,48 @@ class Thumbnail extends PureComponent<Props> {

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) {
tmoldovan8x8 marked this conversation as resolved.
Show resolved Hide resolved
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;
Expand Down Expand Up @@ -188,20 +238,44 @@ class Thumbnail extends PureComponent<Props> {
*/
_onThumbnailLongPress() {
const { _participantId, _local, _isFakeParticipant, _localVideoOwner, dispatch } = this.props;
const { stats } = this.state;

if (_isFakeParticipant && _localVideoOwner) {
dispatch(showSharedVideoMenu(_participantId));
}

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.
*
Expand Down
16 changes: 10 additions & 6 deletions react/features/participants-pane/actions.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,25 @@ 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(participantID: string) {
return openDialog(ConnectionStatusComponent, { participantID });
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(participantId: string) {
return openDialog(RemoteVideoMenu, { participantId });
export function showContextMenuDetails(options: Object) {
return openDialog(RemoteVideoMenu, options);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ class MeetingParticipantItem extends PureComponent<Props> {
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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -40,9 +45,10 @@ class ConnectionStatusButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { dispatch, participantID } = this.props;
const { dispatch, participantID, initialStats } = this.props;

dispatch(openDialog(ConnectionStatusComponent, {
initialStats,
participantID
}));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -114,6 +119,8 @@ class ConnectionStatusComponent extends Component<Props, State> {
codecString: 'N/A',
connectionString: 'N/A'
};

this.state = this._buildState(this.props.initialStats);
}

/**
Expand Down Expand Up @@ -228,6 +235,17 @@ class ConnectionStatusComponent extends Component<Props, State> {
}
}

/**
* Stop listening for stat updates.
*
* @inheritdoc
* returns {void}
*/
componentWillUnmount() {
tmoldovan8x8 marked this conversation as resolved.
Show resolved Hide resolved
statsEmitter.unsubscribeToClientStats(
this.props.participantID, this._onStatsUpdated);
}

_onStatsUpdated: Object => void;

/**
Expand All @@ -252,7 +270,7 @@ class ConnectionStatusComponent extends Component<Props, State> {
* @private
* @returns {State}
*/
_buildState(stats) {
_buildState(stats = {}) {
const { download: downloadBitrate, upload: uploadBitrate } = this._extractBitrate(stats) ?? {};

const { download: downloadPacketLost, upload: uploadPacketLost } = this._extractPacketLost(stats) ?? {};
Expand Down
13 changes: 11 additions & 2 deletions react/features/video-menu/components/native/RemoteVideoMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -130,6 +135,7 @@ class RemoteVideoMenu extends PureComponent<Props> {
_disableKick,
_disableRemoteMute,
_disableGrantModerator,
_initialStats,
_isParticipantAvailable,
_rooms,
_currentRoomId,
Expand Down Expand Up @@ -157,7 +163,9 @@ class RemoteVideoMenu extends PureComponent<Props> {
{ !_disableGrantModerator && <GrantModeratorButton { ...buttonProps } /> }
<PinButton { ...buttonProps } />
<PrivateMessageButton { ...buttonProps } />
<ConnectionStatusButton { ...buttonProps } />
<ConnectionStatusButton
initialStats = { _initialStats }
{ ...buttonProps } />
{_rooms.length > 1 && <>
<Divider style = { styles.divider } />
<View style = { styles.contextMenuItem }>
Expand Down Expand Up @@ -229,7 +237,7 @@ class RemoteVideoMenu extends PureComponent<Props> {
*/
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;
Expand All @@ -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),
Expand Down