@stream-io/video-client 0.3.8 → 0.3.10

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.
@@ -2,21 +2,16 @@ import { SfuEventKinds, SfuEventListener } from './rtc';
2
2
  import { TrackType } from './gen/video/sfu/models/models';
3
3
  import { CallState } from './store';
4
4
  import { AcceptCallResponse, BlockUserResponse, EndCallResponse, GetCallResponse, GetOrCreateCallRequest, GetOrCreateCallResponse, GoLiveRequest, GoLiveResponse, ListRecordingsResponse, MuteUsersResponse, PinRequest, PinResponse, QueryMembersRequest, QueryMembersResponse, RejectCallResponse, RequestPermissionRequest, RequestPermissionResponse, SendEventResponse, SendReactionRequest, SendReactionResponse, StartBroadcastingResponse, StartRecordingResponse, StopBroadcastingResponse, StopLiveResponse, StopRecordingResponse, UnblockUserResponse, UnpinRequest, UnpinResponse, UpdateCallMembersRequest, UpdateCallMembersResponse, UpdateCallRequest, UpdateCallResponse, UpdateUserPermissionsRequest, UpdateUserPermissionsResponse } from './gen/coordinator';
5
- import { CallConstructor, CallLeaveOptions, DebounceType, JoinCallData, PublishOptions, SubscriptionChanges } from './types';
6
- import { ViewportTracker } from './helpers/ViewportTracker';
5
+ import { CallConstructor, CallLeaveOptions, DebounceType, JoinCallData, PublishOptions, SubscriptionChanges, VideoTrackType } from './types';
6
+ import { DynascaleManager } from './helpers/DynascaleManager';
7
7
  import { PermissionsContext } from './permissions';
8
8
  import { StreamClient } from './coordinator/connection/client';
9
9
  import { CallEventHandler, CallEventTypes, EventTypes, Logger } from './coordinator/connection/types';
10
- import { CameraManager } from './devices/CameraManager';
11
- import { MicrophoneManager } from './devices/MicrophoneManager';
10
+ import { CameraManager, MicrophoneManager } from './devices';
12
11
  /**
13
12
  * An object representation of a `Call`.
14
13
  */
