@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.
Files changed (99) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/index.browser.es.js +1704 -1762
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +1706 -1780
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +1704 -1762
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +61 -30
  9. package/dist/src/StreamSfuClient.d.ts +4 -5
  10. package/dist/src/devices/CameraManager.d.ts +5 -8
  11. package/dist/src/devices/InputMediaDeviceManager.d.ts +5 -5
  12. package/dist/src/devices/MicrophoneManager.d.ts +7 -2
  13. package/dist/src/devices/ScreenShareManager.d.ts +1 -2
  14. package/dist/src/gen/coordinator/index.d.ts +904 -515
  15. package/dist/src/gen/video/sfu/event/events.d.ts +38 -19
  16. package/dist/src/gen/video/sfu/models/models.d.ts +76 -9
  17. package/dist/src/helpers/array.d.ts +7 -0
  18. package/dist/src/permissions/PermissionsContext.d.ts +6 -0
  19. package/dist/src/rtc/BasePeerConnection.d.ts +90 -0
  20. package/dist/src/rtc/Dispatcher.d.ts +0 -1
  21. package/dist/src/rtc/IceTrickleBuffer.d.ts +3 -2
  22. package/dist/src/rtc/Publisher.d.ts +32 -86
  23. package/dist/src/rtc/Subscriber.d.ts +4 -56
  24. package/dist/src/rtc/TransceiverCache.d.ts +55 -0
  25. package/dist/src/rtc/codecs.d.ts +1 -15
  26. package/dist/src/rtc/helpers/sdp.d.ts +8 -0
  27. package/dist/src/rtc/helpers/tracks.d.ts +1 -0
  28. package/dist/src/rtc/index.d.ts +3 -0
  29. package/dist/src/rtc/videoLayers.d.ts +11 -25
  30. package/dist/src/stats/{stateStoreStatsReporter.d.ts → CallStateStatsReporter.d.ts} +5 -1
  31. package/dist/src/stats/SfuStatsReporter.d.ts +4 -2
  32. package/dist/src/stats/index.d.ts +1 -1
  33. package/dist/src/stats/types.d.ts +8 -0
  34. package/dist/src/store/CallState.d.ts +47 -5
  35. package/dist/src/store/rxUtils.d.ts +15 -1
  36. package/dist/src/types.d.ts +26 -22
  37. package/package.json +1 -1
  38. package/src/Call.ts +310 -271
  39. package/src/StreamSfuClient.ts +9 -14
  40. package/src/StreamVideoClient.ts +1 -1
  41. package/src/__tests__/Call.publishing.test.ts +306 -0
  42. package/src/devices/CameraManager.ts +33 -16
  43. package/src/devices/InputMediaDeviceManager.ts +36 -27
  44. package/src/devices/MicrophoneManager.ts +29 -8
  45. package/src/devices/ScreenShareManager.ts +6 -8
  46. package/src/devices/__tests__/CameraManager.test.ts +111 -14
  47. package/src/devices/__tests__/InputMediaDeviceManager.test.ts +4 -4
  48. package/src/devices/__tests__/MicrophoneManager.test.ts +59 -21
  49. package/src/devices/__tests__/ScreenShareManager.test.ts +5 -5
  50. package/src/devices/__tests__/mocks.ts +1 -0
  51. package/src/events/__tests__/internal.test.ts +132 -0
  52. package/src/events/__tests__/mutes.test.ts +0 -3
  53. package/src/events/__tests__/speaker.test.ts +92 -0
  54. package/src/events/participant.ts +3 -4
  55. package/src/gen/coordinator/index.ts +902 -514
  56. package/src/gen/video/sfu/event/events.ts +91 -30
  57. package/src/gen/video/sfu/models/models.ts +105 -13
  58. package/src/helpers/array.ts +14 -0
  59. package/src/permissions/PermissionsContext.ts +22 -0
  60. package/src/permissions/__tests__/PermissionsContext.test.ts +40 -0
  61. package/src/rpc/__tests__/createClient.test.ts +38 -0
  62. package/src/rpc/createClient.ts +11 -5
  63. package/src/rtc/BasePeerConnection.ts +240 -0
  64. package/src/rtc/Dispatcher.ts +0 -9
  65. package/src/rtc/IceTrickleBuffer.ts +24 -4
  66. package/src/rtc/Publisher.ts +210 -528
  67. package/src/rtc/Subscriber.ts +26 -200
  68. package/src/rtc/TransceiverCache.ts +120 -0
  69. package/src/rtc/__tests__/Publisher.test.ts +407 -210
  70. package/src/rtc/__tests__/Subscriber.test.ts +88 -36
  71. package/src/rtc/__tests__/mocks/webrtc.mocks.ts +22 -2
  72. package/src/rtc/__tests__/videoLayers.test.ts +161 -54
  73. package/src/rtc/codecs.ts +1 -131
  74. package/src/rtc/helpers/__tests__/rtcConfiguration.test.ts +34 -0
  75. package/src/rtc/helpers/__tests__/sdp.test.ts +59 -0
  76. package/src/rtc/helpers/sdp.ts +30 -0
  77. package/src/rtc/helpers/tracks.ts +3 -0
  78. package/src/rtc/index.ts +4 -0
  79. package/src/rtc/videoLayers.ts +68 -76
  80. package/src/stats/{stateStoreStatsReporter.ts → CallStateStatsReporter.ts} +58 -27
  81. package/src/stats/SfuStatsReporter.ts +31 -3
  82. package/src/stats/index.ts +1 -1
  83. package/src/stats/types.ts +12 -0
  84. package/src/store/CallState.ts +115 -5
  85. package/src/store/__tests__/CallState.test.ts +101 -0
  86. package/src/store/rxUtils.ts +23 -1
  87. package/src/types.ts +27 -22
  88. package/dist/src/helpers/sdp-munging.d.ts +0 -24
  89. package/dist/src/rtc/bitrateLookup.d.ts +0 -2
  90. package/dist/src/rtc/helpers/iceCandidate.d.ts +0 -2
  91. package/src/helpers/__tests__/hq-audio-sdp.ts +0 -332
  92. package/src/helpers/__tests__/sdp-munging.test.ts +0 -283
  93. package/src/helpers/sdp-munging.ts +0 -265
  94. package/src/rtc/__tests__/bitrateLookup.test.ts +0 -12
  95. package/src/rtc/__tests__/codecs.test.ts +0 -145
  96. package/src/rtc/bitrateLookup.ts +0 -61
  97. package/src/rtc/helpers/iceCandidate.ts +0 -16
  98. /package/dist/src/{compatibility.d.ts → helpers/compatibility.d.ts} +0 -0
  99. /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
+ }
@@ -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<ICETrickle>();
12
- readonly publisherCandidates = new ReplaySubject<ICETrickle>();
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(iceTrickle);
19
+ this.subscriberCandidates.next(iceCandidate);
17
20
  } else if (iceTrickle.peerType === PeerType.PUBLISHER_UNSPECIFIED) {
18
- this.publisherCandidates.next(iceTrickle);
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
+ };