2020-07-15 15:22:00 +00:00
|
|
|
// @flow
|
|
|
|
|
2021-10-15 15:55:23 +00:00
|
|
|
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
|
|
|
|
2020-07-15 15:22:00 +00:00
|
|
|
import { getAmplitudeIdentity } from '../analytics';
|
2022-04-01 12:50:52 +00:00
|
|
|
import { CONFERENCE_UNIQUE_ID_SET, E2E_RTT_CHANGED, getConferenceOptions, getRoomName } from '../base/conference';
|
2022-04-08 12:24:58 +00:00
|
|
|
import { LIB_WILL_INIT } from '../base/lib-jitsi-meet/actionTypes';
|
2021-09-07 13:20:50 +00:00
|
|
|
import { DOMINANT_SPEAKER_CHANGED, getLocalParticipant } from '../base/participants';
|
2020-07-15 15:22:00 +00:00
|
|
|
import { MiddlewareRegistry } from '../base/redux';
|
2022-05-16 13:56:37 +00:00
|
|
|
import { TRACK_ADDED, TRACK_UPDATED } from '../base/tracks';
|
2022-04-06 09:10:31 +00:00
|
|
|
import { ADD_FACE_EXPRESSION } from '../face-landmarks/actionTypes';
|
2020-07-15 15:22:00 +00:00
|
|
|
|
|
|
|
import RTCStats from './RTCStats';
|
2021-09-07 13:20:50 +00:00
|
|
|
import { canSendRtcstatsData, isRtcstatsEnabled } from './functions';
|
2020-07-15 15:22:00 +00:00
|
|
|
import logger from './logger';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Middleware which intercepts lib-jitsi-meet initialization and conference join in order init the
|
|
|
|
* rtcstats-client.
|
|
|
|
*
|
|
|
|
* @param {Store} store - The redux store.
|
|
|
|
* @returns {Function}
|
|
|
|
*/
|
|
|
|
MiddlewareRegistry.register(store => next => action => {
|
|
|
|
const state = store.getState();
|
|
|
|
const config = state['features/base/config'];
|
|
|
|
const { analytics } = config;
|
|
|
|
|
|
|
|
switch (action.type) {
|
|
|
|
case LIB_WILL_INIT: {
|
2020-09-24 09:53:31 +00:00
|
|
|
if (isRtcstatsEnabled(state)) {
|
2020-07-15 15:22:00 +00:00
|
|
|
// RTCStats "proxies" WebRTC functions such as GUM and RTCPeerConnection by rewriting the global
|
|
|
|
// window functions. Because lib-jitsi-meet uses references to those functions that are taken on
|
|
|
|
// init, we need to add these proxies before it initializes, otherwise lib-jitsi-meet will use the
|
|
|
|
// original non proxy versions of these functions.
|
|
|
|
try {
|
2021-04-14 09:32:16 +00:00
|
|
|
// Default poll interval is 1000ms and standard stats will be used, if not provided in the config.
|
2020-07-15 15:22:00 +00:00
|
|
|
const pollInterval = analytics.rtcstatsPollInterval || 1000;
|
2021-04-14 09:32:16 +00:00
|
|
|
const useLegacy = analytics.rtcstatsUseLegacy || false;
|
|
|
|
|
2020-07-15 15:22:00 +00:00
|
|
|
|
|
|
|
// Initialize but don't connect to the rtcstats server wss, as it will start sending data for all
|
|
|
|
// media calls made even before the conference started.
|
|
|
|
RTCStats.init({
|
2021-04-14 09:32:16 +00:00
|
|
|
endpoint: analytics.rtcstatsEndpoint,
|
|
|
|
useLegacy,
|
|
|
|
pollInterval
|
2020-07-15 15:22:00 +00:00
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
logger.error('Failed to initialize RTCStats: ', error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2022-05-16 13:56:37 +00:00
|
|
|
case TRACK_ADDED: {
|
|
|
|
if (canSendRtcstatsData(state)) {
|
|
|
|
const jitsiTrack = action?.track?.jitsiTrack;
|
|
|
|
const { ssrc, videoType } = jitsiTrack || { };
|
|
|
|
|
|
|
|
// Remote tracks store their ssrc in the jitsiTrack object. Local tracks don't. See getSsrcByTrack.
|
|
|
|
if (videoType && ssrc && !jitsiTrack.isLocal()) {
|
|
|
|
RTCStats.sendVideoTypeData({
|
|
|
|
ssrc,
|
|
|
|
videoType
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case TRACK_UPDATED: {
|
|
|
|
if (canSendRtcstatsData(state)) {
|
|
|
|
const { videoType, jitsiTrack } = action?.track || { };
|
|
|
|
const { ssrc } = jitsiTrack || { };
|
|
|
|
|
|
|
|
// if the videoType of the remote track has changed we expect to find it in track.videoType. grep for
|
|
|
|
// trackVideoTypeChanged.
|
|
|
|
if (videoType && ssrc && !jitsiTrack.isLocal()) {
|
|
|
|
|
|
|
|
RTCStats.sendVideoTypeData({
|
|
|
|
ssrc,
|
|
|
|
videoType
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-02-03 10:28:39 +00:00
|
|
|
case CONFERENCE_UNIQUE_ID_SET: {
|
2021-09-07 13:20:50 +00:00
|
|
|
if (canSendRtcstatsData(state)) {
|
|
|
|
|
2020-07-15 15:22:00 +00:00
|
|
|
// Once the conference started connect to the rtcstats server and send data.
|
|
|
|
try {
|
|
|
|
RTCStats.connect();
|
|
|
|
|
|
|
|
const localParticipant = getLocalParticipant(state);
|
2021-09-07 13:20:50 +00:00
|
|
|
const options = getConferenceOptions(state);
|
|
|
|
|
2020-07-15 15:22:00 +00:00
|
|
|
|
2021-02-03 10:28:39 +00:00
|
|
|
// Unique identifier for a conference session, not to be confused with meeting name
|
|
|
|
// i.e. If all participants leave a meeting it will have a different value on the next join.
|
|
|
|
const { conference } = action;
|
|
|
|
const meetingUniqueId = conference && conference.getMeetingUniqueId();
|
|
|
|
|
2020-07-15 15:22:00 +00:00
|
|
|
// The current implementation of rtcstats-server is configured to send data to amplitude, thus
|
2021-03-16 15:59:33 +00:00
|
|
|
// we add identity specific information so we can correlate on the amplitude side. If amplitude is
|
2020-07-15 15:22:00 +00:00
|
|
|
// not configured an empty object will be sent.
|
|
|
|
// The current configuration of the conference is also sent as metadata to rtcstats server.
|
|
|
|
// This is done in order to facilitate queries based on different conference configurations.
|
|
|
|
// e.g. Find all RTCPeerConnections that connect to a specific shard or were created in a
|
|
|
|
// conference with a specific version.
|
2021-10-15 15:55:23 +00:00
|
|
|
// XXX(george): we also want to be able to correlate between rtcstats and callstats, so we're
|
|
|
|
// appending the callstats user name (if it exists) to the display name.
|
|
|
|
const displayName = options.statisticsId
|
|
|
|
|| options.statisticsDisplayName
|
|
|
|
|| jitsiLocalStorage.getItem('callStatsUserName');
|
|
|
|
|
2020-07-15 15:22:00 +00:00
|
|
|
RTCStats.sendIdentityData({
|
|
|
|
...getAmplitudeIdentity(),
|
2021-09-07 13:20:50 +00:00
|
|
|
...options,
|
|
|
|
endpointId: localParticipant?.id,
|
2021-04-14 09:32:16 +00:00
|
|
|
confName: getRoomName(state),
|
2021-10-15 15:55:23 +00:00
|
|
|
displayName,
|
2021-02-03 10:28:39 +00:00
|
|
|
meetingUniqueId
|
2020-07-15 15:22:00 +00:00
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
// If the connection failed do not impact jitsi-meet just silently fail.
|
|
|
|
logger.error('RTCStats connect failed with: ', error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2021-09-07 13:20:50 +00:00
|
|
|
case DOMINANT_SPEAKER_CHANGED: {
|
|
|
|
if (canSendRtcstatsData(state)) {
|
|
|
|
const { id, previousSpeakers } = action.participant;
|
|
|
|
|
|
|
|
RTCStats.sendDominantSpeakerData({ dominantSpeakerEndpoint: id,
|
|
|
|
previousSpeakers });
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2022-04-01 12:50:52 +00:00
|
|
|
case E2E_RTT_CHANGED: {
|
|
|
|
if (canSendRtcstatsData(state)) {
|
|
|
|
const { participant, rtt } = action.e2eRtt;
|
|
|
|
|
|
|
|
RTCStats.sendE2eRttData({
|
|
|
|
remoteEndpointId: participant.getId(),
|
|
|
|
rtt,
|
|
|
|
remoteRegion: participant.getProperty('region')
|
|
|
|
});
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2022-04-06 09:10:31 +00:00
|
|
|
case ADD_FACE_EXPRESSION: {
|
2021-11-26 10:24:28 +00:00
|
|
|
if (canSendRtcstatsData(state)) {
|
2022-04-11 09:26:31 +00:00
|
|
|
const { duration, faceExpression, timestamp } = action;
|
2021-11-26 10:24:28 +00:00
|
|
|
|
2022-04-06 09:10:31 +00:00
|
|
|
RTCStats.sendFaceExpressionData({
|
2021-11-26 10:24:28 +00:00
|
|
|
duration,
|
2022-05-17 11:57:18 +00:00
|
|
|
faceLandmarks: faceExpression,
|
2022-04-11 09:26:31 +00:00
|
|
|
timestamp
|
2021-11-26 10:24:28 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2020-07-15 15:22:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return next(action);
|
|
|
|
});
|