@stream-io/video-client 0.0.1-alpha.10
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/LICENSE +219 -0
- package/README.md +14 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +296 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/src/Batcher.ts.html +214 -0
- package/coverage/lcov-report/src/CallDropScheduler.ts.html +661 -0
- package/coverage/lcov-report/src/StreamSfuClient.ts.html +640 -0
- package/coverage/lcov-report/src/StreamVideoClient.ts.html +1594 -0
- package/coverage/lcov-report/src/config/defaultConfigs.ts.html +130 -0
- package/coverage/lcov-report/src/config/index.html +116 -0
- package/coverage/lcov-report/src/coordinator/StreamCoordinatorClient.ts.html +430 -0
- package/coverage/lcov-report/src/coordinator/connection/base64.ts.html +325 -0
- package/coverage/lcov-report/src/coordinator/connection/client.ts.html +2527 -0
- package/coverage/lcov-report/src/coordinator/connection/connection.ts.html +2335 -0
- package/coverage/lcov-report/src/coordinator/connection/connection_fallback.ts.html +802 -0
- package/coverage/lcov-report/src/coordinator/connection/errors.ts.html +295 -0
- package/coverage/lcov-report/src/coordinator/connection/index.html +251 -0
- package/coverage/lcov-report/src/coordinator/connection/insights.ts.html +349 -0
- package/coverage/lcov-report/src/coordinator/connection/signing.ts.html +397 -0
- package/coverage/lcov-report/src/coordinator/connection/token_manager.ts.html +565 -0
- package/coverage/lcov-report/src/coordinator/connection/types.ts.html +418 -0
- package/coverage/lcov-report/src/coordinator/connection/utils.ts.html +529 -0
- package/coverage/lcov-report/src/coordinator/index.html +116 -0
- package/coverage/lcov-report/src/events/call.ts.html +583 -0
- package/coverage/lcov-report/src/events/index.html +161 -0
- package/coverage/lcov-report/src/events/internal.ts.html +226 -0
- package/coverage/lcov-report/src/events/participant.ts.html +376 -0
- package/coverage/lcov-report/src/events/speaker.ts.html +271 -0
- package/coverage/lcov-report/src/gen/google/protobuf/index.html +131 -0
- package/coverage/lcov-report/src/gen/google/protobuf/struct.ts.html +1528 -0
- package/coverage/lcov-report/src/gen/google/protobuf/timestamp.ts.html +958 -0
- package/coverage/lcov-report/src/gen/video/sfu/event/events.ts.html +5971 -0
- package/coverage/lcov-report/src/gen/video/sfu/event/index.html +116 -0
- package/coverage/lcov-report/src/gen/video/sfu/models/index.html +116 -0
- package/coverage/lcov-report/src/gen/video/sfu/models/models.ts.html +3271 -0
- package/coverage/lcov-report/src/index.html +161 -0
- package/coverage/lcov-report/src/rpc/index.html +131 -0
- package/coverage/lcov-report/src/rpc/index.ts.html +91 -0
- package/coverage/lcov-report/src/rpc/latency.ts.html +214 -0
- package/coverage/lcov-report/src/rtc/Call.ts.html +1840 -0
- package/coverage/lcov-report/src/rtc/CallMetadata.ts.html +157 -0
- package/coverage/lcov-report/src/rtc/Dispatcher.ts.html +223 -0
- package/coverage/lcov-report/src/rtc/IceTrickleBuffer.ts.html +148 -0
- package/coverage/lcov-report/src/rtc/callEventHandlers.ts.html +196 -0
- package/coverage/lcov-report/src/rtc/codecs.ts.html +268 -0
- package/coverage/lcov-report/src/rtc/helpers/iceCandidate.ts.html +133 -0
- package/coverage/lcov-report/src/rtc/helpers/index.html +131 -0
- package/coverage/lcov-report/src/rtc/helpers/tracks.ts.html +139 -0
- package/coverage/lcov-report/src/rtc/index.html +251 -0
- package/coverage/lcov-report/src/rtc/publisher.ts.html +1000 -0
- package/coverage/lcov-report/src/rtc/signal.ts.html +187 -0
- package/coverage/lcov-report/src/rtc/subscriber.ts.html +340 -0
- package/coverage/lcov-report/src/rtc/videoLayers.ts.html +394 -0
- package/coverage/lcov-report/src/stats/index.html +116 -0
- package/coverage/lcov-report/src/stats/state-store-stats-reporter.ts.html +1177 -0
- package/coverage/lcov-report/src/store/index.html +146 -0
- package/coverage/lcov-report/src/store/index.ts.html +91 -0
- package/coverage/lcov-report/src/store/rxUtils.ts.html +211 -0
- package/coverage/lcov-report/src/store/stateStore.ts.html +1108 -0
- package/coverage/lcov.info +11463 -0
- package/coverage/tmp/coverage-2131-1675959950516-2.json +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +8636 -0
- package/dist/index.js.map +1 -0
- package/dist/src/Batcher.d.ts +12 -0
- package/dist/src/CallDropScheduler.d.ts +44 -0
- package/dist/src/StreamSfuClient.d.ts +25 -0
- package/dist/src/StreamVideoClient.d.ts +145 -0
- package/dist/src/__tests__/StreamVideoClient.test.d.ts +1 -0
- package/dist/src/config/defaultConfigs.d.ts +2 -0
- package/dist/src/config/types.d.ts +29 -0
- package/dist/src/coordinator/StreamCoordinatorClient.d.ts +19 -0
- package/dist/src/coordinator/connection/base64.d.ts +2 -0
- package/dist/src/coordinator/connection/client.d.ts +174 -0
- package/dist/src/coordinator/connection/connection.d.ts +139 -0
- package/dist/src/coordinator/connection/connection_fallback.d.ts +38 -0
- package/dist/src/coordinator/connection/errors.d.ts +16 -0
- package/dist/src/coordinator/connection/events.d.ts +10 -0
- package/dist/src/coordinator/connection/insights.d.ts +58 -0
- package/dist/src/coordinator/connection/signing.d.ts +30 -0
- package/dist/src/coordinator/connection/token_manager.d.ts +39 -0
- package/dist/src/coordinator/connection/types.d.ts +87 -0
- package/dist/src/coordinator/connection/utils.d.ts +25 -0
- package/dist/src/devices.d.ts +79 -0
- package/dist/src/events/call.d.ts +26 -0
- package/dist/src/events/internal.d.ts +8 -0
- package/dist/src/events/participant.d.ts +21 -0
- package/dist/src/events/speaker.d.ts +10 -0
- package/dist/src/gen/coordinator/index.d.ts +1973 -0
- package/dist/src/gen/google/protobuf/descriptor.d.ts +1650 -0
- package/dist/src/gen/google/protobuf/duration.d.ts +113 -0
- package/dist/src/gen/google/protobuf/struct.d.ts +184 -0
- package/dist/src/gen/google/protobuf/timestamp.d.ts +158 -0
- package/dist/src/gen/video/coordinator/broadcast_v1/broadcast.d.ts +66 -0
- package/dist/src/gen/video/coordinator/call_v1/call.d.ts +254 -0
- package/dist/src/gen/video/coordinator/client_v1_rpc/client_rpc.client.d.ts +351 -0
- package/dist/src/gen/video/coordinator/client_v1_rpc/client_rpc.d.ts +1488 -0
- package/dist/src/gen/video/coordinator/client_v1_rpc/envelopes.d.ts +143 -0
- package/dist/src/gen/video/coordinator/client_v1_rpc/websocket.d.ts +292 -0
- package/dist/src/gen/video/coordinator/edge_v1/edge.d.ts +183 -0
- package/dist/src/gen/video/coordinator/event_v1/event.d.ts +411 -0
- package/dist/src/gen/video/coordinator/geofence_v1/geofence.d.ts +63 -0
- package/dist/src/gen/video/coordinator/member_v1/member.d.ts +59 -0
- package/dist/src/gen/video/coordinator/participant_v1/participant.d.ts +103 -0
- package/dist/src/gen/video/coordinator/push_v1/push.d.ts +240 -0
- package/dist/src/gen/video/coordinator/stat_v1/stat.d.ts +308 -0
- package/dist/src/gen/video/coordinator/user_v1/user.d.ts +112 -0
- package/dist/src/gen/video/coordinator/utils_v1/utils.d.ts +47 -0
- package/dist/src/gen/video/sfu/event/events.d.ts +736 -0
- package/dist/src/gen/video/sfu/models/models.d.ts +460 -0
- package/dist/src/gen/video/sfu/signal_rpc/signal.client.d.ts +89 -0
- package/dist/src/gen/video/sfu/signal_rpc/signal.d.ts +320 -0
- package/dist/src/helpers/browsers.d.ts +8 -0
- package/dist/src/helpers/sound-detector.d.ts +34 -0
- package/dist/src/rpc/createClient.d.ts +10 -0
- package/dist/src/rpc/index.d.ts +2 -0
- package/dist/src/rpc/latency.d.ts +9 -0
- package/dist/src/rtc/Call.d.ts +180 -0
- package/dist/src/rtc/CallMetadata.d.ts +9 -0
- package/dist/src/rtc/Dispatcher.d.ts +9 -0
- package/dist/src/rtc/IceTrickleBuffer.d.ts +11 -0
- package/dist/src/rtc/callEventHandlers.d.ts +5 -0
- package/dist/src/rtc/codecs.d.ts +2 -0
- package/dist/src/rtc/helpers/iceCandidate.d.ts +2 -0
- package/dist/src/rtc/helpers/tracks.d.ts +3 -0
- package/dist/src/rtc/publisher.d.ts +53 -0
- package/dist/src/rtc/signal.d.ts +5 -0
- package/dist/src/rtc/subscriber.d.ts +7 -0
- package/dist/src/rtc/types.d.ts +84 -0
- package/dist/src/rtc/videoLayers.d.ts +17 -0
- package/dist/src/stats/coordinator-stats-reporter.d.ts +10 -0
- package/dist/src/stats/state-store-stats-reporter.d.ts +57 -0
- package/dist/src/stats/types.d.ts +42 -0
- package/dist/src/store/index.d.ts +2 -0
- package/dist/src/store/rxUtils.d.ts +18 -0
- package/dist/src/store/stateStore.d.ts +182 -0
- package/generate-openapi.sh +32 -0
- package/index.ts +31 -0
- package/openapitools.json +7 -0
- package/package.json +54 -0
- package/rollup.config.mjs +48 -0
- package/src/Batcher.ts +43 -0
- package/src/CallDropScheduler.ts +192 -0
- package/src/StreamSfuClient.ts +185 -0
- package/src/StreamVideoClient.ts +503 -0
- package/src/__tests__/StreamVideoClient.test.ts +83 -0
- package/src/config/defaultConfigs.ts +15 -0
- package/src/config/types.ts +30 -0
- package/src/coordinator/StreamCoordinatorClient.ts +115 -0
- package/src/coordinator/connection/base64.ts +80 -0
- package/src/coordinator/connection/client.ts +814 -0
- package/src/coordinator/connection/connection.ts +750 -0
- package/src/coordinator/connection/connection_fallback.ts +239 -0
- package/src/coordinator/connection/errors.ts +70 -0
- package/src/coordinator/connection/events.ts +13 -0
- package/src/coordinator/connection/insights.ts +88 -0
- package/src/coordinator/connection/signing.ts +104 -0
- package/src/coordinator/connection/token_manager.ts +160 -0
- package/src/coordinator/connection/types.ts +111 -0
- package/src/coordinator/connection/utils.ts +148 -0
- package/src/devices.ts +266 -0
- package/src/events/call.ts +166 -0
- package/src/events/internal.ts +47 -0
- package/src/events/participant.ts +97 -0
- package/src/events/speaker.ts +62 -0
- package/src/gen/coordinator/index.ts +1956 -0
- package/src/gen/google/protobuf/descriptor.ts +3466 -0
- package/src/gen/google/protobuf/duration.ts +232 -0
- package/src/gen/google/protobuf/struct.ts +481 -0
- package/src/gen/google/protobuf/timestamp.ts +291 -0
- package/src/gen/video/coordinator/broadcast_v1/broadcast.ts +154 -0
- package/src/gen/video/coordinator/call_v1/call.ts +651 -0
- package/src/gen/video/coordinator/client_v1_rpc/client_rpc.client.ts +463 -0
- package/src/gen/video/coordinator/client_v1_rpc/client_rpc.ts +3819 -0
- package/src/gen/video/coordinator/client_v1_rpc/envelopes.ts +424 -0
- package/src/gen/video/coordinator/client_v1_rpc/websocket.ts +719 -0
- package/src/gen/video/coordinator/edge_v1/edge.ts +532 -0
- package/src/gen/video/coordinator/event_v1/event.ts +1171 -0
- package/src/gen/video/coordinator/geofence_v1/geofence.ts +128 -0
- package/src/gen/video/coordinator/member_v1/member.ts +138 -0
- package/src/gen/video/coordinator/participant_v1/participant.ts +261 -0
- package/src/gen/video/coordinator/push_v1/push.ts +651 -0
- package/src/gen/video/coordinator/stat_v1/stat.ts +656 -0
- package/src/gen/video/coordinator/user_v1/user.ts +277 -0
- package/src/gen/video/coordinator/utils_v1/utils.ts +98 -0
- package/src/gen/video/sfu/event/events.ts +1962 -0
- package/src/gen/video/sfu/models/models.ts +1062 -0
- package/src/gen/video/sfu/signal_rpc/signal.client.ts +108 -0
- package/src/gen/video/sfu/signal_rpc/signal.ts +906 -0
- package/src/helpers/browsers.ts +13 -0
- package/src/helpers/sound-detector.ts +85 -0
- package/src/rpc/createClient.ts +50 -0
- package/src/rpc/index.ts +2 -0
- package/src/rpc/latency.ts +43 -0
- package/src/rtc/Call.ts +585 -0
- package/src/rtc/CallMetadata.ts +24 -0
- package/src/rtc/Dispatcher.ts +46 -0
- package/src/rtc/IceTrickleBuffer.ts +21 -0
- package/src/rtc/callEventHandlers.ts +37 -0
- package/src/rtc/codecs.ts +61 -0
- package/src/rtc/helpers/iceCandidate.ts +16 -0
- package/src/rtc/helpers/tracks.ts +18 -0
- package/src/rtc/publisher.ts +305 -0
- package/src/rtc/signal.ts +34 -0
- package/src/rtc/subscriber.ts +85 -0
- package/src/rtc/types.ts +105 -0
- package/src/rtc/videoLayers.ts +103 -0
- package/src/stats/coordinator-stats-reporter.ts +167 -0
- package/src/stats/state-store-stats-reporter.ts +364 -0
- package/src/stats/types.ts +46 -0
- package/src/store/index.ts +2 -0
- package/src/store/rxUtils.ts +42 -0
- package/src/store/stateStore.ts +341 -0
- package/tsconfig.json +25 -0
- package/typedoc.json +11 -0
- package/vite.config.ts +11 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export type OptimalVideoLayer = RTCRtpEncodingParameters & {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
|
|
5
|
+
// defined here until we have this prop included in TypeScript
|
|
6
|
+
// https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1380
|
|
7
|
+
maxFramerate?: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const findOptimalVideoLayers = (videoTrack: MediaStreamTrack) => {
|
|
11
|
+
const steps: [number, number, number][] = [
|
|
12
|
+
[1920, 1080, 3000000],
|
|
13
|
+
[1280, 720, 1250000],
|
|
14
|
+
[960, 540, 850000],
|
|
15
|
+
[640, 480, 500000],
|
|
16
|
+
[320, 240, 250000],
|
|
17
|
+
[160, 120, 125000],
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const optimalVideoLayers: OptimalVideoLayer[] = [];
|
|
21
|
+
const settings = videoTrack.getSettings();
|
|
22
|
+
for (let step = 0; step < steps.length; step++) {
|
|
23
|
+
const [w, h, maxBitrate] = steps[step];
|
|
24
|
+
// found ideal layer
|
|
25
|
+
if (w === settings.width && h === settings.height) {
|
|
26
|
+
let scaleFactor: number = 1;
|
|
27
|
+
['f', 'h', 'q'].forEach((rid) => {
|
|
28
|
+
// Reversing the order [f, h, q] to [q, h, f] as Chrome uses encoding index
|
|
29
|
+
// when deciding which layer to disable when CPU or bandwidth is constrained.
|
|
30
|
+
// Encodings should be ordered in increasing spatial resolution order.
|
|
31
|
+
optimalVideoLayers.unshift({
|
|
32
|
+
active: true,
|
|
33
|
+
rid,
|
|
34
|
+
width: w / scaleFactor,
|
|
35
|
+
height: h / scaleFactor,
|
|
36
|
+
maxBitrate: maxBitrate / scaleFactor,
|
|
37
|
+
scaleResolutionDownBy: scaleFactor,
|
|
38
|
+
maxFramerate: {
|
|
39
|
+
f: 30,
|
|
40
|
+
h: 25,
|
|
41
|
+
q: 20,
|
|
42
|
+
}[rid],
|
|
43
|
+
});
|
|
44
|
+
scaleFactor *= 2;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// for simplicity, we start with all layers enabled, then this function
|
|
51
|
+
// will clear/reassign the layers that are not needed
|
|
52
|
+
return withSimulcastConstraints(settings, optimalVideoLayers);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Browsers have different simulcast constraints for different video resolutions.
|
|
57
|
+
*
|
|
58
|
+
* This function modifies the provided list of video layers according to the
|
|
59
|
+
* current implementation of simulcast constraints in the Chromium based browsers.
|
|
60
|
+
*
|
|
61
|
+
* https://chromium.googlesource.com/external/webrtc/+/refs/heads/main/media/engine/simulcast.cc#90
|
|
62
|
+
*/
|
|
63
|
+
const withSimulcastConstraints = (
|
|
64
|
+
settings: MediaTrackSettings,
|
|
65
|
+
optimalVideoLayers: OptimalVideoLayer[],
|
|
66
|
+
) => {
|
|
67
|
+
let layers;
|
|
68
|
+
|
|
69
|
+
const size = Math.max(settings.width || 0, settings.height || 0);
|
|
70
|
+
if (size <= 320) {
|
|
71
|
+
// provide only one layer 320x240 (q), the one with the highest quality
|
|
72
|
+
layers = optimalVideoLayers.filter((layer) => layer.rid === 'f');
|
|
73
|
+
} else if (size <= 640) {
|
|
74
|
+
// provide two layers, 160x120 (q) and 640x480 (h)
|
|
75
|
+
layers = optimalVideoLayers.filter((layer) => layer.rid !== 'h');
|
|
76
|
+
} else {
|
|
77
|
+
// provide three layers for sizes > 640x480
|
|
78
|
+
layers = optimalVideoLayers;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const ridMapping = ['q', 'h', 'f'];
|
|
82
|
+
return layers.map((layer, index) => ({
|
|
83
|
+
...layer,
|
|
84
|
+
rid: ridMapping[index], // reassign rid
|
|
85
|
+
}));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const findOptimalScreenSharingLayers = (
|
|
89
|
+
videoTrack: MediaStreamTrack,
|
|
90
|
+
): OptimalVideoLayer[] => {
|
|
91
|
+
const settings = videoTrack.getSettings();
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
active: true,
|
|
95
|
+
rid: 'q', // single track, start from 'q'
|
|
96
|
+
width: settings.width || 0,
|
|
97
|
+
height: settings.height || 0,
|
|
98
|
+
maxBitrate: 3000000,
|
|
99
|
+
scaleResolutionDownBy: 1,
|
|
100
|
+
maxFramerate: 30,
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { StreamVideoReadOnlyStateStore } from '../store';
|
|
2
|
+
import {
|
|
3
|
+
ReportCallStatEventRequest,
|
|
4
|
+
ReportCallStatEventResponse,
|
|
5
|
+
ReportCallStatsResponse,
|
|
6
|
+
} from '../gen/video/coordinator/client_v1_rpc/client_rpc';
|
|
7
|
+
import {
|
|
8
|
+
MediaStateChange,
|
|
9
|
+
MediaStateChangeReason,
|
|
10
|
+
MediaType,
|
|
11
|
+
MediaDirection,
|
|
12
|
+
} from '../gen/video/coordinator/stat_v1/stat';
|
|
13
|
+
import { pairwise, throttleTime } from 'rxjs';
|
|
14
|
+
import { StreamVideoParticipant } from '../rtc/types';
|
|
15
|
+
import { TrackType } from '../gen/video/sfu/models/models';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Collects stat metrics and events from the state store and sends them to the Coordinator API
|
|
19
|
+
* @param readOnlyStateStore
|
|
20
|
+
* @param sendStatMetrics
|
|
21
|
+
* @param sendStatEvent
|
|
22
|
+
*/
|
|
23
|
+
export const reportStats = (
|
|
24
|
+
readOnlyStateStore: StreamVideoReadOnlyStateStore,
|
|
25
|
+
sendStatMetrics: (stats: Object) => Promise<ReportCallStatsResponse | void>,
|
|
26
|
+
sendStatEvent: (
|
|
27
|
+
statEvent: ReportCallStatEventRequest['event'],
|
|
28
|
+
) => Promise<ReportCallStatEventResponse | void>,
|
|
29
|
+
) => {
|
|
30
|
+
reportStatMetrics(readOnlyStateStore, sendStatMetrics);
|
|
31
|
+
reportStatEvents(readOnlyStateStore, sendStatEvent);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const reportStatMetrics = (
|
|
35
|
+
readOnlyStateStore: StreamVideoReadOnlyStateStore,
|
|
36
|
+
sendStatMetrics: (stats: Object) => Promise<ReportCallStatsResponse | void>,
|
|
37
|
+
) => {
|
|
38
|
+
readOnlyStateStore.callStatsReport$
|
|
39
|
+
.pipe(throttleTime(15000))
|
|
40
|
+
.subscribe((report) => {
|
|
41
|
+
if (report?.publisherRawStats) {
|
|
42
|
+
const s: Record<string, any> = {};
|
|
43
|
+
report.publisherRawStats.forEach((v) => {
|
|
44
|
+
s[v.id] = v;
|
|
45
|
+
});
|
|
46
|
+
sendStatMetrics(s);
|
|
47
|
+
}
|
|
48
|
+
if (report?.subscriberRawStats) {
|
|
49
|
+
const s: Record<string, any> = {};
|
|
50
|
+
report.subscriberRawStats.forEach((v) => {
|
|
51
|
+
s[v.id] = v;
|
|
52
|
+
});
|
|
53
|
+
sendStatMetrics(s);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const reportStatEvents = (
|
|
59
|
+
store: StreamVideoReadOnlyStateStore,
|
|
60
|
+
sendStatEvent: (
|
|
61
|
+
statEvent: ReportCallStatEventRequest['event'],
|
|
62
|
+
) => Promise<ReportCallStatEventResponse | void>,
|
|
63
|
+
) => {
|
|
64
|
+
store.localParticipant$
|
|
65
|
+
.pipe(pairwise())
|
|
66
|
+
.subscribe(([prevLocalParticipant, currentLocalParticipant]) => {
|
|
67
|
+
if (!prevLocalParticipant && currentLocalParticipant) {
|
|
68
|
+
const event: ReportCallStatEventRequest['event'] = {
|
|
69
|
+
oneofKind: 'participantConnected',
|
|
70
|
+
participantConnected: {},
|
|
71
|
+
};
|
|
72
|
+
sendStatEvent(event);
|
|
73
|
+
}
|
|
74
|
+
if (prevLocalParticipant && !currentLocalParticipant) {
|
|
75
|
+
const event: ReportCallStatEventRequest['event'] = {
|
|
76
|
+
oneofKind: 'participantDisconnected',
|
|
77
|
+
participantDisconnected: {},
|
|
78
|
+
};
|
|
79
|
+
sendStatEvent(event);
|
|
80
|
+
}
|
|
81
|
+
if (
|
|
82
|
+
(!prevLocalParticipant?.audioStream &&
|
|
83
|
+
currentLocalParticipant?.audioStream) ||
|
|
84
|
+
(prevLocalParticipant?.audioStream &&
|
|
85
|
+
!currentLocalParticipant?.audioStream)
|
|
86
|
+
) {
|
|
87
|
+
const event: ReportCallStatEventRequest['event'] = {
|
|
88
|
+
oneofKind: 'mediaStateChanged',
|
|
89
|
+
mediaStateChanged: {
|
|
90
|
+
mediaType: MediaType.AUDIO,
|
|
91
|
+
change: currentLocalParticipant?.audioStream
|
|
92
|
+
? MediaStateChange.STARTED
|
|
93
|
+
: MediaStateChange.ENDED,
|
|
94
|
+
reason: MediaStateChangeReason.CONNECTION,
|
|
95
|
+
direction: MediaDirection.SEND,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
sendStatEvent(event);
|
|
99
|
+
}
|
|
100
|
+
if (
|
|
101
|
+
(!prevLocalParticipant?.videoStream &&
|
|
102
|
+
currentLocalParticipant?.videoStream) ||
|
|
103
|
+
(prevLocalParticipant?.videoStream &&
|
|
104
|
+
!currentLocalParticipant?.videoStream)
|
|
105
|
+
) {
|
|
106
|
+
const event: ReportCallStatEventRequest['event'] = {
|
|
107
|
+
oneofKind: 'mediaStateChanged',
|
|
108
|
+
mediaStateChanged: {
|
|
109
|
+
mediaType: MediaType.VIDEO,
|
|
110
|
+
change: currentLocalParticipant?.videoStream
|
|
111
|
+
? MediaStateChange.STARTED
|
|
112
|
+
: MediaStateChange.ENDED,
|
|
113
|
+
reason: MediaStateChangeReason.CONNECTION,
|
|
114
|
+
direction: MediaDirection.SEND,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
sendStatEvent(event);
|
|
118
|
+
}
|
|
119
|
+
if (prevLocalParticipant && currentLocalParticipant) {
|
|
120
|
+
if (
|
|
121
|
+
isPublishingTrackOfType(prevLocalParticipant, TrackType.AUDIO) !==
|
|
122
|
+
isPublishingTrackOfType(currentLocalParticipant, TrackType.AUDIO)
|
|
123
|
+
) {
|
|
124
|
+
const event: ReportCallStatEventRequest['event'] = {
|
|
125
|
+
oneofKind: 'mediaStateChanged',
|
|
126
|
+
mediaStateChanged: {
|
|
127
|
+
mediaType: MediaType.AUDIO,
|
|
128
|
+
change: isPublishingTrackOfType(
|
|
129
|
+
currentLocalParticipant,
|
|
130
|
+
TrackType.AUDIO,
|
|
131
|
+
)
|
|
132
|
+
? MediaStateChange.ENDED
|
|
133
|
+
: MediaStateChange.STARTED,
|
|
134
|
+
reason: MediaStateChangeReason.MUTE,
|
|
135
|
+
direction: MediaDirection.SEND,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
sendStatEvent(event);
|
|
139
|
+
}
|
|
140
|
+
if (
|
|
141
|
+
isPublishingTrackOfType(prevLocalParticipant, TrackType.VIDEO) !==
|
|
142
|
+
isPublishingTrackOfType(currentLocalParticipant, TrackType.VIDEO)
|
|
143
|
+
) {
|
|
144
|
+
const event: ReportCallStatEventRequest['event'] = {
|
|
145
|
+
oneofKind: 'mediaStateChanged',
|
|
146
|
+
mediaStateChanged: {
|
|
147
|
+
mediaType: MediaType.VIDEO,
|
|
148
|
+
change: isPublishingTrackOfType(
|
|
149
|
+
currentLocalParticipant,
|
|
150
|
+
TrackType.VIDEO,
|
|
151
|
+
)
|
|
152
|
+
? MediaStateChange.ENDED
|
|
153
|
+
: MediaStateChange.STARTED,
|
|
154
|
+
reason: MediaStateChangeReason.MUTE,
|
|
155
|
+
direction: MediaDirection.SEND,
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
sendStatEvent(event);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const isPublishingTrackOfType = (
|
|
165
|
+
participant: StreamVideoParticipant,
|
|
166
|
+
type: TrackType,
|
|
167
|
+
) => participant.publishedTracks.includes(type);
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AggregatedStatsReport,
|
|
3
|
+
BaseStats,
|
|
4
|
+
CallStatsReport,
|
|
5
|
+
ParticipantsStatsReport,
|
|
6
|
+
StatsReport,
|
|
7
|
+
} from './types';
|
|
8
|
+
import { StreamVideoWriteableStateStore } from '../store';
|
|
9
|
+
import { Publisher } from '../rtc/publisher';
|
|
10
|
+
|
|
11
|
+
export type StatsReporterOpts = {
|
|
12
|
+
subscriber: RTCPeerConnection;
|
|
13
|
+
publisher: Publisher;
|
|
14
|
+
store: StreamVideoWriteableStateStore;
|
|
15
|
+
pollingIntervalInMs?: number;
|
|
16
|
+
edgeName?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type StatsReporter = {
|
|
20
|
+
/**
|
|
21
|
+
* Will turn on stats reporting for a given sessionId.
|
|
22
|
+
*
|
|
23
|
+
* @param sessionId the session id.
|
|
24
|
+
*/
|
|
25
|
+
startReportingStatsFor: (sessionId: string) => void;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Will turn off stats reporting for a given sessionId.
|
|
29
|
+
*
|
|
30
|
+
* @param sessionId the session id.
|
|
31
|
+
*/
|
|
32
|
+
stopReportingStatsFor: (sessionId: string) => void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Helper method for retrieving stats for a given peer connection kind
|
|
36
|
+
* and media stream flowing through it.
|
|
37
|
+
*
|
|
38
|
+
* @param kind the peer connection kind (subscriber or publisher).
|
|
39
|
+
* @param mediaStream the media stream.
|
|
40
|
+
*/
|
|
41
|
+
getStatsForStream: (
|
|
42
|
+
kind: 'subscriber' | 'publisher',
|
|
43
|
+
mediaStream: MediaStream,
|
|
44
|
+
) => Promise<StatsReport[]>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Helper method for retrieving raw stats for a given peer connection kind.
|
|
48
|
+
*
|
|
49
|
+
* @param kind the peer connection kind (subscriber or publisher).
|
|
50
|
+
* @param selector the track selector. If not provided, stats for all tracks will be returned.
|
|
51
|
+
*/
|
|
52
|
+
getRawStatsForTrack: (
|
|
53
|
+
kind: 'subscriber' | 'publisher',
|
|
54
|
+
selector?: MediaStreamTrack,
|
|
55
|
+
) => Promise<RTCStatsReport | undefined>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Stops the stats reporter and releases all resources.
|
|
59
|
+
*/
|
|
60
|
+
stop: () => void;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates a new StatsReporter instance that collects metrics about the ongoing call and reports them to the state store
|
|
65
|
+
*/
|
|
66
|
+
export const createStatsReporter = ({
|
|
67
|
+
subscriber,
|
|
68
|
+
publisher,
|
|
69
|
+
store,
|
|
70
|
+
edgeName,
|
|
71
|
+
pollingIntervalInMs = 2000,
|
|
72
|
+
}: StatsReporterOpts): StatsReporter => {
|
|
73
|
+
const getRawStatsForTrack = async (
|
|
74
|
+
kind: 'subscriber' | 'publisher',
|
|
75
|
+
selector?: MediaStreamTrack,
|
|
76
|
+
) => {
|
|
77
|
+
if (kind === 'subscriber' && subscriber) {
|
|
78
|
+
return subscriber.getStats(selector);
|
|
79
|
+
} else if (kind === 'publisher' && publisher) {
|
|
80
|
+
return publisher.getStats(selector);
|
|
81
|
+
} else {
|
|
82
|
+
console.warn(`Can't retrieve RTC stats for`, kind);
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const getStatsForStream = async (
|
|
88
|
+
kind: 'subscriber' | 'publisher',
|
|
89
|
+
mediaStream: MediaStream,
|
|
90
|
+
) => {
|
|
91
|
+
const pc = kind === 'subscriber' ? subscriber : publisher;
|
|
92
|
+
const statsForStream: StatsReport[] = [];
|
|
93
|
+
for (let track of mediaStream.getTracks()) {
|
|
94
|
+
const report = await pc.getStats(track);
|
|
95
|
+
const stats = transform(report, {
|
|
96
|
+
// @ts-ignore
|
|
97
|
+
trackKind: track.kind,
|
|
98
|
+
kind,
|
|
99
|
+
});
|
|
100
|
+
statsForStream.push(stats);
|
|
101
|
+
}
|
|
102
|
+
return statsForStream;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const startReportingStatsFor = (sessionId: string) => {
|
|
106
|
+
sessionIdsToTrack.add(sessionId);
|
|
107
|
+
void run();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const stopReportingStatsFor = (sessionId: string) => {
|
|
111
|
+
sessionIdsToTrack.delete(sessionId);
|
|
112
|
+
void run();
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const sessionIdsToTrack = new Set<string>();
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The main stats reporting loop.
|
|
119
|
+
*/
|
|
120
|
+
const run = async () => {
|
|
121
|
+
const participants = store.getCurrentValue(store.participantsSubject);
|
|
122
|
+
const participantStats: ParticipantsStatsReport = {};
|
|
123
|
+
const sessionIds = new Set(sessionIdsToTrack);
|
|
124
|
+
if (sessionIds.size > 0) {
|
|
125
|
+
for (let participant of participants) {
|
|
126
|
+
if (!sessionIds.has(participant.sessionId)) continue;
|
|
127
|
+
const kind = participant.isLoggedInUser ? 'publisher' : 'subscriber';
|
|
128
|
+
try {
|
|
129
|
+
const mergedStream = new MediaStream([
|
|
130
|
+
...(participant.videoStream?.getVideoTracks() || []),
|
|
131
|
+
...(participant.audioStream?.getAudioTracks() || []),
|
|
132
|
+
]);
|
|
133
|
+
participantStats[participant.sessionId] = await getStatsForStream(
|
|
134
|
+
kind,
|
|
135
|
+
mergedStream,
|
|
136
|
+
);
|
|
137
|
+
mergedStream.getTracks().forEach((t) => {
|
|
138
|
+
mergedStream.removeTrack(t);
|
|
139
|
+
});
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error(`Failed to collect stats for ${kind}`, participant, e);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const [subscriberStats, publisherStats] = await Promise.all([
|
|
147
|
+
subscriber
|
|
148
|
+
.getStats()
|
|
149
|
+
.then((report) =>
|
|
150
|
+
transform(report, {
|
|
151
|
+
kind: 'subscriber',
|
|
152
|
+
trackKind: 'video',
|
|
153
|
+
}),
|
|
154
|
+
)
|
|
155
|
+
.then(aggregate),
|
|
156
|
+
publisher
|
|
157
|
+
.getStats()
|
|
158
|
+
.then((report) =>
|
|
159
|
+
transform(report, {
|
|
160
|
+
kind: 'publisher',
|
|
161
|
+
trackKind: 'video',
|
|
162
|
+
}),
|
|
163
|
+
)
|
|
164
|
+
.then(aggregate),
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
const [subscriberRawStats, publisherRawStats] = await Promise.all([
|
|
168
|
+
getRawStatsForTrack('subscriber'),
|
|
169
|
+
getRawStatsForTrack('publisher'),
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
const statsReport: CallStatsReport = {
|
|
173
|
+
datacenter: edgeName || 'N/A',
|
|
174
|
+
publisherStats,
|
|
175
|
+
subscriberStats,
|
|
176
|
+
subscriberRawStats,
|
|
177
|
+
publisherRawStats,
|
|
178
|
+
participants: participantStats,
|
|
179
|
+
timestamp: Date.now(),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
store.setCurrentValue(store.callStatsReportSubject, statsReport);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
let timeoutId: NodeJS.Timeout | undefined;
|
|
186
|
+
if (pollingIntervalInMs > 0) {
|
|
187
|
+
const loop = async () => {
|
|
188
|
+
await run().catch((e) => {
|
|
189
|
+
console.log('Failed to collect stats', e);
|
|
190
|
+
});
|
|
191
|
+
timeoutId = setTimeout(loop, pollingIntervalInMs);
|
|
192
|
+
};
|
|
193
|
+
void loop();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const stop = () => {
|
|
197
|
+
if (timeoutId) {
|
|
198
|
+
clearTimeout(timeoutId);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
getRawStatsForTrack,
|
|
204
|
+
getStatsForStream,
|
|
205
|
+
startReportingStatsFor,
|
|
206
|
+
stopReportingStatsFor,
|
|
207
|
+
stop,
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export type StatsTransformOpts = {
|
|
212
|
+
/**
|
|
213
|
+
* The kind of track we are transforming stats for.
|
|
214
|
+
*/
|
|
215
|
+
trackKind: 'audio' | 'video';
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The kind of peer connection we are transforming stats for.
|
|
219
|
+
*/
|
|
220
|
+
kind: 'subscriber' | 'publisher';
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Transforms raw RTC stats into a slimmer and uniform across browsers format.
|
|
225
|
+
*
|
|
226
|
+
* @param report the report to transform.
|
|
227
|
+
* @param opts the transform options.
|
|
228
|
+
*/
|
|
229
|
+
const transform = (
|
|
230
|
+
report: RTCStatsReport,
|
|
231
|
+
opts: StatsTransformOpts,
|
|
232
|
+
): StatsReport => {
|
|
233
|
+
const { trackKind, kind } = opts;
|
|
234
|
+
const direction = kind === 'subscriber' ? 'inbound-rtp' : 'outbound-rtp';
|
|
235
|
+
const stats = flatten(report);
|
|
236
|
+
const streams = stats
|
|
237
|
+
.filter(
|
|
238
|
+
(stat) =>
|
|
239
|
+
stat.type === direction &&
|
|
240
|
+
(stat as RTCRtpStreamStats).kind === trackKind,
|
|
241
|
+
)
|
|
242
|
+
.map((stat): BaseStats => {
|
|
243
|
+
const rtcStreamStats = stat as RTCInboundRtpStreamStats &
|
|
244
|
+
RTCOutboundRtpStreamStats;
|
|
245
|
+
|
|
246
|
+
const codec = stats.find(
|
|
247
|
+
(s) => s.type === 'codec' && s.id === rtcStreamStats.codecId,
|
|
248
|
+
) as { mimeType: string } | undefined; // FIXME OL: incorrect type!
|
|
249
|
+
|
|
250
|
+
const transport = stats.find(
|
|
251
|
+
(s) => s.type === 'transport' && s.id === rtcStreamStats.transportId,
|
|
252
|
+
) as RTCTransportStats | undefined;
|
|
253
|
+
|
|
254
|
+
let roundTripTime: number | undefined;
|
|
255
|
+
if (transport && transport.dtlsState === 'connected') {
|
|
256
|
+
const candidatePair = stats.find(
|
|
257
|
+
(s) =>
|
|
258
|
+
s.type === 'candidate-pair' &&
|
|
259
|
+
s.id === transport.selectedCandidatePairId,
|
|
260
|
+
) as RTCIceCandidatePairStats | undefined;
|
|
261
|
+
roundTripTime = candidatePair?.currentRoundTripTime;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
bytesSent: rtcStreamStats.bytesSent,
|
|
266
|
+
bytesReceived: rtcStreamStats.bytesReceived,
|
|
267
|
+
codec: codec?.mimeType,
|
|
268
|
+
currentRoundTripTime: roundTripTime,
|
|
269
|
+
frameHeight: rtcStreamStats.frameHeight,
|
|
270
|
+
frameWidth: rtcStreamStats.frameWidth,
|
|
271
|
+
framesPerSecond: rtcStreamStats.framesPerSecond,
|
|
272
|
+
jitter: rtcStreamStats.jitter,
|
|
273
|
+
kind: rtcStreamStats.kind,
|
|
274
|
+
// @ts-ignore: available in Chrome only, TS doesn't recognize this
|
|
275
|
+
qualityLimitationReason: rtcStreamStats.qualityLimitationReason,
|
|
276
|
+
rid: rtcStreamStats.rid,
|
|
277
|
+
ssrc: rtcStreamStats.ssrc,
|
|
278
|
+
};
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
rawStats: report,
|
|
283
|
+
streams,
|
|
284
|
+
timestamp: Date.now(),
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Aggregates generic stats.
|
|
290
|
+
*
|
|
291
|
+
* @param stats the stats to aggregate.
|
|
292
|
+
*/
|
|
293
|
+
const aggregate = (stats: StatsReport): AggregatedStatsReport => {
|
|
294
|
+
const aggregatedStats: AggregatedStatsReport = {
|
|
295
|
+
totalBytesSent: 0,
|
|
296
|
+
totalBytesReceived: 0,
|
|
297
|
+
averageJitterInMs: 0,
|
|
298
|
+
averageRoundTripTimeInMs: 0,
|
|
299
|
+
qualityLimitationReasons: 'none',
|
|
300
|
+
highestFrameWidth: 0,
|
|
301
|
+
highestFrameHeight: 0,
|
|
302
|
+
highestFramesPerSecond: 0,
|
|
303
|
+
timestamp: Date.now(),
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
let maxArea = -1;
|
|
307
|
+
const area = (w: number, h: number) => w * h;
|
|
308
|
+
|
|
309
|
+
const qualityLimitationReasons = new Set<string>();
|
|
310
|
+
const streams = stats.streams;
|
|
311
|
+
const report = streams.reduce((acc, stream) => {
|
|
312
|
+
acc.totalBytesSent += stream.bytesSent || 0;
|
|
313
|
+
acc.totalBytesReceived += stream.bytesReceived || 0;
|
|
314
|
+
acc.averageJitterInMs += stream.jitter || 0;
|
|
315
|
+
acc.averageRoundTripTimeInMs += stream.currentRoundTripTime || 0;
|
|
316
|
+
|
|
317
|
+
// naive calculation of the highest resolution
|
|
318
|
+
const streamArea = area(stream.frameWidth || 0, stream.frameHeight || 0);
|
|
319
|
+
if (streamArea > maxArea) {
|
|
320
|
+
acc.highestFrameWidth = stream.frameWidth || 0;
|
|
321
|
+
acc.highestFrameHeight = stream.frameHeight || 0;
|
|
322
|
+
acc.highestFramesPerSecond = stream.framesPerSecond || 0;
|
|
323
|
+
maxArea = streamArea;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
qualityLimitationReasons.add(stream.qualityLimitationReason || '');
|
|
327
|
+
return acc;
|
|
328
|
+
}, aggregatedStats);
|
|
329
|
+
|
|
330
|
+
if (streams.length > 0) {
|
|
331
|
+
report.averageJitterInMs = Math.round(
|
|
332
|
+
(report.averageJitterInMs / streams.length) * 1000,
|
|
333
|
+
);
|
|
334
|
+
report.averageRoundTripTimeInMs = Math.round(
|
|
335
|
+
(report.averageRoundTripTimeInMs / streams.length) * 1000,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const qualityLimitationReason = [
|
|
340
|
+
qualityLimitationReasons.has('cpu') && 'cpu',
|
|
341
|
+
qualityLimitationReasons.has('bandwidth') && 'bandwidth',
|
|
342
|
+
qualityLimitationReasons.has('other') && 'other',
|
|
343
|
+
]
|
|
344
|
+
.filter(Boolean)
|
|
345
|
+
.join(', ');
|
|
346
|
+
if (qualityLimitationReason) {
|
|
347
|
+
report.qualityLimitationReasons = qualityLimitationReason;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return report;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Flatten the stats report into an array of stats objects.
|
|
355
|
+
*
|
|
356
|
+
* @param report the report to flatten.
|
|
357
|
+
*/
|
|
358
|
+
const flatten = (report: RTCStatsReport) => {
|
|
359
|
+
const stats: RTCStats[] = [];
|
|
360
|
+
report.forEach((s) => {
|
|
361
|
+
stats.push(s);
|
|
362
|
+
});
|
|
363
|
+
return stats;
|
|
364
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type BaseStats = {
|
|
2
|
+
bytesSent?: number;
|
|
3
|
+
bytesReceived?: number;
|
|
4
|
+
codec?: string;
|
|
5
|
+
currentRoundTripTime?: number;
|
|
6
|
+
frameWidth?: number;
|
|
7
|
+
frameHeight?: number;
|
|
8
|
+
framesPerSecond?: number;
|
|
9
|
+
jitter?: number;
|
|
10
|
+
kind?: string;
|
|
11
|
+
qualityLimitationReason?: string;
|
|
12
|
+
rid?: string;
|
|
13
|
+
ssrc?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type StatsReport = {
|
|
17
|
+
rawStats?: RTCStatsReport;
|
|
18
|
+
streams: BaseStats[];
|
|
19
|
+
timestamp: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type AggregatedStatsReport = {
|
|
23
|
+
totalBytesSent: number;
|
|
24
|
+
totalBytesReceived: number;
|
|
25
|
+
averageJitterInMs: number;
|
|
26
|
+
averageRoundTripTimeInMs: number;
|
|
27
|
+
qualityLimitationReasons: string;
|
|
28
|
+
highestFrameWidth: number;
|
|
29
|
+
highestFrameHeight: number;
|
|
30
|
+
highestFramesPerSecond: number;
|
|
31
|
+
timestamp: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type ParticipantsStatsReport = {
|
|
35
|
+
[sessionId: string]: StatsReport[] | undefined;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type CallStatsReport = {
|
|
39
|
+
datacenter: string;
|
|
40
|
+
publisherStats: AggregatedStatsReport;
|
|
41
|
+
publisherRawStats?: RTCStatsReport;
|
|
42
|
+
subscriberStats: AggregatedStatsReport;
|
|
43
|
+
subscriberRawStats?: RTCStatsReport;
|
|
44
|
+
participants: ParticipantsStatsReport;
|
|
45
|
+
timestamp: number;
|
|
46
|
+
};
|