@stream-io/video-client 1.28.1 → 1.30.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.
@@ -41,6 +41,9 @@ export declare abstract class InputMediaDeviceManager<T extends InputMediaDevice
41
41
  * Stops or pauses the stream based on state.disableMode
42
42
  * @param {boolean} [forceStop=false] when true, stops the tracks regardless of the state.disableMode
43
43
  */
44
+ disable(options: {
45
+ forceStop?: boolean;
46
+ }): Promise<void>;
44
47
  disable(forceStop?: boolean): Promise<void>;
45
48
  /**
46
49
  * Returns a promise that resolves when all pe
@@ -1,5 +1,5 @@
1
1
  import { MessageType } from '@protobuf-ts/runtime';
2
- import { CallEndedReason, CallGrants, CallState, ClientCapability, ClientDetails, Codec, ConnectionQuality, Error as Error$, GoAwayReason, ICETrickle as ICETrickle$, Participant, ParticipantCount, PeerType, Pin, PublishOption, SubscribeOption, TrackInfo, TrackType, TrackUnpublishReason, WebsocketReconnectStrategy } from '../models/models';
2
+ import { CallEndedReason, CallGrants, CallState, ClientCapability, ClientDetails, Codec, ConnectionQuality, Error as Error$, GoAwayReason, ICETrickle as ICETrickle$, Participant, ParticipantCount, ParticipantSource, PeerType, Pin, PublishOption, SubscribeOption, TrackInfo, TrackType, TrackUnpublishReason, WebsocketReconnectStrategy } from '../models/models';
3
3
  import { TrackSubscriptionDetails } from '../signal_rpc/signal';
4
4
  /**
5
5
  * SFUEvent is a message that is sent from the SFU to the client.
@@ -472,6 +472,10 @@ export interface JoinRequest {
472
472
  * @generated from protobuf field: repeated stream.video.sfu.models.ClientCapability capabilities = 11;
473
473
  */
474
474
  capabilities: ClientCapability[];
475
+ /**
476
+ * @generated from protobuf field: stream.video.sfu.models.ParticipantSource source = 12;
477
+ */
478
+ source: ParticipantSource;
475
479
  }
476
480
  /**
477
481
  * @generated from protobuf message stream.video.sfu.event.ReconnectDetails
@@ -133,6 +133,10 @@ export interface Participant {
133
133
  * @generated from protobuf field: repeated string roles = 13;
134
134
  */
135
135
  roles: string[];
136
+ /**
137
+ * @generated from protobuf field: stream.video.sfu.models.ParticipantSource source = 14;
138
+ */
139
+ source: ParticipantSource;
136
140
  }
137
141
  /**
138
142
  * @generated from protobuf message stream.video.sfu.models.StreamQuality
@@ -755,6 +759,37 @@ export declare enum TrackType {
755
759
  */
756
760
  SCREEN_SHARE_AUDIO = 4
757
761
  }
762
+ /**
763
+ * must be aligned with kit
764
+ *
765
+ * @generated from protobuf enum stream.video.sfu.models.ParticipantSource
766
+ */
767
+ export declare enum ParticipantSource {
768
+ /**
769
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_WEBRTC_UNSPECIFIED = 0;
770
+ */
771
+ WEBRTC_UNSPECIFIED = 0,
772
+ /**
773
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_RTMP = 1;
774
+ */
775
+ RTMP = 1,
776
+ /**
777
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_WHIP = 2;
778
+ */
779
+ WHIP = 2,
780
+ /**
781
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_SIP = 3;
782
+ */
783
+ SIP = 3,
784
+ /**
785
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_RTSP = 4;
786
+ */
787
+ RTSP = 4,
788
+ /**
789
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_SRT = 5;
790
+ */
791
+ SRT = 5
792
+ }
758
793
  /**
759
794
  * @generated from protobuf enum stream.video.sfu.models.ErrorCode
760
795
  */
@@ -1,5 +1,6 @@
1
1
  import { Comparator } from './';
2
2
  import { StreamVideoParticipant } from '../types';
