@stream-io/video-client 0.0.1-alpha.7

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 (157) hide show
  1. package/LICENSE +219 -0
  2. package/README.md +14 -0
  3. package/dist/index.d.ts +23 -0
  4. package/dist/index.js +14663 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/src/Batcher.d.ts +12 -0
  7. package/dist/src/CallDropScheduler.d.ts +44 -0
  8. package/dist/src/StreamSfuClient.d.ts +25 -0
  9. package/dist/src/StreamVideoClient.d.ts +145 -0
  10. package/dist/src/__tests__/StreamVideoClient.test.d.ts +1 -0
  11. package/dist/src/config/defaultConfigs.d.ts +2 -0
  12. package/dist/src/config/types.d.ts +29 -0
  13. package/dist/src/coordinator/StreamCoordinatorClient.d.ts +19 -0
  14. package/dist/src/coordinator/connection/base64.d.ts +2 -0
  15. package/dist/src/coordinator/connection/client.d.ts +174 -0
  16. package/dist/src/coordinator/connection/connection.d.ts +139 -0
  17. package/dist/src/coordinator/connection/connection_fallback.d.ts +38 -0
  18. package/dist/src/coordinator/connection/errors.d.ts +16 -0
  19. package/dist/src/coordinator/connection/events.d.ts +7 -0
  20. package/dist/src/coordinator/connection/insights.d.ts +58 -0
  21. package/dist/src/coordinator/connection/signing.d.ts +30 -0
  22. package/dist/src/coordinator/connection/token_manager.d.ts +39 -0
  23. package/dist/src/coordinator/connection/types.d.ts +96 -0
  24. package/dist/src/coordinator/connection/utils.d.ts +25 -0
  25. package/dist/src/devices.d.ts +79 -0
  26. package/dist/src/events/call.d.ts +26 -0
  27. package/dist/src/events/internal.d.ts +8 -0
  28. package/dist/src/events/participant.d.ts +21 -0
  29. package/dist/src/events/speaker.d.ts +10 -0
  30. package/dist/src/gen/coordinator/index.d.ts +1664 -0
  31. package/dist/src/gen/google/protobuf/descriptor.d.ts +1650 -0
  32. package/dist/src/gen/google/protobuf/duration.d.ts +113 -0
  33. package/dist/src/gen/google/protobuf/struct.d.ts +184 -0
  34. package/dist/src/gen/google/protobuf/timestamp.d.ts +158 -0
  35. package/dist/src/gen/video/coordinator/broadcast_v1/broadcast.d.ts +66 -0
  36. package/dist/src/gen/video/coordinator/call_v1/call.d.ts +254 -0
  37. package/dist/src/gen/video/coordinator/client_v1_rpc/client_rpc.client.d.ts +351 -0
  38. package/dist/src/gen/video/coordinator/client_v1_rpc/client_rpc.d.ts +1488 -0
  39. package/dist/src/gen/video/coordinator/client_v1_rpc/envelopes.d.ts +143 -0
  40. package/dist/src/gen/video/coordinator/client_v1_rpc/websocket.d.ts +292 -0
  41. package/dist/src/gen/video/coordinator/edge_v1/edge.d.ts +183 -0
  42. package/dist/src/gen/video/coordinator/event_v1/event.d.ts +411 -0
  43. package/dist/src/gen/video/coordinator/geofence_v1/geofence.d.ts +63 -0
  44. package/dist/src/gen/video/coordinator/member_v1/member.d.ts +59 -0
  45. package/dist/src/gen/video/coordinator/participant_v1/participant.d.ts +103 -0
  46. package/dist/src/gen/video/coordinator/push_v1/push.d.ts +240 -0
  47. package/dist/src/gen/video/coordinator/stat_v1/stat.d.ts +308 -0
  48. package/dist/src/gen/video/coordinator/user_v1/user.d.ts +112 -0
  49. package/dist/src/gen/video/coordinator/utils_v1/utils.d.ts +47 -0
  50. package/dist/src/gen/video/sfu/event/events.d.ts +736 -0
  51. package/dist/src/gen/video/sfu/models/models.d.ts +460 -0
  52. package/dist/src/gen/video/sfu/signal_rpc/signal.client.d.ts +89 -0
  53. package/dist/src/gen/video/sfu/signal_rpc/signal.d.ts +320 -0
  54. package/dist/src/helpers/browsers.d.ts +8 -0
  55. package/dist/src/helpers/sound-detector.d.ts +34 -0
  56. package/dist/src/rpc/createClient.d.ts +10 -0
  57. package/dist/src/rpc/index.d.ts +2 -0
  58. package/dist/src/rpc/latency.d.ts +9 -0
  59. package/dist/src/rtc/Call.d.ts +180 -0
  60. package/dist/src/rtc/CallMetadata.d.ts +9 -0
  61. package/dist/src/rtc/Dispatcher.d.ts +9 -0
  62. package/dist/src/rtc/IceTrickleBuffer.d.ts +11 -0
  63. package/dist/src/rtc/callEventHandlers.d.ts +5 -0
  64. package/dist/src/rtc/codecs.d.ts +2 -0
  65. package/dist/src/rtc/helpers/iceCandidate.d.ts +2 -0
  66. package/dist/src/rtc/helpers/tracks.d.ts +3 -0
  67. package/dist/src/rtc/publisher.d.ts +53 -0
  68. package/dist/src/rtc/signal.d.ts +5 -0
  69. package/dist/src/rtc/subscriber.d.ts +7 -0
  70. package/dist/src/rtc/types.d.ts +84 -0
  71. package/dist/src/rtc/videoLayers.d.ts +17 -0
  72. package/dist/src/stats/coordinator-stats-reporter.d.ts +10 -0
  73. package/dist/src/stats/state-store-stats-reporter.d.ts +57 -0
  74. package/dist/src/stats/types.d.ts +42 -0
  75. package/dist/src/store/index.d.ts +2 -0
  76. package/dist/src/store/rxUtils.d.ts +18 -0
  77. package/dist/src/store/stateStore.d.ts +182 -0
  78. package/generate-openapi.sh +32 -0
  79. package/index.ts +30 -0
  80. package/openapitools.json +7 -0
  81. package/package.json +54 -0
  82. package/rollup.config.mjs +48 -0
  83. package/src/Batcher.ts +43 -0
  84. package/src/CallDropScheduler.ts +192 -0
  85. package/src/StreamSfuClient.ts +185 -0
  86. package/src/StreamVideoClient.ts +487 -0
  87. package/src/__tests__/StreamVideoClient.test.ts +83 -0
  88. package/src/config/defaultConfigs.ts +15 -0
  89. package/src/config/types.ts +30 -0
  90. package/src/coordinator/StreamCoordinatorClient.ts +111 -0
  91. package/src/coordinator/connection/base64.ts +80 -0
  92. package/src/coordinator/connection/client.ts +815 -0
  93. package/src/coordinator/connection/connection.ts +750 -0
  94. package/src/coordinator/connection/connection_fallback.ts +239 -0
  95. package/src/coordinator/connection/errors.ts +70 -0
  96. package/src/coordinator/connection/events.ts +10 -0
  97. package/src/coordinator/connection/insights.ts +88 -0
  98. package/src/coordinator/connection/signing.ts +104 -0
  99. package/src/coordinator/connection/token_manager.ts +160 -0
  100. package/src/coordinator/connection/types.ts +120 -0
  101. package/src/coordinator/connection/utils.ts +148 -0
  102. package/src/devices.ts +266 -0
  103. package/src/events/call.ts +166 -0
  104. package/src/events/internal.ts +47 -0
  105. package/src/events/participant.ts +97 -0
  106. package/src/events/speaker.ts +62 -0
  107. package/src/gen/coordinator/index.ts +1653 -0
  108. package/src/gen/google/protobuf/descriptor.ts +3466 -0
  109. package/src/gen/google/protobuf/duration.ts +232 -0
  110. package/src/gen/google/protobuf/struct.ts +481 -0
  111. package/src/gen/google/protobuf/timestamp.ts +291 -0
  112. package/src/gen/video/coordinator/broadcast_v1/broadcast.ts +154 -0
  113. package/src/gen/video/coordinator/call_v1/call.ts +651 -0
  114. package/src/gen/video/coordinator/client_v1_rpc/client_rpc.client.ts +463 -0
  115. package/src/gen/video/coordinator/client_v1_rpc/client_rpc.ts +3819 -0
  116. package/src/gen/video/coordinator/client_v1_rpc/envelopes.ts +424 -0
  117. package/src/gen/video/coordinator/client_v1_rpc/websocket.ts +719 -0
  118. package/src/gen/video/coordinator/edge_v1/edge.ts +532 -0
  119. package/src/gen/video/coordinator/event_v1/event.ts +1171 -0
  120. package/src/gen/video/coordinator/geofence_v1/geofence.ts +128 -0
  121. package/src/gen/video/coordinator/member_v1/member.ts +138 -0
  122. package/src/gen/video/coordinator/participant_v1/participant.ts +261 -0
  123. package/src/gen/video/coordinator/push_v1/push.ts +651 -0
  124. package/src/gen/video/coordinator/stat_v1/stat.ts +656 -0
  125. package/src/gen/video/coordinator/user_v1/user.ts +277 -0
  126. package/src/gen/video/coordinator/utils_v1/utils.ts +98 -0
  127. package/src/gen/video/sfu/event/events.ts +1962 -0
  128. package/src/gen/video/sfu/models/models.ts +1062 -0
  129. package/src/gen/video/sfu/signal_rpc/signal.client.ts +108 -0
  130. package/src/gen/video/sfu/signal_rpc/signal.ts +906 -0
  131. package/src/helpers/browsers.ts +13 -0
  132. package/src/helpers/sound-detector.ts +85 -0
  133. package/src/rpc/createClient.ts +50 -0
  134. package/src/rpc/index.ts +2 -0
  135. package/src/rpc/latency.ts +43 -0
  136. package/src/rtc/Call.ts +585 -0
  137. package/src/rtc/CallMetadata.ts +24 -0
  138. package/src/rtc/Dispatcher.ts +46 -0
  139. package/src/rtc/IceTrickleBuffer.ts +21 -0
  140. package/src/rtc/callEventHandlers.ts +37 -0
  141. package/src/rtc/codecs.ts +61 -0
  142. package/src/rtc/helpers/iceCandidate.ts +16 -0
  143. package/src/rtc/helpers/tracks.ts +18 -0
  144. package/src/rtc/publisher.ts +305 -0
  145. package/src/rtc/signal.ts +34 -0
  146. package/src/rtc/subscriber.ts +85 -0
  147. package/src/rtc/types.ts +105 -0
  148. package/src/rtc/videoLayers.ts +103 -0
  149. package/src/stats/coordinator-stats-reporter.ts +167 -0
  150. package/src/stats/state-store-stats-reporter.ts +364 -0
  151. package/src/stats/types.ts +46 -0
  152. package/src/store/index.ts +2 -0
  153. package/src/store/rxUtils.ts +42 -0
  154. package/src/store/stateStore.ts +341 -0
  155. package/tsconfig.json +25 -0
  156. package/typedoc.json +11 -0
  157. package/vite.config.ts +11 -0
