@stream-io/video-client 1.14.0 → 1.15.1

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 (92) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/index.browser.es.js +1533 -1783
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +1514 -1783
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +1533 -1783
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +43 -28
  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/video/sfu/event/events.d.ts +38 -19
  15. package/dist/src/gen/video/sfu/models/models.d.ts +76 -9
  16. package/dist/src/helpers/array.d.ts +7 -0
  17. package/dist/src/permissions/PermissionsContext.d.ts +6 -0
  18. package/dist/src/rtc/BasePeerConnection.d.ts +90 -0
  19. package/dist/src/rtc/Dispatcher.d.ts +0 -1
  20. package/dist/src/rtc/IceTrickleBuffer.d.ts +3 -2
  21. package/dist/src/rtc/Publisher.d.ts +32 -86
  22. package/dist/src/rtc/Subscriber.d.ts +4 -56
  23. package/dist/src/rtc/TransceiverCache.d.ts +55 -0
  24. package/dist/src/rtc/codecs.d.ts +1 -15
  25. package/dist/src/rtc/helpers/sdp.d.ts +8 -0
  26. package/dist/src/rtc/helpers/tracks.d.ts +1 -0
  27. package/dist/src/rtc/index.d.ts +3 -0
  28. package/dist/src/rtc/videoLayers.d.ts +11 -25
  29. package/dist/src/stats/{stateStoreStatsReporter.d.ts → CallStateStatsReporter.d.ts} +5 -1
  30. package/dist/src/stats/SfuStatsReporter.d.ts +4 -2
  31. package/dist/src/stats/index.d.ts +1 -1
  32. package/dist/src/stats/types.d.ts +8 -0
  33. package/dist/src/types.d.ts +12 -22
  34. package/package.json +1 -1
  35. package/src/Call.ts +254 -268
  36. package/src/StreamSfuClient.ts +9 -14
  37. package/src/StreamVideoClient.ts +1 -1
  38. package/src/__tests__/Call.publishing.test.ts +306 -0
  39. package/src/devices/CameraManager.ts +33 -16
  40. package/src/devices/InputMediaDeviceManager.ts +38 -27
  41. package/src/devices/MicrophoneManager.ts +29 -8
  42. package/src/devices/ScreenShareManager.ts +6 -8
  43. package/src/devices/__tests__/CameraManager.test.ts +111 -14
  44. package/src/devices/__tests__/InputMediaDeviceManager.test.ts +4 -4
  45. package/src/devices/__tests__/MicrophoneManager.test.ts +59 -21
  46. package/src/devices/__tests__/ScreenShareManager.test.ts +5 -5
  47. package/src/devices/__tests__/mocks.ts +1 -0
  48. package/src/events/__tests__/internal.test.ts +132 -0
  49. package/src/events/__tests__/mutes.test.ts +0 -3
  50. package/src/events/__tests__/speaker.test.ts +92 -0
  51. package/src/events/participant.ts +3 -4
  52. package/src/gen/video/sfu/event/events.ts +91 -30
  53. package/src/gen/video/sfu/models/models.ts +105 -13
  54. package/src/helpers/array.ts +14 -0
  55. package/src/permissions/PermissionsContext.ts +22 -0
  56. package/src/permissions/__tests__/PermissionsContext.test.ts +40 -0
  57. package/src/rpc/__tests__/createClient.test.ts +38 -0
  58. package/src/rpc/createClient.ts +11 -5
  59. package/src/rtc/BasePeerConnection.ts +240 -0
  60. package/src/rtc/Dispatcher.ts +0 -9
  61. package/src/rtc/IceTrickleBuffer.ts +24 -4
  62. package/src/rtc/Publisher.ts +210 -528
  63. package/src/rtc/Subscriber.ts +26 -200
  64. package/src/rtc/TransceiverCache.ts +120 -0
  65. package/src/rtc/__tests__/Publisher.test.ts +407 -210
  66. package/src/rtc/__tests__/Subscriber.test.ts +88 -36
  67. package/src/rtc/__tests__/mocks/webrtc.mocks.ts +22 -2
  68. package/src/rtc/__tests__/videoLayers.test.ts +161 -54
  69. package/src/rtc/codecs.ts +1 -131
  70. package/src/rtc/helpers/__tests__/rtcConfiguration.test.ts +34 -0
  71. package/src/rtc/helpers/__tests__/sdp.test.ts +59 -0
  72. package/src/rtc/helpers/sdp.ts +30 -0
  73. package/src/rtc/helpers/tracks.ts +3 -0
  74. package/src/rtc/index.ts +4 -0
  75. package/src/rtc/videoLayers.ts +68 -76
  76. package/src/stats/{stateStoreStatsReporter.ts → CallStateStatsReporter.ts} +58 -27
  77. package/src/stats/SfuStatsReporter.ts +31 -3
  78. package/src/stats/index.ts +1 -1
  79. package/src/stats/types.ts +12 -0
  80. package/src/types.ts +12 -22
  81. package/dist/src/helpers/sdp-munging.d.ts +0 -24
  82. package/dist/src/rtc/bitrateLookup.d.ts +0 -2
  83. package/dist/src/rtc/helpers/iceCandidate.d.ts +0 -2
  84. package/src/helpers/__tests__/hq-audio-sdp.ts +0 -332
  85. package/src/helpers/__tests__/sdp-munging.test.ts +0 -283
  86. package/src/helpers/sdp-munging.ts +0 -265
  87. package/src/rtc/__tests__/bitrateLookup.test.ts +0 -12
  88. package/src/rtc/__tests__/codecs.test.ts +0 -145
  89. package/src/rtc/bitrateLookup.ts +0 -61
  90. package/src/rtc/helpers/iceCandidate.ts +0 -16
  91. /package/dist/src/{compatibility.d.ts → helpers/compatibility.d.ts} +0 -0
  92. /package/src/{compatibility.ts → helpers/compatibility.ts} +0 -0