3
+ import { ParticipantSource } from '../gen/video/sfu/models/models';
3
4
  /**
4
5
  * A comparator which sorts participants by the fact that they are the dominant speaker or not.
5
6
  *
@@ -42,6 +43,13 @@ export declare const publishingAudio: Comparator<StreamVideoParticipant>;
42
43
  * @param b the second participant.
43
44
  */
44
45
  export declare const pinned: Comparator<StreamVideoParticipant>;
46
+ /**
47
+ * A comparator creator which will set up a comparator which prioritizes
48
+ * participants who are from a specific source (e.g., WebRTC, RTMP, WHIP...).
49
+ *
50
+ * @param source the source to prioritize.
51
+ */
52
+ export declare const withParticipantSource: (source: ParticipantSource) => Comparator<StreamVideoParticipant>;
45
53
  /**
46
54
  * A comparator creator which will set up a comparator which prioritizes
47
55
  * participants who have a specific reaction.
@@ -5,7 +5,7 @@ import { type ClosedCaptionsSettings, type StreamVideoParticipant, type StreamVi
5
5
  import { CallStatsReport } from '../stats';
6
6
  import { CallClosedCaption, CallIngressResponse, CallResponse, CallSessionResponse, CallSettingsResponse, EgressResponse, MemberResponse, OwnCapability, ThumbnailResponse, UserResponse, VideoEvent } from '../gen/coordinator';
7
7
  import { ReconnectDetails } from '../gen/video/sfu/event/events';
8
- import { CallState as SfuCallState, Pin, TrackType } from '../gen/video/sfu/models/models';
8
+ import { CallState as SfuCallState, Pin, TrackType, CallGrants } from '../gen/video/sfu/models/models';
9
9
  import { Comparator } from '../sorting';
10
10
  type OrphanedTrack = {
11
11
  id: string;
@@ -45,6 +45,7 @@ export declare class CallState {
45
45
  private callStatsReportSubject;
46
46
  private closedCaptionsSubject;
47
47
  private orphanedTracks;
48
+ private callGrantsSubject;
48
49
  /**
49
50
  * The time the call session actually started.
50
51
  * Useful for displaying the call duration.
@@ -371,6 +372,13 @@ export declare class CallState {
371
372
  * @param capabilities the capabilities to set.
372
373
  */
373
374
  setOwnCapabilities: (capabilities: Patch<OwnCapability[]>) => OwnCapability[];
375
+ /**
376
+ * Sets the call grants (used for own capabilities).
377
+ *
378
+ * @internal
379
+ * @param grants the grants to set.
380
+ */
381
+ setCallGrants: (grants: Patch<CallGrants>) => CallGrants;
374
382
  /**
375
383
  * The backstage state.
376
384
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "1.28.1",
3
+ "version": "1.30.0",
4
4
  "main": "dist/index.cjs.js",
5
5
  "module": "dist/index.es.js",
6
6
  "browser": "dist/index.browser.es.js",
package/src/Call.ts CHANGED
@@ -112,6 +112,7 @@ import {
112
112
  ClientCapability,
113
113
  ClientDetails,
114
114
  Codec,
115
+ ParticipantSource,
115
116
  PublishOption,
116
117
  SubscribeOption,
117
118
  TrackType,
@@ -1015,6 +1016,7 @@ export class Call {
1015
1016
  preferredPublishOptions,
1016
1017
  preferredSubscribeOptions,
1017
1018
  capabilities: Array.from(this.clientCapabilities),
1019
+ source: ParticipantSource.WEBRTC_UNSPECIFIED,
1018
1020
  });
1019
1021
 
1020
1022
  this.currentPublishOptions = publishOptions;
@@ -25,7 +25,7 @@ const tokenProvider = (userId: string) => {
25
25
  };
26
26
  };
27
27
 
28
- describe('StreamVideoClient - coordinator API', () => {
28
+ describe.skip('StreamVideoClient - coordinator API', () => {
29
29
  let client: StreamVideoClient;
30
30
  const user = {
31
31
  id: 'sara',
@@ -665,12 +665,19 @@ export class StreamClient {
665
665
  };
666
666
  }
667
667
 
668
+ const {
669
+ params: axiosConfigParams,
670
+ headers: axiosConfigHeaders,
671
+ ...axiosRequestConfig
672
+ } = this.options.axiosRequestConfig || {};
673
+
668
674
  return {
669
675
  params: {
670
676
  user_id: this.userID,
671
677
  connection_id: this._getConnectionID(),
672
678
  api_key: this.key,
673
679
  ...options.params,
680
+ ...axiosConfigParams,
674
681
  },
675
682
  headers: {
676
683
  ...authorization,
@@ -680,9 +687,10 @@ export class StreamClient {
680
687
  : this.getAuthType(),
681
688
  'X-Stream-Client': this.getUserAgent(),
682
689
  ...options.headers,
690
+ ...axiosConfigHeaders,
683
691
  },
684
692
  ...options.config,
685
- ...this.options.axiosRequestConfig,
693
+ ...axiosRequestConfig,
686
694
  };
687
695
  };
688
696
 
@@ -111,7 +111,14 @@ export abstract class InputMediaDeviceManager<
111
111
  * Stops or pauses the stream based on state.disableMode
112
112
  * @param {boolean} [forceStop=false] when true, stops the tracks regardless of the state.disableMode
113
113
  */
