@stream-io/video-client 0.0.13 → 0.0.15
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 +14 -0
- package/dist/index.browser.es.js +237 -130
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +239 -129
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +237 -130
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +2 -1
- package/dist/src/StreamSfuClient.d.ts +1 -0
- package/dist/src/StreamVideoClient.d.ts +5 -1
- package/dist/src/coordinator/connection/types.d.ts +3 -2
- package/dist/src/coordinator/connection/utils.d.ts +2 -1
- package/dist/src/logger.d.ts +4 -0
- package/dist/src/rtc/Dispatcher.d.ts +2 -0
- package/dist/src/rtc/IceTrickleBuffer.d.ts +2 -0
- package/dist/src/rtc/publisher.d.ts +1 -0
- package/dist/src/store/CallState.d.ts +2 -0
- package/dist/src/types.d.ts +1 -1
- package/index.ts +1 -0
- package/package.json +1 -1
- package/src/Call.ts +69 -42
- package/src/StreamSfuClient.ts +70 -29
- package/src/StreamVideoClient.ts +46 -3
- package/src/coordinator/connection/client.ts +22 -29
- package/src/coordinator/connection/connection.ts +2 -3
- package/src/coordinator/connection/connection_fallback.ts +0 -1
- package/src/coordinator/connection/types.ts +4 -2
- package/src/coordinator/connection/utils.ts +5 -2
- package/src/devices/devices.ts +10 -3
- package/src/events/__tests__/call-permissions.test.ts +2 -2
- package/src/events/call.ts +11 -4
- package/src/events/sessions.ts +7 -2
- package/src/logger.ts +45 -0
- package/src/rtc/Dispatcher.ts +14 -4
- package/src/rtc/IceTrickleBuffer.ts +8 -1
- package/src/rtc/__tests__/publisher.test.ts +1 -1
- package/src/rtc/codecs.ts +7 -5
- package/src/rtc/flows/join.ts +4 -1
- package/src/rtc/publisher.ts +31 -12
- package/src/rtc/signal.ts +8 -7
- package/src/rtc/subscriber.ts +16 -10
- package/src/stats/state-store-stats-reporter.ts +12 -4
- package/src/store/CallState.ts +7 -2
- package/src/types.ts +3 -2
package/src/rtc/publisher.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
setPreferredCodec,
|
|
25
25
|
toggleDtx,
|
|
26
26
|
} from '../helpers/sdp-munging';
|
|
27
|
+
import { Logger } from '../coordinator/connection/types';
|
|
27
28
|
|
|
28
29
|
export type PublisherOpts = {
|
|
29
30
|
sfuClient: StreamSfuClient;
|
|
@@ -63,6 +64,7 @@ export class Publisher {
|
|
|
63
64
|
private isDtxEnabled: boolean;
|
|
64
65
|
private isRedEnabled: boolean;
|
|
65
66
|
private preferredVideoCodec?: string;
|
|
67
|
+
private logger?: Logger;
|
|
66
68
|
|
|
67
69
|
constructor({
|
|
68
70
|
connectionConfig,
|
|
@@ -124,7 +126,10 @@ export class Publisher {
|
|
|
124
126
|
* Once the track has ended, it will notify the SFU and update the state.
|
|
125
127
|
*/
|
|
126
128
|
const handleTrackEnded = async () => {
|
|
127
|
-
|
|
129
|
+
this.logger?.(
|
|
130
|
+
'info',
|
|
131
|
+
`Track ${TrackType[trackType]} has ended, notifying the SFU`,
|
|
132
|
+
);
|
|
128
133
|
await this.notifyTrackMuteStateChanged(
|
|
129
134
|
mediaStream,
|
|
130
135
|
track,
|
|
@@ -165,7 +170,8 @@ export class Publisher {
|
|
|
165
170
|
this.transceiverRegistry[trackType] = transceiver;
|
|
166
171
|
|
|
167
172
|
if ('setCodecPreferences' in transceiver && codecPreferences) {
|
|
168
|
-
|
|
173
|
+
this.logger?.(
|
|
174
|
+
'info',
|
|
169
175
|
`Setting ${TrackType[trackType]} codec preferences`,
|
|
170
176
|
codecPreferences,
|
|
171
177
|
);
|
|
@@ -283,7 +289,11 @@ export class Publisher {
|
|
|
283
289
|
};
|
|
284
290
|
|
|
285
291
|
updateVideoPublishQuality = async (enabledRids: string[]) => {
|
|
286
|
-
|
|
292
|
+
this.logger?.(
|
|
293
|
+
'info',
|
|
294
|
+
'Update publish quality, requested rids by SFU:',
|
|
295
|
+
enabledRids,
|
|
296
|
+
);
|
|
287
297
|
|
|
288
298
|
const videoSender = this.transceiverRegistry[TrackType.VIDEO]?.sender;
|
|
289
299
|
if (!videoSender) return;
|
|
@@ -300,10 +310,11 @@ export class Publisher {
|
|
|
300
310
|
});
|
|
301
311
|
if (changed) {
|
|
302
312
|
if (params.encodings.length === 0) {
|
|
303
|
-
|
|
313
|
+
this.logger?.('warn', 'No suitable video encoding quality found');
|
|
304
314
|
}
|
|
305
315
|
await videoSender.setParameters(params);
|
|
306
|
-
|
|
316
|
+
this.logger?.(
|
|
317
|
+
'info',
|
|
307
318
|
`Update publish quality, enabled rids: ${params.encodings
|
|
308
319
|
.filter((e) => e.active)
|
|
309
320
|
.map((e) => e.rid)
|
|
@@ -342,7 +353,7 @@ export class Publisher {
|
|
|
342
353
|
private onIceCandidate = async (e: RTCPeerConnectionIceEvent) => {
|
|
343
354
|
const { candidate } = e;
|
|
344
355
|
if (!candidate) {
|
|
345
|
-
|
|
356
|
+
this.logger?.('warn', 'null ice candidate');
|
|
346
357
|
return;
|
|
347
358
|
}
|
|
348
359
|
await this.sfuClient.iceTrickle({
|
|
@@ -352,7 +363,7 @@ export class Publisher {
|
|
|
352
363
|
};
|
|
353
364
|
|
|
354
365
|
private onNegotiationNeeded = async () => {
|
|
355
|
-
|
|
366
|
+
this.logger?.('info', 'AAA onNegotiationNeeded');
|
|
356
367
|
const offer = await this.publisher.createOffer();
|
|
357
368
|
let sdp = offer.sdp;
|
|
358
369
|
if (sdp) {
|
|
@@ -430,7 +441,10 @@ export class Publisher {
|
|
|
430
441
|
sdp: response.sdp,
|
|
431
442
|
});
|
|
432
443
|
} catch (e) {
|
|
433
|
-
|
|
444
|
+
this.logger?.('error', `Publisher: setRemoteDescription error`, {
|
|
445
|
+
sdp: response.sdp,
|
|
446
|
+
error: e,
|
|
447
|
+
});
|
|
434
448
|
}
|
|
435
449
|
|
|
436
450
|
this.sfuClient.iceTrickleBuffer.publisherCandidates.subscribe(
|
|
@@ -439,7 +453,10 @@ export class Publisher {
|
|
|
439
453
|
const iceCandidate = JSON.parse(candidate.iceCandidate);
|
|
440
454
|
await this.publisher.addIceCandidate(iceCandidate);
|
|
441
455
|
} catch (e) {
|
|
442
|
-
|
|
456
|
+
this.logger?.('error', `Publisher: ICE candidate error`, {
|
|
457
|
+
error: e,
|
|
458
|
+
candidate,
|
|
459
|
+
});
|
|
443
460
|
}
|
|
444
461
|
},
|
|
445
462
|
);
|
|
@@ -449,18 +466,20 @@ export class Publisher {
|
|
|
449
466
|
const errorMessage =
|
|
450
467
|
e instanceof RTCPeerConnectionIceErrorEvent &&
|
|
451
468
|
`${e.errorCode}: ${e.errorText}`;
|
|
452
|
-
|
|
469
|
+
this.logger?.('error', `Publisher: ICE Candidate error`, errorMessage);
|
|
453
470
|
};
|
|
454
471
|
|
|
455
472
|
private onIceConnectionStateChange = () => {
|
|
456
|
-
|
|
473
|
+
this.logger?.(
|
|
474
|
+
'error',
|
|
457
475
|
`Publisher: ICE Connection state changed`,
|
|
458
476
|
this.publisher.iceConnectionState,
|
|
459
477
|
);
|
|
460
478
|
};
|
|
461
479
|
|
|
462
480
|
private onIceGatheringStateChange = () => {
|
|
463
|
-
|
|
481
|
+
this.logger?.(
|
|
482
|
+
'error',
|
|
464
483
|
`Publisher: ICE Gathering State`,
|
|
465
484
|
this.publisher.iceGatheringState,
|
|
466
485
|
);
|
package/src/rtc/signal.ts
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { SfuEvent } from '../gen/video/sfu/event/events';
|
|
2
|
+
import { getLogger } from '../logger';
|
|
2
3
|
|
|
3
4
|
export const createWebSocketSignalChannel = (opts: {
|
|
4
5
|
endpoint: string;
|
|
5
6
|
onMessage?: (message: SfuEvent) => void;
|
|
6
7
|
}) => {
|
|
8
|
+
const logger = getLogger(['sfu-client']);
|
|
7
9
|
const { endpoint, onMessage } = opts;
|
|
8
10
|
const ws = new WebSocket(endpoint);
|
|
9
11
|
ws.binaryType = 'arraybuffer'; // do we need this?
|
|
10
12
|
|
|
11
13
|
ws.addEventListener('error', (e) => {
|
|
12
|
-
|
|
14
|
+
logger?.('error', 'Signaling WS channel error', e);
|
|
13
15
|
});
|
|
14
16
|
|
|
15
17
|
ws.addEventListener('close', (e) => {
|
|
16
|
-
|
|
18
|
+
logger?.('info', 'Signaling WS channel is closed', e);
|
|
17
19
|
});
|
|
18
20
|
|
|
19
21
|
ws.addEventListener('open', (e) => {
|
|
20
|
-
|
|
22
|
+
logger?.('info', 'Signaling WS channel is open', e);
|
|
21
23
|
});
|
|
22
24
|
|
|
23
25
|
if (onMessage) {
|
|
@@ -30,11 +32,10 @@ export const createWebSocketSignalChannel = (opts: {
|
|
|
30
32
|
|
|
31
33
|
onMessage(message);
|
|
32
34
|
} catch (err) {
|
|
33
|
-
|
|
35
|
+
logger?.(
|
|
36
|
+
'error',
|
|
34
37
|
'Failed to decode a message. Check whether the Proto models match.',
|
|
35
|
-
e
|
|
36
|
-
e,
|
|
37
|
-
err,
|
|
38
|
+
{ event: e, error: err },
|
|
38
39
|
);
|
|
39
40
|
}
|
|
40
41
|
});
|
package/src/rtc/subscriber.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { StreamSfuClient } from '../StreamSfuClient';
|
|
|
2
2
|
import { getIceCandidate } from './helpers/iceCandidate';
|
|
3
3
|
import { PeerType } from '../gen/video/sfu/models/models';
|
|
4
4
|
import { Dispatcher } from './Dispatcher';
|
|
5
|
+
import { getLogger } from '../logger';
|
|
5
6
|
|
|
6
7
|
export type SubscriberOpts = {
|
|
7
8
|
sfuClient: StreamSfuClient;
|
|
@@ -16,13 +17,14 @@ export const createSubscriber = ({
|
|
|
16
17
|
connectionConfig,
|
|
17
18
|
onTrack,
|
|
18
19
|
}: SubscriberOpts) => {
|
|
20
|
+
const logger = getLogger(['sfu-client']);
|
|
19
21
|
const subscriber = new RTCPeerConnection(connectionConfig);
|
|
20
22
|
attachDebugEventListeners(subscriber);
|
|
21
23
|
|
|
22
24
|
subscriber.addEventListener('icecandidate', async (e) => {
|
|
23
25
|
const { candidate } = e;
|
|
24
26
|
if (!candidate) {
|
|
25
|
-
|
|
27
|
+
logger?.('warn', 'null ice candidate');
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -40,7 +42,7 @@ export const createSubscriber = ({
|
|
|
40
42
|
const unsubscribe = dispatcher.on('subscriberOffer', async (message) => {
|
|
41
43
|
if (message.eventPayload.oneofKind !== 'subscriberOffer') return;
|
|
42
44
|
const { subscriberOffer } = message.eventPayload;
|
|
43
|
-
|
|
45
|
+
logger?.('info', 'Received subscriberOffer', subscriberOffer);
|
|
44
46
|
|
|
45
47
|
await subscriber.setRemoteDescription({
|
|
46
48
|
type: 'offer',
|
|
@@ -52,7 +54,10 @@ export const createSubscriber = ({
|
|
|
52
54
|
const iceCandidate = JSON.parse(candidate.iceCandidate);
|
|
53
55
|
await subscriber.addIceCandidate(iceCandidate);
|
|
54
56
|
} catch (e) {
|
|
55
|
-
|
|
57
|
+
logger?.('error', `Subscriber: ICE candidate error`, {
|
|
58
|
+
error: e,
|
|
59
|
+
candidate,
|
|
60
|
+
});
|
|
56
61
|
}
|
|
57
62
|
});
|
|
58
63
|
|
|
@@ -80,22 +85,23 @@ export const createSubscriber = ({
|
|
|
80
85
|
};
|
|
81
86
|
|
|
82
87
|
const attachDebugEventListeners = (subscriber: RTCPeerConnection) => {
|
|
88
|
+
const logger = getLogger(['sfu-client']);
|
|
83
89
|
subscriber.addEventListener('icecandidateerror', (e) => {
|
|
84
90
|
const errorMessage =
|
|
85
91
|
e instanceof RTCPeerConnectionIceErrorEvent &&
|
|
86
92
|
`${e.errorCode}: ${e.errorText}`;
|
|
87
|
-
|
|
93
|
+
logger?.('error', `Subscriber: ICE Candidate error: ${errorMessage}`);
|
|
88
94
|
});
|
|
89
95
|
subscriber.addEventListener('iceconnectionstatechange', () => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
subscriber.iceConnectionState
|
|
96
|
+
logger?.(
|
|
97
|
+
'info',
|
|
98
|
+
`Subscriber: ICE Connection state changed: ${subscriber.iceConnectionState}`,
|
|
93
99
|
);
|
|
94
100
|
});
|
|
95
101
|
subscriber.addEventListener('icegatheringstatechange', () => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
subscriber.iceGatheringState
|
|
102
|
+
logger?.(
|
|
103
|
+
'info',
|
|
104
|
+
`Subscriber: ICE Gathering State: ${subscriber.iceGatheringState}`,
|
|
99
105
|
);
|
|
100
106
|
});
|
|
101
107
|
};
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
} from './types';
|
|
7
7
|
import { CallState } from '../store';
|
|
8
8
|
import { Publisher } from '../rtc';
|
|
9
|
+
import { getLogger } from '../logger';
|
|
9
10
|
|
|
10
11
|
export type StatsReporterOpts = {
|
|
11
12
|
subscriber: RTCPeerConnection;
|
|
@@ -69,6 +70,7 @@ export const createStatsReporter = ({
|
|
|
69
70
|
edgeName,
|
|
70
71
|
pollingIntervalInMs = 2000,
|
|
71
72
|
}: StatsReporterOpts): StatsReporter => {
|
|
73
|
+
const logger = getLogger(['stats']);
|
|
72
74
|
const getRawStatsForTrack = async (
|
|
73
75
|
kind: 'subscriber' | 'publisher',
|
|
74
76
|
selector?: MediaStreamTrack,
|
|
@@ -78,7 +80,7 @@ export const createStatsReporter = ({
|
|
|
78
80
|
} else if (kind === 'publisher' && publisher) {
|
|
79
81
|
return publisher.getStats(selector);
|
|
80
82
|
} else {
|
|
81
|
-
|
|
83
|
+
logger('warn', `Can't retrieve RTC stats for ${kind}`);
|
|
82
84
|
return undefined;
|
|
83
85
|
}
|
|
84
86
|
};
|
|
@@ -122,7 +124,9 @@ export const createStatsReporter = ({
|
|
|
122
124
|
if (sessionIds.size > 0) {
|
|
123
125
|
for (let participant of state.participants) {
|
|
124
126
|
if (!sessionIds.has(participant.sessionId)) continue;
|
|
125
|
-
const kind = participant.
|
|
127
|
+
const kind = participant.isLocalParticipant
|
|
128
|
+
? 'publisher'
|
|
129
|
+
: 'subscriber';
|
|
126
130
|
try {
|
|
127
131
|
const mergedStream = new MediaStream([
|
|
128
132
|
...(participant.videoStream?.getVideoTracks() || []),
|
|
@@ -136,7 +140,11 @@ export const createStatsReporter = ({
|
|
|
136
140
|
mergedStream.removeTrack(t);
|
|
137
141
|
});
|
|
138
142
|
} catch (e) {
|
|
139
|
-
|
|
143
|
+
logger(
|
|
144
|
+
'error',
|
|
145
|
+
`Failed to collect stats for ${kind} if ${participant.userId}`,
|
|
146
|
+
e,
|
|
147
|
+
);
|
|
140
148
|
}
|
|
141
149
|
}
|
|
142
150
|
}
|
|
@@ -182,7 +190,7 @@ export const createStatsReporter = ({
|
|
|
182
190
|
if (pollingIntervalInMs > 0) {
|
|
183
191
|
const loop = async () => {
|
|
184
192
|
await run().catch((e) => {
|
|
185
|
-
|
|
193
|
+
logger('warn', 'Failed to collect stats', e);
|
|
186
194
|
});
|
|
187
195
|
timeoutId = setTimeout(loop, pollingIntervalInMs);
|
|
188
196
|
};
|
package/src/store/CallState.ts
CHANGED
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
import { TrackType } from '../gen/video/sfu/models/models';
|
|
21
21
|
import { Comparator } from '../sorting';
|
|
22
22
|
import * as SortingPreset from '../sorting/presets';
|
|
23
|
+
import { getLogger } from '../logger';
|
|
24
|
+
import { Logger } from '../coordinator/connection/types';
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Represents the state of the current call.
|
|
@@ -267,6 +269,8 @@ export class CallState {
|
|
|
267
269
|
*/
|
|
268
270
|
callingState$: Observable<CallingState>;
|
|
269
271
|
|
|
272
|
+
readonly logger: Logger;
|
|
273
|
+
|
|
270
274
|
/**
|
|
271
275
|
* A list of comparators that are used to sort the participants.
|
|
272
276
|
*
|
|
@@ -280,6 +284,7 @@ export class CallState {
|
|
|
280
284
|
*
|
|
281
285
|
*/
|
|
282
286
|
constructor() {
|
|
287
|
+
this.logger = getLogger(['call-state']);
|
|
283
288
|
this.participants$ = this.participantsSubject.pipe(
|
|
284
289
|
map((ps) => ps.sort(this.sortParticipantsBy)),
|
|
285
290
|
);
|
|
@@ -289,7 +294,7 @@ export class CallState {
|
|
|
289
294
|
);
|
|
290
295
|
|
|
291
296
|
this.remoteParticipants$ = this.participants$.pipe(
|
|
292
|
-
map((participants) => participants.filter((p) => !p.
|
|
297
|
+
map((participants) => participants.filter((p) => !p.isLocalParticipant)),
|
|
293
298
|
);
|
|
294
299
|
|
|
295
300
|
this.pinnedParticipants$ = this.participants$.pipe(
|
|
@@ -616,7 +621,7 @@ export class CallState {
|
|
|
616
621
|
) => {
|
|
617
622
|
const participant = this.findParticipantBySessionId(sessionId);
|
|
618
623
|
if (!participant) {
|
|
619
|
-
|
|
624
|
+
this.logger('warn', `Participant with sessionId ${sessionId} not found`);
|
|
620
625
|
return;
|
|
621
626
|
}
|
|
622
627
|
|
package/src/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type { StreamClient } from './coordinator/connection/client';
|
|
|
13
13
|
import type { Comparator } from './sorting';
|
|
14
14
|
import type { StreamVideoWriteableStateStore } from './store';
|
|
15
15
|
import { AxiosError } from 'axios';
|
|
16
|
+
import { Logger } from './coordinator/connection/types';
|
|
16
17
|
|
|
17
18
|
export type StreamReaction = Pick<
|
|
18
19
|
ReactionResponse,
|
|
@@ -66,7 +67,7 @@ export interface StreamVideoParticipant extends Participant {
|
|
|
66
67
|
/**
|
|
67
68
|
* True if the participant is the local participant.
|
|
68
69
|
*/
|
|
69
|
-
|
|
70
|
+
isLocalParticipant?: boolean;
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
73
|
* Timestamp of when the participant is pinned
|
|
@@ -109,7 +110,7 @@ export interface StreamVideoLocalParticipant extends StreamVideoParticipant {
|
|
|
109
110
|
export const isStreamVideoLocalParticipant = (
|
|
110
111
|
p: StreamVideoParticipant | StreamVideoLocalParticipant,
|
|
111
112
|
): p is StreamVideoLocalParticipant => {
|
|
112
|
-
return !!p.
|
|
113
|
+
return !!p.isLocalParticipant;
|
|
113
114
|
};
|
|
114
115
|
|
|
115
116
|
/**
|