@stream-io/video-client 1.13.1 → 1.15.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/CHANGELOG.md +14 -0
- package/dist/index.browser.es.js +1704 -1762
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +1706 -1780
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +1704 -1762
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +61 -30
- package/dist/src/StreamSfuClient.d.ts +4 -5
- package/dist/src/devices/CameraManager.d.ts +5 -8
- package/dist/src/devices/InputMediaDeviceManager.d.ts +5 -5
- package/dist/src/devices/MicrophoneManager.d.ts +7 -2
- package/dist/src/devices/ScreenShareManager.d.ts +1 -2
- package/dist/src/gen/coordinator/index.d.ts +904 -515
- package/dist/src/gen/video/sfu/event/events.d.ts +38 -19
- package/dist/src/gen/video/sfu/models/models.d.ts +76 -9
- package/dist/src/helpers/array.d.ts +7 -0
- package/dist/src/permissions/PermissionsContext.d.ts +6 -0
- package/dist/src/rtc/BasePeerConnection.d.ts +90 -0
- package/dist/src/rtc/Dispatcher.d.ts +0 -1
- package/dist/src/rtc/IceTrickleBuffer.d.ts +3 -2
- package/dist/src/rtc/Publisher.d.ts +32 -86
- package/dist/src/rtc/Subscriber.d.ts +4 -56
- package/dist/src/rtc/TransceiverCache.d.ts +55 -0
- package/dist/src/rtc/codecs.d.ts +1 -15
- package/dist/src/rtc/helpers/sdp.d.ts +8 -0
- package/dist/src/rtc/helpers/tracks.d.ts +1 -0
- package/dist/src/rtc/index.d.ts +3 -0
- package/dist/src/rtc/videoLayers.d.ts +11 -25
- package/dist/src/stats/{stateStoreStatsReporter.d.ts → CallStateStatsReporter.d.ts} +5 -1
- package/dist/src/stats/SfuStatsReporter.d.ts +4 -2
- package/dist/src/stats/index.d.ts +1 -1
- package/dist/src/stats/types.d.ts +8 -0
- package/dist/src/store/CallState.d.ts +47 -5
- package/dist/src/store/rxUtils.d.ts +15 -1
- package/dist/src/types.d.ts +26 -22
- package/package.json +1 -1
- package/src/Call.ts +310 -271
- package/src/StreamSfuClient.ts +9 -14
- package/src/StreamVideoClient.ts +1 -1
- package/src/__tests__/Call.publishing.test.ts +306 -0
- package/src/devices/CameraManager.ts +33 -16
- package/src/devices/InputMediaDeviceManager.ts +36 -27
- package/src/devices/MicrophoneManager.ts +29 -8
- package/src/devices/ScreenShareManager.ts +6 -8
- package/src/devices/__tests__/CameraManager.test.ts +111 -14
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +4 -4
- package/src/devices/__tests__/MicrophoneManager.test.ts +59 -21
- package/src/devices/__tests__/ScreenShareManager.test.ts +5 -5
- package/src/devices/__tests__/mocks.ts +1 -0
- package/src/events/__tests__/internal.test.ts +132 -0
- package/src/events/__tests__/mutes.test.ts +0 -3
- package/src/events/__tests__/speaker.test.ts +92 -0
- package/src/events/participant.ts +3 -4
- package/src/gen/coordinator/index.ts +902 -514
- package/src/gen/video/sfu/event/events.ts +91 -30
- package/src/gen/video/sfu/models/models.ts +105 -13
- package/src/helpers/array.ts +14 -0
- package/src/permissions/PermissionsContext.ts +22 -0
- package/src/permissions/__tests__/PermissionsContext.test.ts +40 -0
- package/src/rpc/__tests__/createClient.test.ts +38 -0
- package/src/rpc/createClient.ts +11 -5
- package/src/rtc/BasePeerConnection.ts +240 -0
- package/src/rtc/Dispatcher.ts +0 -9
- package/src/rtc/IceTrickleBuffer.ts +24 -4
- package/src/rtc/Publisher.ts +210 -528
- package/src/rtc/Subscriber.ts +26 -200
- package/src/rtc/TransceiverCache.ts +120 -0
- package/src/rtc/__tests__/Publisher.test.ts +407 -210
- package/src/rtc/__tests__/Subscriber.test.ts +88 -36
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +22 -2
- package/src/rtc/__tests__/videoLayers.test.ts +161 -54
- package/src/rtc/codecs.ts +1 -131
- package/src/rtc/helpers/__tests__/rtcConfiguration.test.ts +34 -0
- package/src/rtc/helpers/__tests__/sdp.test.ts +59 -0
- package/src/rtc/helpers/sdp.ts +30 -0
- package/src/rtc/helpers/tracks.ts +3 -0
- package/src/rtc/index.ts +4 -0
- package/src/rtc/videoLayers.ts +68 -76
- package/src/stats/{stateStoreStatsReporter.ts → CallStateStatsReporter.ts} +58 -27
- package/src/stats/SfuStatsReporter.ts +31 -3
- package/src/stats/index.ts +1 -1
- package/src/stats/types.ts +12 -0
- package/src/store/CallState.ts +115 -5
- package/src/store/__tests__/CallState.test.ts +101 -0
- package/src/store/rxUtils.ts +23 -1
- package/src/types.ts +27 -22
- package/dist/src/helpers/sdp-munging.d.ts +0 -24
- package/dist/src/rtc/bitrateLookup.d.ts +0 -2
- package/dist/src/rtc/helpers/iceCandidate.d.ts +0 -2
- package/src/helpers/__tests__/hq-audio-sdp.ts +0 -332
- package/src/helpers/__tests__/sdp-munging.test.ts +0 -283
- package/src/helpers/sdp-munging.ts +0 -265
- package/src/rtc/__tests__/bitrateLookup.test.ts +0 -12
- package/src/rtc/__tests__/codecs.test.ts +0 -145
- package/src/rtc/bitrateLookup.ts +0 -61
- package/src/rtc/helpers/iceCandidate.ts +0 -16
- /package/dist/src/{compatibility.d.ts → helpers/compatibility.d.ts} +0 -0
- /package/src/{compatibility.ts → helpers/compatibility.ts} +0 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { getLogger } from '../logger';
|
|
2
|
+
import type {
|
|
3
|
+
CallEventListener,
|
|
4
|
+
Logger,
|
|
5
|
+
} from '../coordinator/connection/types';
|
|
6
|
+
import { CallingState, CallState } from '../store';
|
|
7
|
+
import { createSafeAsyncSubscription } from '../store/rxUtils';
|
|
8
|
+
import { PeerType } from '../gen/video/sfu/models/models';
|
|
9
|
+
import { StreamSfuClient } from '../StreamSfuClient';
|
|
10
|
+
import { AllSfuEvents, Dispatcher } from './Dispatcher';
|
|
11
|
+
import { withoutConcurrency } from '../helpers/concurrency';
|
|
12
|
+
|
|
13
|
+
export type BasePeerConnectionOpts = {
|
|
14
|
+
sfuClient: StreamSfuClient;
|
|
15
|
+
state: CallState;
|
|
16
|
+
connectionConfig?: RTCConfiguration;
|
|
17
|
+
dispatcher: Dispatcher;
|
|
18
|
+
onUnrecoverableError?: () => void;
|
|
19
|
+
logTag: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A base class for the `Publisher` and `Subscriber` classes.
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export abstract class BasePeerConnection {
|
|
27
|
+
protected readonly logger: Logger;
|
|
28
|
+
protected readonly peerType: PeerType;
|
|
29
|
+
protected readonly pc: RTCPeerConnection;
|
|
30
|
+
protected readonly state: CallState;
|
|
31
|
+
protected readonly dispatcher: Dispatcher;
|
|
32
|
+
protected sfuClient: StreamSfuClient;
|
|
33
|
+
|
|
34
|
+
protected readonly onUnrecoverableError?: () => void;
|
|
35
|
+
protected isIceRestarting = false;
|
|
36
|
+
|
|
37
|
+
private readonly subscriptions: (() => void)[] = [];
|
|
38
|
+
private unsubscribeIceTrickle?: () => void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Constructs a new `BasePeerConnection` instance.
|
|
42
|
+
*/
|
|
43
|
+
protected constructor(
|
|
44
|
+
peerType: PeerType,
|
|
45
|
+
{
|
|
46
|
+
sfuClient,
|
|
47
|
+
connectionConfig,
|
|
48
|
+
state,
|
|
49
|
+
dispatcher,
|
|
50
|
+
onUnrecoverableError,
|
|
51
|
+
logTag,
|
|
52
|
+
}: BasePeerConnectionOpts,
|
|
53
|
+
) {
|
|
54
|
+
this.peerType = peerType;
|
|
55
|
+
this.sfuClient = sfuClient;
|
|
56
|
+
this.state = state;
|
|
57
|
+
this.dispatcher = dispatcher;
|
|
58
|
+
this.onUnrecoverableError = onUnrecoverableError;
|
|
59
|
+
this.logger = getLogger([
|
|
60
|
+
peerType === PeerType.SUBSCRIBER ? 'Subscriber' : 'Publisher',
|
|
61
|
+
logTag,
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
this.pc = new RTCPeerConnection(connectionConfig);
|
|
65
|
+
this.pc.addEventListener('icecandidate', this.onIceCandidate);
|
|
66
|
+
this.pc.addEventListener('icecandidateerror', this.onIceCandidateError);
|
|
67
|
+
this.pc.addEventListener(
|
|
68
|
+
'iceconnectionstatechange',
|
|
69
|
+
this.onIceConnectionStateChange,
|
|
70
|
+
);
|
|
71
|
+
this.pc.addEventListener('icegatheringstatechange', this.onIceGatherChange);
|
|
72
|
+
this.pc.addEventListener('signalingstatechange', this.onSignalingChange);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Disposes the `RTCPeerConnection` instance.
|
|
77
|
+
*/
|
|
78
|
+
dispose = () => {
|
|
79
|
+
this.detachEventHandlers();
|
|
80
|
+
this.pc.close();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Detaches the event handlers from the `RTCPeerConnection`.
|
|
85
|
+
*/
|
|
86
|
+
protected detachEventHandlers() {
|
|
87
|
+
this.pc.removeEventListener('icecandidate', this.onIceCandidate);
|
|
88
|
+
this.pc.removeEventListener('icecandidateerror', this.onIceCandidateError);
|
|
89
|
+
this.pc.removeEventListener('signalingstatechange', this.onSignalingChange);
|
|
90
|
+
this.pc.removeEventListener(
|
|
91
|
+
'iceconnectionstatechange',
|
|
92
|
+
this.onIceConnectionStateChange,
|
|
93
|
+
);
|
|
94
|
+
this.pc.removeEventListener(
|
|
95
|
+
'icegatheringstatechange',
|
|
96
|
+
this.onIceGatherChange,
|
|
97
|
+
);
|
|
98
|
+
this.unsubscribeIceTrickle?.();
|
|
99
|
+
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Performs an ICE restart on the `RTCPeerConnection`.
|
|
104
|
+
*/
|
|
105
|
+
protected abstract restartIce(): Promise<void>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Handles events synchronously.
|
|
109
|
+
* Consecutive events are queued and executed one after the other.
|
|
110
|
+
*/
|
|
111
|
+
protected on = <E extends keyof AllSfuEvents>(
|
|
112
|
+
event: E,
|
|
113
|
+
fn: CallEventListener<E>,
|
|
114
|
+
): void => {
|
|
115
|
+
this.subscriptions.push(
|
|
116
|
+
this.dispatcher.on(event, (e) => {
|
|
117
|
+
withoutConcurrency(`pc.${event}`, async () => fn(e)).catch((err) => {
|
|
118
|
+
this.logger('warn', `Error handling ${event}`, err);
|
|
119
|
+
});
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Appends the trickled ICE candidates to the `RTCPeerConnection`.
|
|
126
|
+
*/
|
|
127
|
+
protected addTrickledIceCandidates = () => {
|
|
128
|
+
const { iceTrickleBuffer } = this.sfuClient;
|
|
129
|
+
const observable =
|
|
130
|
+
this.peerType === PeerType.SUBSCRIBER
|
|
131
|
+
? iceTrickleBuffer.subscriberCandidates
|
|
132
|
+
: iceTrickleBuffer.publisherCandidates;
|
|
133
|
+
|
|
134
|
+
this.unsubscribeIceTrickle?.();
|
|
135
|
+
this.unsubscribeIceTrickle = createSafeAsyncSubscription(
|
|
136
|
+
observable,
|
|
137
|
+
async (candidate) => {
|
|
138
|
+
return this.pc.addIceCandidate(candidate).catch((e) => {
|
|
139
|
+
this.logger('warn', `ICE candidate error`, e, candidate);
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Sets the SFU client to use.
|
|
147
|
+
*
|
|
148
|
+
* @param sfuClient the SFU client to use.
|
|
149
|
+
*/
|
|
150
|
+
setSfuClient = (sfuClient: StreamSfuClient) => {
|
|
151
|
+
this.sfuClient = sfuClient;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Returns the result of the `RTCPeerConnection.getStats()` method
|
|
156
|
+
* @param selector an optional `MediaStreamTrack` to get the stats for.
|
|
157
|
+
*/
|
|
158
|
+
getStats = (selector?: MediaStreamTrack | null) => {
|
|
159
|
+
return this.pc.getStats(selector);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Handles the ICECandidate event and
|
|
164
|
+
* Initiates an ICE Trickle process with the SFU.
|
|
165
|
+
*/
|
|
166
|
+
private onIceCandidate = (e: RTCPeerConnectionIceEvent) => {
|
|
167
|
+
const { candidate } = e;
|
|
168
|
+
if (!candidate) {
|
|
169
|
+
this.logger('debug', 'null ice candidate');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const iceCandidate = this.toJSON(candidate);
|
|
174
|
+
this.sfuClient
|
|
175
|
+
.iceTrickle({ peerType: this.peerType, iceCandidate })
|
|
176
|
+
.catch((err) => this.logger('warn', `ICETrickle failed`, err));
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Converts the ICE candidate to a JSON string.
|
|
181
|
+
*/
|
|
182
|
+
private toJSON = (candidate: RTCIceCandidate): string => {
|
|
183
|
+
if (!candidate.usernameFragment) {
|
|
184
|
+
// react-native-webrtc doesn't include usernameFragment in the candidate
|
|
185
|
+
const segments = candidate.candidate.split(' ');
|
|
186
|
+
const ufragIndex = segments.findIndex((s) => s === 'ufrag') + 1;
|
|
187
|
+
const usernameFragment = segments[ufragIndex];
|
|
188
|
+
return JSON.stringify({ ...candidate, usernameFragment });
|
|
189
|
+
}
|
|
190
|
+
return JSON.stringify(candidate.toJSON());
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Handles the ICE connection state change event.
|
|
195
|
+
*/
|
|
196
|
+
private onIceConnectionStateChange = () => {
|
|
197
|
+
const state = this.pc.iceConnectionState;
|
|
198
|
+
this.logger('debug', `ICE connection state changed`, state);
|
|
199
|
+
|
|
200
|
+
if (this.state.callingState === CallingState.RECONNECTING) return;
|
|
201
|
+
|
|
202
|
+
// do nothing when ICE is restarting
|
|
203
|
+
if (this.isIceRestarting) return;
|
|
204
|
+
|
|
205
|
+
if (state === 'failed' || state === 'disconnected') {
|
|
206
|
+
this.logger('debug', `Attempting to restart ICE`);
|
|
207
|
+
this.restartIce().catch((e) => {
|
|
208
|
+
this.logger('error', `ICE restart failed`, e);
|
|
209
|
+
this.onUnrecoverableError?.();
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Handles the ICE candidate error event.
|
|
216
|
+
*/
|
|
217
|
+
private onIceCandidateError = (e: Event) => {
|
|
218
|
+
const errorMessage =
|
|
219
|
+
e instanceof RTCPeerConnectionIceErrorEvent &&
|
|
220
|
+
`${e.errorCode}: ${e.errorText}`;
|
|
221
|
+
const iceState = this.pc.iceConnectionState;
|
|
222
|
+
const logLevel =
|
|
223
|
+
iceState === 'connected' || iceState === 'checking' ? 'debug' : 'warn';
|
|
224
|
+
this.logger(logLevel, `ICE Candidate error`, errorMessage);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Handles the ICE gathering state change event.
|
|
229
|
+
*/
|
|
230
|
+
private onIceGatherChange = () => {
|
|
231
|
+
this.logger('debug', `ICE Gathering State`, this.pc.iceGatheringState);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Handles the signaling state change event.
|
|
236
|
+
*/
|
|
237
|
+
private onSignalingChange = () => {
|
|
238
|
+
this.logger('debug', `Signaling state changed`, this.pc.signalingState);
|
|
239
|
+
};
|
|
240
|
+
}
|
package/src/rtc/Dispatcher.ts
CHANGED
|
@@ -42,7 +42,6 @@ const sfuEventKinds: { [key in SfuEventKinds]: undefined } = {
|
|
|
42
42
|
callEnded: undefined,
|
|
43
43
|
participantUpdated: undefined,
|
|
44
44
|
participantMigrationComplete: undefined,
|
|
45
|
-
codecNegotiationComplete: undefined,
|
|
46
45
|
changePublishOptions: undefined,
|
|
47
46
|
};
|
|
48
47
|
|
|
@@ -95,12 +94,4 @@ export class Dispatcher {
|
|
|
95
94
|
(f) => f !== fn,
|
|
96
95
|
);
|
|
97
96
|
};
|
|
98
|
-
|
|
99
|
-
offAll = (eventName?: SfuEventKinds) => {
|
|
100
|
-
if (eventName) {
|
|
101
|
-
this.subscribers[eventName] = [];
|
|
102
|
-
} else {
|
|
103
|
-
this.subscribers = {};
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
97
|
}
|
|
@@ -8,17 +8,37 @@ import { getLogger } from '../logger';
|
|
|
8
8
|
* - https://bloggeek.me/webrtcglossary/trickle-ice/
|
|
9
9
|
*/
|
|
10
10
|
export class IceTrickleBuffer {
|
|
11
|
-
readonly subscriberCandidates = new ReplaySubject<
|
|
12
|
-
readonly publisherCandidates = new ReplaySubject<
|
|
11
|
+
readonly subscriberCandidates = new ReplaySubject<RTCIceCandidateInit>();
|
|
12
|
+
readonly publisherCandidates = new ReplaySubject<RTCIceCandidateInit>();
|
|
13
13
|
|
|
14
14
|
push = (iceTrickle: ICETrickle) => {
|
|
15
|
+
const iceCandidate = toIceCandidate(iceTrickle);
|
|
16
|
+
if (!iceCandidate) return;
|
|
17
|
+
|
|
15
18
|
if (iceTrickle.peerType === PeerType.SUBSCRIBER) {
|
|
16
|
-
this.subscriberCandidates.next(
|
|
19
|
+
this.subscriberCandidates.next(iceCandidate);
|
|
17
20
|
} else if (iceTrickle.peerType === PeerType.PUBLISHER_UNSPECIFIED) {
|
|
18
|
-
this.publisherCandidates.next(
|
|
21
|
+
this.publisherCandidates.next(iceCandidate);
|
|
19
22
|
} else {
|
|
20
23
|
const logger = getLogger(['sfu-client']);
|
|
21
24
|
logger('warn', `ICETrickle, Unknown peer type`, iceTrickle);
|
|
22
25
|
}
|
|
23
26
|
};
|
|
27
|
+
|
|
28
|
+
dispose = () => {
|
|
29
|
+
this.subscriberCandidates.complete();
|
|
30
|
+
this.publisherCandidates.complete();
|
|
31
|
+
};
|
|
24
32
|
}
|
|
33
|
+
|
|
34
|
+
const toIceCandidate = (
|
|
35
|
+
iceTrickle: ICETrickle,
|
|
36
|
+
): RTCIceCandidateInit | undefined => {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(iceTrickle.iceCandidate);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
const logger = getLogger(['sfu-client']);
|
|
41
|
+
logger('error', `Failed to parse ICE Trickle`, e, iceTrickle);
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
};
|