114
- async disable(forceStop: boolean = false) {
114
+ async disable(options: { forceStop?: boolean }): Promise<void>;
115
+ async disable(forceStop?: boolean): Promise<void>;
116
+ async disable(forceStopOrOptions?: boolean | { forceStop?: boolean }) {
117
+ const forceStop =
118
+ typeof forceStopOrOptions === 'boolean'
119
+ ? forceStopOrOptions
120
+ : (forceStopOrOptions?.forceStop ?? false);
121
+
115
122
  this.state.prevStatus = this.state.optimisticStatus;
116
123
  if (!forceStop && this.state.optimisticStatus === 'disabled') {
117
124
  return;
@@ -1,6 +1,5 @@
1
1
  import { CallState } from '../store';
2
2
  import type { CallGrantsUpdated } from '../gen/video/sfu/event/events';
3
- import { OwnCapability } from '../gen/coordinator';
4
3
 
5
4
  /**
6
5
  * Event handler that watches for `callGrantsUpdated` events.
@@ -10,26 +9,9 @@ import { OwnCapability } from '../gen/coordinator';
10
9
  export const watchCallGrantsUpdated = (state: CallState) => {
11
10
  return function onCallGrantsUpdated(event: CallGrantsUpdated) {
12
11
  const { currentGrants } = event;
13
- if (currentGrants) {
14
- const { canPublishAudio, canPublishVideo, canScreenshare } =
15
- currentGrants;
16
12
 
17
- const update: Partial<Record<OwnCapability, boolean>> = {
18
- [OwnCapability.SEND_AUDIO]: canPublishAudio,
19
- [OwnCapability.SEND_VIDEO]: canPublishVideo,
20
- [OwnCapability.SCREENSHARE]: canScreenshare,
21
- };
13
+ if (!currentGrants) return;
22
14
 
23
- const nextCapabilities = state.ownCapabilities.filter(
24
- (capability) => update[capability] !== false,
25
- );
26
- Object.entries(update).forEach(([capability, value]) => {
27
- if (value && !nextCapabilities.includes(capability as OwnCapability)) {
28
- nextCapabilities.push(capability as OwnCapability);
29
- }
30
- });
31
-
32
- state.setOwnCapabilities(nextCapabilities);
33
- }
15
+ state.setCallGrants(currentGrants);
34
16
  };
35
17
  };
@@ -15,6 +15,7 @@ import {
15
15
  ICETrickle as ICETrickle$,
16
16
  Participant,
17
17
  ParticipantCount,
18
+ ParticipantSource,
18
19
  PeerType,
19
20
  Pin,
20
21
  PublishOption,
@@ -522,6 +523,10 @@ export interface JoinRequest {
522
523
  * @generated from protobuf field: repeated stream.video.sfu.models.ClientCapability capabilities = 11;
523
524
  */
524
525
  capabilities: ClientCapability[];
526
+ /**
527
+ * @generated from protobuf field: stream.video.sfu.models.ParticipantSource source = 12;
528
+ */
529
+ source: ParticipantSource;
525
530
  }
526
531
  /**
527
532
  * @generated from protobuf message stream.video.sfu.event.ReconnectDetails
@@ -1404,6 +1409,16 @@ class JoinRequest$Type extends MessageType<JoinRequest> {
1404
1409
  'CLIENT_CAPABILITY_',
1405
1410
  ],
1406
1411
  },
1412
+ {
1413
+ no: 12,
1414
+ name: 'source',
1415
+ kind: 'enum',
1416
+ T: () => [
1417
+ 'stream.video.sfu.models.ParticipantSource',
1418
+ ParticipantSource,
1419
+ 'PARTICIPANT_SOURCE_',
1420
+ ],
1421
+ },
1407
1422
  ]);
1408
1423
  }
1409
1424
  }
@@ -137,6 +137,10 @@ export interface Participant {
137
137
  * @generated from protobuf field: repeated string roles = 13;
138
138
  */
139
139
  roles: string[];
140
+ /**
141
+ * @generated from protobuf field: stream.video.sfu.models.ParticipantSource source = 14;
142
+ */
143
+ source: ParticipantSource;
140
144
  }
141
145
  /**
142
146
  * @generated from protobuf message stream.video.sfu.models.StreamQuality
@@ -759,6 +763,37 @@ export enum TrackType {
759
763
  */
760
764
  SCREEN_SHARE_AUDIO = 4,
761
765
  }