15
14
  export declare class Call {
16
- /**
17
- * ViewportTracker instance
18
- */
19
- readonly viewportTracker: ViewportTracker;
20
15
  /**
21
16
  * The type of the call.
22
17
  */
@@ -46,6 +41,10 @@ export declare class Call {
46
41
  * Device manager for the microhpone
47
42
  */
48
43
  readonly microphone: MicrophoneManager;
44
+ /**
45
+ * The DynascaleManager instance.
46
+ */
47
+ readonly dynascaleManager: DynascaleManager;
49
48
  /**
50
49
  * Flag telling whether this call is a "ringing" call.
51
50
  */
@@ -225,11 +224,11 @@ export declare class Call {
225
224
  * You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
226
225
  * You can only subscribe for tracks after the participant started publishing the given kind of track.
227
226
  *
228
- * @param kind the kind of subscription to update.
227
+ * @param trackType the kind of subscription to update.
229
228
  * @param changes the list of subscription changes to do.
230
229
  * @param type the debounce type to use for the update.
231
230
  */
232
- updateSubscriptionsPartial: (kind: 'video' | 'screen', changes: SubscriptionChanges, type?: DebounceType) => void;
231
+ updateSubscriptionsPartial: (trackType: VideoTrackType | 'video' | 'screen', changes: SubscriptionChanges, type?: DebounceType) => void;
233
232
  private updateSubscriptions;
234
233
  /**
235
234
  * Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state [store variable](./StreamVideoClient.md/#readonlystatestore)).
@@ -477,4 +476,45 @@ export declare class Call {
477
476
  }) => Promise<SendEventResponse>;
478
477
  private initCamera;
479
478
  private initMic;
479
+ /**
480
+ * Will begin tracking the given element for visibility changes within the
481
+ * configured viewport element (`call.setViewport`).
482
+ *
483
+ * @param element the element to track.
484
+ * @param sessionId the session id.
485
+ * @param trackType the video mode.
486
+ */
487
+ trackElementVisibility: <T extends HTMLElement>(element: T, sessionId: string, trackType: VideoTrackType) => () => void;
488
+ /**
489
+ * Sets the viewport element to track bound video elements for visibility.
490
+ *
491
+ * @param element the viewport element.
492
+ */
493
+ setViewport: <T extends HTMLElement>(element: T) => () => void;
494
+ /**
495
+ * Binds a DOM <video> element to the given session id.
496
+ * This method will make sure that the video element will play
497
+ * the correct video stream for the given session id.
498
+ *
499
+ * Under the hood, it would also keep track of the video element dimensions
500
+ * and update the subscription accordingly in order to optimize the bandwidth.
501
+ *
502
+ * If a "viewport" is configured, the video element will be automatically
503
+ * tracked for visibility and the subscription will be updated accordingly.
504
+ *
505
+ * @param videoElement the video element to bind to.
506
+ * @param sessionId the session id.
507
+ * @param trackType the kind of video.
508
+ */
509
+ bindVideoElement: (videoElement: HTMLVideoElement, sessionId: string, trackType: VideoTrackType) => (() => void) | undefined;
510
+ /**
511
+ * Binds a DOM <audio> element to the given session id.
512
+ *
513
+ * This method will make sure that the audio element will
514
+ * play the correct audio stream for the given session id.
515
+ *
516
+ * @param audioElement the audio element to bind to.
517
+ * @param sessionId the session id.
518
+ */
519
+ bindAudioElement: (audioElement: HTMLAudioElement, sessionId: string) => (() => void) | undefined;
480
520
  }
@@ -0,0 +1,70 @@
1
+ import { Call } from '../Call';
2
+ import { VideoTrackType } from '../types';
3
+ import { ViewportTracker } from './ViewportTracker';
4
+ /**
5
+ * A manager class that handles dynascale related tasks like:
6
+ *
7
+ * - binding video elements to session ids
8
+ * - binding audio elements to session ids
9
+ * - tracking element visibility
10
+ * - updating subscriptions based on viewport visibility
11
+ * - updating subscriptions based on video element dimensions
12
+ * - updating subscriptions based on published tracks
13
+ */
14
+ export declare class DynascaleManager {
15
+ /**
16
+ * The viewport tracker instance.
17
+ */
18
+ readonly viewportTracker: ViewportTracker;
19
+ private logger;
20
+ private call;
21
+ /**
22
+ * Creates a new DynascaleManager instance.
23
+ *
24
+ * @param call the call to manage.
25
+ */
26
+ constructor(call: Call);
27
+ /**
28
+ * Will begin tracking the given element for visibility changes within the
29
+ * configured viewport element (`call.setViewport`).
30
+ *
31
+ * @param element the element to track.
32
+ * @param sessionId the session id.
33
+ * @param trackType the kind of video.
34
+ * @returns Untrack.
35
+ */
36
+ trackElementVisibility: <T extends HTMLElement>(element: T, sessionId: string, trackType: VideoTrackType) => () => void;
37
+ /**
38
+ * Sets the viewport element to track bound video elements for visibility.
39
+ *
40
+ * @param element the viewport element.
41
+ */
42
+ setViewport: <T extends HTMLElement>(element: T) => () => void;
43
+ /**
44
+ * Binds a DOM <video> element to the given session id.
45
+ * This method will make sure that the video element will play
46
+ * the correct video stream for the given session id.
47
+ *
48
+ * Under the hood, it would also keep track of the video element dimensions
49
+ * and update the subscription accordingly in order to optimize the bandwidth.
50
+ *
51
+ * If a "viewport" is configured, the video element will be automatically
52
+ * tracked for visibility and the subscription will be updated accordingly.
53
+ *
54
+ * @param videoElement the video element to bind to.
55
+ * @param sessionId the session id.
56
+ * @param trackType the kind of video.
57
+ */
58
+ bindVideoElement: (videoElement: HTMLVideoElement, sessionId: string, trackType: VideoTrackType) => (() => void) | undefined;
59
+ /**
60
+ * Binds a DOM <audio> element to the given session id.
61
+ *
62
+ * This method will make sure that the audio element will
63
+ * play the correct audio stream for the given session id.
64
+ *
65
+ * @param audioElement the audio element to bind to.
66
+ * @param sessionId the session id.
67
+ * @returns a cleanup function that will unbind the audio element.
68
+ */
69
+ bindAudioElement: (audioElement: HTMLAudioElement, sessionId: string) => (() => void) | undefined;
70
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @vitest-environment happy-dom
3
+ */
4
+ import '../../rtc/__tests__/mocks/webrtc.mocks';
@@ -11,7 +11,7 @@ export declare enum VisibilityState {
11
11
  INVISIBLE = "INVISIBLE"
12
12
  }
13
13
  export declare enum DebounceType {
14
- IMMEDIATE = 0,
14
+ IMMEDIATE = 20,
15
15
  FAST = 100,
16
16
  MEDIUM = 600,
17
17
  SLOW = 1200
@@ -56,11 +56,9 @@ export interface StreamVideoParticipant extends Participant {
56
56
  */
57
57
  reaction?: StreamReaction;
58
58
  /**
59
- * The visibility state of the participant's video element
60
- * within the pre-configured viewport.
61
- * @default VisibilityState.UNKNOWN
59
+ * The visibility state of the participant's tracks within a defined viewport.
62
60
  */
63
- viewportVisibilityState?: VisibilityState;
61
+ viewportVisibilityState?: Record<VideoTrackType, VisibilityState>;
64
62
  }
65
63
  export interface StreamVideoLocalParticipant extends StreamVideoParticipant {
66
64
  /**
@@ -82,6 +80,7 @@ export interface StreamVideoLocalParticipant extends StreamVideoParticipant {
82
80
  */
83
81
  audioOutputDeviceId?: string;
84
82
  }
83
+ export type VideoTrackType = 'videoTrack' | 'screenShareTrack';
85
84
  /**
86
85
  * Represents a participant's pin state.
87
86
  */
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "0.3.8";
1
+ export declare const version = "0.3.10";
package/index.ts CHANGED
@@ -18,9 +18,10 @@ export * from './src/StreamSfuClient';
18
18
  export * from './src/devices';
19
19
  export * from './src/store';
20
20
  export * from './src/sorting';
21
+ export * from './src/helpers/DynascaleManager';
21
22
  export * from './src/helpers/ViewportTracker';
22
-
23
23
  export * from './src/helpers/sound-detector';
24
24
  export * as Browsers from './src/helpers/browsers';
25
+
25
26
  export * from './src/client-details';
26
27
  export * from './src/logger';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
@@ -46,20 +46,21 @@
46
46
  "devDependencies": {
47
47
  "@openapitools/openapi-generator-cli": "^2.6.0",
48
48
  "@rollup/plugin-replace": "^5.0.2",
49
- "@rollup/plugin-typescript": "^11.1.0",
49
+ "@rollup/plugin-typescript": "^11.1.2",
50
50
  "@types/jsonwebtoken": "^9.0.1",
51
51
  "@types/rimraf": "^3.0.2",
52
52
  "@types/sdp-transform": "^2.4.6",
53
53
  "@types/ua-parser-js": "^0.7.36",
54
54
  "@types/ws": "^8.5.4",
55
- "@vitest/coverage-c8": "^0.31.0",
55
+ "@vitest/coverage-v8": "^0.34.2",
56
56
  "dotenv": "^16.3.1",
57
+ "happy-dom": "^10.11.0",
57
58
  "prettier": "^2.8.4",
58
59
  "rimraf": "^3.0.2",
59
- "rollup": "^3.25.1",
60
+ "rollup": "^3.28.1",
60
61
  "typescript": "^4.9.5",
61
- "vite": "^4.3.9",
62
- "vitest": "^0.30.1",
63
- "vitest-mock-extended": "^1.1.3"
62
+ "vite": "^4.4.9",
63
+ "vitest": "^0.34.3",
64
+ "vitest-mock-extended": "^1.2.0"
64
65
  }
65
66
  }
package/src/Call.ts CHANGED
@@ -76,6 +76,7 @@ import {
76
76
  StreamVideoParticipant,
77
77
  StreamVideoParticipantPatches,
78
78
  SubscriptionChanges,
79
+ VideoTrackType,
79
80
  VisibilityState,
80
81
  } from './types';
81
82
  import {
@@ -96,7 +97,7 @@ import {
96
97
  createStatsReporter,
97
98
  StatsReporter,
98
99
  } from './stats/state-store-stats-reporter';
99
- import { ViewportTracker } from './helpers/ViewportTracker';
100
+ import { DynascaleManager } from './helpers/DynascaleManager';
100
101
  import { PermissionsContext } from './permissions';
101
102
  import { CallTypes } from './CallType';
102
103
  import { StreamClient } from './coordinator/connection/client';
@@ -115,19 +116,12 @@ import {
115
116
  } from './coordinator/connection/types';
116
117
  import { getClientDetails, getSdkInfo } from './client-details';
117
118
  import { getLogger } from './logger';
118
- import { CameraManager } from './devices/CameraManager';
119
- import { MicrophoneManager } from './devices/MicrophoneManager';
120
- import { CameraDirection } from './devices/CameraManagerState';
119
+ import { CameraDirection, CameraManager, MicrophoneManager } from './devices';
121
120
 
122
121
  /**
123
122
  * An object representation of a `Call`.
124
123
  */
125
124
  export class Call {
126
- /**
127
- * ViewportTracker instance
128
- */
129
- readonly viewportTracker = new ViewportTracker();
130
-
131
125
  /**
132
126
  * The type of the call.
133
127
  */
@@ -164,6 +158,11 @@ export class Call {
164
158
  */
165
159
  readonly microphone: MicrophoneManager;
166
160
 
161
+ /**
162
+ * The DynascaleManager instance.
163
+ */
164
+ readonly dynascaleManager = new DynascaleManager(this);
165
+
167
166
  /**
168
167
  * Flag telling whether this call is a "ringing" call.
169
168
  */
@@ -202,7 +201,7 @@ export class Call {
202
201
  * A typical use case is to clean up some global event handlers.
203
202
  * @private
204
203
  */
205
- private readonly leaveCallHooks: Function[] = [];
204
+ private readonly leaveCallHooks: Set<Function> = new Set();
206
205
 
207
206
  private readonly streamClientBasePath: string;
208
207
  private streamClientEventHandlers = new Map<Function, CallEventHandler>();
@@ -253,12 +252,12 @@ export class Call {
253
252
  this.state.updateFromEvent(event);
254
253
  });
255
254
 
256
- this.leaveCallHooks.push(
255
+ this.leaveCallHooks.add(
257
256
  registerEventHandlers(this, this.state, this.dispatcher),
258
257
  );
259
258
  this.registerEffects();
260
259
 
261
- this.leaveCallHooks.push(
260
+ this.leaveCallHooks.add(
262
261
  createSubscription(
263
262
  this.trackSubscriptionsSubject.pipe(
264
263
  debounce((v) => timer(v.type)),
@@ -273,13 +272,15 @@ export class Call {
273
272
  }
274
273
 
275
274
  private registerEffects() {
276
- this.leaveCallHooks.push(
275
+ this.leaveCallHooks.add(
277
276
  // handles updating the permissions context when the settings change.
278
277
  createSubscription(this.state.settings$, (settings) => {
279
278
  if (!settings) return;
280
279
  this.permissionsContext.setCallSettings(settings);
281
280
  }),
281
+ );
282
282
 
283
+ this.leaveCallHooks.add(
283
284
  // handle the case when the user permissions are modified.
284
285
  createSubscription(this.state.ownCapabilities$, (ownCapabilities) => {
285
286
  // update the permission context.
@@ -306,7 +307,9 @@ export class Call {
306
307
  }
307
308
  }
308
309
  }),
310
+ );
309
311
 
312
+ this.leaveCallHooks.add(
310
313
  // handles the case when the user is blocked by the call owner.
311
314
  createSubscription(this.state.blockedUserIds$, async (blockedUserIds) => {
312
315
  if (!blockedUserIds) return;
@@ -316,7 +319,9 @@ export class Call {
316
319
  await this.leave();
317
320
  }
318
321
  }),
322
+ );
319
323
 
324
+ this.leaveCallHooks.add(
320
325
  // watch for auto drop cancellation
321
326
  createSubscription(this.state.callingState$, (callingState) => {
322
327
  if (!this.ringing) return;
@@ -329,7 +334,9 @@ export class Call {
329
334
  this.dropTimeout = undefined;
330
335
  }
331
336
  }),
337
+ );
332
338
 
339
+ this.leaveCallHooks.add(
333
340
  // "ringing" mode effects and event handlers
334
341
  createSubscription(this.ringingSubject, (isRinging) => {
335
342
  if (!isRinging) return;
@@ -337,7 +344,7 @@ export class Call {
337
344
  if (this.state.callingState === CallingState.IDLE) {
338
345
  this.state.setCallingState(CallingState.RINGING);
339
346
  }
340
- this.leaveCallHooks.push(registerRingingCallEventHandlers(this));
347
+ this.leaveCallHooks.add(registerRingingCallEventHandlers(this));
341
348
  }),
342
349
  );
343
350
  }
@@ -815,7 +822,7 @@ export class Call {
815
822
  },
816
823
  );
817
824
 
818
- this.leaveCallHooks.push(() => {
825
+ this.leaveCallHooks.add(() => {
819
826
  unsubscribeOnlineEvent();
820
827
  unsubscribeOfflineEvent();
821
828
  });
@@ -904,7 +911,10 @@ export class Call {
904
911
  return currentParticipants.map((p) => {
905
912
  const participant: StreamVideoParticipant = Object.assign(p, {
906
913
  isLocalParticipant: p.sessionId === sfuClient.sessionId,
907
- viewportVisibilityState: VisibilityState.UNKNOWN,
914
+ viewportVisibilityState: {
915
+ videoTrack: VisibilityState.UNKNOWN,
916
+ screenShareTrack: VisibilityState.UNKNOWN,
917
+ },
908
918
  });
909
919
  // We need to preserve the local state of the participant
910
920
  // (e.g. videoDimension, visibilityState, pinnedAt, etc.)
@@ -1108,22 +1118,42 @@ export class Call {
1108
1118
  * You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
1109
1119
  * You can only subscribe for tracks after the participant started publishing the given kind of track.
1110
1120
  *
1111
- * @param kind the kind of subscription to update.
1121
+ * @param trackType the kind of subscription to update.
1112
1122
  * @param changes the list of subscription changes to do.
1113
1123
  * @param type the debounce type to use for the update.
1114
1124
  */
1115
1125
  updateSubscriptionsPartial = (
1116
- kind: 'video' | 'screen',
1126
+ trackType: VideoTrackType | 'video' | 'screen',
1117
1127
  changes: SubscriptionChanges,
1118
1128
  type: DebounceType = DebounceType.SLOW,
1119
1129
  ) => {
1130
+ if (trackType === 'video') {
1131
+ this.logger(
1132
+ 'warn',
1133
+ `updateSubscriptionsPartial: ${trackType} is deprecated. Please switch to 'videoTrack'`,
1134
+ );
1135
+ trackType = 'videoTrack';
1136
+ } else if (trackType === 'screen') {
1137
+ this.logger(
1138
+ 'warn',
1139
+ `updateSubscriptionsPartial: ${trackType} is deprecated. Please switch to 'screenShareTrack'`,
1140
+ );
1141
+ trackType = 'screenShareTrack';
1142
+ }
1143
+
1120
1144
  const participants = this.state.updateParticipants(
1121
1145
  Object.entries(changes).reduce<StreamVideoParticipantPatches>(
1122
1146
  (acc, [sessionId, change]) => {
1147
+ if (change.dimension?.height) {
1148
+ change.dimension.height = Math.ceil(change.dimension.height);
1149
+ }
1150
+ if (change.dimension?.width) {
1151
+ change.dimension.width = Math.ceil(change.dimension.width);
1152
+ }
1123
1153
  const prop: keyof StreamVideoParticipant | undefined =
1124
- kind === 'video'
1154
+ trackType === 'videoTrack'
1125
1155
  ? 'videoDimension'
1126
- : kind === 'screen'
1156
+ : trackType === 'screenShareTrack'
1127
1157
  ? 'screenShareDimension'
1128
1158
  : undefined;
1129
1159
  if (prop) {
@@ -1147,9 +1177,9 @@ export class Call {
1147
1177
  type: DebounceType = DebounceType.SLOW,
1148
1178
  ) => {
1149
1179
  const subscriptions: TrackSubscriptionDetails[] = [];
1150
- participants.forEach((p) => {
1180
+ for (const p of participants) {
1151
1181
  // we don't want to subscribe to our own tracks
1152
- if (p.isLocalParticipant) return;
1182
+ if (p.isLocalParticipant) continue;
1153
1183
 
1154
1184
  // NOTE: audio tracks don't have to be requested explicitly
1155
1185
  // as the SFU will implicitly subscribe us to all of them,
@@ -1174,7 +1204,7 @@ export class Call {
1174
1204
  dimension: p.screenShareDimension,
1175
1205
  });
1176
1206
  }
1177
- });
1207
+ }
1178
1208
  // schedule update
1179
1209
  this.trackSubscriptionsSubject.next({ type, data: subscriptions });
1180
1210
  };
@@ -1680,7 +1710,7 @@ export class Call {
1680
1710
  )
1681
1711
  .subscribe();
1682
1712
 
1683
- this.leaveCallHooks.push(() => {
1713
+ this.leaveCallHooks.add(() => {
1684
1714
  !subscription.closed && subscription.unsubscribe();
1685
1715
  });
1686
1716
  };
@@ -1800,4 +1830,90 @@ export class Call {
1800
1830
  await this.microphone.enable();
1801
1831
  }
1802
1832
  }
1833
+
1834
+ /**
1835
+ * Will begin tracking the given element for visibility changes within the
1836
+ * configured viewport element (`call.setViewport`).
1837
+ *
1838
+ * @param element the element to track.
1839
+ * @param sessionId the session id.
1840
+ * @param trackType the video mode.
1841
+ */
1842
+ trackElementVisibility = <T extends HTMLElement>(
1843
+ element: T,
1844
+ sessionId: string,
1845
+ trackType: VideoTrackType,
1846
+ ) => {
1847
+ return this.dynascaleManager.trackElementVisibility(
1848
+ element,
1849
+ sessionId,
1850
+ trackType,
1851
+ );
1852
+ };
1853
+
1854
+ /**
1855
+ * Sets the viewport element to track bound video elements for visibility.
1856
+ *
1857
+ * @param element the viewport element.
1858
+ */
1859
+ setViewport = <T extends HTMLElement>(element: T) => {
1860
+ return this.dynascaleManager.setViewport(element);
1861
+ };
1862
+
1863
+ /**
1864
+ * Binds a DOM <video> element to the given session id.
1865
+ * This method will make sure that the video element will play
1866
+ * the correct video stream for the given session id.
1867
+ *
1868
+ * Under the hood, it would also keep track of the video element dimensions
1869
+ * and update the subscription accordingly in order to optimize the bandwidth.
1870
+ *
1871
+ * If a "viewport" is configured, the video element will be automatically
1872
+ * tracked for visibility and the subscription will be updated accordingly.
1873
+ *
1874
+ * @param videoElement the video element to bind to.
1875
+ * @param sessionId the session id.
1876
+ * @param trackType the kind of video.
1877
+ */
1878
+ bindVideoElement = (
1879
+ videoElement: HTMLVideoElement,
1880
+ sessionId: string,
1881
+ trackType: VideoTrackType,
1882
+ ) => {
1883
+ const unbind = this.dynascaleManager.bindVideoElement(
1884
+ videoElement,
1885
+ sessionId,
1886
+ trackType,
1887
+ );
1888
+
1889
+ if (!unbind) return;
1890
+ this.leaveCallHooks.add(unbind);
1891
+ return () => {
1892
+ this.leaveCallHooks.delete(unbind);
1893
+ unbind();
1894
+ };
1895
+ };
1896
+
1897
+ /**
1898
+ * Binds a DOM <audio> element to the given session id.
1899
+ *
1900
+ * This method will make sure that the audio element will
1901
+ * play the correct audio stream for the given session id.
1902
+ *
1903
+ * @param audioElement the audio element to bind to.
1904
+ * @param sessionId the session id.
1905
+ */
1906
+ bindAudioElement = (audioElement: HTMLAudioElement, sessionId: string) => {
1907
+ const unbind = this.dynascaleManager.bindAudioElement(
1908
+ audioElement,
1909
+ sessionId,
1910
+ );
1911
+
1912
+ if (!unbind) return;
1913
+ this.leaveCallHooks.add(unbind);
1914
+ return () => {
1915
+ this.leaveCallHooks.delete(unbind);
1916
+ unbind();
1917
+ };
1918
+ };
1803
1919
  }
@@ -34,7 +34,10 @@ describe('Participant events', () => {
34
34
  {
35
35
  userId: 'user-id',
36
36
  sessionId: 'session-id',
37
- viewportVisibilityState: VisibilityState.UNKNOWN,
37
+ viewportVisibilityState: {
38
+ videoTrack: VisibilityState.UNKNOWN,
39
+ screenShareTrack: VisibilityState.UNKNOWN,
40
+ },
38
41
  },
39
42
  ]);
40
43
 
@@ -23,7 +23,10 @@ export const watchParticipantJoined = (state: CallState) => {
23
23
  Object.assign<StreamVideoParticipant, Partial<StreamVideoParticipant>>(
24
24
  participant,
25
25
  {
26
- viewportVisibilityState: VisibilityState.UNKNOWN,
26
+ viewportVisibilityState: {
27
+ videoTrack: VisibilityState.UNKNOWN,
28
+ screenShareTrack: VisibilityState.UNKNOWN,
29
+ },
27
30
  },
28
31
  ),
29
32
  );