@@ -8,10 +8,10 @@ import { GoAwayReason } from '../models/models';
8
8
  import { CallGrants } from '../models/models';
9
9
  import { Codec } from '../models/models';
10
10
  import { ConnectionQuality } from '../models/models';
11
- import { PublishOptions } from '../models/models';
12
11
  import { CallState } from '../models/models';
13
12
  import { TrackSubscriptionDetails } from '../signal_rpc/signal';
14
13
  import { TrackInfo } from '../models/models';
14
+ import { SubscribeOption } from '../models/models';
15
15
  import { ClientDetails } from '../models/models';
16
16
  import { TrackUnpublishReason } from '../models/models';
17
17
  import { Participant } from '../models/models';
@@ -244,16 +244,6 @@ export interface SfuEvent {
244
244
  */
245
245
  participantMigrationComplete: ParticipantMigrationComplete;
246
246
  }
247
- | {
248
- oneofKind: 'codecNegotiationComplete';
249
- /**
250
- * CodecNegotiationComplete is sent to signal the completion of a codec negotiation.
251
- * SDKs can safely stop previous transceivers
252
- *
253
- * @generated from protobuf field: stream.video.sfu.event.CodecNegotiationComplete codec_negotiation_complete = 26;
254
- */
255
- codecNegotiationComplete: CodecNegotiationComplete;
256
- }
257
247
  | {
258
248
  oneofKind: 'changePublishOptions';
259
249
  /**
@@ -272,14 +262,18 @@ export interface SfuEvent {
272
262
  */
273
263
  export interface ChangePublishOptions {
274
264
  /**
275
- * @generated from protobuf field: stream.video.sfu.models.PublishOption publish_option = 1;
265
+ * @generated from protobuf field: repeated stream.video.sfu.models.PublishOption publish_options = 1;
276
266
  */
277
- publishOption?: PublishOption;
267
+ publishOptions: PublishOption[];
268
+ /**
269
+ * @generated from protobuf field: string reason = 2;
270
+ */
271
+ reason: string;
278
272
  }
279
273
  /**
280
- * @generated from protobuf message stream.video.sfu.event.CodecNegotiationComplete
274
+ * @generated from protobuf message stream.video.sfu.event.ChangePublishOptionsComplete
281
275
  */
282
- export interface CodecNegotiationComplete {}
276
+ export interface ChangePublishOptionsComplete {}
283
277
  /**
284
278
  * @generated from protobuf message stream.video.sfu.event.ParticipantMigrationComplete
285
279
  */
@@ -504,6 +498,14 @@ export interface JoinRequest {
504
498
  * @generated from protobuf field: stream.video.sfu.event.ReconnectDetails reconnect_details = 7;
505
499
  */
506
500
  reconnectDetails?: ReconnectDetails;
501
+ /**
502
+ * @generated from protobuf field: repeated stream.video.sfu.models.PublishOption preferred_publish_options = 9;
503
+ */
504
+ preferredPublishOptions: PublishOption[];
505
+ /**
506
+ * @generated from protobuf field: repeated stream.video.sfu.models.SubscribeOption preferred_subscribe_options = 10;
507
+ */
508
+ preferredSubscribeOptions: SubscribeOption[];
507
509
  }
508
510
  /**
509
511
  * @generated from protobuf message stream.video.sfu.event.ReconnectDetails
@@ -570,9 +572,9 @@ export interface JoinResponse {
570
572
  */
571
573
  fastReconnectDeadlineSeconds: number;
572
574
  /**
573
- * @generated from protobuf field: stream.video.sfu.models.PublishOptions publish_options = 4;
575
+ * @generated from protobuf field: repeated stream.video.sfu.models.PublishOption publish_options = 4;
574
576
  */
575
- publishOptions?: PublishOptions;
577
+ publishOptions: PublishOption[];
576
578
  }
