@livedigital/client 3.34.0-createCustomMediaTracks.1 → 3.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/events.d.ts +0 -1
- package/dist/constants/events.ts +0 -1
- package/dist/engine/Peer.d.ts +1 -2
- package/dist/engine/PeerConsumer.d.ts +0 -1
- package/dist/engine/PeerProducer.d.ts +0 -1
- package/dist/engine/WIDHandler.d.ts +21 -0
- package/dist/engine/WebRTCStats/TransportsStatsProvider.d.ts +5 -1
- package/dist/engine/analyticsApiClient/MetricsApi.d.ts +8 -0
- package/dist/engine/analyticsApiClient/index.d.ts +3 -0
- package/dist/engine/analyticsApiClient/types.d.ts +10 -3
- package/dist/engine/handlers/MediaSoupEventHandler.d.ts +0 -1
- package/dist/engine/index.d.ts +4 -7
- package/dist/engine/media/index.d.ts +0 -1
- package/dist/engine/media/tracks/PeerTrack.d.ts +0 -1
- package/dist/engine/network/DataChannelsManager.d.ts +0 -1
- package/dist/index.d.ts +2 -5
- package/dist/index.es.js +3 -3
- package/dist/index.js +3 -3
- package/dist/inversify.tokens.d.ts +4 -1
- package/dist/types/common.d.ts +5 -10
- package/dist/types/engine.d.ts +2 -6
- package/dist/types/network.d.ts +2 -1
- package/package.json +1 -1
- package/src/constants/events.ts +0 -1
- package/src/engine/Peer.ts +1 -10
- package/src/engine/PeerConsumer.ts +0 -3
- package/src/engine/PeerProducer.ts +0 -2
- package/src/engine/WIDHandler.ts +142 -0
- package/src/engine/WebRTCStats/TransportsStatsProvider.ts +51 -7
- package/src/engine/analyticsApiClient/MetricsApi.ts +30 -0
- package/src/engine/analyticsApiClient/index.ts +10 -0
- package/src/engine/analyticsApiClient/types.ts +12 -4
- package/src/engine/handlers/MediaSoupEventHandler.ts +0 -22
- package/src/engine/index.ts +12 -94
- package/src/engine/media/index.ts +0 -5
- package/src/engine/media/tracks/PeerTrack.ts +0 -4
- package/src/engine/network/DataChannelsManager.ts +0 -38
- package/src/index.ts +1 -14
- package/src/inversify.config.ts +42 -4
- package/src/inversify.tokens.ts +3 -0
- package/src/types/common.ts +6 -12
- package/src/types/engine.ts +2 -7
- package/src/types/network.ts +2 -1
|
@@ -30,5 +30,8 @@ export declare enum TOKEN {
|
|
|
30
30
|
IntegrationsService = "IntegrationsService",
|
|
31
31
|
ProcessorsCache = "ProcessorsCache",
|
|
32
32
|
StatsHandler = "StatsHandler",
|
|
33
|
-
DataChannelsManager = "DataChannelsManager"
|
|
33
|
+
DataChannelsManager = "DataChannelsManager",
|
|
34
|
+
WIDHandler = "WIDHandler",
|
|
35
|
+
AnalyticsApiClient = "AnalyticsApiClient",
|
|
36
|
+
TransportsStatsProvider = "TransportsStatsProvider"
|
|
34
37
|
}
|
package/dist/types/common.d.ts
CHANGED
|
@@ -62,11 +62,16 @@ export declare type AvailableMediaDevices = {
|
|
|
62
62
|
video: MediaDeviceInfo[];
|
|
63
63
|
audio: MediaDeviceInfo[];
|
|
64
64
|
};
|
|
65
|
+
export declare enum DatacenterType {
|
|
66
|
+
P2P = "p2p",
|
|
67
|
+
Common = "common"
|
|
68
|
+
}
|
|
65
69
|
export declare type JoinChannelParams = {
|
|
66
70
|
channelId: string;
|
|
67
71
|
token: string;
|
|
68
72
|
role: Role;
|
|
69
73
|
appData?: Record<string, unknown>;
|
|
74
|
+
isP2pCall?: boolean;
|
|
70
75
|
};
|
|
71
76
|
export declare type ChannelEvent = {
|
|
72
77
|
eventName: string;
|
|
@@ -119,11 +124,6 @@ export declare type CreateScreenMediaOptions = BaseVideoTrackOptions & BaseAudio
|
|
|
119
124
|
videoEncoderConfig?: VideoEncoderConfig;
|
|
120
125
|
audioEncoderConfig?: AudioEncoderConfig;
|
|
121
126
|
};
|
|
122
|
-
export declare type CreateCustomMediaOptions = BaseVideoTrackOptions & BaseAudioTrackOptions & {
|
|
123
|
-
videoEncoderConfig?: VideoEncoderConfig;
|
|
124
|
-
audioEncoderConfig?: AudioEncoderConfig;
|
|
125
|
-
mediaStream: MediaStream;
|
|
126
|
-
};
|
|
127
127
|
export declare enum TrackLabel {
|
|
128
128
|
Camera = "camera",
|
|
129
129
|
CameraPreview = "camera-preview",
|
|
@@ -152,10 +152,6 @@ export declare type ChangePreferredLayersPayload = PreferredLayersParams & {
|
|
|
152
152
|
spatialLayer: number;
|
|
153
153
|
temporalLayer: number;
|
|
154
154
|
};
|
|
155
|
-
export declare type ConsumerScorePayload = {
|
|
156
|
-
consumerId: string;
|
|
157
|
-
score: number;
|
|
158
|
-
};
|
|
159
155
|
export declare type CreateVideoTrackParams = {
|
|
160
156
|
videoTrackOptions: boolean | MediaTrackConstraints;
|
|
161
157
|
encoderConfig: VideoEncoderConfig;
|
|
@@ -244,7 +240,6 @@ export declare type TrackInboundStats = {
|
|
|
244
240
|
currentSpatialLayerParams?: SpatialLayerParams;
|
|
245
241
|
availableSpatialLayers?: SpatialLayerParams[];
|
|
246
242
|
requestedSpatialLayer?: number;
|
|
247
|
-
score: number;
|
|
248
243
|
dtlsState?: RTCDtlsTransportState;
|
|
249
244
|
rtcStats?: ExtendedRTCInboundRtpStreamStats;
|
|
250
245
|
};
|
package/dist/types/engine.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { ActivityConfirmationRequiredPayload, AvailableMediaDevices, ChannelEven
|
|
|
6
6
|
import { BaseTrack, InitEffectsSDKParams, Track } from './media';
|
|
7
7
|
export declare type IssuesHandler = (issues: IssueDetectorResult) => void;
|
|
8
8
|
export declare type StatsHandler = (stats: StatsReportItem[]) => void;
|
|
9
|
-
export declare type NetworkScoresUpdatedHandler = (
|
|
9
|
+
export declare type NetworkScoresUpdatedHandler = (networkScores: NetworkScores) => void;
|
|
10
10
|
export interface CreateIssueDetectorParams {
|
|
11
11
|
disableWid: boolean;
|
|
12
12
|
onIssues?: IssuesHandler;
|
|
@@ -25,11 +25,7 @@ export interface ConnectParams {
|
|
|
25
25
|
channelId: string;
|
|
26
26
|
role: Role;
|
|
27
27
|
token: string;
|
|
28
|
-
|
|
29
|
-
export interface NodeActiveStreamsStat {
|
|
30
|
-
node?: string;
|
|
31
|
-
inboundActiveStreams: number;
|
|
32
|
-
outboundActiveStreams: number;
|
|
28
|
+
isP2pCall?: boolean;
|
|
33
29
|
}
|
|
34
30
|
export interface ChannelStateInconsistentPayload {
|
|
35
31
|
type: InconsistenceType;
|
package/dist/types/network.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Role } from './common';
|
|
1
|
+
import { DatacenterType, Role } from './common';
|
|
2
2
|
import { SocketIOEvents } from './socket';
|
|
3
3
|
import { NETWORK_OBSERVER_EVENTS } from '../constants/events';
|
|
4
4
|
export declare type GetNodeRequest = {
|
|
5
5
|
channelId: string;
|
|
6
6
|
role: Role;
|
|
7
|
+
datacenterType?: DatacenterType;
|
|
7
8
|
};
|
|
8
9
|
export declare type GetNodeResponse = {
|
|
9
10
|
webSocketUrl: string;
|
package/package.json
CHANGED
package/src/constants/events.ts
CHANGED
|
@@ -86,7 +86,6 @@ export const MEDIASOUP_EVENTS = {
|
|
|
86
86
|
closeConsumer: 'consumer.close',
|
|
87
87
|
pauseConsumer: 'consumer.pause',
|
|
88
88
|
resumeConsumer: 'consumer.resume',
|
|
89
|
-
consumerScore: 'consumer.score',
|
|
90
89
|
consumerChangePreferredLayers: 'consumer.changeConsumerPreferredLayers',
|
|
91
90
|
consumerRequestKeyFrame: 'consumer.requestKeyFrame',
|
|
92
91
|
transportCreate: 'transport.create',
|
package/src/engine/Peer.ts
CHANGED
|
@@ -5,7 +5,7 @@ import EnhancedEventEmitter from '../EnhancedEventEmitter';
|
|
|
5
5
|
import validateAppData from '../helpers/appDataValidator';
|
|
6
6
|
import { logResponse } from '../helpers/peer';
|
|
7
7
|
import {
|
|
8
|
-
ChangePreferredLayersPayload,
|
|
8
|
+
ChangePreferredLayersPayload, PayloadOfPublishedMedia,
|
|
9
9
|
PayloadOfUnpublishedMedia, PeerGroup, PeerInfo, PeerShortData, ProducerData,
|
|
10
10
|
ProducerSetMaxSpatialLayer, Role, SubscribeOptions, TrackLabel, TrackLabelString,
|
|
11
11
|
} from '../types/common';
|
|
@@ -70,7 +70,6 @@ export type PeerObserverEvents = {
|
|
|
70
70
|
[MEDIASOUP_EVENTS.closeConsumer]: [consumerId: string];
|
|
71
71
|
[MEDIASOUP_EVENTS.pauseConsumer]: [consumerId: string];
|
|
72
72
|
[MEDIASOUP_EVENTS.resumeConsumer]: [consumerId: string];
|
|
73
|
-
[MEDIASOUP_EVENTS.consumerScore]: [ConsumerScorePayload];
|
|
74
73
|
[MEDIASOUP_EVENTS.consumerChangePreferredLayers]: [ChangePreferredLayersPayload];
|
|
75
74
|
[MEDIASOUP_EVENTS.producerSetMaxSpatialLayer]: [ProducerSetMaxSpatialLayer];
|
|
76
75
|
[CHANNEL_EVENTS.updatePeerAppData]: [Record<string, unknown>];
|
|
@@ -434,13 +433,6 @@ class Peer {
|
|
|
434
433
|
}
|
|
435
434
|
});
|
|
436
435
|
|
|
437
|
-
this.observer.on(MEDIASOUP_EVENTS.consumerScore, (payload: ConsumerScorePayload) => {
|
|
438
|
-
const consumer = this.getConsumerById(payload.consumerId);
|
|
439
|
-
if (consumer) {
|
|
440
|
-
consumer.score = payload.score;
|
|
441
|
-
}
|
|
442
|
-
});
|
|
443
|
-
|
|
444
436
|
this.observer.on(MEDIASOUP_EVENTS.producerSetMaxSpatialLayer, async (payload: ProducerSetMaxSpatialLayer) => {
|
|
445
437
|
const { producerId, spatialLayer } = payload;
|
|
446
438
|
const consumer = this.getConsumerByProducerId(producerId);
|
|
@@ -505,7 +497,6 @@ class Peer {
|
|
|
505
497
|
MEDIASOUP_EVENTS.closeConsumer,
|
|
506
498
|
MEDIASOUP_EVENTS.pauseConsumer,
|
|
507
499
|
MEDIASOUP_EVENTS.resumeConsumer,
|
|
508
|
-
MEDIASOUP_EVENTS.consumerScore,
|
|
509
500
|
MEDIASOUP_EVENTS.consumerChangePreferredLayers,
|
|
510
501
|
MEDIASOUP_EVENTS.producerSetMaxSpatialLayer,
|
|
511
502
|
CHANNEL_EVENTS.updatePeerAppData,
|
|
@@ -18,8 +18,6 @@ export interface PeerConsumerConstructorParams extends PeerConsumerDependencies
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
class PeerConsumer {
|
|
21
|
-
public score = 10;
|
|
22
|
-
|
|
23
21
|
public spatialLayers = 0;
|
|
24
22
|
|
|
25
23
|
public temporalLayers = 0;
|
|
@@ -191,7 +189,6 @@ class PeerConsumer {
|
|
|
191
189
|
return {
|
|
192
190
|
consumerId: this.consumer.id,
|
|
193
191
|
codec: this.consumer.rtpParameters.codecs.find((codec) => filterStatsCodecs(codec)),
|
|
194
|
-
score: this.score,
|
|
195
192
|
dtlsState: this.consumer.rtpReceiver?.transport?.state,
|
|
196
193
|
availableSpatialLayers: this.availableSpatialLayers,
|
|
197
194
|
currentSpatialLayerParams: this.currentSpatialLayerParams,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { MessageBatcher } from 'message-batcher';
|
|
2
|
+
import { IssueDetectorResult, NetworkScores } from 'webrtc-issue-detector';
|
|
3
|
+
import { inject, injectable } from 'inversify';
|
|
4
|
+
import { NetworkMetric } from './analyticsApiClient/types';
|
|
5
|
+
import clientMetaProvider from '../ClientMetaProvider';
|
|
6
|
+
import { TOKEN } from '../inversify.tokens';
|
|
7
|
+
import { getClientUniqueId } from './analyticsApiClient/helper';
|
|
8
|
+
import AnalyticsApiClient from './analyticsApiClient';
|
|
9
|
+
import type { IPeersService } from './Peers';
|
|
10
|
+
import SocketIO from './network/Socket';
|
|
11
|
+
import { MyPeer } from './MyPeer';
|
|
12
|
+
import type { LoggerFactory } from '../types/container';
|
|
13
|
+
import Logger from './Logger';
|
|
14
|
+
import TransportsStatsProvider from './WebRTCStats/TransportsStatsProvider';
|
|
15
|
+
|
|
16
|
+
const WIDSupportedIssueReasons = [
|
|
17
|
+
'frozen-video-track',
|
|
18
|
+
'network-media-sync-failure',
|
|
19
|
+
'encoder-cpu-throttling',
|
|
20
|
+
'decoder-cpu-throttling',
|
|
21
|
+
'missing-video-stream-data',
|
|
22
|
+
'missing-audio-stream-data',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
interface NodeActiveStreamsStat {
|
|
26
|
+
node?: string;
|
|
27
|
+
inboundActiveStreams: number;
|
|
28
|
+
outboundActiveStreams: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@injectable()
|
|
32
|
+
class WIDHandler {
|
|
33
|
+
readonly #networkScoresBatcher: MessageBatcher;
|
|
34
|
+
|
|
35
|
+
readonly #webrtcIssuesBatcher: MessageBatcher;
|
|
36
|
+
|
|
37
|
+
readonly #logger: Logger;
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
@inject(TOKEN.AnalyticsApiClient) private readonly analyticsApiClient: AnalyticsApiClient,
|
|
41
|
+
@inject(TOKEN.Peers) private readonly peers: IPeersService,
|
|
42
|
+
@inject(TOKEN.SocketIO) private readonly socket: SocketIO,
|
|
43
|
+
@inject(TOKEN.MyPeer) private readonly myPeer: MyPeer,
|
|
44
|
+
@inject(TOKEN.LoggerFactory) loggerFactory: LoggerFactory,
|
|
45
|
+
@inject(TOKEN.TransportsStatsProvider) private readonly transportsStatsProvider: TransportsStatsProvider,
|
|
46
|
+
) {
|
|
47
|
+
this.#logger = loggerFactory('IntegrationsService');
|
|
48
|
+
this.#webrtcIssuesBatcher = new MessageBatcher({ MaxBatchSize: 50, MaxDelay: 60000, MinDelay: 30000 });
|
|
49
|
+
this.#webrtcIssuesBatcher
|
|
50
|
+
.on('batch', (reasons: string[]) => {
|
|
51
|
+
this.analyticsApiClient.metrics.sendWIDMetrics({
|
|
52
|
+
reasons,
|
|
53
|
+
clientMeta: this.getClientMeta(),
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.#networkScoresBatcher = new MessageBatcher({ MaxBatchSize: 100, MaxDelay: 60000, MinDelay: 30000 });
|
|
58
|
+
this.#networkScoresBatcher
|
|
59
|
+
.on('batch', (metrics: NetworkMetric[]) => {
|
|
60
|
+
this.analyticsApiClient.metrics.sendNetworkMetrics({
|
|
61
|
+
metrics,
|
|
62
|
+
clientMeta: this.getClientMeta(),
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
handleIssues(issues: IssueDetectorResult) {
|
|
68
|
+
issues.map((issue) => this.#logger.info('WebRTCIssue', issue));
|
|
69
|
+
|
|
70
|
+
const supportedIssues = issues
|
|
71
|
+
.filter((issue) => WIDSupportedIssueReasons.includes(issue.reason));
|
|
72
|
+
|
|
73
|
+
if (supportedIssues.length === 0) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
supportedIssues
|
|
78
|
+
.map((issue) => this.#webrtcIssuesBatcher.Queue(issue.reason));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
handleNetworkScores(networkScores: NetworkScores) {
|
|
82
|
+
const {
|
|
83
|
+
inbound,
|
|
84
|
+
outbound,
|
|
85
|
+
statsSamples: { inboundStatsSample, outboundStatsSample },
|
|
86
|
+
} = networkScores;
|
|
87
|
+
const nodeActiveStreamsStat = this.getNodeActiveStreamsStat();
|
|
88
|
+
const node = (nodeActiveStreamsStat.node)?.replace(/https:\/\/app-\d+-/, '');
|
|
89
|
+
const transportsStats = this.transportsStatsProvider.getStats();
|
|
90
|
+
|
|
91
|
+
if (inbound !== undefined && inboundStatsSample) {
|
|
92
|
+
this.#networkScoresBatcher.Queue({
|
|
93
|
+
jitter: Math.round(inboundStatsSample.avgJitter * 1000),
|
|
94
|
+
packetLoss: inboundStatsSample.packetsLoss,
|
|
95
|
+
rtt: inboundStatsSample.rtt,
|
|
96
|
+
mos: Math.round(inbound * 10) / 10,
|
|
97
|
+
streams: nodeActiveStreamsStat.inboundActiveStreams,
|
|
98
|
+
direction: 'inbound',
|
|
99
|
+
node,
|
|
100
|
+
bitrate: transportsStats?.inbound?.bitrate ?? 0,
|
|
101
|
+
timestamp: (new Date()).toISOString(),
|
|
102
|
+
} as NetworkMetric);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (outbound !== undefined && outboundStatsSample) {
|
|
106
|
+
this.#networkScoresBatcher.Queue({
|
|
107
|
+
jitter: Math.round(outboundStatsSample.avgJitter * 1000),
|
|
108
|
+
packetLoss: outboundStatsSample.packetsLoss,
|
|
109
|
+
rtt: outboundStatsSample.rtt,
|
|
110
|
+
mos: Math.round(outbound * 10) / 10,
|
|
111
|
+
streams: nodeActiveStreamsStat.outboundActiveStreams,
|
|
112
|
+
direction: 'outbound',
|
|
113
|
+
node,
|
|
114
|
+
bitrate: transportsStats?.outbound?.bitrate ?? 0,
|
|
115
|
+
timestamp: (new Date()).toISOString(),
|
|
116
|
+
} as NetworkMetric);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private getNodeActiveStreamsStat(): NodeActiveStreamsStat {
|
|
121
|
+
const node = this.socket.getServerUrl();
|
|
122
|
+
const inboundActiveStreams = this.peers.hosts()
|
|
123
|
+
.filter((peer) => peer.id !== this.myPeer?.id)
|
|
124
|
+
.reduce((total, peer) => total + peer.getActiveTracksCount(), 0);
|
|
125
|
+
const myPeer = this.myPeer.get();
|
|
126
|
+
const outboundActiveStreams = myPeer?.getActivePublishersCount() || 0;
|
|
127
|
+
return {
|
|
128
|
+
node,
|
|
129
|
+
inboundActiveStreams,
|
|
130
|
+
outboundActiveStreams,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private getClientMeta() {
|
|
135
|
+
return {
|
|
136
|
+
...clientMetaProvider.meta,
|
|
137
|
+
clientUniqueId: getClientUniqueId(),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default WIDHandler;
|
|
@@ -1,18 +1,33 @@
|
|
|
1
|
+
import { inject, injectable } from 'inversify';
|
|
1
2
|
import ConnectionStatManager from './ConnectionStatsManager';
|
|
2
3
|
import Media from '../media';
|
|
3
4
|
import { IPeersService } from '../Peers';
|
|
4
5
|
import { TransportsStatsProviderParams, TransportsWebRTCStats } from './types';
|
|
6
|
+
import { TOKEN } from '../../inversify.tokens';
|
|
7
|
+
import type { LoggerFactory } from '../../types/container';
|
|
8
|
+
import Logger from '../Logger';
|
|
5
9
|
|
|
10
|
+
@injectable()
|
|
6
11
|
export default class TransportsStatsProvider {
|
|
7
|
-
#
|
|
12
|
+
#initialized = false;
|
|
8
13
|
|
|
9
|
-
#
|
|
14
|
+
#logger: Logger;
|
|
10
15
|
|
|
11
|
-
#
|
|
16
|
+
#sendTransportConnectionManager?: ConnectionStatManager;
|
|
12
17
|
|
|
13
|
-
#
|
|
18
|
+
#receiveTransportConnectionManager?: ConnectionStatManager;
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
#media?: Media;
|
|
21
|
+
|
|
22
|
+
#peers?: IPeersService;
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
@inject(TOKEN.LoggerFactory) loggerFactory: LoggerFactory,
|
|
26
|
+
) {
|
|
27
|
+
this.#logger = loggerFactory('IntegrationsService');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
init({
|
|
16
31
|
sendTransportConnectionManager,
|
|
17
32
|
receiveTransportConnectionManager,
|
|
18
33
|
media,
|
|
@@ -22,17 +37,38 @@ export default class TransportsStatsProvider {
|
|
|
22
37
|
this.#receiveTransportConnectionManager = receiveTransportConnectionManager;
|
|
23
38
|
this.#media = media;
|
|
24
39
|
this.#peers = peers;
|
|
40
|
+
this.#initialized = true;
|
|
25
41
|
}
|
|
26
42
|
|
|
27
43
|
getStats(): TransportsWebRTCStats {
|
|
44
|
+
try {
|
|
45
|
+
return this.doGetStats();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
this.#logger.error('Failed to get stats', error);
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
clear() {
|
|
53
|
+
this.#sendTransportConnectionManager = undefined;
|
|
54
|
+
this.#receiveTransportConnectionManager = undefined;
|
|
55
|
+
this.#media = undefined;
|
|
56
|
+
this.#peers = undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private doGetStats() {
|
|
60
|
+
if (!this.#initialized) {
|
|
61
|
+
throw new Error('TransportsStatsProvider is not initialized');
|
|
62
|
+
}
|
|
63
|
+
|
|
28
64
|
return {
|
|
29
|
-
...(this.#sendTransportConnectionManager
|
|
65
|
+
...(this.#sendTransportConnectionManager?.connectionStats?.id ? {
|
|
30
66
|
outbound: {
|
|
31
67
|
...this.#sendTransportConnectionManager.connectionStats,
|
|
32
68
|
bitrate: this.calculateOutboundTracksBitrate(),
|
|
33
69
|
},
|
|
34
70
|
} : {}),
|
|
35
|
-
...(this.#receiveTransportConnectionManager
|
|
71
|
+
...(this.#receiveTransportConnectionManager?.connectionStats?.id ? {
|
|
36
72
|
inbound: {
|
|
37
73
|
...this.#receiveTransportConnectionManager.connectionStats,
|
|
38
74
|
bitrate: this.calculateInboundTracksBitrate(),
|
|
@@ -42,6 +78,10 @@ export default class TransportsStatsProvider {
|
|
|
42
78
|
}
|
|
43
79
|
|
|
44
80
|
private calculateInboundTracksBitrate(): number {
|
|
81
|
+
if (this.#peers === undefined) {
|
|
82
|
+
throw new Error('Peers is undefined');
|
|
83
|
+
}
|
|
84
|
+
|
|
45
85
|
return this.#peers.hosts().reduce((sumPeersBitrate, peer) => {
|
|
46
86
|
if (peer.isMe) {
|
|
47
87
|
return sumPeersBitrate;
|
|
@@ -59,6 +99,10 @@ export default class TransportsStatsProvider {
|
|
|
59
99
|
}
|
|
60
100
|
|
|
61
101
|
private calculateOutboundTracksBitrate(): number {
|
|
102
|
+
if (this.#media === undefined) {
|
|
103
|
+
throw new Error('Media is undefined');
|
|
104
|
+
}
|
|
105
|
+
|
|
62
106
|
return this.#media.getAllTracks().reduce((sumTracksBitrate, track) => {
|
|
63
107
|
if (!track.isPublished) {
|
|
64
108
|
return sumTracksBitrate;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
import axiosRetry from 'axios-retry';
|
|
3
|
+
import { NetworkMetricsBatch, WIDMetricsBatch } from './types';
|
|
4
|
+
|
|
5
|
+
const RETRY_COUNT = 2;
|
|
6
|
+
const RETRY_DELAY_IN_MS = 1500;
|
|
7
|
+
|
|
8
|
+
export default class MetricsApi {
|
|
9
|
+
private readonly api: AxiosInstance;
|
|
10
|
+
|
|
11
|
+
constructor(api: AxiosInstance) {
|
|
12
|
+
this.api = api;
|
|
13
|
+
axiosRetry(this.api, {
|
|
14
|
+
retries: RETRY_COUNT,
|
|
15
|
+
shouldResetTimeout: true,
|
|
16
|
+
retryDelay: (retryCount) => retryCount * RETRY_DELAY_IN_MS,
|
|
17
|
+
retryCondition: (error) => /^\/?log\//.test(error.config?.url ?? '') || false,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async sendNetworkMetrics(metrics: NetworkMetricsBatch): Promise<void> {
|
|
22
|
+
return this.api.post('/analytics/network-metrics', metrics);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async sendWIDMetrics(metrics: WIDMetricsBatch): Promise<void> {
|
|
26
|
+
return this.api.post('/analytics/wid-metrics', {
|
|
27
|
+
...metrics,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import axios, { AxiosInstance } from 'axios';
|
|
2
2
|
import qs from 'qs';
|
|
3
|
+
import { injectable } from 'inversify';
|
|
3
4
|
import { receiveClientTrackingId } from './helper';
|
|
4
5
|
import LogApi from './LogApi';
|
|
6
|
+
import MetricsApi from './MetricsApi';
|
|
5
7
|
|
|
8
|
+
@injectable()
|
|
6
9
|
export default class AnalyticsApiClient {
|
|
7
10
|
private readonly api: AxiosInstance;
|
|
8
11
|
|
|
9
12
|
private readonly logApi: LogApi;
|
|
10
13
|
|
|
14
|
+
private readonly metricsApi: MetricsApi;
|
|
15
|
+
|
|
11
16
|
constructor() {
|
|
12
17
|
this.api = axios.create({
|
|
13
18
|
baseURL: process.env.LIVEDIGITAL_APP_ANALYTICS_API_BASE_URL,
|
|
@@ -24,9 +29,14 @@ export default class AnalyticsApiClient {
|
|
|
24
29
|
this.api.defaults.headers.common.Expires = '0';
|
|
25
30
|
|
|
26
31
|
this.logApi = new LogApi(this.api);
|
|
32
|
+
this.metricsApi = new MetricsApi(this.api);
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
get log(): LogApi {
|
|
30
36
|
return this.logApi;
|
|
31
37
|
}
|
|
38
|
+
|
|
39
|
+
get metrics(): MetricsApi {
|
|
40
|
+
return this.metricsApi;
|
|
41
|
+
}
|
|
32
42
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ClientMetaPayload } from '../../ClientMetaProvider';
|
|
1
2
|
import { LogToRemotePayload } from '../Logger';
|
|
2
3
|
|
|
3
4
|
export interface BatchLogPayload {
|
|
@@ -14,12 +15,19 @@ export interface NetworkMetric {
|
|
|
14
15
|
node: string;
|
|
15
16
|
direction: 'inbound' | 'outbound';
|
|
16
17
|
timestamp: string;
|
|
18
|
+
bitrate: number;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
export interface
|
|
21
|
+
export interface NetworkMetricsBatch {
|
|
20
22
|
metrics: NetworkMetric[];
|
|
21
|
-
clientMeta: {
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
clientMeta: ClientMetaPayload & {
|
|
24
|
+
clientUniqueId: string
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WIDMetricsBatch {
|
|
29
|
+
reasons: string[],
|
|
30
|
+
clientMeta: ClientMetaPayload & {
|
|
31
|
+
clientUniqueId: string
|
|
24
32
|
};
|
|
25
33
|
}
|
|
@@ -206,28 +206,6 @@ class MediaSoupEventHandler {
|
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
handleConsumerScoreEvent(payload: string): void {
|
|
210
|
-
try {
|
|
211
|
-
const { peerId, consumerId, score } = JSON.parse(payload) as {
|
|
212
|
-
peerId: string;
|
|
213
|
-
consumerId: string;
|
|
214
|
-
score: number;
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const peer = this.peers.get(peerId);
|
|
218
|
-
if (!peer) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
peer.observer.safeEmit(MEDIASOUP_EVENTS.consumerScore, { consumerId, score });
|
|
223
|
-
} catch (error: unknown) {
|
|
224
|
-
this.#logger.error('Failed to handle consumer score event', {
|
|
225
|
-
error,
|
|
226
|
-
payload,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
209
|
private removeEventListeners() {
|
|
232
210
|
if (!this.eventsQueue) {
|
|
233
211
|
return;
|