@stream-io/video-client 1.7.4 → 1.8.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.
@@ -1,7 +1,7 @@
1
1
  import { Publisher, Subscriber } from './rtc';
2
2
  import { CallState } from './store';
3
- import type { AcceptCallResponse, BlockUserResponse, CollectUserFeedbackResponse, EndCallResponse, GetCallResponse, GetCallStatsResponse, GetOrCreateCallRequest, GetOrCreateCallResponse, GoLiveRequest, GoLiveResponse, JoinCallResponse, ListRecordingsResponse, ListTranscriptionsResponse, MuteUsersResponse, PinRequest, PinResponse, QueryCallMembersRequest, QueryCallMembersResponse, RejectCallResponse, RequestPermissionRequest, RequestPermissionResponse, SendCallEventResponse, SendReactionRequest, SendReactionResponse, StartHLSBroadcastingResponse, StartRecordingRequest, StartRecordingResponse, StartTranscriptionRequest, StartTranscriptionResponse, StopHLSBroadcastingResponse, StopLiveResponse, StopRecordingResponse, StopTranscriptionResponse, UnblockUserResponse, UnpinRequest, UnpinResponse, UpdateCallMembersRequest, UpdateCallMembersResponse, UpdateCallRequest, UpdateCallResponse, UpdateUserPermissionsRequest, UpdateUserPermissionsResponse } from './gen/coordinator';
4
- import { AudioTrackType, CallConstructor, CallLeaveOptions, DebounceType, JoinCallData, PublishOptions, SubscriptionChanges, TrackMuteType, VideoTrackType } from './types';
3
+ import type { AcceptCallResponse, BlockUserResponse, CollectUserFeedbackResponse, EndCallResponse, GetCallResponse, GetCallStatsResponse, GetOrCreateCallRequest, GetOrCreateCallResponse, GoLiveRequest, GoLiveResponse, JoinCallResponse, ListRecordingsResponse, ListTranscriptionsResponse, MuteUsersResponse, PinRequest, PinResponse, QueryCallMembersRequest, QueryCallMembersResponse, RejectCallResponse, RequestPermissionRequest, RequestPermissionResponse, SendCallEventResponse, SendReactionRequest, SendReactionResponse, StartHLSBroadcastingResponse, StartRecordingRequest, StartRecordingResponse, StartTranscriptionRequest, StartTranscriptionResponse, StopHLSBroadcastingResponse, StopLiveResponse, StopRecordingResponse, StopTranscriptionResponse, UnblockUserResponse, UnpinRequest, UnpinResponse, UpdateCallMembersRequest, UpdateCallMembersResponse, UpdateCallRequest, UpdateCallResponse, UpdateUserPermissionsRequest, UpdateUserPermissionsResponse, VideoResolution } from './gen/coordinator';
4
+ import { AudioTrackType, CallConstructor, CallLeaveOptions, JoinCallData, PublishOptions, TrackMuteType, VideoTrackType } from './types';
5
5
  import { VideoLayerSetting } from './gen/video/sfu/event/events';
6
6
  import { TrackType } from './gen/video/sfu/models/models';
7
7
  import { DynascaleManager } from './helpers/DynascaleManager';