577
579
  /**
578
580
  * ParticipantJoined is fired when a user joins a call
@@ -729,6 +731,14 @@ export interface AudioSender {
729
731
  * @generated from protobuf field: stream.video.sfu.models.Codec codec = 2;
730
732
  */
731
733
  codec?: Codec;
734
+ /**
735
+ * @generated from protobuf field: stream.video.sfu.models.TrackType track_type = 3;
736
+ */
737
+ trackType: TrackType;
738
+ /**
739
+ * @generated from protobuf field: int32 publish_option_id = 4;
740
+ */
741
+ publishOptionId: number;
732
742
  }
733
743
  /**
734
744
  * VideoLayerSetting is used to specify various parameters of a particular encoding in simulcast.
@@ -779,6 +789,14 @@ export interface VideoSender {
779
789
  * @generated from protobuf field: repeated stream.video.sfu.event.VideoLayerSetting layers = 3;
780
790
  */
781
791
  layers: VideoLayerSetting[];
792
+ /**
793
+ * @generated from protobuf field: stream.video.sfu.models.TrackType track_type = 4;
794
+ */
795
+ trackType: TrackType;
796
+ /**
797
+ * @generated from protobuf field: int32 publish_option_id = 5;
798
+ */
799
+ publishOptionId: number;
782
800
  }
783
801
  /**
784
802
  * sent to users when they need to change the quality of their video
@@ -1000,13 +1018,6 @@ class SfuEvent$Type extends MessageType<SfuEvent> {
1000
1018
  oneof: 'eventPayload',
1001
1019
  T: () => ParticipantMigrationComplete,
1002
1020
  },
1003
- {
1004
- no: 26,
1005
- name: 'codec_negotiation_complete',
1006
- kind: 'message',
1007
- oneof: 'eventPayload',
1008
- T: () => CodecNegotiationComplete,
1009
- },
1010
1021
  {
1011
1022
  no: 27,
1012
1023
  name: 'change_publish_options',
@@ -1027,10 +1038,12 @@ class ChangePublishOptions$Type extends MessageType<ChangePublishOptions> {
1027
1038
  super('stream.video.sfu.event.ChangePublishOptions', [
1028
1039
  {
1029
1040
  no: 1,
1030
- name: 'publish_option',
1041
+ name: 'publish_options',
1031
1042
  kind: 'message',
1043
+ repeat: 1 /*RepeatType.PACKED*/,
1032
1044
  T: () => PublishOption,
1033
1045
  },
1046
+ { no: 2, name: 'reason', kind: 'scalar', T: 9 /*ScalarType.STRING*/ },
1034
1047
  ]);
1035
1048
  }
1036
1049
  }
