@stream-io/video-client 1.0.3 → 1.0.4
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/CHANGELOG.md +7 -0
- package/dist/index.browser.es.js +53 -43
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +53 -43
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +53 -43
- package/dist/index.es.js.map +1 -1
- package/dist/src/stats/SfuStatsReporter.d.ts +2 -2
- package/dist/src/stats/stateStoreStatsReporter.d.ts +3 -2
- package/dist/src/stats/utils.d.ts +3 -0
- package/package.json +1 -1
- package/src/Call.ts +12 -5
- package/src/stats/SfuStatsReporter.ts +6 -15
- package/src/stats/stateStoreStatsReporter.ts +29 -21
- package/src/stats/utils.ts +15 -11
|
@@ -6,14 +6,14 @@ export type SfuStatsReporterOptions = {
|
|
|
6
6
|
options: StatsOptions;
|
|
7
7
|
clientDetails: LocalClientDetailsType;
|
|
8
8
|
subscriber: Subscriber;
|
|
9
|
-
publisher
|
|
9
|
+
publisher?: Publisher;
|
|
10
10
|
};
|
|
11
11
|
export declare class SfuStatsReporter {
|
|
12
12
|
private readonly logger;
|
|
13
13
|
readonly options: StatsOptions;
|
|
14
14
|
private readonly sfuClient;
|
|
15
15
|
private readonly subscriber;
|
|
16
|
-
private readonly publisher
|
|
16
|
+
private readonly publisher?;
|
|
17
17
|
private intervalId;
|
|
18
18
|
private readonly sdkName;
|
|
19
19
|
private readonly sdkVersion;
|
|
@@ -3,8 +3,9 @@ import { CallState } from '../store';
|
|
|
3
3
|
import { Publisher, Subscriber } from '../rtc';
|
|
4
4
|
export type StatsReporterOpts = {
|
|
5
5
|
subscriber: Subscriber;
|
|
6
|
-
publisher
|
|
6
|
+
publisher?: Publisher;
|
|
7
7
|
state: CallState;
|
|
8
|
+
datacenter: string;
|
|
8
9
|
pollingIntervalInMs?: number;
|
|
9
10
|
};
|
|
10
11
|
export type StatsReporter = {
|
|
@@ -43,7 +44,7 @@ export type StatsReporter = {
|
|
|
43
44
|
/**
|
|
44
45
|
* Creates a new StatsReporter instance that collects metrics about the ongoing call and reports them to the state store
|
|
45
46
|
*/
|
|
46
|
-
export declare const createStatsReporter: ({ subscriber, publisher, state, pollingIntervalInMs, }: StatsReporterOpts) => StatsReporter;
|
|
47
|
+
export declare const createStatsReporter: ({ subscriber, publisher, state, datacenter, pollingIntervalInMs, }: StatsReporterOpts) => StatsReporter;
|
|
47
48
|
export type StatsTransformOpts = {
|
|
48
49
|
/**
|
|
49
50
|
* The kind of track we are transforming stats for.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LocalClientDetailsType } from '../client-details';
|
|
2
|
+
import { Sdk } from '../gen/video/sfu/models/models';
|
|
2
3
|
/**
|
|
3
4
|
* Flatten the stats report into an array of stats objects.
|
|
4
5
|
*
|
|
@@ -15,3 +16,5 @@ export declare const getSdkSignature: (clientDetails: LocalClientDetailsType) =>
|
|
|
15
16
|
sdkName: string;
|
|
16
17
|
sdkVersion: string;
|
|
17
18
|
};
|
|
19
|
+
export declare const getSdkName: (sdk: Sdk | undefined) => "stream-react" | "stream-react-native" | "stream-js";
|
|
20
|
+
export declare const getSdkVersion: (sdk: Sdk | undefined) => string;
|
package/package.json
CHANGED
package/src/Call.ts
CHANGED
|
@@ -1001,7 +1001,10 @@ export class Call {
|
|
|
1001
1001
|
});
|
|
1002
1002
|
}
|
|
1003
1003
|
|
|
1004
|
-
|
|
1004
|
+
// anonymous users can't publish anything hence, there is no need
|
|
1005
|
+
// to create Publisher Peer Connection for them
|
|
1006
|
+
const isAnonymous = this.streamClient.user?.type === 'anonymous';
|
|
1007
|
+
if (!this.publisher && !isAnonymous) {
|
|
1005
1008
|
const audioSettings = this.state.settings?.audio;
|
|
1006
1009
|
const isDtxEnabled = !!audioSettings?.opus_dtx_enabled;
|
|
1007
1010
|
const isRedEnabled = !!audioSettings?.redundant_coding_enabled;
|
|
@@ -1020,6 +1023,7 @@ export class Call {
|
|
|
1020
1023
|
subscriber: this.subscriber,
|
|
1021
1024
|
publisher: this.publisher,
|
|
1022
1025
|
state: this.state,
|
|
1026
|
+
datacenter: this.sfuClient.edgeName,
|
|
1023
1027
|
});
|
|
1024
1028
|
}
|
|
1025
1029
|
|
|
@@ -1069,14 +1073,17 @@ export class Call {
|
|
|
1069
1073
|
}
|
|
1070
1074
|
if (isMigrating) {
|
|
1071
1075
|
await this.subscriber.migrateTo(sfuClient, connectionConfig);
|
|
1072
|
-
await this.publisher
|
|
1076
|
+
await this.publisher?.migrateTo(sfuClient, connectionConfig);
|
|
1073
1077
|
} else if (isReconnecting) {
|
|
1074
1078
|
if (reconnected) {
|
|
1075
1079
|
// update the SFU client instance on the subscriber and publisher
|
|
1076
1080
|
this.subscriber.setSfuClient(sfuClient);
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1081
|
+
// publisher might not be there (anonymous users)
|
|
1082
|
+
if (this.publisher) {
|
|
1083
|
+
this.publisher.setSfuClient(sfuClient);
|
|
1084
|
+
// and perform a full ICE restart on the publisher
|
|
1085
|
+
await this.publisher.restartIce();
|
|
1086
|
+
}
|
|
1080
1087
|
} else if (previousSfuClient?.isFastReconnecting) {
|
|
1081
1088
|
// reconnection wasn't possible, so we need to do a full rejoin
|
|
1082
1089
|
return await reconnect('full', 're-attempting').catch((err) => {
|
|
@@ -2,15 +2,14 @@ import { StreamSfuClient } from '../StreamSfuClient';
|
|
|
2
2
|
import { StatsOptions } from '../gen/coordinator';
|
|
3
3
|
import { getLogger } from '../logger';
|
|
4
4
|
import { Publisher, Subscriber } from '../rtc';
|
|
5
|
-
import {
|
|
6
|
-
import { flatten } from './utils';
|
|
5
|
+
import { flatten, getSdkName, getSdkVersion } from './utils';
|
|
7
6
|
import { getWebRTCInfo, LocalClientDetailsType } from '../client-details';
|
|
8
7
|
|
|
9
8
|
export type SfuStatsReporterOptions = {
|
|
10
9
|
options: StatsOptions;
|
|
11
10
|
clientDetails: LocalClientDetailsType;
|
|
12
11
|
subscriber: Subscriber;
|
|
13
|
-
publisher
|
|
12
|
+
publisher?: Publisher;
|
|
14
13
|
};
|
|
15
14
|
|
|
16
15
|
export class SfuStatsReporter {
|
|
@@ -20,7 +19,7 @@ export class SfuStatsReporter {
|
|
|
20
19
|
|
|
21
20
|
private readonly sfuClient: StreamSfuClient;
|
|
22
21
|
private readonly subscriber: Subscriber;
|
|
23
|
-
private readonly publisher
|
|
22
|
+
private readonly publisher?: Publisher;
|
|
24
23
|
|
|
25
24
|
private intervalId: NodeJS.Timeout | undefined;
|
|
26
25
|
private readonly sdkName: string;
|
|
@@ -39,16 +38,8 @@ export class SfuStatsReporter {
|
|
|
39
38
|
|
|
40
39
|
const { sdk, browser } = clientDetails;
|
|
41
40
|
|
|
42
|
-
this.sdkName =
|
|
43
|
-
|
|
44
|
-
? 'stream-react'
|
|
45
|
-
: sdk && sdk.type === SdkType.REACT_NATIVE
|
|
46
|
-
? 'stream-react-native'
|
|
47
|
-
: 'stream-js';
|
|
48
|
-
|
|
49
|
-
this.sdkVersion = sdk
|
|
50
|
-
? `${sdk.major}.${sdk.minor}.${sdk.patch}`
|
|
51
|
-
: '0.0.0-development';
|
|
41
|
+
this.sdkName = getSdkName(sdk);
|
|
42
|
+
this.sdkVersion = getSdkVersion(sdk);
|
|
52
43
|
|
|
53
44
|
// The WebRTC version if passed from the SDK, it is taken else the browser info is sent.
|
|
54
45
|
this.webRTCVersion =
|
|
@@ -60,7 +51,7 @@ export class SfuStatsReporter {
|
|
|
60
51
|
private run = async () => {
|
|
61
52
|
const [subscriberStats, publisherStats] = await Promise.all([
|
|
62
53
|
this.subscriber.getStats().then(flatten).then(JSON.stringify),
|
|
63
|
-
this.publisher
|
|
54
|
+
this.publisher?.getStats().then(flatten).then(JSON.stringify) ?? '[]',
|
|
64
55
|
]);
|
|
65
56
|
|
|
66
57
|
await this.sfuClient.sendStats({
|
|
@@ -11,8 +11,9 @@ import { flatten } from './utils';
|
|
|
11
11
|
|
|
12
12
|
export type StatsReporterOpts = {
|
|
13
13
|
subscriber: Subscriber;
|
|
14
|
-
publisher
|
|
14
|
+
publisher?: Publisher;
|
|
15
15
|
state: CallState;
|
|
16
|
+
datacenter: string;
|
|
16
17
|
pollingIntervalInMs?: number;
|
|
17
18
|
};
|
|
18
19
|
|
|
@@ -67,6 +68,7 @@ export const createStatsReporter = ({
|
|
|
67
68
|
subscriber,
|
|
68
69
|
publisher,
|
|
69
70
|
state,
|
|
71
|
+
datacenter,
|
|
70
72
|
pollingIntervalInMs = 2000,
|
|
71
73
|
}: StatsReporterOpts): StatsReporter => {
|
|
72
74
|
const logger = getLogger(['stats']);
|
|
@@ -79,7 +81,6 @@ export const createStatsReporter = ({
|
|
|
79
81
|
} else if (kind === 'publisher' && publisher) {
|
|
80
82
|
return publisher.getStats(selector);
|
|
81
83
|
} else {
|
|
82
|
-
logger('warn', `Can't retrieve RTC stats for ${kind}`);
|
|
83
84
|
return undefined;
|
|
84
85
|
}
|
|
85
86
|
};
|
|
@@ -89,6 +90,7 @@ export const createStatsReporter = ({
|
|
|
89
90
|
mediaStream: MediaStream,
|
|
90
91
|
) => {
|
|
91
92
|
const pc = kind === 'subscriber' ? subscriber : publisher;
|
|
93
|
+
if (!pc) return [];
|
|
92
94
|
const statsForStream: StatsReport[] = [];
|
|
93
95
|
for (let track of mediaStream.getTracks()) {
|
|
94
96
|
const report = await pc.getStats(track);
|
|
@@ -141,7 +143,7 @@ export const createStatsReporter = ({
|
|
|
141
143
|
} catch (e) {
|
|
142
144
|
logger(
|
|
143
145
|
'error',
|
|
144
|
-
`Failed to collect stats for ${kind}
|
|
146
|
+
`Failed to collect stats for ${kind} of ${participant.userId}`,
|
|
145
147
|
e,
|
|
146
148
|
);
|
|
147
149
|
}
|
|
@@ -159,23 +161,25 @@ export const createStatsReporter = ({
|
|
|
159
161
|
)
|
|
160
162
|
.then(aggregate),
|
|
161
163
|
publisher
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
? publisher
|
|
165
|
+
.getStats()
|
|
166
|
+
.then((report) =>
|
|
167
|
+
transform(report, {
|
|
168
|
+
kind: 'publisher',
|
|
169
|
+
trackKind: 'video',
|
|
170
|
+
}),
|
|
171
|
+
)
|
|
172
|
+
.then(aggregate)
|
|
173
|
+
: getEmptyStats(),
|
|
170
174
|
]);
|
|
171
175
|
|
|
172
176
|
const [subscriberRawStats, publisherRawStats] = await Promise.all([
|
|
173
177
|
getRawStatsForTrack('subscriber'),
|
|
174
|
-
getRawStatsForTrack('publisher'),
|
|
178
|
+
publisher ? getRawStatsForTrack('publisher') : undefined,
|
|
175
179
|
]);
|
|
176
180
|
|
|
177
181
|
state.setCallStatsReport({
|
|
178
|
-
datacenter
|
|
182
|
+
datacenter,
|
|
179
183
|
publisherStats,
|
|
180
184
|
subscriberStats,
|
|
181
185
|
subscriberRawStats,
|
|
@@ -288,14 +292,9 @@ const transform = (
|
|
|
288
292
|
};
|
|
289
293
|
};
|
|
290
294
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
* @param stats the stats to aggregate.
|
|
295
|
-
*/
|
|
296
|
-
const aggregate = (stats: StatsReport): AggregatedStatsReport => {
|
|
297
|
-
const aggregatedStats: AggregatedStatsReport = {
|
|
298
|
-
rawReport: stats,
|
|
295
|
+
const getEmptyStats = (stats?: StatsReport): AggregatedStatsReport => {
|
|
296
|
+
return {
|
|
297
|
+
rawReport: stats ?? { streams: [], timestamp: Date.now() },
|
|
299
298
|
totalBytesSent: 0,
|
|
300
299
|
totalBytesReceived: 0,
|
|
301
300
|
averageJitterInMs: 0,
|
|
@@ -306,6 +305,15 @@ const aggregate = (stats: StatsReport): AggregatedStatsReport => {
|
|
|
306
305
|
highestFramesPerSecond: 0,
|
|
307
306
|
timestamp: Date.now(),
|
|
308
307
|
};
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Aggregates generic stats.
|
|
312
|
+
*
|
|
313
|
+
* @param stats the stats to aggregate.
|
|
314
|
+
*/
|
|
315
|
+
const aggregate = (stats: StatsReport): AggregatedStatsReport => {
|
|
316
|
+
const aggregatedStats = getEmptyStats(stats);
|
|
309
317
|
|
|
310
318
|
let maxArea = -1;
|
|
311
319
|
const area = (w: number, h: number) => w * h;
|
package/src/stats/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LocalClientDetailsType } from '../client-details';
|
|
2
|
-
import { SdkType } from '../gen/video/sfu/models/models';
|
|
2
|
+
import { Sdk, SdkType } from '../gen/video/sfu/models/models';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Flatten the stats report into an array of stats objects.
|
|
@@ -16,16 +16,8 @@ export const flatten = (report: RTCStatsReport) => {
|
|
|
16
16
|
|
|
17
17
|
export const getSdkSignature = (clientDetails: LocalClientDetailsType) => {
|
|
18
18
|
const { sdk, ...platform } = clientDetails;
|
|
19
|
-
const sdkName =
|
|
20
|
-
|
|
21
|
-
? 'stream-react'
|
|
22
|
-
: sdk && sdk.type === SdkType.REACT_NATIVE
|
|
23
|
-
? 'stream-react-native'
|
|
24
|
-
: 'stream-js';
|
|
25
|
-
|
|
26
|
-
const sdkVersion = sdk
|
|
27
|
-
? `${sdk.major}.${sdk.minor}.${sdk.patch}`
|
|
28
|
-
: '0.0.0-development';
|
|
19
|
+
const sdkName = getSdkName(sdk);
|
|
20
|
+
const sdkVersion = getSdkVersion(sdk);
|
|
29
21
|
|
|
30
22
|
return {
|
|
31
23
|
sdkName,
|
|
@@ -33,3 +25,15 @@ export const getSdkSignature = (clientDetails: LocalClientDetailsType) => {
|
|
|
33
25
|
...platform,
|
|
34
26
|
};
|
|
35
27
|
};
|
|
28
|
+
|
|
29
|
+
export const getSdkName = (sdk: Sdk | undefined) => {
|
|
30
|
+
return sdk && sdk.type === SdkType.REACT
|
|
31
|
+
? 'stream-react'
|
|
32
|
+
: sdk && sdk.type === SdkType.REACT_NATIVE
|
|
33
|
+
? 'stream-react-native'
|
|
34
|
+
: 'stream-js';
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const getSdkVersion = (sdk: Sdk | undefined) => {
|
|
38
|
+
return sdk ? `${sdk.major}.${sdk.minor}.${sdk.patch}` : '0.0.0-development';
|
|
39
|
+
};
|