766
+ /**
767
+ * must be aligned with kit
768
+ *
769
+ * @generated from protobuf enum stream.video.sfu.models.ParticipantSource
770
+ */
771
+ export enum ParticipantSource {
772
+ /**
773
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_WEBRTC_UNSPECIFIED = 0;
774
+ */
775
+ WEBRTC_UNSPECIFIED = 0,
776
+ /**
777
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_RTMP = 1;
778
+ */
779
+ RTMP = 1,
780
+ /**
781
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_WHIP = 2;
782
+ */
783
+ WHIP = 2,
784
+ /**
785
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_SIP = 3;
786
+ */
787
+ SIP = 3,
788
+ /**
789
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_RTSP = 4;
790
+ */
791
+ RTSP = 4,
792
+ /**
793
+ * @generated from protobuf enum value: PARTICIPANT_SOURCE_SRT = 5;
794
+ */
795
+ SRT = 5,
796
+ }
762
797
  /**
763
798
  * @generated from protobuf enum stream.video.sfu.models.ErrorCode
764
799
  */
@@ -1211,6 +1246,16 @@ class Participant$Type extends MessageType<Participant> {
1211
1246
  repeat: 2 /*RepeatType.UNPACKED*/,
1212
1247
  T: 9 /*ScalarType.STRING*/,
1213
1248
  },
1249
+ {
1250
+ no: 14,
1251
+ name: 'source',
1252
+ kind: 'enum',
1253
+ T: () => [
1254
+ 'stream.video.sfu.models.ParticipantSource',
1255
+ ParticipantSource,
1256
+ 'PARTICIPANT_SOURCE_',
1257
+ ],
1258
+ },
1214
1259
  ]);
1215
1260
  }
1216
1261
  }
@@ -1,5 +1,6 @@
1
1
  import { Comparator } from './';
2
2
  import { StreamVideoParticipant } from '../types';