@@ -0,0 +1,487 @@
1
+ import {
2
+ StreamVideoReadOnlyStateStore,
3
+ StreamVideoWriteableStateStore,
4
+ } from './store';
5
+ import type {
6
+ DatacenterResponse,
7
+ GetCallEdgeServerRequest,
8
+ GetOrCreateCallRequest,
9
+ ICEServer,
10
+ } from './gen/coordinator';
11
+
12
+ import type { ReportCallStatEventRequest } from './gen/video/coordinator/client_v1_rpc/client_rpc';
13
+ import { measureResourceLoadLatencyTo } from './rpc';
14
+ import { StreamSfuClient } from './StreamSfuClient';
15
+ import { Call } from './rtc/Call';
16
+ import { CallMetadata } from './rtc/CallMetadata';
17
+
18
+ // import { reportStats } from './stats/coordinator-stats-reporter';
19
+ import { Timestamp } from './gen/google/protobuf/timestamp';
20
+ import { Batcher } from './Batcher';
21
+ import {
22
+ watchCallAccepted,
23
+ watchCallCancelled,
24
+ watchCallCreated,
25
+ watchCallRejected,
26
+ } from './events/call';
27
+ import { CALL_CONFIG } from './config/defaultConfigs';
28
+ import { CallConfig } from './config/types';
29
+ import { CallDropScheduler } from './CallDropScheduler';
30
+ import { StreamCoordinatorClient } from './coordinator/StreamCoordinatorClient';
31
+ import {
32
+ EventHandler,
33
+ StreamClientOptions,
34
+ TokenOrProvider,
35
+ User,
36
+ } from './coordinator/connection/types';
37
+
38
+ /**
39
+ * A `StreamVideoClient` instance lets you communicate with our API, and authenticate users.
40
+ */
41
+ export class StreamVideoClient {
42
+ /**
43
+ * Configuration parameters for controlling call behavior.
44
+ */
45
+ callConfig: CallConfig;
46
+ /**
47
+ * A reactive store that exposes all the state variables in a reactive manner - you can subscribe to changes of the different state variables. Our library is built in a way that all state changes are exposed in this store, so all UI changes in your application should be handled by subscribing to these variables.
48
+ * @angular If you're using our Angular SDK, you shouldn't be interacting with the state store directly, instead, you should be using the [`StreamVideoService`](./StreamVideoService.md).
49
+ */
50
+ readonly readOnlyStateStore: StreamVideoReadOnlyStateStore;
51
+ private readonly writeableStateStore: StreamVideoWriteableStateStore;
52
+ private callDropScheduler: CallDropScheduler | undefined;
53
+ private coordinatorClient: StreamCoordinatorClient;
54
+ /**
55
+ * @internal
56
+ */
57
+ public readonly userBatcher: Batcher<string>;
58
+
59
+ /**
60
+ * You should create only one instance of `StreamVideoClient`.
61
+ * @angular If you're using our Angular SDK, you shouldn't be calling the `constructor` directly, instead you should be using [`StreamVideoService`](./StreamVideoService.md/#init).
62
+ * @param apiKey your Stream API key
63
+ * @param opts the options for the client.
64
+ * @param {CallConfig} [callConfig=CALL_CONFIG.meeting] custom call configuration
65
+ */
66
+ constructor(
67
+ apiKey: string,
68
+ opts?: StreamClientOptions,
69
+ callConfig: CallConfig = CALL_CONFIG.meeting,
70
+ ) {
71
+ this.callConfig = callConfig;
72
+ this.coordinatorClient = new StreamCoordinatorClient(apiKey, opts);
73
+
74
+ this.writeableStateStore = new StreamVideoWriteableStateStore();
75
+ this.readOnlyStateStore = new StreamVideoReadOnlyStateStore(
76
+ this.writeableStateStore,
77
+ );
78
+
79
+ this.userBatcher = new Batcher<string>(
80
+ 3000,
81
+ // this.handleUserBatch,
82
+ () => {},
83
+ );
84
+
85
+ // reportStats(
86
+ // this.readOnlyStateStore,
87
+ // (e) =>
88
+ // this.reportCallStats(e).catch((err) => {
89
+ // console.error('Failed to report stats', err);
90
+ // }),
91
+ // (e) =>
92
+ // this.reportCallStatEvent(e).catch((err) => {
93
+ // console.error('Failed to report stats', err);
94
+ // }),
95
+ // );
96
+ }
97
+
98
+ // private handleUserBatch = (idList: string[]) => {
99
+ // this.client
100
+ // .queryUsers({
101
+ // mqJson: new TextEncoder().encode(
102
+ // JSON.stringify({ id: { $in: idList } }),
103
+ // ),
104
+ // sorts: [],
105
+ // })
106
+ // .then(({ response: { users } }) => {
107
+ // const mappedUsers = users.reduce<Record<string, User>>(
108
+ // (userMap, user) => {
109
+ // userMap[user.id] ??= user;
110
+ // return userMap;
111
+ // },
112
+ // {},
113
+ // );
114
+ //
115
+ // this.writeableStateStore.setCurrentValue(
116
+ // this.writeableStateStore.participantsSubject,
117
+ // (participants) =>
118
+ // participants.map((participant) => {
119
+ // const user = mappedUsers[participant.userId];
120
+ // return user ? { ...participant, user } : participant;
121
+ // }),
122
+ // );
123
+ // });
124
+ // };
125
+
126
+ /**
127
+ * Connects the given user to the client.
128
+ * Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.
129
+ * If the connection is successful, the connected user [state variable](#readonlystatestore) will be updated accordingly.
130
+ *
131
+ * @param user the user to connect.
132
+ * @param tokenOrProvider a token or a function that returns a token.
133
+ */
134
+ connectUser = async (user: User, tokenOrProvider: TokenOrProvider) => {
135
+ await this.coordinatorClient.connectUser(user, tokenOrProvider);
136
+
137
+ this.callDropScheduler = new CallDropScheduler(
138
+ this.writeableStateStore,
139
+ this.callConfig,
140
+ this.rejectCall,
141
+ this.cancelCall,
142
+ );
143
+
144
+ this.on('call.created', watchCallCreated(this.writeableStateStore));
145
+ this.on('call.accepted', watchCallAccepted(this.writeableStateStore));
146
+ this.on('call.rejected', watchCallRejected(this.writeableStateStore));
147
+ this.on('call.cancelled', watchCallCancelled(this.writeableStateStore));
148
+
149
+ this.writeableStateStore.setCurrentValue(
150
+ this.writeableStateStore.connectedUserSubject,
151
+ user,
152
+ );
153
+ };
154
+
155
+ /**
156
+ * Disconnects the currently connected user from the client.
157
+ *
158
+ * If the connection is successfully disconnected, the connected user [state variable](#readonlystatestore) will be updated accordingly
159
+ */
160
+ disconnectUser = async () => {
161
+ await this.coordinatorClient.disconnectUser();
162
+ this.callDropScheduler?.cleanUp();
163
+ this.writeableStateStore.setCurrentValue(
164
+ this.writeableStateStore.connectedUserSubject,
165
+ undefined,
166
+ );
167
+ };
168
+
169
+ /**
170
+ * You can subscribe to WebSocket events provided by the API.
171
+ * To remove a subscription, call the `off` method or, execute the returned unsubscribe function.
172
+ * Please note that subscribing to WebSocket events is an advanced use-case, for most use-cases it should be enough to watch for changes in the reactive [state store](#readonlystatestore).
173
+ *
174
+ * @param eventName the event name.
175
+ * @param callback the callback which will be called when the event is emitted.
176
+ * @returns an unsubscribe function.
177
+ */
178
+ on = (eventName: string, callback: EventHandler) => {
179
+ return this.coordinatorClient.on(eventName, callback);
180
+ };
181
+
182
+ /**
183
+ * Remove subscription for WebSocket events that were created by the `on` method.
184
+ *
185
+ * @param event the event name.
186
+ * @param callback the callback which was passed to the `on` method.
187
+ */
188
+ off = (event: string, callback: EventHandler) => {
189
+ return this.coordinatorClient.off(event, callback);
190
+ };
191
+
192
+ /**
193
+ * Allows you to create new calls with the given parameters.
194
+ * If a call with the same combination of type and id already exists, it will be returned.
195
+ *
196
+ * Causes the CallCreated event to be emitted to all the call members in case this call didnot exist before.
197
+ *
198
+ * @param id the id of the call.
199
+ * @param type the type of the call.
200
+ * @param data the data for the call.
201
+ * @returns A call metadata with information about the call.
202
+ */
203
+ getOrCreateCall = async (
204
+ id: string,
205
+ type: string,
206
+ data?: GetOrCreateCallRequest,
207
+ ) => {
208
+ const response = await this.coordinatorClient.getOrCreateCall(
209
+ id,
210
+ type,
211
+ data,
212
+ );
213
+ const { call } = response;
214
+ if (!call) {
215
+ console.log(`Call with id ${id} and type ${type} could not be created`);
216
+ return;
217
+ }
218
+
219
+ const currentPendingCalls = this.writeableStateStore.getCurrentValue(
220
+ this.writeableStateStore.pendingCallsSubject,
221
+ );
222
+ const callAlreadyRegistered = currentPendingCalls.find(
223
+ (pendingCall) => pendingCall.call.id === call.id,
224
+ );
225
+
226
+ if (!callAlreadyRegistered) {
227
+ this.writeableStateStore.setCurrentValue(
228
+ this.writeableStateStore.pendingCallsSubject,
229
+ (pendingCalls) => [...pendingCalls, new CallMetadata(call)],
230
+ );
231
+ return response;
232
+ } else {
233
+ // TODO: handle error?
234
+ return undefined;
235
+ }
236
+ };
237
+
238
+ /**
239
+ * Signals other users that I have accepted the incoming call.
240
+ * Causes the `CallAccepted` event to be emitted to all the call members.
241
+ * @param callCid config ID of the rejected call
242
+ * @returns
243
+ */
244
+ acceptCall = async (callCid: string) => {
245
+ // FIXME OL: change the method's signature to accept callId and callType
246
+ const [type, id] = callCid.split(':');
247
+ await this.coordinatorClient.sendEvent(id, type, {
248
+ event_type: 'call.accepted',
249
+ });
250
+ return await this.joinCall(id, type);
251
+ };
252
+
253
+ /**
254
+ * Signals other users that I have rejected the incoming call.
255
+ * Causes the `CallRejected` event to be emitted to all the call members.
256
+ * @param callCid config ID of the rejected call
257
+ * @returns
258
+ */
259
+ rejectCall = async (callCid: string) => {
260
+ // FIXME OL: change the method's signature to accept callId and callType
261
+ const [type, id] = callCid.split(':');
262
+ this.writeableStateStore.setCurrentValue(
263
+ this.writeableStateStore.pendingCallsSubject,
264
+ (pendingCalls) =>
265
+ pendingCalls.filter(
266
+ (incomingCall) => incomingCall.call.cid !== callCid,
267
+ ),
268
+ );
269
+ await this.coordinatorClient.sendEvent(id, type, {
270
+ event_type: 'call.rejected',
271
+ });
272
+ };
273
+
274
+ /**
275
+ * Signals other users that I have cancelled my call to them before they accepted it.
276
+ * Causes the CallCancelled event to be emitted to all the call members.
277
+ *
278
+ * Cancelling a call is only possible before the local participant joined the call.
279
+ * @param callCid config ID of the cancelled call
280
+ * @returns
281
+ */
282
+ cancelCall = async (callCid: string) => {
283
+ // FIXME OL: change the method's signature to accept callId and callType
284
+ const [type, id] = callCid.split(':');
285
+ const store = this.writeableStateStore;
286
+ const activeCall = store.getCurrentValue(store.activeCallSubject);
287
+ const leavingActiveCall = activeCall?.data.call.cid === callCid;
288
+ if (leavingActiveCall) {
289
+ activeCall.leave();
290
+ } else {
291
+ store.setCurrentValue(store.pendingCallsSubject, (pendingCalls) =>
292
+ pendingCalls.filter((pendingCall) => pendingCall.call.cid !== callCid),
293
+ );
294
+ }
295
+
296
+ const remoteParticipants = store.getCurrentValue(store.remoteParticipants$);
297
+ if (!remoteParticipants.length && !leavingActiveCall) {
298
+ await this.coordinatorClient.sendEvent(id, type, {
299
+ event_type: 'call.cancelled',
300
+ });
301
+ }
302
+ };
303
+
304
+ /**
305
+ * Allows you to create a new call with the given parameters and joins the call immediately.
306
+ * If a call with the same combination of `type` and `id` already exists, it will join the existing call.
307
+ *
308
+ * @param id the id of the call.
309
+ * @param type the type of the call.
310
+ * @param data the data for the call.
311
+ * @returns A [`Call`](./Call.md) instance that can be used to interact with the call.
312
+ */
313
+ joinCall = async (
314
+ id: string,
315
+ type: string,
316
+ data?: GetOrCreateCallRequest,
317
+ ) => {
318
+ const joinCallResponse = await this.coordinatorClient.joinCall(
319
+ id,
320
+ type,
321
+ data,
322
+ );
323
+
324
+ const { call: callMeta, edges, members } = joinCallResponse;
325
+ if (callMeta && edges) {
326
+ const edge = await this.getCallEdgeServer(id, type, edges);
327
+ if (edge.credentials && edge.credentials.server) {
328
+ // TODO OL: compute the initial value from `activeCallSubject`
329
+ this.writeableStateStore.setCurrentValue(
330
+ this.writeableStateStore.callRecordingInProgressSubject,
331
+ !!callMeta.record_egress, // FIXME OL: this is not correct
332
+ );
333
+
334
+ const { server, ice_servers, token } = edge.credentials;
335
+ const sfuClient = new StreamSfuClient(server.url!, token!);
336
+ const metadata = new CallMetadata(callMeta, members);
337
+ const callOptions = {
338
+ connectionConfig: this.toRtcConfiguration(ice_servers),
339
+ edgeName: server!.edge_name,
340
+ };
341
+ const call = new Call(
342
+ metadata,
343
+ sfuClient,
344
+ callOptions,
345
+ this.writeableStateStore,
346
+ this.userBatcher,
347
+ );
348
+ await call.join();
349
+
350
+ this.writeableStateStore.setCurrentValue(
351
+ this.writeableStateStore.activeCallSubject,
352
+ call,
353
+ );
354
+
355
+ return call;
356
+ } else {
357
+ // TODO: handle error?
358
+ return undefined;
359
+ }
360
+ } else {
361
+ // TODO: handle error?
362
+ return undefined;
363
+ }
364
+ };
365
+
366
+ /**
367
+ * Starts recording for the call described by the given `callId` and `callType`.
368
+ * @param callId can be extracted from a [`Call` instance](./Call.md/#data)
369
+ * @param callType can be extracted from a [`Call` instance](./Call.md/#data)
370
+ */
371
+ startRecording = async (callId: string, callType: string) => {
372
+ await this.coordinatorClient.startRecording(callId, callType);
373
+ this.writeableStateStore.setCurrentValue(
374
+ this.writeableStateStore.callRecordingInProgressSubject,
375
+ true,
376
+ );
377
+ };
378
+
379
+ /**
380
+ * Stops recording for the call described by the given `callId` and `callType`.
381
+ * @param callId can be extracted from a [`Call` instance](./Call.md/#data)
382
+ * @param callType can be extracted from a [`Call` instance](./Call.md/#data)
383
+ */
384
+ stopRecording = async (callId: string, callType: string) => {
385
+ await this.coordinatorClient.stopRecording(callId, callType);
386
+ this.writeableStateStore.setCurrentValue(
387
+ this.writeableStateStore.callRecordingInProgressSubject,
388
+ false,
389
+ );
390
+ };
391
+
392
+ /**
393
+ * Reports call WebRTC metrics to coordinator API
394
+ * @param stats
395
+ * @returns
396
+ */
397
+ private reportCallStats = async (stats: Object) => {
398
+ const callMetadata = this.writeableStateStore.getCurrentValue(
399
+ this.writeableStateStore.activeCallSubject,
400
+ )?.data;
401
+
402
+ if (!callMetadata) {
403
+ console.log("There isn't an active call");
404
+ return;
405
+ }
406
+ const request = {
407
+ callCid: callMetadata.call.cid,
408
+ statsJson: new TextEncoder().encode(JSON.stringify(stats)),
409
+ };
410
+ await this.coordinatorClient.reportCallStats(
411
+ callMetadata.call.id!,
412
+ callMetadata.call.type!,
413
+ request,
414
+ );
415
+ };
416
+
417
+ private getCallEdgeServer = async (
418
+ id: string,
419
+ type: string,
420
+ edges: DatacenterResponse[],
421
+ ) => {
422
+ const latencyByEdge: GetCallEdgeServerRequest['latency_measurements'] = {};
423
+ await Promise.all(
424
+ edges.map(async (edge) => {
425
+ latencyByEdge[edge.name!] = await measureResourceLoadLatencyTo(
426
+ edge.latency_url!,
427
+ );
428
+ }),
429
+ );
430
+
431
+ return await this.coordinatorClient.getCallEdgeServer(id, type, {
432
+ latency_measurements: latencyByEdge,
433
+ });
434
+ };
435
+
436
+ private toRtcConfiguration = (config?: ICEServer[]) => {
437
+ if (!config || config.length === 0) return undefined;
438
+ const rtcConfig: RTCConfiguration = {
439
+ iceServers: config.map((ice) => ({
440
+ urls: ice.urls!,
441
+ username: ice.username,
442
+ credential: ice.password,
443
+ })),
444
+ };
445
+ return rtcConfig;
446
+ };
447
+
448
+ /**
449
+ * Reports call events (for example local participant muted themselves) to the coordinator API
450
+ * @param statEvent
451
+ * @returns
452
+ */
453
+ private reportCallStatEvent = async (
454
+ statEvent: ReportCallStatEventRequest['event'],
455
+ ) => {
456
+ const callMetadata = this.writeableStateStore.getCurrentValue(
457
+ this.writeableStateStore.activeCallSubject,
458
+ )?.data;
459
+ if (!callMetadata) {
460
+ console.log("There isn't an active call");
461
+ return;
462
+ }
463
+
464
+ const request = {
465
+ callCid: callMetadata.call.cid,
466
+ timestamp: Timestamp.fromDate(new Date()),
467
+ event: statEvent,
468
+ };
469
+ await this.coordinatorClient.reportCallStatEvent(
470
+ callMetadata.call.id!,
471
+ callMetadata.call.type!,
472
+ request,
473
+ );
474
+ };
475
+
476
+ /**
477
+ * Sets the `participant.isPinned` value.
478
+ * @param sessionId the session id of the participant
479
+ * @param isPinned the value to set the participant.isPinned
480
+ * @returns
481
+ */
482
+ setParticipantIsPinned = (sessionId: string, isPinned: boolean): void => {
483
+ this.writeableStateStore.updateParticipant(sessionId, {
484
+ isPinned,
485
+ });
486
+ };
487
+ }
@@ -0,0 +1,83 @@
1
+ import { describe, it, vi, beforeEach, expect } from 'vitest';
2
+ import { StreamVideoClient } from '../StreamVideoClient';
3
+ import { StreamVideoParticipant } from '../rtc/types';
4
+ import { mock } from 'vitest-mock-extended';
5
+
6
+ describe('StreamVideoClient', () => {
7
+ let client: StreamVideoClient;
8
+ const getPinnedParticipants = () =>
9
+ client.readOnlyStateStore.getCurrentValue(
10
+ client.readOnlyStateStore.pinnedParticipants$,
11
+ );
12
+
13
+ beforeEach(() => {
14
+ vi.mock('../rpc/createClient', () => {
15
+ return {
16
+ createCoordinatorClient: vi.fn(),
17
+ withHeaders: vi.fn(),
18
+ };
19
+ });
20
+ vi.mock('../ws/connection', () => {
21
+ return {
22
+ createSocketConnection: vi.fn(),
23
+ };
24
+ });
25
+ vi.mock('../stats/coordinator-stats-reporter');
26
+ client = new StreamVideoClient('123');
27
+ });
28
+
29
+ // it('should connect', async () => {
30
+ // const user = {
31
+ // id: 'marcelo',
32
+ // name: 'marcelo',
33
+ // role: 'admin',
34
+ // teams: ['team-1, team-2'],
35
+ // imageUrl: '/profile.png',
36
+ // customJson: new Uint8Array(),
37
+ // };
38
+ // const apiKey = '123';
39
+ // const token = 'abc';
40
+ // await client.connect(apiKey, token, user);
41
+ //
42
+ // expect(createSocketConnection).toHaveBeenCalledWith(
43
+ // expect.anything(),
44
+ // apiKey,
45
+ // token,
46
+ // user,
47
+ // );
48
+ // });
49
+
50
+ it('does not pin a participant ', async () => {
51
+ client.setParticipantIsPinned('non-existing-essionid', true);
52
+
53
+ const participantsInStore = client.readOnlyStateStore.getCurrentValue(
54
+ client.readOnlyStateStore.pinnedParticipants$,
55
+ );
56
+ expect(participantsInStore).toHaveLength(0);
57
+ });
58
+
59
+ it('does pin one participant which populates pinnedParticipants', async () => {
60
+ const p1 = mock<StreamVideoParticipant>({
61
+ isPinned: false,
62
+ sessionId: '123abc',
63
+ publishedTracks: [],
64
+ });
65
+ const p2 = mock<StreamVideoParticipant>({
66
+ isPinned: false,
67
+ sessionId: '456def',
68
+ });
69
+ let pinnedParticipants = getPinnedParticipants();
70
+ expect(pinnedParticipants).toHaveLength(0);
71
+
72
+ client['writeableStateStore'].setCurrentValue(
73
+ client['writeableStateStore'].participantsSubject,
74
+ [p1, p2],
75
+ );
76
+
77
+ client.setParticipantIsPinned(p2.sessionId, true);
78
+
79
+ pinnedParticipants = getPinnedParticipants();
80
+ expect(pinnedParticipants).toHaveLength(1);
81
+ expect(pinnedParticipants[0].sessionId).toEqual(p2.sessionId);
82
+ });
83
+ });
@@ -0,0 +1,15 @@
1
+ import { CallConfig, CallType } from './types';
2
+
3
+ export const CALL_CONFIG: Record<CallType, CallConfig> = {
4
+ ring: {
5
+ autoRejectTimeoutInMs: 30 * 1000,
6
+ autoRejectWhenInCall: false,
7
+ joinCallInstantly: false,
8
+ playSounds: true,
9
+ },
10
+ meeting: {
11
+ autoRejectWhenInCall: false,
12
+ joinCallInstantly: true,
13
+ playSounds: false,
14
+ },
15
+ };
@@ -0,0 +1,30 @@
1
+ export type CallType = 'ring' | 'meeting';
2
+
3
+ export type CallConfig = {
4
+ /**
5
+ * Optional parameter to define after how many milliseconds without receiving CallAccepted event should the outgoing call be cancelled.
6
+ * If not defined, the call will not be cancelled automatically by the client and the user is expected to cancel or leave the call manually.
7
+ * Note: Is relevant to outgoing calls only.
8
+ */
9
+ autoCancelTimeoutInMs?: number;
10
+ /**
11
+ * Optional parameter to define after how many milliseconds an incoming ring call should be automatically rejected.
12
+ * If not defined, the call will not be rejected automatically by the client and the user is expected to reject the call manually.
13
+ * Note: Is relevant to incoming calls only.
14
+ */
15
+ autoRejectTimeoutInMs?: number;
16
+ /**
17
+ * Optional parameter enabling automatic rejections of incoming calls while participating at an active call.
18
+ */
19
+ autoRejectWhenInCall?: boolean;
20
+ /**
21
+ * Flag signals to SDK components not to wait until CallAccepted event is received,
22
+ * but join the call immediately upon emitting a new PendingCall by the stateStore.
23
+ * Note: Is relevant to outgoing calls only. Once the caller joins an outgoing call it's not possible to cancel that call.
24
+ */
25
+ joinCallInstantly?: boolean;
26
+ /**
27
+ * Flag signals, whether sounds should be played upon initiation of a new call.
28
+ */
29
+ playSounds?: boolean;
30
+ };