@@ -1039,15 +1052,16 @@ class ChangePublishOptions$Type extends MessageType<ChangePublishOptions> {
1039
1052
  */
1040
1053
  export const ChangePublishOptions = new ChangePublishOptions$Type();
1041
1054
  // @generated message type with reflection information, may provide speed optimized methods
1042
- class CodecNegotiationComplete$Type extends MessageType<CodecNegotiationComplete> {
1055
+ class ChangePublishOptionsComplete$Type extends MessageType<ChangePublishOptionsComplete> {
1043
1056
  constructor() {
1044
- super('stream.video.sfu.event.CodecNegotiationComplete', []);
1057
+ super('stream.video.sfu.event.ChangePublishOptionsComplete', []);
1045
1058
  }
1046
1059
  }
1047
1060
  /**
1048
- * @generated MessageType for protobuf message stream.video.sfu.event.CodecNegotiationComplete
1061
+ * @generated MessageType for protobuf message stream.video.sfu.event.ChangePublishOptionsComplete
1049
1062
  */
1050
- export const CodecNegotiationComplete = new CodecNegotiationComplete$Type();
1063
+ export const ChangePublishOptionsComplete =
1064
+ new ChangePublishOptionsComplete$Type();
1051
1065
  // @generated message type with reflection information, may provide speed optimized methods
1052
1066
  class ParticipantMigrationComplete$Type extends MessageType<ParticipantMigrationComplete> {
1053
1067
  constructor() {
@@ -1306,6 +1320,20 @@ class JoinRequest$Type extends MessageType<JoinRequest> {
1306
1320
  kind: 'message',
1307
1321
  T: () => ReconnectDetails,
1308
1322
  },
1323
+ {
1324
+ no: 9,
1325
+ name: 'preferred_publish_options',
1326
+ kind: 'message',
1327
+ repeat: 1 /*RepeatType.PACKED*/,
1328
+ T: () => PublishOption,
1329
+ },
1330
+ {
1331
+ no: 10,
1332
+ name: 'preferred_subscribe_options',
1333
+ kind: 'message',
1334
+ repeat: 1 /*RepeatType.PACKED*/,
1335
+ T: () => SubscribeOption,
1336
+ },
1309
1337
  ]);
1310
1338
  }
1311
1339
  }
@@ -1413,7 +1441,8 @@ class JoinResponse$Type extends MessageType<JoinResponse> {
1413
1441
  no: 4,
1414
1442
  name: 'publish_options',
1415
1443
  kind: 'message',
1416
- T: () => PublishOptions,
1444
+ repeat: 1 /*RepeatType.PACKED*/,
1445
+ T: () => PublishOption,
1417
1446
  },
1418
1447
  ]);
1419
1448
  }
@@ -1578,6 +1607,22 @@ class AudioSender$Type extends MessageType<AudioSender> {
1578
1607
  constructor() {
1579
1608
  super('stream.video.sfu.event.AudioSender', [
1580
1609
  { no: 2, name: 'codec', kind: 'message', T: () => Codec },
1610
+ {
1611
+ no: 3,
1612
+ name: 'track_type',
1613
+ kind: 'enum',
1614
+ T: () => [
1615
+ 'stream.video.sfu.models.TrackType',
1616
+ TrackType,
1617
+ 'TRACK_TYPE_',
1618
+ ],
1619
+ },
1620
+ {
1621
+ no: 4,
1622
+ name: 'publish_option_id',
1623
+ kind: 'scalar',
1624
+ T: 5 /*ScalarType.INT32*/,
1625
+ },
1581
1626
  ]);
1582
1627
  }
1583
1628
  }
@@ -1630,6 +1675,22 @@ class VideoSender$Type extends MessageType<VideoSender> {
1630
1675
  repeat: 1 /*RepeatType.PACKED*/,
1631
1676
  T: () => VideoLayerSetting,
1632
1677
  },
1678
+ {
1679
+ no: 4,
1680
+ name: 'track_type',
1681
+ kind: 'enum',
1682
+ T: () => [
1683
+ 'stream.video.sfu.models.TrackType',
1684
+ TrackType,
1685
+ 'TRACK_TYPE_',
1686
+ ],
1687
+ },
1688
+ {
1689
+ no: 5,
1690
+ name: 'publish_option_id',
1691
+ kind: 'scalar',
1692
+ T: 5 /*ScalarType.INT32*/,
1693
+ },
1633
1694
  ]);
1634
1695
  }
1635
1696
  }
