UNPKG

@observertc/observer-js

Version:

Server Side NodeJS Library for processing ObserveRTC Samples

789 lines (656 loc) 29.8 kB
import { EventEmitter } from 'events'; import { ObservedPeerConnection } from './ObservedPeerConnection'; import { createLogger } from './common/logger'; // eslint-disable-next-line camelcase import { ClientEvent, ClientMetaData, ClientSample, PeerConnectionSample, ClientIssue, ExtensionStat } from './schema/ClientSample'; import * as MetaData from './schema/ClientMetaTypes'; import { ClientEventTypes } from './schema/ClientEventTypes'; import { ObservedCall } from './ObservedCall'; import { ClientMetaTypes } from './schema/ClientMetaTypes'; import { parseJsonAs } from './common/utils'; import { CalculatedScore } from './scores/CalculatedScore'; import { Detectors } from './detectors/Detectors'; const logger = createLogger('ObservedClient'); export type ObservedClientSettings<AppData extends Record<string, unknown> = Record<string, unknown>> = { clientId: string; appData?: AppData; }; export type ObservedClientEvents = { update: [sample: ClientSample, elapsedTimeInMs: number]; close: []; joined: []; issue: [ClientIssue]; metaData: [ClientMetaData]; rejoined: [timestamp: number]; left: []; usingturn: [boolean]; usermediaerror: [string]; extensionStats: [ExtensionStat]; clientEvent: [ClientEvent]; newpeerconnection: [ObservedPeerConnection]; }; export declare interface ObservedClient { on<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this; off<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this; once<U extends keyof ObservedClientEvents>(event: U, listener: (...args: ObservedClientEvents[U]) => void): this; emit<U extends keyof ObservedClientEvents>(event: U, ...args: ObservedClientEvents[U]): boolean; } export class ObservedClient<AppData extends Record<string, unknown> = Record<string, unknown>> extends EventEmitter { public readonly detectors: Detectors; public readonly clientId: string; public readonly observedPeerConnections = new Map<string, ObservedPeerConnection>(); public readonly calculatedScore: CalculatedScore = { weight: 1, value: undefined, }; public appData: AppData; public attachments?: Record<string, unknown>; public updated = Date.now(); public acceptedSamples = 0; public closed = false; public joinedAt?: number; public leftAt?: number; public closedAt?: number; public lastSampleTimestamp?: number; // the timestamp of the CLIENT_JOINED event public operationSystem?: MetaData.OperationSystem; public engine?: MetaData.Engine; public platform?: MetaData.Platform; public browser?: MetaData.Browser; public mediaConstraints: string[] = []; public usingTURN = false; public usingTCP = false; public availableOutgoingBitrate = 0; public availableIncomingBitrate = 0; public totalInboundPacketsLost = 0; public totalInboundPacketsReceived = 0; public totalOutboundPacketsSent = 0; public totalDataChannelBytesSent = 0; public totalDataChannelBytesReceived = 0; public totalDataChannelMessagesSent = 0; public totalDataChannelMessagesReceived = 0; public totalReceivedAudioBytes = 0; public totalReceivedVideoBytes = 0; public totalSentAudioBytes = 0; public totalSentVideoBytes = 0; public totalSentBytes = 0; public totalReceivedBytes = 0; public deltaReceivedAudioBytes = 0; public deltaReceivedVideoBytes = 0; public deltaSentAudioBytes = 0; public deltaSentVideoBytes = 0; public deltaDataChannelBytesSent = 0; public deltaDataChannelBytesReceived = 0; public deltaDataChannelMessagesSent = 0; public deltaDataChannelMessagesReceived = 0; public deltaInboundPacketsLost = 0; public deltaInboundPacketsReceived = 0; public deltaOutboundPacketsSent = 0; public deltaTransportSentBytes = 0; public deltaTransportReceivedBytes = 0; public deltaRttLt50Measurements = 0; public deltaRttLt150Measurements = 0; public deltaRttLt300Measurements = 0; public deltaRttGtOrEq300Measurements = 0; public currentMaxRttInMs?: number; public currentMinRttInMs?: number; public currentAvgRttInMs?: number; public sendingAudioBitrate = 0; public sendingVideoBitrate = 0; public receivingAudioBitrate = 0; public receivingVideoBitrate = 0; public numberOfInboundRtpStreams = 0; public numberOfInbundTracks = 0; public numberOfOutboundRtpStreams = 0; public numberOfOutboundTracks = 0; public numberOfDataChannels = 0; public totalRttLt50Measurements = 0; public totalRttLt150Measurements = 0; public totalRttLt300Measurements = 0; public totalRttGtOrEq300Measurements = 0; public deltaNumberOfIssues = 0; public totalScoreSum = 0; public numberOfScoreMeasurements = 0; public totalNumberOfIssues = 0; public readonly mediaDevices: MetaData.MediaDeviceInfo[] = []; public issues: ClientIssue[] = []; private _injections: Pick<ClientSample, 'clientEvents' | 'clientIssues' | 'extensionStats' | 'attachments' | 'clientMetaItems'> = {}; public constructor(settings: ObservedClientSettings<AppData>, public readonly call: ObservedCall) { super(); this.setMaxListeners(Infinity); this.clientId = settings.clientId; this.appData = settings.appData ?? {} as AppData; this.detectors = new Detectors(); } public get numberOfPeerConnections() { return this.observedPeerConnections.size; } public get score() { return this.calculatedScore.value; } public close() { if (this.closed) return; this.closed = true; this._injections.clientEvents?.forEach((clientEvent) => this._processClientEvent(clientEvent)); this._injections.clientIssues?.forEach((clientIssue) => this.addIssue(clientIssue)); this._injections.extensionStats?.forEach((extensionStat) => this.addExtensionStats(extensionStat)); this._injections.clientMetaItems?.forEach((clientMetaItem) => this.addMetadata(clientMetaItem)); Array.from(this.observedPeerConnections.values()).forEach((peerConnection) => peerConnection.close()); if (!this.leftAt) { this.leftAt = this.lastSampleTimestamp; if (this.leftAt) { this.emit('left'); } } this.closedAt = Date.now(); this.emit('close'); } public accept(sample: ClientSample): void { if (this.closed) throw new Error(`Client ${this.clientId} is closed`); const now = Date.now(); const elapsedInMs = now - this.updated; const elapsedInSeconds = elapsedInMs / 1000; let sumOfRtts = 0; let numberOfRttMeasurements = 0; ++this.acceptedSamples; this.availableIncomingBitrate = 0; this.availableOutgoingBitrate = 0; this.deltaDataChannelBytesReceived = 0; this.deltaDataChannelBytesSent = 0; this.deltaDataChannelMessagesReceived = 0; this.deltaDataChannelMessagesSent = 0; this.deltaInboundPacketsLost = 0; this.deltaInboundPacketsReceived = 0; this.deltaOutboundPacketsSent = 0; this.deltaReceivedAudioBytes = 0; this.deltaReceivedVideoBytes = 0; this.deltaSentAudioBytes = 0; this.deltaSentVideoBytes = 0; this.deltaTransportReceivedBytes = 0; this.deltaTransportSentBytes = 0; this.deltaRttLt50Measurements = 0; this.deltaRttLt150Measurements = 0; this.deltaRttLt300Measurements = 0; this.deltaRttGtOrEq300Measurements = 0; this.deltaNumberOfIssues = 0; this.numberOfDataChannels = 0; this.numberOfInboundRtpStreams = 0; this.numberOfInbundTracks = 0; this.numberOfOutboundRtpStreams = 0; this.numberOfOutboundTracks = 0; this.usingTURN = false; this.usingTCP = false; this.currentMinRttInMs = undefined; this.currentMaxRttInMs = undefined; this._mergeInjections(sample); const clientEventsPostBuffer: ClientEvent[] = []; for (const clientEvent of sample.clientEvents ?? []) { this._processClientEvent(clientEvent, clientEventsPostBuffer); this.call.observer.emit('client-event', this, clientEvent); } for (const metaData of sample.clientMetaItems ?? []) { this.addMetadata(metaData); } for (const issue of sample.clientIssues ?? []) { this.addIssue(issue); ++this.deltaNumberOfIssues; } for (const extensionStat of sample.extensionStats ?? []) { this.addExtensionStats(extensionStat); } for (const pcSample of sample.peerConnections ?? []) { const observedPeerConnection = this._updatePeerConnection(pcSample); if (!observedPeerConnection) continue; this.deltaDataChannelBytesReceived += observedPeerConnection.deltaDataChannelBytesReceived; this.deltaDataChannelBytesSent += observedPeerConnection.deltaDataChannelBytesSent; this.deltaDataChannelMessagesReceived += observedPeerConnection.deltaDataChannelMessagesReceived; this.deltaDataChannelMessagesSent += observedPeerConnection.deltaDataChannelMessagesSent; this.deltaInboundPacketsLost += observedPeerConnection.deltaInboundPacketsLost; this.deltaInboundPacketsReceived += observedPeerConnection.deltaInboundPacketsReceived; this.deltaOutboundPacketsSent += observedPeerConnection.deltaOutboundPacketsSent; this.deltaReceivedAudioBytes += observedPeerConnection.deltaReceivedAudioBytes; this.deltaReceivedVideoBytes += observedPeerConnection.deltaReceivedVideoBytes; this.deltaSentAudioBytes += observedPeerConnection.deltaSentAudioBytes; this.deltaSentVideoBytes += observedPeerConnection.deltaSentVideoBytes; this.deltaTransportReceivedBytes += observedPeerConnection.deltaTransportReceivedBytes; this.deltaTransportSentBytes += observedPeerConnection.deltaTransportSentBytes; this.availableIncomingBitrate += observedPeerConnection.availableIncomingBitrate; this.availableOutgoingBitrate += observedPeerConnection.availableOutgoingBitrate; this.numberOfDataChannels += observedPeerConnection.observedDataChannels.size; this.numberOfInbundTracks += observedPeerConnection.observedInboundTracks.size; this.numberOfOutboundRtpStreams += observedPeerConnection.observedOutboundRtps.size; this.numberOfOutboundTracks += observedPeerConnection.observedOutboundTracks.size; this.numberOfInboundRtpStreams += observedPeerConnection.observedInboundRtps.size; if (observedPeerConnection.usingTURN) { this.usingTURN = true; } if (observedPeerConnection.usingTCP) { this.usingTCP = true; } if (observedPeerConnection.currentRttInMs) { if (this.currentMinRttInMs === undefined || observedPeerConnection.currentRttInMs < this.currentMinRttInMs) { this.currentMinRttInMs = observedPeerConnection.currentRttInMs; } if (this.currentMaxRttInMs === undefined || observedPeerConnection.currentRttInMs > this.currentMaxRttInMs) { this.currentMaxRttInMs = observedPeerConnection.currentRttInMs; } if (observedPeerConnection.currentRttInMs < 50) { this.deltaRttLt50Measurements += 1; } else if (observedPeerConnection.currentRttInMs < 150) { this.deltaRttLt150Measurements += 1; } else if (observedPeerConnection.currentRttInMs < 300) { this.deltaRttLt300Measurements += 1; } else if (300 <= observedPeerConnection.currentRttInMs) { this.deltaRttGtOrEq300Measurements += 1; } sumOfRtts += observedPeerConnection.currentRttInMs; ++numberOfRttMeasurements; } } for (const clientEvent of clientEventsPostBuffer) { this._processClientEvent(clientEvent); } // emit new attachments? this.attachments = sample.attachments; this.totalDataChannelBytesReceived += this.deltaDataChannelBytesReceived; this.totalDataChannelBytesSent += this.deltaDataChannelBytesSent; this.totalDataChannelMessagesReceived += this.deltaDataChannelMessagesReceived; this.totalDataChannelMessagesSent += this.deltaDataChannelMessagesSent; this.totalInboundPacketsLost += this.deltaInboundPacketsLost; this.totalInboundPacketsReceived += this.deltaInboundPacketsReceived; this.totalOutboundPacketsSent += this.deltaOutboundPacketsSent; this.totalReceivedAudioBytes += this.deltaReceivedAudioBytes; this.totalReceivedVideoBytes += this.deltaReceivedVideoBytes; this.totalSentAudioBytes += this.deltaSentAudioBytes; this.totalSentVideoBytes += this.deltaSentVideoBytes; this.totalReceivedBytes += this.deltaTransportReceivedBytes; this.totalSentBytes += this.deltaTransportSentBytes; this.totalRttLt50Measurements += this.deltaRttLt50Measurements; this.totalRttLt150Measurements += this.deltaRttLt150Measurements; this.totalRttLt300Measurements += this.deltaRttLt300Measurements; this.totalRttGtOrEq300Measurements += this.deltaRttGtOrEq300Measurements; this.totalNumberOfIssues += this.deltaNumberOfIssues; this.receivingAudioBitrate = (this.deltaReceivedAudioBytes * 8) / (elapsedInSeconds); this.receivingVideoBitrate = (this.totalReceivedVideoBytes * 8) / (elapsedInSeconds); this.sendingAudioBitrate = (this.deltaSentAudioBytes * 8) / (elapsedInSeconds); this.sendingVideoBitrate = (this.deltaSentVideoBytes * 8) / (elapsedInSeconds); this.currentAvgRttInMs = 0 < numberOfRttMeasurements ? sumOfRtts / numberOfRttMeasurements : undefined; this.calculatedScore.value = sample.score; this.detectors.update(); this.lastSampleTimestamp = sample.timestamp; // emit update this.emit('update', sample, now - this.updated, ); this.updated = now; // if result changed after update if (this.calculatedScore.value) { this.totalScoreSum += this.calculatedScore.value; ++this.numberOfScoreMeasurements; } } private _processClientEvent(event: ClientEvent, postBuffer?: ClientEvent[]) { // eslint-disable-next-line no-console // console.warn('ClientEvent', event); switch (event.type) { case ClientEventTypes.CLIENT_JOINED: { if (event.timestamp) { if (!this.joinedAt) { this.joinedAt = event.timestamp; this.emit('joined'); } else if (this.joinedAt < event.timestamp) { this.emit('rejoined', event.timestamp); } else { this.joinedAt = event.timestamp; logger.warn(`Client ${this.clientId} joinedAt timestamp was updated to ${event.timestamp}. the joined event will not be emitted.`); } } logger.debug('Client %s joined at %o', this.clientId, event); break; } case ClientEventTypes.CLIENT_LEFT: { if (event.timestamp) { if (!this.leftAt) { this.leftAt = event.timestamp; this.emit('left'); } else { logger.warn(`Client ${this.clientId} leftAt timestamp was already set`); } } logger.debug('Client %s left at %o', this.clientId, event); break; } case ClientEventTypes.PEER_CONNECTION_OPENED: { const payload = parseJsonAs<Record<string, unknown>>(event.payload); if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string') { const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId); if (observedPeerConnection) { observedPeerConnection.openedAt = event.timestamp; } else if (postBuffer) { postBuffer.push(event); } else { logger.warn('Received PEER_CONNECTION_OPENED event without a corresponding observedPeerConnection: %o', event); } } break; } case ClientEventTypes.PEER_CONNECTION_CLOSED: { const payload = parseJsonAs<Record<string, unknown>>(event.payload); if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string') { const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId); if (observedPeerConnection) { observedPeerConnection.closedAt = event.timestamp; } else if (postBuffer) { postBuffer.push(event); } else { logger.warn('Received PEER_CONNECTION_CLOSED event without a corresponding observedPeerConnection: %o', event); } } break; } case ClientEventTypes.MEDIA_TRACK_ADDED: { const payload = parseJsonAs<Record<string, unknown>>(event.payload); if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.trackId && typeof payload.trackId === 'string') { const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId); const observedTrack = observedPeerConnection?.observedInboundTracks.get(payload.trackId) ?? observedPeerConnection?.observedOutboundTracks.get(payload.trackId); if (observedTrack) { observedTrack.addedAt = event.timestamp; } else if (postBuffer) { postBuffer.push(event); } else { logger.warn('Received MEDIA_TRACK_ADDED event without a corresponding observedPeerConnection or observedTrack: %o', event); } } break; } case ClientEventTypes.MEDIA_TRACK_REMOVED: { const payload = parseJsonAs<Record<string, unknown>>(event.payload); if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.trackId && typeof payload.trackId === 'string') { const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId); const observedTrack = observedPeerConnection?.observedInboundTracks.get(payload.trackId) ?? observedPeerConnection?.observedOutboundTracks.get(payload.trackId); if (observedTrack) { observedTrack.removedAt = event.timestamp; } else if (postBuffer) { postBuffer.push(event); } else { logger.warn('Received MEDIA_TRACK_REMOVED event without a corresponding observedPeerConnection or observedTrack: %o', event); } } break; } case ClientEventTypes.DATA_CHANNEL_OPEN: { const payload = parseJsonAs<Record<string, unknown>>(event.payload); if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.dataChannelId && typeof payload.dataChannelId === 'string') { const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId); const observedDataChannel = observedPeerConnection?.observedDataChannels.get(payload.dataChannelId); if (observedDataChannel) { observedDataChannel.addedAt = event.timestamp; } else if (postBuffer) { postBuffer.push(event); } else { logger.warn('Received DATA_CHANNEL_OPENED event without a corresponding observedPeerConnection or observedDataChannel: %o', event); } } break; } case ClientEventTypes.DATA_CHANNEL_CLOSED: { const payload = parseJsonAs<Record<string, unknown>>(event.payload); if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.dataChannelId && typeof payload.dataChannelId === 'string') { const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId); const observedDataChannel = observedPeerConnection?.observedDataChannels.get(payload.dataChannelId); if (observedDataChannel) { observedDataChannel.removedAt = event.timestamp; } else if (postBuffer) { postBuffer.push(event); } else { logger.warn('Received DATA_CHANNEL_CLOSE event without a corresponding observedPeerConnection or observedDataChannel: %o', event); } } break; } case ClientEventTypes.MEDIA_TRACK_MUTED: { const payload = parseJsonAs<Record<string, unknown>>(event.payload); if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.trackId && typeof payload.trackId === 'string') { const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId); const observedInboundTrack = observedPeerConnection?.observedInboundTracks.get(payload.trackId); const observedOutboundTrack = observedPeerConnection?.observedOutboundTracks.get(payload.trackId); if (observedPeerConnection) { if (observedInboundTrack) { observedInboundTrack.muted = true; observedPeerConnection?.emit('muted-inbound-track', observedInboundTrack); } else if (observedOutboundTrack) { observedOutboundTrack.muted = true; observedPeerConnection?.emit('muted-outbound-track', observedOutboundTrack); } } else if (postBuffer) { postBuffer.push(event); } else { logger.warn('Received MEDIA_TRACK_MUTED event without a corresponding observedPeerConnection or observedTrack: %o', event); } } break; } case ClientEventTypes.MEDIA_TRACK_UNMUTED: { const payload = parseJsonAs<Record<string, unknown>>(event.payload); if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.trackId && typeof payload.trackId === 'string') { const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId); const observedInboundTrack = observedPeerConnection?.observedInboundTracks.get(payload.trackId); const observedOutboundTrack = observedPeerConnection?.observedOutboundTracks.get(payload.trackId); if (observedPeerConnection) { if (observedInboundTrack) { observedInboundTrack.muted = false; observedPeerConnection?.emit('unmuted-inbound-track', observedInboundTrack); } else if (observedOutboundTrack) { observedOutboundTrack.muted = false; observedPeerConnection?.emit('unmuted-outbound-track', observedOutboundTrack); } } else if (postBuffer) { postBuffer.push(event); } else { logger.warn('Received MEDIA_TRACK_UNMUTED event without a corresponding observedPeerConnection or observedTrack: %o', event); } } break; } case ClientEventTypes.ICE_CONNECTION_STATE_CHANGED: { const payload = parseJsonAs<Record<string, unknown>>(event.payload); if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.iceConnectionState && typeof payload.iceConnectionState === 'string') { const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId); if (observedPeerConnection) { observedPeerConnection.iceConnectionState = payload.iceConnectionState; observedPeerConnection.emit('iceconnectionstatechange', { state: payload.iceConnectionState, }); } else if (postBuffer) { postBuffer.push(event); } else { logger.warn('Received ICE_CONNECTION_STATE_CHANGED event without a corresponding observedPeerConnection: %o', event); } } break; } case ClientEventTypes.ICE_GATHERING_STATE_CHANGED: { const payload = parseJsonAs<Record<string, unknown>>(event.payload); if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.iceGatheringState && typeof payload.iceGatheringState === 'string') { const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId); if (observedPeerConnection) { observedPeerConnection.iceGatheringState = payload.iceGatheringState; observedPeerConnection.emit('icegatheringstatechange', { state: payload.iceGatheringState, }); } else if (postBuffer) { postBuffer.push(event); } else { logger.warn('Received ICE_GATHERING_STATE_CHANGED event without a corresponding observedPeerConnection: %o', event); } } break; } case ClientEventTypes.PEER_CONNECTION_STATE_CHANGED: { const payload = parseJsonAs<Record<string, unknown>>(event.payload); if (payload?.peerConnectionId && typeof payload.peerConnectionId === 'string' && payload?.peerConnectionState && typeof payload.peerConnectionState === 'string') { const observedPeerConnection = this.observedPeerConnections.get(payload.peerConnectionId); if (observedPeerConnection) { observedPeerConnection.connectionState = payload.peerConnectionState; observedPeerConnection.emit('connectionstatechange', { state: payload.peerConnectionState, }); } else if (postBuffer) { postBuffer.push(event); } else { logger.warn('Received PEER_CONNECTION_STATE_CHANGED event without a corresponding observedPeerConnection: %o', event); } } break; } } this.emit('clientEvent', event); } public injectMetaData(metaData: ClientMetaData) { if (this.closed) return; if (!this._injections.clientMetaItems) this._injections.clientMetaItems = []; this._injections.clientMetaItems.push(metaData); } public injectEvent(event: ClientEvent) { if (this.closed) return; if (!this._injections.clientEvents) this._injections.clientEvents = []; this._injections.clientEvents.push(event); } public injectIssue(issue: ClientIssue) { if (this.closed) return; if (!this._injections.clientIssues) this._injections.clientIssues = []; this._injections.clientIssues.push(issue); } public injectExtensionStat(stat: ExtensionStat) { if (this.closed) return; if (!this._injections.extensionStats) this._injections.extensionStats = []; this._injections.extensionStats.push(stat); } public injectAttachment(key: string, value: unknown) { if (this.closed) return; if (!this._injections.attachments) this._injections.attachments = {}; this._injections.attachments[key] = value; } public addMetadata(metadata: ClientMetaData) { if (this.closed) return; switch (metadata.type) { case ClientMetaTypes.BROWSER: { this.browser = parseJsonAs(metadata.payload); break; } case ClientMetaTypes.ENGINE: { this.engine = parseJsonAs(metadata.payload); break; } case ClientMetaTypes.PLATFORM: { this.platform = parseJsonAs(metadata.payload); break; } case ClientMetaTypes.OPERATION_SYSTEM: { this.operationSystem = parseJsonAs(metadata.payload); break; } } this.call.observer.emit('client-metadata', this, metadata); } public addIssue(issue: ClientIssue) { if (this.closed) return; this.emit('issue', issue); this.call.observer.emit('client-issue', this, issue); } public addExtensionStats(stats: ExtensionStat) { this.call.observer.emit('client-extension-stats', this, stats); this.emit('extensionStats', stats); } private _updatePeerConnection(sample: PeerConnectionSample): ObservedPeerConnection | undefined { let observedPeerConnection = this.observedPeerConnections.get(sample.peerConnectionId); if (!observedPeerConnection) { if (!sample.peerConnectionId) { return (logger.warn( `ObservedClient received an invalid PeerConnectionSample (missing peerConnectionId field). ClientId: ${this.clientId}, CallId: ${this.call.callId}`, sample ), void 0); } observedPeerConnection = new ObservedPeerConnection(sample.peerConnectionId, this); observedPeerConnection.once('close', () => { this.observedPeerConnections.delete(sample.peerConnectionId); }); this.observedPeerConnections.set(sample.peerConnectionId, observedPeerConnection); this.emit('newpeerconnection', observedPeerConnection); } observedPeerConnection.accept(sample); return observedPeerConnection; } private _mergeInjections(sample: ClientSample): ClientSample { if (this.closed) return sample; if (this._injections.clientEvents) { if (!sample.clientEvents) sample.clientEvents = []; sample.clientEvents.push(...this._injections.clientEvents); this._injections.clientEvents = undefined; } if (this._injections.clientIssues) { if (!sample.clientIssues) sample.clientIssues = []; sample.clientIssues.push(...this._injections.clientIssues); this._injections.clientIssues = undefined; } if (this._injections.extensionStats) { if (!sample.extensionStats) sample.extensionStats = []; sample.extensionStats.push(...this._injections.extensionStats); this._injections.extensionStats = undefined; } if (this._injections.attachments) { if (!sample.attachments) sample.attachments = {}; Object.assign(sample.attachments, this._injections.attachments); this._injections.attachments = undefined; } if (this._injections.clientMetaItems) { if (!sample.clientMetaItems) sample.clientMetaItems = []; sample.clientMetaItems.push(...this._injections.clientMetaItems); this._injections.clientMetaItems = undefined; } return sample; } // public resetSummaryMetrics() { // this.totalDataChannelBytesReceived = 0; // this.totalDataChannelBytesSent = 0; // this.totalDataChannelMessagesReceived = 0; // this.totalDataChannelMessagesSent = 0; // this.totalInboundPacketsLost = 0; // this.totalInboundPacketsReceived = 0; // this.totalOutboundPacketsSent = 0; // this.totalReceivedAudioBytes = 0; // this.totalReceivedVideoBytes = 0; // this.totalSentAudioBytes = 0; // this.totalSentVideoBytes = 0; // this.totalSentBytes = 0; // this.totalReceivedBytes = 0; // this.totalNumberOfIssues = 0; // this.totalScoreSum = 0; // this.numberOfScoreMeasurements = 0; // } // public createSummary(): ObservedClientSummary { // return { // totalRttLt50Measurements: this.totalRttLt50Measurements, // totalRttLt150Measurements: this.totalRttLt150Measurements, // totalRttLt300Measurements: this.totalRttLt300Measurements, // totalRttGtOrEq300Measurements: this.totalRttGtOrEq300Measurements, // totalDataChannelBytesReceived: this.totalDataChannelBytesReceived, // totalDataChannelBytesSent: this.totalDataChannelBytesSent, // totalDataChannelMessagesReceived: this.totalDataChannelMessagesReceived, // totalDataChannelMessagesSent: this.totalDataChannelMessagesSent, // totalInboundPacketsLost: this.totalInboundPacketsLost, // totalInboundPacketsReceived: this.totalInboundPacketsReceived, // totalOutboundPacketsSent: this.totalOutboundPacketsSent, // totalReceivedAudioBytes: this.totalReceivedAudioBytes, // totalReceivedVideoBytes: this.totalReceivedVideoBytes, // totalSentAudioBytes: this.totalSentAudioBytes, // totalSentVideoBytes: this.totalSentVideoBytes, // totalSentBytes: this.totalSentBytes, // totalReceivedBytes: this.totalReceivedBytes, // numberOfIssues: this.totalNumberOfIssues, // totalScoreSum: this.totalScoreSum, // numberOfScoreMeasurements: this.numberOfScoreMeasurements, // }; // } }