3
+ import { ParticipantSource } from '../gen/video/sfu/models/models';
3
4
  import {
4
5
  hasAudio,
5
6
  hasScreenShare,
@@ -86,6 +87,20 @@ export const pinned: Comparator<StreamVideoParticipant> = (a, b) => {
86
87
  return 0;
87
88
  };
88
89
 
90
+ /**
91
+ * A comparator creator which will set up a comparator which prioritizes
92
+ * participants who are from a specific source (e.g., WebRTC, RTMP, WHIP...).
93
+ *
94
+ * @param source the source to prioritize.
95
+ */
96
+ export const withParticipantSource =
97
+ (source: ParticipantSource): Comparator<StreamVideoParticipant> =>
98
+ (a, b) => {
99
+ if (a.source === source && b.source !== source) return -1;
100
+ if (a.source !== source && b.source === source) return 1;
101
+ return 0;
102
+ };
103
+
89
104
  /**
90
105
  * A comparator creator which will set up a comparator which prioritizes
91
106
  * participants who have a specific reaction.
@@ -1,3 +1,4 @@
1
+ import { ParticipantSource } from '../gen/video/sfu/models/models';
1
2
  import { StreamVideoParticipant, VisibilityState } from '../types';
2
3
  import { combineComparators, conditional } from './comparator';
3
4
  import {
@@ -9,6 +10,7 @@ import {
9
10
  role,
10
11
  screenSharing,
11
12
  speaking,
13
+ withParticipantSource,
12
14
  } from './participants';
13
15
 
14
16
  // a comparator decorator which applies the decorated comparator only if the
@@ -48,7 +50,6 @@ export const defaultSortPreset = combineComparators(
48
50
  publishingAudio,
49
51
  ),
50
52
  ),
51
- // ifInvisibleBy(name),
52
53
  );
53
54
 
54
55
  /**
@@ -66,7 +67,6 @@ export const speakerLayoutSortPreset = combineComparators(
66
67
  publishingAudio,
67
68
  ),
68
69
  ),
69
- // ifInvisibleBy(name),
70
70
  );
71
71
 
72
72
  /**
@@ -84,7 +84,6 @@ export const paginatedLayoutSortPreset = combineComparators(
84
84
  publishingAudio,
85
85
  ),
86
86
  ),
87
- // ifInvisibleOrUnknownBy(name),
88
87
  );
89
88
 
90
89
  /**
@@ -96,10 +95,10 @@ export const livestreamOrAudioRoomSortPreset = combineComparators(
96
95
  dominantSpeaker,
97
96
  speaking,
98
97
  reactionType('raised-hand'),
98
+ withParticipantSource(ParticipantSource.RTMP),
99
99
  publishingVideo,
100
100
  publishingAudio,
101
101
  ),
102
102
  ),
103
103
  role('admin', 'host', 'speaker'),
104
- // name,
105
104
  );
@@ -1,9 +1,12 @@
1
1
  import {
2
2
  BehaviorSubject,
3
+ combineLatest,
3
4
  distinctUntilChanged,
4
5
  map,
5
6
  Observable,
7
+ ReplaySubject,
6
8
  shareReplay,
9
+ startWith,
7
10
  } from 'rxjs';
8
11
  import type { Patch } from './rxUtils';
9
12
  import * as RxUtils from './rxUtils';
@@ -49,6 +52,7 @@ import {
49
52
  CallState as SfuCallState,
50
53
  Pin,
51
54
  TrackType,
55
+ CallGrants,
52
56
  } from '../gen/video/sfu/models/models';
53
57
  import { Comparator, defaultSortPreset } from '../sorting';
54
58
  import { getLogger } from '../logger';
@@ -128,6 +132,8 @@ export class CallState {
128
132
  // We keep these tracks around until we can associate them with a participant.
129
133
  private orphanedTracks: OrphanedTrack[] = [];
130
134
 
135
+ private callGrantsSubject = new ReplaySubject<CallGrants>(1);
136
+
131
137
  // Derived state
132
138
 
133
139
  /**
@@ -394,8 +400,12 @@ export class CallState {
394
400
  */
395
401
  const isShallowEqual = <T>(a: Array<T>, b: Array<T>): boolean => {
396
402
  if (a.length !== b.length) return false;
397
- for (const item of a) if (!b.includes(item)) return false;
398
- for (const item of b) if (!a.includes(item)) return false;
403
+ for (const item of a) {
404
+ if (!b.includes(item)) return false;
405
+ }
406
+ for (const item of b) {
407
+ if (!a.includes(item)) return false;
408
+ }
399
409
  return true;
400
410
  };
401
411
 
@@ -406,8 +416,7 @@ export class CallState {
406
416
  const duc = <T>(
407
417
  subject: BehaviorSubject<T>,
408
418
  comparator?: (a: T, b: T) => boolean,
409
- ): Observable<T> =>
410
- subject.asObservable().pipe(distinctUntilChanged(comparator));
419
+ ): Observable<T> => subject.pipe(distinctUntilChanged(comparator));
411
420
 
412
421
  // primitive values should only emit once the value they hold changes
413
422
  this.anonymousParticipantCount$ = duc(
@@ -416,7 +425,41 @@ export class CallState {
416
425
  this.blockedUserIds$ = duc(this.blockedUserIdsSubject, isShallowEqual);
417
426
  this.backstage$ = duc(this.backstageSubject);
418
427
  this.callingState$ = duc(this.callingStateSubject);
419
- this.ownCapabilities$ = duc(this.ownCapabilitiesSubject, isShallowEqual);
428
+ this.ownCapabilities$ = combineLatest([
429
+ this.ownCapabilitiesSubject,
430
+ this.callGrantsSubject.pipe(startWith(undefined)),
431
+ ]).pipe(
432
+ map(([capabilities, grants]) => {
433
+ if (!grants) return capabilities;
434
+
435
+ const { canPublishAudio, canPublishVideo, canScreenshare } = grants;
436
+
437
+ const update = {
438
+ [OwnCapability.SEND_AUDIO]: canPublishAudio,
439
+ [OwnCapability.SEND_VIDEO]: canPublishVideo,
440
+ [OwnCapability.SCREENSHARE]: canScreenshare,
441
+ } as const;
442
+
443
+ const nextCapabilities = [...capabilities];
444
+
445
+ for (const _capability in update) {
446
+ const capability = _capability as keyof typeof update;
447
+ const allowed = update[capability];
448
+
449
+ // grants take precedence over capabilities, reconstruct the capabilities
450
+ if (allowed && !nextCapabilities.includes(capability)) {
451
+ nextCapabilities.push(capability);
452
+ } else if (!allowed && nextCapabilities.includes(capability)) {
453
+ const index = nextCapabilities.indexOf(capability);
454
+ nextCapabilities.splice(index, 1);
455
+ }
456
+ }
457
+
458
+ return nextCapabilities;
459
+ }),
460
+ distinctUntilChanged(isShallowEqual),
461
+ shareReplay({ bufferSize: 1, refCount: true }),
462
+ );
420
463
  this.participantCount$ = duc(this.participantCountSubject);
421
464
  this.recording$ = duc(this.recordingSubject);
422
465
  this.transcribing$ = duc(this.transcribingSubject);
@@ -767,6 +810,16 @@ export class CallState {
767
810
  return this.setCurrentValue(this.ownCapabilitiesSubject, capabilities);
768
811
  };
769
812
 
813
+ /**
814
+ * Sets the call grants (used for own capabilities).
815
+ *
816
+ * @internal
817
+ * @param grants the grants to set.
818
+ */
819
+ setCallGrants = (grants: Patch<CallGrants>) => {
820
+ return this.setCurrentValue(this.callGrantsSubject, grants);
821
+ };
822
+
770
823
  /**
771
824
  * The backstage state.
772
825
  */
@@ -1412,7 +1465,7 @@ export class CallState {
1412
1465
 
1413
1466
  private updateOwnCapabilities = (event: UpdatedCallPermissionsEvent) => {
1414
1467
  if (event.user.id === this.localParticipant?.userId) {
1415
- this.setCurrentValue(this.ownCapabilitiesSubject, event.own_capabilities);
1468
+ this.setOwnCapabilities(event.own_capabilities);
1416
1469
  }
1417
1470
  };
1418
1471