@@ -70,7 +70,6 @@ export declare class Call {
70
70
  * @private
71
71
  */
72
72
  private readonly dispatcher;
73
- private trackSubscriptionsSubject;
74
73
  private statsReporter?;
75
74
  private sfuStatsReporter?;
76
75
  private dropTimeout;
@@ -326,17 +325,6 @@ export declare class Call {
326
325
  * @internal
327
326
  */
328
327
  notifyNoiseCancellationStopped: () => Promise<void | import("@protobuf-ts/runtime-rpc").FinishedUnaryCall<import("./gen/video/sfu/signal_rpc/signal").StopNoiseCancellationRequest, import("./gen/video/sfu/signal_rpc/signal").StopNoiseCancellationResponse> | undefined>;
329
- /**
330
- * Update track subscription configuration for one or more participants.
331
- * You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
332
- * You can only subscribe for tracks after the participant started publishing the given kind of track.
333
- *
334
- * @param trackType the kind of subscription to update.
335
- * @param changes the list of subscription changes to do.
336
- * @param type the debounce type to use for the update.
337
- */
338
- updateSubscriptionsPartial: (trackType: VideoTrackType, changes: SubscriptionChanges, type?: DebounceType) => void;
339
- private updateSubscriptions;
340
328
  /**
341
329
  * Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
342
330
  * This is usually helpful when detailed stats for a specific participant are needed.
@@ -650,4 +638,20 @@ export declare class Call {
650
638
  bindCallThumbnailElement: (imageElement: HTMLImageElement, opts?: {
651
639
  fallbackImageSource?: string;
652
640
  }) => () => void;
641
+ /**
642
+ * Specify preference for incoming video resolution. The preference will
643
+ * be matched as close as possible, but actual resolution will depend
644
+ * on the video source quality and client network conditions. Will enable
645
+ * incoming video, if previously disabled.
646
+ *
647
+ * @param resolution preferred resolution, or `undefined` to clear preference
648
+ * @param sessionIds optionally specify session ids of the participants this
649
+ * preference has effect on. Affects all participants by default.
650
+ */
651
+ setPreferredIncomingVideoResolution: (resolution: VideoResolution | undefined, sessionIds?: string[]) => void;
652
+ /**
653
+ * Enables or disables incoming video from all remote call participants,
654
+ * and removes any preference for preferred resolution.
655
+ */
656
+ setIncomingVideoEnabled: (enabled: boolean) => void;
653
657
  }
@@ -1,6 +1,21 @@
1
- import { Call } from '../Call';
2
- import { AudioTrackType, VideoTrackType } from '../types';
1
+ import { AudioTrackType, DebounceType, VideoTrackType } from '../types';
2
+ import { VideoDimension } from '../gen/video/sfu/models/models';
3
3
  import { ViewportTracker } from './ViewportTracker';
4
+ import type { TrackSubscriptionDetails } from '../gen/video/sfu/signal_rpc/signal';
5
+ import type { CallState } from '../store';
6
+ import type { StreamSfuClient } from '../StreamSfuClient';
7
+ import { SpeakerManager } from '../devices';
8
+ type VideoTrackSubscriptionOverride = {
9
+ enabled: true;
10
+ dimension: VideoDimension;
11
+ } | {
12
+ enabled: false;
13
+ };
14
+ declare const globalOverrideKey: unique symbol;
15
+ interface VideoTrackSubscriptionOverrides {
16
+ [sessionId: string]: VideoTrackSubscriptionOverride | undefined;
17
+ [globalOverrideKey]?: VideoTrackSubscriptionOverride;
18
+ }
4
19
  /**
5
20
  * A manager class that handles dynascale related tasks like:
6
21
  *
@@ -17,13 +32,34 @@ export declare class DynascaleManager {
17
32
  */
18
33
  readonly viewportTracker: ViewportTracker;
19
34
  private logger;
20
- private call;
35
+ private callState;
36
+ private speaker;
37
+ private sfuClient;
38
+ private pendingSubscriptionsUpdate;
39
+ private videoTrackSubscriptionOverridesSubject;
40
+ videoTrackSubscriptionOverrides$: import("rxjs").Observable<VideoTrackSubscriptionOverrides>;
41
+ incomingVideoSettings$: import("rxjs").Observable<{
42
+ enabled: boolean;
43
+ preferredResolution: VideoDimension | undefined;
44
+ participants: {
45
+ [k: string]: {
46
+ enabled: boolean;
47
+ preferredResolution: VideoDimension | undefined;
48
+ };
49
+ };
50
+ isParticipantVideoEnabled: (sessionId: string) => boolean;
51
+ }>;
21
52
  /**
22
53
  * Creates a new DynascaleManager instance.
23
54
  *
24
55
  * @param call the call to manage.
25
56
  */
26
- constructor(call: Call);
57
+ constructor(callState: CallState, speaker: SpeakerManager);
58
+ setSfuClient(sfuClient: StreamSfuClient | undefined): void;
59
+ get trackSubscriptions(): TrackSubscriptionDetails[];
60
+ get videoTrackSubscriptionOverrides(): VideoTrackSubscriptionOverrides;
61
+ setVideoTrackSubscriptionOverrides: (override: VideoTrackSubscriptionOverride | undefined, sessionIds?: string[]) => VideoTrackSubscriptionOverrides;
62
+ applyTrackSubscriptions: (debounceType?: DebounceType) => void;
27
63
  /**
28
64
  * Will begin tracking the given element for visibility changes within the
29
65
  * configured viewport element (`call.setViewport`).
@@ -69,3 +105,4 @@ export declare class DynascaleManager {
69
105
  */
70
106
  bindAudioElement: (audioElement: HTMLAudioElement, sessionId: string, trackType: AudioTrackType) => (() => void) | undefined;
71
107
  }