@@ -5,6 +5,7 @@
5
5
  import { MessageType } from '@protobuf-ts/runtime';
6
6
  import { Struct } from '../../../google/protobuf/struct';
7
7
  import { Timestamp } from '../../../google/protobuf/timestamp';
8
+
8
9
  /**
9
10
  * CallState is the current state of the call
10
11
  * as seen by an SFU.
@@ -194,49 +195,108 @@ export interface VideoLayer {
194
195
  quality: VideoQuality;
195
196
  }
196
197
  /**
197
- * @generated from protobuf message stream.video.sfu.models.PublishOptions
198
+ * SubscribeOption represents the configuration options for subscribing to a track.
199
+ *
200
+ * @generated from protobuf message stream.video.sfu.models.SubscribeOption
198
201
  */
199
- export interface PublishOptions {
202
+ export interface SubscribeOption {
203
+ /**
204
+ * The type of the track being subscribed (e.g., video, screenshare).
205
+ *
206
+ * @generated from protobuf field: stream.video.sfu.models.TrackType track_type = 1;
207
+ */
208
+ trackType: TrackType;
200
209
  /**
201
- * @generated from protobuf field: repeated stream.video.sfu.models.PublishOption codecs = 1;
210
+ * The codecs supported by the subscriber for decoding tracks.
211
+ *
212
+ * @generated from protobuf field: repeated stream.video.sfu.models.Codec codecs = 2;
202
213
  */
203
- codecs: PublishOption[];
214
+ codecs: Codec[];
204
215
  }
205
216
  /**
217
+ * PublishOption represents the configuration options for publishing a track.
218
+ *
206
219
  * @generated from protobuf message stream.video.sfu.models.PublishOption
207
220
  */
208
221
  export interface PublishOption {
209
222
  /**
223
+ * The type of the track being published (e.g., video, screenshare).
224
+ *
210
225
  * @generated from protobuf field: stream.video.sfu.models.TrackType track_type = 1;
211
226
  */
212
227
  trackType: TrackType;
213
228
  /**
229
+ * The codec to be used for encoding the track (e.g., VP8, VP9, H264).
230
+ *
214
231
  * @generated from protobuf field: stream.video.sfu.models.Codec codec = 2;
215
232
  */
216
233
  codec?: Codec;
217
234
  /**
235
+ * The target bitrate for the published track, in bits per second.
236
+ *
218
237
  * @generated from protobuf field: int32 bitrate = 3;
219
238
  */
220
239
  bitrate: number;
221
240
  /**
241
+ * The target frames per second (FPS) for video encoding.
242
+ *
222
243
  * @generated from protobuf field: int32 fps = 4;
223
244
  */
224
245
  fps: number;
225
246
  /**
247
+ * The maximum number of spatial layers to send.
248
+ * - For SVC (e.g., VP9), spatial layers downscale by a factor of 2:
249
+ * - 1 layer: full resolution
250
+ * - 2 layers: full resolution + half resolution
251
+ * - 3 layers: full resolution + half resolution + quarter resolution
252
+ * - For non-SVC codecs (e.g., VP8/H264), this determines the number of
253
+ * encoded resolutions (e.g., quarter, half, full) sent for simulcast.
254
+ *
226
255
  * @generated from protobuf field: int32 max_spatial_layers = 5;
227
256
  */
228
257
  maxSpatialLayers: number;
229
258
  /**
259
+ * The maximum number of temporal layers for scalable video coding (SVC).
260
+ * Temporal layers allow varying frame rates for different bandwidths.
261
+ *
230
262
  * @generated from protobuf field: int32 max_temporal_layers = 6;
231
263
  */
232
264
  maxTemporalLayers: number;
265
+ /**
266
+ * The dimensions of the video (e.g., width and height in pixels).
267
+ * Spatial layers are based on this base resolution. For example, if the base
268
+ * resolution is 1280x720:
269
+ * - Full resolution (1 layer) = 1280x720
270
+ * - Half resolution (2 layers) = 640x360
271
+ * - Quarter resolution (3 layers) = 320x180
272
+ *
273
+ * @generated from protobuf field: stream.video.sfu.models.VideoDimension video_dimension = 7;
274
+ */
275
+ videoDimension?: VideoDimension;
276
+ /**
277
+ * The unique identifier for the publish request.
278
+ * - This `id` is assigned exclusively by the SFU. Any `id` set by the client
279
+ * in the `PublishOption` will be ignored and overwritten by the SFU.
280
+ * - The primary purpose of this `id` is to uniquely identify each publish
281
+ * request, even in scenarios where multiple publish requests for the same
282
+ * `track_type` and `codec` are active simultaneously.
283
+ * For example:
284
+ * - A user may publish two tracks of the same type (e.g., video) and codec
285
+ * (e.g., VP9) concurrently.
286
+ * - This uniqueness ensures that individual requests can be managed
287
+ * independently. For instance, an `id` is critical when stopping a specific
288
+ * publish request without affecting others.
289
+ *
290
+ * @generated from protobuf field: int32 id = 8;
291
+ */
292
+ id: number;
233
293
  }
234
294
  /**
235
295
  * @generated from protobuf message stream.video.sfu.models.Codec
236
296
  */
237
297
  export interface Codec {
238
298
  /**
239
- * @generated from protobuf field: uint32 payload_type = 11;
299
+ * @generated from protobuf field: uint32 payload_type = 16;
240
300
  */
241
301
  payloadType: number;
242
302
  /**
@@ -248,7 +308,7 @@ export interface Codec {
248
308
  */
249
309
  clockRate: number;
250
310
  /**
251
- * @generated from protobuf field: string encoding_parameters = 13;
311
+ * @generated from protobuf field: string encoding_parameters = 15;
252
312
  */
253
313
  encodingParameters: string;
254
314
  /**
@@ -311,6 +371,14 @@ export interface TrackInfo {
311
371
  * @generated from protobuf field: bool muted = 10;
312
372
  */
313
373
  muted: boolean;
374
+ /**
375
+ * @generated from protobuf field: stream.video.sfu.models.Codec codec = 11;
376
+ */
377
+ codec?: Codec;
378
+ /**
379
+ * @generated from protobuf field: int32 publish_option_id = 12;
380
+ */
381
+ publishOptionId: number;
314
382
  }
315
383
  /**
316
384
  * @generated from protobuf message stream.video.sfu.models.Error
@@ -1102,23 +1170,33 @@ class VideoLayer$Type extends MessageType<VideoLayer> {
1102
1170
  */
1103
1171
  export const VideoLayer = new VideoLayer$Type();
1104
1172
  // @generated message type with reflection information, may provide speed optimized methods
1105
- class PublishOptions$Type extends MessageType<PublishOptions> {
1173
+ class SubscribeOption$Type extends MessageType<SubscribeOption> {
1106
1174
  constructor() {
1107
- super('stream.video.sfu.models.PublishOptions', [
1175
+ super('stream.video.sfu.models.SubscribeOption', [
1108
1176
  {
1109
1177
  no: 1,
1178
+ name: 'track_type',
1179
+ kind: 'enum',
1180
+ T: () => [
1181
+ 'stream.video.sfu.models.TrackType',
1182
+ TrackType,
1183
+ 'TRACK_TYPE_',
1184
+ ],
1185
+ },
1186
+ {
1187
+ no: 2,
1110
1188
  name: 'codecs',
1111
1189
  kind: 'message',
1112
1190
  repeat: 1 /*RepeatType.PACKED*/,
1113
- T: () => PublishOption,
1191
+ T: () => Codec,
1114
1192
  },
1115
1193
  ]);
1116
1194
  }
1117
1195
  }
1118
1196
  /**
1119
- * @generated MessageType for protobuf message stream.video.sfu.models.PublishOptions
1197
+ * @generated MessageType for protobuf message stream.video.sfu.models.SubscribeOption
1120
1198
  */
1121
- export const PublishOptions = new PublishOptions$Type();
1199
+ export const SubscribeOption = new SubscribeOption$Type();
1122
1200
  // @generated message type with reflection information, may provide speed optimized methods
1123
1201
  class PublishOption$Type extends MessageType<PublishOption> {
1124
1202
  constructor() {
@@ -1148,6 +1226,13 @@ class PublishOption$Type extends MessageType<PublishOption> {
1148
1226
  kind: 'scalar',
1149
1227
  T: 5 /*ScalarType.INT32*/,
1150
1228
  },
1229
+ {
1230
+ no: 7,
1231
+ name: 'video_dimension',
1232
+ kind: 'message',
1233
+ T: () => VideoDimension,
1234
+ },
1235
+ { no: 8, name: 'id', kind: 'scalar', T: 5 /*ScalarType.INT32*/ },
1151
1236
  ]);
1152
1237
  }
1153
1238
  }
@@ -1160,7 +1245,7 @@ class Codec$Type extends MessageType<Codec> {
1160
1245
  constructor() {
1161
1246
  super('stream.video.sfu.models.Codec', [
1162
1247
  {
1163
- no: 11,
1248
+ no: 16,
1164
1249
  name: 'payload_type',
1165
1250
  kind: 'scalar',
1166
1251
  T: 13 /*ScalarType.UINT32*/,
@@ -1173,7 +1258,7 @@ class Codec$Type extends MessageType<Codec> {
1173
1258
  T: 13 /*ScalarType.UINT32*/,
1174
1259
  },
1175
1260
  {
1176
- no: 13,
1261
+ no: 15,
1177
1262
  name: 'encoding_parameters',
1178
1263
  kind: 'scalar',
1179
1264
  T: 9 /*ScalarType.STRING*/,
@@ -1237,6 +1322,13 @@ class TrackInfo$Type extends MessageType<TrackInfo> {
1237
1322
  { no: 8, name: 'stereo', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
1238
1323
  { no: 9, name: 'red', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
1239
1324
  { no: 10, name: 'muted', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
1325
+ { no: 11, name: 'codec', kind: 'message', T: () => Codec },
1326
+ {
1327
+ no: 12,
1328
+ name: 'publish_option_id',
1329
+ kind: 'scalar',
1330
+ T: 5 /*ScalarType.INT32*/,
1331
+ },
1240
1332
  ]);
1241
1333
  }
1242
1334
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Adds unique values to an array.
3
+ *
4
+ * @param arr the array to add to.
5
+ * @param values the values to add.
6
+ */
7
+ export const pushToIfMissing = <T>(arr: T[], ...values: T[]): T[] => {
8
+ for (const v of values) {
9
+ if (!arr.includes(v)) {
10
+ arr.push(v);
11
+ }
12
+ }
13
+ return arr;
14
+ };
@@ -1,4 +1,6 @@
1
1
  import { CallSettingsResponse, OwnCapability } from '../gen/coordinator';
2
+ import { TrackType } from '../gen/video/sfu/models/models';
3
+ import { ensureExhausted } from '../helpers/ensureExhausted';
2
4
 
3
5
  /**
4
6
  * Stores the permissions for the current user and exposes
@@ -39,6 +41,26 @@ export class PermissionsContext {
39
41
  return this.permissions.includes(permission);
40
42
  };
41
43
 
44
+ /**
45
+ * Helper method that checks whether the current user has the permission
46
+ * to publish the given track type.
47
+ */
48
+ canPublish = (trackType: TrackType) => {
49
+ switch (trackType) {
50
+ case TrackType.AUDIO:
51
+ return this.hasPermission(OwnCapability.SEND_AUDIO);
52
+ case TrackType.VIDEO:
53
+ return this.hasPermission(OwnCapability.SEND_VIDEO);
54
+ case TrackType.SCREEN_SHARE:
55
+ case TrackType.SCREEN_SHARE_AUDIO:
56
+ return this.hasPermission(OwnCapability.SCREENSHARE);
57
+ case TrackType.UNSPECIFIED:
58
+ return false;
59
+ default:
60
+ ensureExhausted(trackType, 'Unknown track type');
61
+ }
62
+ };
63
+
42
64
  /**
43
65
  * Checks if the current user can request a specific permission
44
66
  * within the call.
@@ -0,0 +1,40 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { TrackType } from '../../gen/video/sfu/models/models';
3
+ import { CallSettingsResponse, OwnCapability } from '../../gen/coordinator';
4
+ import { PermissionsContext } from '../PermissionsContext';
5
+
6
+ describe('PermissionsContext', () => {
7
+ it('should set permissions', () => {
8
+ const ctx = new PermissionsContext();
9
+ ctx.setPermissions([OwnCapability.SEND_AUDIO]);
10
+ expect(ctx.hasPermission(OwnCapability.SEND_AUDIO)).toBe(true);
11
+ expect(ctx.hasPermission(OwnCapability.SEND_VIDEO)).toBe(false);
12
+ });
13
+
14
+ it('should set call settings', () => {
15
+ const ctx = new PermissionsContext();
16
+ const settings: CallSettingsResponse = {
17
+ // @ts-expect-error incomplete settings
18
+ audio: { access_request_enabled: true },
19
+ // @ts-expect-error incomplete settings
20
+ video: { access_request_enabled: false },
21
+ // @ts-expect-error incomplete settings
22
+ screensharing: { access_request_enabled: false },
23
+ };
24
+ ctx.setCallSettings(settings);
25
+ expect(ctx.canRequest(OwnCapability.SEND_AUDIO)).toBe(true);
26
+ expect(ctx.canRequest(OwnCapability.SEND_VIDEO)).toBe(false);
27
+ expect(ctx.canRequest(OwnCapability.SCREENSHARE)).toBe(false);
28
+ expect(ctx.canRequest(OwnCapability.BLOCK_USERS)).toBe(false);
29
+ });
30
+
31
+ it('should check if user can publish', () => {
32
+ const ctx = new PermissionsContext();
33
+ ctx.setPermissions([OwnCapability.SEND_AUDIO]);
34
+ expect(ctx.canPublish(TrackType.AUDIO)).toBe(true);
35
+ expect(ctx.canPublish(TrackType.VIDEO)).toBe(false);
36
+ expect(ctx.canPublish(TrackType.SCREEN_SHARE)).toBe(false);
37
+ expect(ctx.canPublish(TrackType.SCREEN_SHARE_AUDIO)).toBe(false);
38
+ expect(ctx.canPublish(TrackType.UNSPECIFIED)).toBe(false);
39
+ });
40
+ });
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import {
3
+ createSignalClient,
4
+ withHeaders,
5
+ withRequestLogger,
6
+ } from '../createClient';
7
+ import { TwirpFetchTransport } from '@protobuf-ts/twirp-transport';
8
+
9
+ describe('createClient', () => {
10
+ it('should create a client with TwirpFetchTransport', () => {
11
+ const client = createSignalClient();
12
+ expect(client).toBeDefined();
13
+ // @ts-expect-error - private field
14
+ expect(client._transport).toBeDefined();
15
+ // @ts-expect-error - private field
16
+ expect(client._transport).toBeInstanceOf(TwirpFetchTransport);
17
+ });
18
+
19
+ it('withHeaders should add headers to the request', () => {
20
+ const headers = { Authorization: 'Bearer token' };
21
+ const interceptor = withHeaders(headers);
22
+ const next = vi.fn();
23
+ interceptor.interceptUnary(next, null, null, { meta: {} });
24
+ expect(next).toHaveBeenCalled();
25
+ expect(next.mock.lastCall.at(-1).meta).toEqual(headers);
26
+ });
27
+
28
+ it('withRequestLogger should log the request', () => {
29
+ const logger = vi.fn();
30
+ const level = 'debug';
31
+ const interceptor = withRequestLogger(logger, level);
32
+ const next = vi.fn();
33
+ // @ts-expect-error - private field
34
+ interceptor.interceptUnary(next, { name: 'test' }, null, null);
35
+ expect(next).toHaveBeenCalled();
36
+ expect(logger).toHaveBeenCalled();
37
+ });
38
+ });
@@ -48,11 +48,17 @@ export const withRequestLogger = (
48
48
  input: object,
49
49
  options: RpcOptions,
50
50
  ): UnaryCall => {
51
- logger(level, `Calling SFU RPC method ${method.name}`, {
52
- input,
53
- options,
54
- });
55
- return next(method, input, options);
51
+ let invocation: UnaryCall | undefined;
52
+ try {
53
+ invocation = next(method, input, options);
54
+ } finally {
55
+ logger(level, `Invoked SFU RPC method ${method.name}`, {
56
+ request: invocation?.request,
57
+ headers: invocation?.requestHeaders,
58
+ response: invocation?.response,
59
+ });
60
+ }
61
+ return invocation;
56
62
  },
57
63
  };
58
64
  };