108
+ export {};
@@ -1,7 +1,7 @@
1
1
  import { Observable } from 'rxjs';
2
2
  import type { Patch } from './rxUtils';
3
3
  import { CallingState } from './CallingState';
4
- import { StreamVideoParticipant, StreamVideoParticipantPatch, StreamVideoParticipantPatches } from '../types';
4
+ import { type StreamVideoParticipant, type StreamVideoParticipantPatch, type StreamVideoParticipantPatches, type SubscriptionChanges, VideoTrackType } from '../types';
5
5
  import { CallStatsReport } from '../stats';
6
6
  import { CallIngressResponse, CallResponse, CallSessionResponse, CallSettingsResponse, EgressResponse, MemberResponse, OwnCapability, ThumbnailResponse, UserResponse, WSEvent } from '../gen/coordinator';
7
7
  import { ReconnectDetails } from '../gen/video/sfu/event/events';
@@ -436,6 +436,16 @@ export declare class CallState {
436
436
  * @returns all participants, with all patch applied.
437
437
  */
438
438
  updateParticipants: (patch: StreamVideoParticipantPatches) => StreamVideoParticipant[];
439
+ /**
440
+ * Update track subscription configuration for one or more participants.
441
+ * You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
442
+ * You can only subscribe for tracks after the participant started publishing the given kind of track.
443
+ *
444
+ * @param trackType the kind of subscription to update.
445
+ * @param changes the list of subscription changes to do.
446
+ * @param type the debounce type to use for the update.
447
+ */
448
+ updateParticipantTracks: (trackType: VideoTrackType, changes: SubscriptionChanges) => StreamVideoParticipant[];
439
449
  /**
440
450
  * Updates the call state with the data received from the server.
441
451
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "1.7.4",
3
+ "version": "1.8.0",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
package/src/Call.ts CHANGED
@@ -8,11 +8,6 @@ import {
8
8
  } from './rtc';
9
9
  import { muteTypeToTrackType } from './rtc/helpers/tracks';
10
10
  import { toRtcConfiguration } from './rtc/helpers/rtcConfiguration';
11
- import {
12
- hasScreenShare,
13
- hasScreenShareAudio,
14
- hasVideo,
15
- } from './helpers/participantUtils';
16
11
  import {
17
12
  registerEventHandlers,
18
13
  registerRingingCallEventHandlers,
@@ -79,30 +74,19 @@ import type {
79
74
  UpdateCallResponse,
80
75
  UpdateUserPermissionsRequest,
81
76
  UpdateUserPermissionsResponse,
77
+ VideoResolution,
82
78
  } from './gen/coordinator';
83
79
  import { OwnCapability } from './gen/coordinator';
84
80
  import {
85
81
  AudioTrackType,
86
82
  CallConstructor,
87
83
  CallLeaveOptions,
88
- DebounceType,
89
84
  JoinCallData,
90
85
  PublishOptions,
91
- StreamVideoParticipant,
92
- StreamVideoParticipantPatches,
93
- SubscriptionChanges,
94
86
  TrackMuteType,
95
87
  VideoTrackType,
96
88
  } from './types';
97
- import {
98
- BehaviorSubject,
99
- debounce,
100
- map,
101
- Subject,
102
- takeWhile,
103
- timer,
104
- } from 'rxjs';
105
- import { TrackSubscriptionDetails } from './gen/video/sfu/signal_rpc/signal';
89
+ import { BehaviorSubject, Subject, takeWhile } from 'rxjs';
106
90
  import {
107
91
  ReconnectDetails,
108
92
  VideoLayerSetting,
@@ -196,7 +180,7 @@ export class Call {
196
180
  /**
197
181
  * The DynascaleManager instance.
198
182
  */
199
- readonly dynascaleManager = new DynascaleManager(this);
183
+ readonly dynascaleManager: DynascaleManager;
200
184
 
201
185
  subscriber?: Subscriber;
202
186
  publisher?: Publisher;
@@ -218,11 +202,6 @@ export class Call {
218
202
  */
219
203
  private readonly dispatcher = new Dispatcher();
220
204
 
221
- private trackSubscriptionsSubject = new BehaviorSubject<{
222
- type: DebounceType;
223
- data: TrackSubscriptionDetails[];
224
- }>({ type: DebounceType.MEDIUM, data: [] });
225
-
226
205
  private statsReporter?: StatsReporter;
227
206
  private sfuStatsReporter?: SfuStatsReporter;
228
207
  private dropTimeout: ReturnType<typeof setTimeout> | undefined;
@@ -304,6 +283,7 @@ export class Call {
304
283
  this.microphone = new MicrophoneManager(this);
305
284
  this.speaker = new SpeakerManager(this);
306
285
  this.screenShare = new ScreenShareManager(this);
286
+ this.dynascaleManager = new DynascaleManager(this.state, this.speaker);
307
287
  }
308
288
 
309
289
  private async setup() {
@@ -321,19 +301,6 @@ export class Call {
321
301
  this.registerEffects();
322
302
  this.registerReconnectHandlers();
323
303
 
324
- this.leaveCallHooks.add(
325
- createSubscription(
326
- this.trackSubscriptionsSubject.pipe(
327
- debounce((v) => timer(v.type)),
328
- map((v) => v.data),
329
- ),
330
- (subscriptions) =>
331
- this.sfuClient?.updateSubscriptions(subscriptions).catch((err) => {
332
- this.logger('debug', `Failed to update track subscriptions`, err);
333
- }),
334
- ),
335
- );
336
-
337
304
  if (this.state.callingState === CallingState.LEFT) {
338
305
  this.state.setCallingState(CallingState.IDLE);
339
306
  }
@@ -582,6 +549,7 @@ export class Call {
582
549
 
583
550
  await this.sfuClient?.leaveAndClose(reason);
584
551
  this.sfuClient = undefined;
552
+ this.dynascaleManager.setSfuClient(undefined);
585
553
 
586
554
  this.state.setCallingState(CallingState.LEFT);
587
555
 
@@ -812,6 +780,7 @@ export class Call {
812
780
  })
813
781
  : previousSfuClient;
814
782
  this.sfuClient = sfuClient;
783
+ this.dynascaleManager.setSfuClient(sfuClient);
815
784
 
816
785
  const clientDetails = getClientDetails();
817
786
  // we don't need to send JoinRequest if we are re-using an existing healthy SFU client
@@ -904,11 +873,10 @@ export class Call {
904
873
  const strategy = this.reconnectStrategy;
905
874
  const performingRejoin = strategy === WebsocketReconnectStrategy.REJOIN;
906
875
  const announcedTracks = this.publisher?.getAnnouncedTracks() || [];
907
- const subscribedTracks = getCurrentValue(this.trackSubscriptionsSubject);
908
876
  return {
909
877
  strategy,
910
878
  announcedTracks,
911
- subscriptions: subscribedTracks.data || [],
879
+ subscriptions: this.dynascaleManager.trackSubscriptions,
912
880
  reconnectAttempt: this.reconnectAttempts,
913
881
  fromSfuId: migratingFromSfuId || '',
914
882
  previousSessionId: performingRejoin ? previousSessionId || '' : '',
@@ -1357,7 +1325,7 @@ export class Call {
1357
1325
  private restoreSubscribedTracks = () => {
1358
1326
  const { remoteParticipants } = this.state;
1359
1327
  if (remoteParticipants.length <= 0) return;
1360
- this.updateSubscriptions(remoteParticipants, DebounceType.FAST);
1328
+ this.dynascaleManager.applyTrackSubscriptions(undefined);
1361
1329
  };
1362
1330
 
1363
1331
  /**
@@ -1521,87 +1489,6 @@ export class Call {
1521
1489
  });
1522
1490
  };
1523
1491
 
1524
- /**
1525
- * Update track subscription configuration for one or more participants.
1526
- * You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
1527
- * You can only subscribe for tracks after the participant started publishing the given kind of track.
1528
- *
1529
- * @param trackType the kind of subscription to update.
1530
- * @param changes the list of subscription changes to do.
1531
- * @param type the debounce type to use for the update.
1532
- */
1533
- updateSubscriptionsPartial = (
1534
- trackType: VideoTrackType,
1535
- changes: SubscriptionChanges,
1536
- type: DebounceType = DebounceType.SLOW,
1537
- ) => {
1538
- const participants = this.state.updateParticipants(
1539
- Object.entries(changes).reduce<StreamVideoParticipantPatches>(
1540
- (acc, [sessionId, change]) => {
1541
- if (change.dimension) {
1542
- change.dimension.height = Math.ceil(change.dimension.height);
1543
- change.dimension.width = Math.ceil(change.dimension.width);
1544
- }
1545
- const prop: keyof StreamVideoParticipant | undefined =
1546
- trackType === 'videoTrack'
1547
- ? 'videoDimension'
1548
- : trackType === 'screenShareTrack'
1549
- ? 'screenShareDimension'
1550
- : undefined;
1551
- if (prop) {
1552
- acc[sessionId] = {
1553
- [prop]: change.dimension,
1554
- };
1555
- }
1556
- return acc;
1557
- },
1558
- {},
1559
- ),
1560
- );
1561
-
1562
- this.updateSubscriptions(participants, type);
1563
- };
1564
-
1565
- private updateSubscriptions = (
1566
- participants: StreamVideoParticipant[],
1567
- type: DebounceType = DebounceType.SLOW,
1568
- ) => {
1569
- const subscriptions: TrackSubscriptionDetails[] = [];
1570
- for (const p of participants) {
1571
- // we don't want to subscribe to our own tracks
1572
- if (p.isLocalParticipant) continue;
1573
-
1574
- // NOTE: audio tracks don't have to be requested explicitly
1575
- // as the SFU will implicitly subscribe us to all of them,
1576
- // once they become available.
1577
- if (p.videoDimension && hasVideo(p)) {
1578
- subscriptions.push({
1579
- userId: p.userId,
1580
- sessionId: p.sessionId,
1581
- trackType: TrackType.VIDEO,
1582
- dimension: p.videoDimension,
1583
- });
1584
- }
1585
- if (p.screenShareDimension && hasScreenShare(p)) {
1586
- subscriptions.push({
1587
- userId: p.userId,
1588
- sessionId: p.sessionId,
1589
- trackType: TrackType.SCREEN_SHARE,
1590
- dimension: p.screenShareDimension,
1591
- });
1592
- }
1593
- if (hasScreenShareAudio(p)) {
1594
- subscriptions.push({
1595
- userId: p.userId,
1596
- sessionId: p.sessionId,
1597
- trackType: TrackType.SCREEN_SHARE_AUDIO,
1598
- });
1599
- }
1600
- }
1601
- // schedule update
1602
- this.trackSubscriptionsSubject.next({ type, data: subscriptions });
1603
- };
1604
-
1605
1492
  /**
1606
1493
  * Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
1607
1494
  * This is usually helpful when detailed stats for a specific participant are needed.
@@ -2387,4 +2274,41 @@ export class Call {
2387
2274
  imageElement.removeEventListener('error', handleError);
2388
2275
  };
2389
2276
  };
2277
+
2278
+ /**
2279
+ * Specify preference for incoming video resolution. The preference will
2280
+ * be matched as close as possible, but actual resolution will depend
2281
+ * on the video source quality and client network conditions. Will enable
2282
+ * incoming video, if previously disabled.
2283
+ *
2284
+ * @param resolution preferred resolution, or `undefined` to clear preference
2285
+ * @param sessionIds optionally specify session ids of the participants this
2286
+ * preference has effect on. Affects all participants by default.
2287
+ */
2288
+ setPreferredIncomingVideoResolution = (
2289
+ resolution: VideoResolution | undefined,
2290
+ sessionIds?: string[],
2291
+ ) => {
2292
+ this.dynascaleManager.setVideoTrackSubscriptionOverrides(
2293
+ resolution
2294
+ ? {
2295
+ enabled: true,
2296
+ dimension: resolution,
2297
+ }
2298
+ : undefined,
2299
+ sessionIds,
2300
+ );
2301
+ this.dynascaleManager.applyTrackSubscriptions();
2302
+ };
2303
+
2304
+ /**
2305
+ * Enables or disables incoming video from all remote call participants,
2306
+ * and removes any preference for preferred resolution.
2307
+ */
2308
+ setIncomingVideoEnabled = (enabled: boolean) => {
2309
+ this.dynascaleManager.setVideoTrackSubscriptionOverrides(
2310
+ enabled ? undefined : { enabled: false },
2311
+ );
2312
+ this.dynascaleManager.applyTrackSubscriptions();
2313
+ };
2390
2314
  }