@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.
package/dist/index.cjs.js CHANGED
@@ -5734,7 +5734,7 @@ exports.VisibilityState = void 0;
5734
5734
  })(exports.VisibilityState || (exports.VisibilityState = {}));
5735
5735
  exports.DebounceType = void 0;
5736
5736
  (function (DebounceType) {
5737
- DebounceType[DebounceType["IMMEDIATE"] = 0] = "IMMEDIATE";
5737
+ DebounceType[DebounceType["IMMEDIATE"] = 20] = "IMMEDIATE";
5738
5738
  DebounceType[DebounceType["FAST"] = 100] = "FAST";
5739
5739
  DebounceType[DebounceType["MEDIUM"] = 600] = "MEDIUM";
5740
5740
  DebounceType[DebounceType["SLOW"] = 1200] = "SLOW";
@@ -7609,10 +7609,20 @@ const retryable = (rpc, logger) => __awaiter(void 0, void 0, void 0, function* (
7609
7609
  */
7610
7610
  const getCurrentValue = (observable$) => {
7611
7611
  let value;
7612
+ let err = undefined;
7612
7613
  observable$
7613
7614
  .pipe(operators.take(1))
7614
- .subscribe((v) => (value = v))
7615
+ .subscribe({
7616
+ next: (v) => {
7617
+ value = v;
7618
+ },
7619
+ error: (e) => {
7620
+ err = e;
7621
+ },
7622
+ })
7615
7623
  .unsubscribe();
7624
+ if (err)
7625
+ throw err;
7616
7626
  return value;
7617
7627
  };
7618
7628
  /**
@@ -7971,8 +7981,11 @@ const hasAudio = (p) => p.publishedTracks.includes(TrackType.AUDIO);
7971
7981
  // a comparator decorator which applies the decorated comparator only if the
7972
7982
  // participant is invisible.
7973
7983
  // This ensures stable sorting when all participants are visible.
7974
- const ifInvisibleBy = conditional((a, b) => a.viewportVisibilityState === exports.VisibilityState.INVISIBLE ||
7975
- b.viewportVisibilityState === exports.VisibilityState.INVISIBLE);
7984
+ const ifInvisibleBy = conditional((a, b) => {
7985
+ var _a, _b;
7986
+ return ((_a = a.viewportVisibilityState) === null || _a === void 0 ? void 0 : _a.videoTrack) === exports.VisibilityState.INVISIBLE ||
7987
+ ((_b = b.viewportVisibilityState) === null || _b === void 0 ? void 0 : _b.videoTrack) === exports.VisibilityState.INVISIBLE;
7988
+ });
7976
7989
  /**
7977
7990
  * The default sorting preset.
7978
7991
  */
@@ -8849,7 +8862,10 @@ const watchParticipantJoined = (state) => {
8849
8862
  // response and then follow up with a `participantJoined` event for
8850
8863
  // already announced participants.
8851
8864
  state.updateOrAddParticipant(participant.sessionId, Object.assign(participant, {
8852
- viewportVisibilityState: exports.VisibilityState.UNKNOWN,
8865
+ viewportVisibilityState: {
8866
+ videoTrack: exports.VisibilityState.UNKNOWN,
8867
+ screenShareTrack: exports.VisibilityState.UNKNOWN,
8868
+ },
8853
8869
  }));
8854
8870
  };
8855
8871
  };
@@ -9406,6 +9422,234 @@ class ViewportTracker {
9406
9422
  }
9407
9423
  }
9408
9424
 
9425
+ const DEFAULT_VIEWPORT_VISIBILITY_STATE = {
9426
+ videoTrack: exports.VisibilityState.UNKNOWN,
9427
+ screenShareTrack: exports.VisibilityState.UNKNOWN,
9428
+ };
9429
+ /**
9430
+ * A manager class that handles dynascale related tasks like:
9431
+ *
9432
+ * - binding video elements to session ids
9433
+ * - binding audio elements to session ids
9434
+ * - tracking element visibility
9435
+ * - updating subscriptions based on viewport visibility
9436
+ * - updating subscriptions based on video element dimensions
9437
+ * - updating subscriptions based on published tracks
9438
+ */
9439
+ class DynascaleManager {
9440
+ /**
9441
+ * Creates a new DynascaleManager instance.
9442
+ *
9443
+ * @param call the call to manage.
9444
+ */
9445
+ constructor(call) {
9446
+ /**
9447
+ * The viewport tracker instance.
9448
+ */
9449
+ this.viewportTracker = new ViewportTracker();
9450
+ this.logger = getLogger(['DynascaleManager']);
9451
+ /**
9452
+ * Will begin tracking the given element for visibility changes within the
9453
+ * configured viewport element (`call.setViewport`).
9454
+ *
9455
+ * @param element the element to track.
9456
+ * @param sessionId the session id.
9457
+ * @param trackType the kind of video.
9458
+ * @returns Untrack.
9459
+ */
9460
+ this.trackElementVisibility = (element, sessionId, trackType) => {
9461
+ const cleanup = this.viewportTracker.observe(element, (entry) => {
9462
+ this.call.state.updateParticipant(sessionId, (participant) => {
9463
+ var _a;
9464
+ const previousVisibilityState = (_a = participant.viewportVisibilityState) !== null && _a !== void 0 ? _a : DEFAULT_VIEWPORT_VISIBILITY_STATE;
9465
+ // observer triggers when the element is "moved" to be a fullscreen element
9466
+ // keep it VISIBLE if that happens to prevent fullscreen with placeholder
9467
+ const isVisible = entry.isIntersecting || document.fullscreenElement === element
9468
+ ? exports.VisibilityState.VISIBLE
9469
+ : exports.VisibilityState.INVISIBLE;
9470
+ return Object.assign(Object.assign({}, participant), { viewportVisibilityState: Object.assign(Object.assign({}, previousVisibilityState), { [trackType]: isVisible }) });
9471
+ });
9472
+ });
9473
+ return () => {
9474
+ cleanup();
9475
+ // reset visibility state to UNKNOWN upon cleanup
9476
+ // so that the layouts that are not actively observed
9477
+ // can still function normally (runtime layout switching)
9478
+ this.call.state.updateParticipant(sessionId, (participant) => {
9479
+ var _a;
9480
+ const previousVisibilityState = (_a = participant.viewportVisibilityState) !== null && _a !== void 0 ? _a : DEFAULT_VIEWPORT_VISIBILITY_STATE;
9481
+ return Object.assign(Object.assign({}, participant), { viewportVisibilityState: Object.assign(Object.assign({}, previousVisibilityState), { [trackType]: exports.VisibilityState.UNKNOWN }) });
9482
+ });
9483
+ };
9484
+ };
9485
+ /**
9486
+ * Sets the viewport element to track bound video elements for visibility.
9487
+ *
9488
+ * @param element the viewport element.
9489
+ */
9490
+ this.setViewport = (element) => {
9491
+ return this.viewportTracker.setViewport(element);
9492
+ };
9493
+ /**
9494
+ * Binds a DOM <video> element to the given session id.
9495
+ * This method will make sure that the video element will play
9496
+ * the correct video stream for the given session id.
9497
+ *
9498
+ * Under the hood, it would also keep track of the video element dimensions
9499
+ * and update the subscription accordingly in order to optimize the bandwidth.
9500
+ *
9501
+ * If a "viewport" is configured, the video element will be automatically
9502
+ * tracked for visibility and the subscription will be updated accordingly.
9503
+ *
9504
+ * @param videoElement the video element to bind to.
9505
+ * @param sessionId the session id.
9506
+ * @param trackType the kind of video.
9507
+ */
9508
+ this.bindVideoElement = (videoElement, sessionId, trackType) => {
9509
+ const boundParticipant = this.call.state.findParticipantBySessionId(sessionId);
9510
+ if (!boundParticipant)
9511
+ return;
9512
+ const requestTrackWithDimensions = (debounceType, dimension) => {
9513
+ this.call.updateSubscriptionsPartial(trackType, { [sessionId]: { dimension } }, debounceType);
9514
+ };
9515
+ const participant$ = this.call.state.participants$.pipe(rxjs.map((participants) => participants.find((participant) => participant.sessionId === sessionId)), rxjs.takeWhile((participant) => !!participant), rxjs.distinctUntilChanged());
9516
+ // keep copy for resize observer handler
9517
+ let viewportVisibilityState;
9518
+ const viewportVisibilityStateSubscription = boundParticipant.isLocalParticipant
9519
+ ? null
9520
+ : participant$
9521
+ .pipe(rxjs.map((p) => { var _a; return (_a = p.viewportVisibilityState) === null || _a === void 0 ? void 0 : _a[trackType]; }), rxjs.distinctUntilChanged())
9522
+ .subscribe((nextViewportVisibilityState) => {
9523
+ // skip initial trigger
9524
+ if (!viewportVisibilityState) {
9525
+ viewportVisibilityState =
9526
+ nextViewportVisibilityState !== null && nextViewportVisibilityState !== void 0 ? nextViewportVisibilityState : exports.VisibilityState.UNKNOWN;
9527
+ return;
9528
+ }
9529
+ viewportVisibilityState =
9530
+ nextViewportVisibilityState !== null && nextViewportVisibilityState !== void 0 ? nextViewportVisibilityState : exports.VisibilityState.UNKNOWN;
9531
+ if (nextViewportVisibilityState === exports.VisibilityState.INVISIBLE) {
9532
+ return requestTrackWithDimensions(exports.DebounceType.MEDIUM, undefined);
9533
+ }
9534
+ requestTrackWithDimensions(exports.DebounceType.MEDIUM, {
9535
+ width: videoElement.clientWidth,
9536
+ height: videoElement.clientHeight,
9537
+ });
9538
+ });
9539
+ let lastDimensions;
9540
+ const resizeObserver = boundParticipant.isLocalParticipant
9541
+ ? null
9542
+ : new ResizeObserver(() => {
9543
+ const currentDimensions = `${videoElement.clientWidth},${videoElement.clientHeight}`;
9544
+ // skip initial trigger
9545
+ if (!lastDimensions) {
9546
+ lastDimensions = currentDimensions;
9547
+ return;
9548
+ }
9549
+ if (lastDimensions === currentDimensions ||
9550
+ viewportVisibilityState === exports.VisibilityState.INVISIBLE) {
9551
+ return;
9552
+ }
9553
+ requestTrackWithDimensions(exports.DebounceType.SLOW, {
9554
+ width: videoElement.clientWidth,
9555
+ height: videoElement.clientHeight,
9556
+ });
9557
+ lastDimensions = currentDimensions;
9558
+ });
9559
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(videoElement);
9560
+ const publishedTracksSubscription = boundParticipant.isLocalParticipant
9561
+ ? null
9562
+ : participant$
9563
+ .pipe(rxjs.distinctUntilKeyChanged('publishedTracks'), rxjs.map((p) => p.publishedTracks.includes(trackType === 'videoTrack'
9564
+ ? TrackType.VIDEO
9565
+ : TrackType.SCREEN_SHARE)), rxjs.distinctUntilChanged())
9566
+ .subscribe((isPublishing) => {
9567
+ if (isPublishing) {
9568
+ // the participant just started to publish a track
9569
+ requestTrackWithDimensions(exports.DebounceType.IMMEDIATE, {
9570
+ width: videoElement.clientWidth,
9571
+ height: videoElement.clientHeight,
9572
+ });
9573
+ }
9574
+ else {
9575
+ // the participant just stopped publishing a track
9576
+ requestTrackWithDimensions(exports.DebounceType.IMMEDIATE, undefined);
9577
+ }
9578
+ });
9579
+ const streamSubscription = participant$
9580
+ .pipe(rxjs.distinctUntilKeyChanged(trackType === 'videoTrack' ? 'videoStream' : 'screenShareStream'))
9581
+ .subscribe((p) => {
9582
+ const source = trackType === 'videoTrack' ? p.videoStream : p.screenShareStream;
9583
+ if (videoElement.srcObject === source)
9584
+ return;
9585
+ setTimeout(() => {
9586
+ videoElement.srcObject = source !== null && source !== void 0 ? source : null;
9587
+ if (videoElement.srcObject) {
9588
+ videoElement.play().catch((e) => {
9589
+ this.logger('warn', `Failed to play stream`, e);
9590
+ });
9591
+ }
9592
+ }, 0);
9593
+ });
9594
+ videoElement.playsInline = true;
9595
+ videoElement.autoplay = true;
9596
+ // explicitly marking the element as muted will allow autoplay to work
9597
+ // without prior user interaction:
9598
+ // https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide
9599
+ videoElement.muted = true;
9600
+ return () => {
9601
+ viewportVisibilityStateSubscription === null || viewportVisibilityStateSubscription === void 0 ? void 0 : viewportVisibilityStateSubscription.unsubscribe();
9602
+ publishedTracksSubscription === null || publishedTracksSubscription === void 0 ? void 0 : publishedTracksSubscription.unsubscribe();
9603
+ streamSubscription.unsubscribe();
9604
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
9605
+ };
9606
+ };
9607
+ /**
9608
+ * Binds a DOM <audio> element to the given session id.
9609
+ *
9610
+ * This method will make sure that the audio element will
9611
+ * play the correct audio stream for the given session id.
9612
+ *
9613
+ * @param audioElement the audio element to bind to.
9614
+ * @param sessionId the session id.
9615
+ * @returns a cleanup function that will unbind the audio element.
9616
+ */
9617
+ this.bindAudioElement = (audioElement, sessionId) => {
9618
+ const participant = this.call.state.findParticipantBySessionId(sessionId);
9619
+ if (!participant || participant.isLocalParticipant)
9620
+ return;
9621
+ const participant$ = this.call.state.participants$.pipe(rxjs.map((participants) => participants.find((p) => p.sessionId === sessionId)), rxjs.takeWhile((p) => !!p), rxjs.distinctUntilChanged());
9622
+ const updateMediaStreamSubscription = participant$
9623
+ .pipe(rxjs.distinctUntilKeyChanged('audioStream'))
9624
+ .subscribe((p) => {
9625
+ const source = p.audioStream;
9626
+ if (audioElement.srcObject === source)
9627
+ return;
9628
+ setTimeout(() => {
9629
+ audioElement.srcObject = source !== null && source !== void 0 ? source : null;
9630
+ if (audioElement.srcObject) {
9631
+ audioElement.play().catch((e) => {
9632
+ this.logger('warn', `Failed to play stream`, e);
9633
+ });
9634
+ }
9635
+ });
9636
+ });
9637
+ const sinkIdSubscription = this.call.state.localParticipant$.subscribe((p) => {
9638
+ if (p && p.audioOutputDeviceId && 'setSinkId' in audioElement) {
9639
+ // @ts-expect-error setSinkId is not yet in the lib
9640
+ audioElement.setSinkId(p.audioOutputDeviceId);
9641
+ }
9642
+ });
9643
+ audioElement.autoplay = true;
9644
+ return () => {
9645
+ sinkIdSubscription.unsubscribe();
9646
+ updateMediaStreamSubscription.unsubscribe();
9647
+ };
9648
+ };
9649
+ this.call = call;
9650
+ }
9651
+ }
9652
+
9409
9653
  /**
9410
9654
  * Stores the permissions for the current user and exposes
9411
9655
  * a few helper methods which make it easier to work with permissions.
@@ -9545,127 +9789,6 @@ const CallTypes = new CallTypesRegistry([
9545
9789
  }),
9546
9790
  ]);
9547
9791
 
9548
- class InputMediaDeviceManagerState {
9549
- constructor(disableMode = 'stop-tracks') {
9550
- this.disableMode = disableMode;
9551
- this.statusSubject = new rxjs.BehaviorSubject(undefined);
9552
- this.mediaStreamSubject = new rxjs.BehaviorSubject(undefined);
9553
- this.selectedDeviceSubject = new rxjs.BehaviorSubject(undefined);
9554
- /**
9555
- * Gets the current value of an observable, or undefined if the observable has
9556
- * not emitted a value yet.
9557
- *
9558
- * @param observable$ the observable to get the value from.
9559
- */
9560
- this.getCurrentValue = getCurrentValue;
9561
- /**
9562
- * Updates the value of the provided Subject.
9563
- * An `update` can either be a new value or a function which takes
9564
- * the current value and returns a new value.
9565
- *
9566
- * @internal
9567
- *
9568
- * @param subject the subject to update.
9569
- * @param update the update to apply to the subject.
9570
- * @return the updated value.
9571
- */
9572
- this.setCurrentValue = setCurrentValue;
9573
- this.mediaStream$ = this.mediaStreamSubject.asObservable();
9574
- this.selectedDevice$ = this.selectedDeviceSubject
9575
- .asObservable()
9576
- .pipe(rxjs.distinctUntilChanged());
9577
- this.status$ = this.statusSubject
9578
- .asObservable()
9579
- .pipe(rxjs.distinctUntilChanged());
9580
- }
9581
- /**
9582
- * The device status
9583
- */
9584
- get status() {
9585
- return this.getCurrentValue(this.status$);
9586
- }
9587
- /**
9588
- * The currently selected device
9589
- */
9590
- get selectedDevice() {
9591
- return this.getCurrentValue(this.selectedDevice$);
9592
- }
9593
- /**
9594
- * The current media stream, or `undefined` if the device is currently disabled.
9595
- */
9596
- get mediaStream() {
9597
- return this.getCurrentValue(this.mediaStream$);
9598
- }
9599
- /**
9600
- * @internal
9601
- * @param status
9602
- */
9603
- setStatus(status) {
9604
- this.setCurrentValue(this.statusSubject, status);
9605
- }
9606
- /**
9607
- * @internal
9608
- * @param stream
9609
- */
9610
- setMediaStream(stream) {
9611
- this.setCurrentValue(this.mediaStreamSubject, stream);
9612
- if (stream) {
9613
- this.setDevice(this.getDeviceIdFromStream(stream));
9614
- }
9615
- }
9616
- /**
9617
- * @internal
9618
- * @param stream
9619
- */
9620
- setDevice(deviceId) {
9621
- this.setCurrentValue(this.selectedDeviceSubject, deviceId);
9622
- }
9623
- }
9624
-
9625
- class CameraManagerState extends InputMediaDeviceManagerState {
9626
- constructor() {
9627
- super('stop-tracks');
9628
- this.directionSubject = new rxjs.BehaviorSubject(undefined);
9629
- this.direction$ = this.directionSubject
9630
- .asObservable()
9631
- .pipe(rxjs.distinctUntilChanged());
9632
- }
9633
- /**
9634
- * The preferred camera direction
9635
- * front - means the camera facing the user
9636
- * back - means the camera facing the environment
9637
- */
9638
- get direction() {
9639
- return this.getCurrentValue(this.direction$);
9640
- }
9641
- /**
9642
- * @internal
9643
- */
9644
- setDirection(direction) {
9645
- this.setCurrentValue(this.directionSubject, direction);
9646
- }
9647
- /**
9648
- * @internal
9649
- */
9650
- setMediaStream(stream) {
9651
- var _a;
9652
- super.setMediaStream(stream);
9653
- if (stream) {
9654
- // RN getSettings() doesn't return facingMode, so we don't verify camera direction
9655
- const direction = isReactNative()
9656
- ? this.direction
9657
- : ((_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().facingMode) === 'environment'
9658
- ? 'back'
9659
- : 'front';
9660
- this.setDirection(direction);
9661
- }
9662
- }
9663
- getDeviceIdFromStream(stream) {
9664
- var _a;
9665
- return (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
9666
- }
9667
- }
9668
-
9669
9792
  const getDevices = (constraints) => {
9670
9793
  return new rxjs.Observable((subscriber) => {
9671
9794
  navigator.mediaDevices
@@ -10061,6 +10184,127 @@ class InputMediaDeviceManager {
10061
10184
  }
10062
10185
  }
10063
10186
 
10187
+ class InputMediaDeviceManagerState {
10188
+ constructor(disableMode = 'stop-tracks') {
10189
+ this.disableMode = disableMode;
10190
+ this.statusSubject = new rxjs.BehaviorSubject(undefined);
10191
+ this.mediaStreamSubject = new rxjs.BehaviorSubject(undefined);
10192
+ this.selectedDeviceSubject = new rxjs.BehaviorSubject(undefined);
10193
+ /**
10194
+ * Gets the current value of an observable, or undefined if the observable has
10195
+ * not emitted a value yet.
10196
+ *
10197
+ * @param observable$ the observable to get the value from.
10198
+ */
10199
+ this.getCurrentValue = getCurrentValue;
10200
+ /**
10201
+ * Updates the value of the provided Subject.
10202
+ * An `update` can either be a new value or a function which takes
10203
+ * the current value and returns a new value.
10204
+ *
10205
+ * @internal
10206
+ *
10207
+ * @param subject the subject to update.
10208
+ * @param update the update to apply to the subject.
10209
+ * @return the updated value.
10210
+ */
10211
+ this.setCurrentValue = setCurrentValue;
10212
+ this.mediaStream$ = this.mediaStreamSubject.asObservable();
10213
+ this.selectedDevice$ = this.selectedDeviceSubject
10214
+ .asObservable()
10215
+ .pipe(rxjs.distinctUntilChanged());
10216
+ this.status$ = this.statusSubject
10217
+ .asObservable()
10218
+ .pipe(rxjs.distinctUntilChanged());
10219
+ }
10220
+ /**
10221
+ * The device status
10222
+ */
10223
+ get status() {
10224
+ return this.getCurrentValue(this.status$);
10225
+ }
10226
+ /**
10227
+ * The currently selected device
10228
+ */
10229
+ get selectedDevice() {
10230
+ return this.getCurrentValue(this.selectedDevice$);
10231
+ }
10232
+ /**
10233
+ * The current media stream, or `undefined` if the device is currently disabled.
10234
+ */
10235
+ get mediaStream() {
10236
+ return this.getCurrentValue(this.mediaStream$);
10237
+ }
10238
+ /**
10239
+ * @internal
10240
+ * @param status
10241
+ */
10242
+ setStatus(status) {
10243
+ this.setCurrentValue(this.statusSubject, status);
10244
+ }
10245
+ /**
10246
+ * @internal
10247
+ * @param stream
10248
+ */
10249
+ setMediaStream(stream) {
10250
+ this.setCurrentValue(this.mediaStreamSubject, stream);
10251
+ if (stream) {
10252
+ this.setDevice(this.getDeviceIdFromStream(stream));
10253
+ }
10254
+ }
10255
+ /**
10256
+ * @internal
10257
+ * @param stream
10258
+ */
10259
+ setDevice(deviceId) {
10260
+ this.setCurrentValue(this.selectedDeviceSubject, deviceId);
10261
+ }
10262
+ }
10263
+
10264
+ class CameraManagerState extends InputMediaDeviceManagerState {
10265
+ constructor() {
10266
+ super('stop-tracks');
10267
+ this.directionSubject = new rxjs.BehaviorSubject(undefined);
10268
+ this.direction$ = this.directionSubject
10269
+ .asObservable()
10270
+ .pipe(rxjs.distinctUntilChanged());
10271
+ }
10272
+ /**
10273
+ * The preferred camera direction
10274
+ * front - means the camera facing the user
10275
+ * back - means the camera facing the environment
10276
+ */
10277
+ get direction() {
10278
+ return this.getCurrentValue(this.direction$);
10279
+ }
10280
+ /**
10281
+ * @internal
10282
+ */
10283
+ setDirection(direction) {
10284
+ this.setCurrentValue(this.directionSubject, direction);
10285
+ }
10286
+ /**
10287
+ * @internal
10288
+ */
10289
+ setMediaStream(stream) {
10290
+ var _a;
10291
+ super.setMediaStream(stream);
10292
+ if (stream) {
10293
+ // RN getSettings() doesn't return facingMode, so we don't verify camera direction
10294
+ const direction = isReactNative()
10295
+ ? this.direction
10296
+ : ((_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().facingMode) === 'environment'
10297
+ ? 'back'
10298
+ : 'front';
10299
+ this.setDirection(direction);
10300
+ }
10301
+ }
10302
+ getDeviceIdFromStream(stream) {
10303
+ var _a;
10304
+ return (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
10305
+ }
10306
+ }
10307
+
10064
10308
  class CameraManager extends InputMediaDeviceManager {
10065
10309
  constructor(call) {
10066
10310
  super(call, new CameraManagerState());
@@ -10196,14 +10440,14 @@ class Call {
10196
10440
  * method to construct a `Call` instance.
10197
10441
  */
10198
10442
  constructor({ type, id, streamClient, members, ownCapabilities, sortParticipantsBy, clientStore, ringing = false, watching = false, }) {
10199
- /**
10200
- * ViewportTracker instance
10201
- */
10202
- this.viewportTracker = new ViewportTracker();
10203
10443
  /**
10204
10444
  * The state of this call.
10205
10445
  */
10206
10446
  this.state = new CallState();
10447
+ /**
10448
+ * The DynascaleManager instance.
10449
+ */
10450
+ this.dynascaleManager = new DynascaleManager(this);
10207
10451
  /**
10208
10452
  * The permissions context of this call.
10209
10453
  */
@@ -10221,7 +10465,7 @@ class Call {
10221
10465
  * A typical use case is to clean up some global event handlers.
10222
10466
  * @private
10223
10467
  */
10224
- this.leaveCallHooks = [];
10468
+ this.leaveCallHooks = new Set();
10225
10469
  this.streamClientEventHandlers = new Map();
10226
10470
  /**
10227
10471
  * Leave the call and stop the media streams that were published by the call.
@@ -10527,7 +10771,7 @@ class Call {
10527
10771
  unsubscribeOfflineEvent();
10528
10772
  this.state.setCallingState(exports.CallingState.OFFLINE);
10529
10773
  });
10530
- this.leaveCallHooks.push(() => {
10774
+ this.leaveCallHooks.add(() => {
10531
10775
  unsubscribeOnlineEvent();
10532
10776
  unsubscribeOfflineEvent();
10533
10777
  });
@@ -10604,7 +10848,10 @@ class Call {
10604
10848
  return currentParticipants.map((p) => {
10605
10849
  const participant = Object.assign(p, {
10606
10850
  isLocalParticipant: p.sessionId === sfuClient.sessionId,
10607
- viewportVisibilityState: exports.VisibilityState.UNKNOWN,
10851
+ viewportVisibilityState: {
10852
+ videoTrack: exports.VisibilityState.UNKNOWN,
10853
+ screenShareTrack: exports.VisibilityState.UNKNOWN,
10854
+ },
10608
10855
  });
10609
10856
  // We need to preserve the local state of the participant
10610
10857
  // (e.g. videoDimension, visibilityState, pinnedAt, etc.)
@@ -10761,15 +11008,30 @@ class Call {
10761
11008
  * You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
10762
11009
  * You can only subscribe for tracks after the participant started publishing the given kind of track.
10763
11010
  *
10764
- * @param kind the kind of subscription to update.
11011
+ * @param trackType the kind of subscription to update.
10765
11012
  * @param changes the list of subscription changes to do.
10766
11013
  * @param type the debounce type to use for the update.
10767
11014
  */
10768
- this.updateSubscriptionsPartial = (kind, changes, type = exports.DebounceType.SLOW) => {
11015
+ this.updateSubscriptionsPartial = (trackType, changes, type = exports.DebounceType.SLOW) => {
11016
+ if (trackType === 'video') {
11017
+ this.logger('warn', `updateSubscriptionsPartial: ${trackType} is deprecated. Please switch to 'videoTrack'`);
11018
+ trackType = 'videoTrack';
11019
+ }
11020
+ else if (trackType === 'screen') {
11021
+ this.logger('warn', `updateSubscriptionsPartial: ${trackType} is deprecated. Please switch to 'screenShareTrack'`);
11022
+ trackType = 'screenShareTrack';
11023
+ }
10769
11024
  const participants = this.state.updateParticipants(Object.entries(changes).reduce((acc, [sessionId, change]) => {
10770
- const prop = kind === 'video'
11025
+ var _a, _b;
11026
+ if ((_a = change.dimension) === null || _a === void 0 ? void 0 : _a.height) {
11027
+ change.dimension.height = Math.ceil(change.dimension.height);
11028
+ }
11029
+ if ((_b = change.dimension) === null || _b === void 0 ? void 0 : _b.width) {
11030
+ change.dimension.width = Math.ceil(change.dimension.width);
11031
+ }
11032
+ const prop = trackType === 'videoTrack'
10771
11033
  ? 'videoDimension'
10772
- : kind === 'screen'
11034
+ : trackType === 'screenShareTrack'
10773
11035
  ? 'screenShareDimension'
10774
11036
  : undefined;
10775
11037
  if (prop) {
@@ -10785,10 +11047,10 @@ class Call {
10785
11047
  };
10786
11048
  this.updateSubscriptions = (participants, type = exports.DebounceType.SLOW) => {
10787
11049
  const subscriptions = [];
10788
- participants.forEach((p) => {
11050
+ for (const p of participants) {
10789
11051
  // we don't want to subscribe to our own tracks
10790
11052
  if (p.isLocalParticipant)
10791
- return;
11053
+ continue;
10792
11054
  // NOTE: audio tracks don't have to be requested explicitly
10793
11055
  // as the SFU will implicitly subscribe us to all of them,
10794
11056
  // once they become available.
@@ -10809,7 +11071,7 @@ class Call {
10809
11071
  dimension: p.screenShareDimension,
10810
11072
  });
10811
11073
  }
10812
- });
11074
+ }
10813
11075
  // schedule update
10814
11076
  this.trackSubscriptionsSubject.next({ type, data: subscriptions });
10815
11077
  };
@@ -11198,7 +11460,7 @@ class Call {
11198
11460
  this.dropTimeout = setTimeout(() => this.leave(), timeoutMs);
11199
11461
  }), rxjs.takeWhile(() => !!this.clientStore.calls.find((call) => call.cid === this.cid)))
11200
11462
  .subscribe();
11201
- this.leaveCallHooks.push(() => {
11463
+ this.leaveCallHooks.add(() => {
11202
11464
  !subscription.closed && subscription.unsubscribe();
11203
11465
  });
11204
11466
  };
@@ -11225,6 +11487,69 @@ class Call {
11225
11487
  this.sendCustomEvent = (payload) => __awaiter(this, void 0, void 0, function* () {
11226
11488
  return this.streamClient.post(`${this.streamClientBasePath}/event`, { custom: payload });
11227
11489
  });
11490
+ /**
11491
+ * Will begin tracking the given element for visibility changes within the
11492
+ * configured viewport element (`call.setViewport`).
11493
+ *
11494
+ * @param element the element to track.
11495
+ * @param sessionId the session id.
11496
+ * @param trackType the video mode.
11497
+ */
11498
+ this.trackElementVisibility = (element, sessionId, trackType) => {
11499
+ return this.dynascaleManager.trackElementVisibility(element, sessionId, trackType);
11500
+ };
11501
+ /**
11502
+ * Sets the viewport element to track bound video elements for visibility.
11503
+ *
11504
+ * @param element the viewport element.
11505
+ */
11506
+ this.setViewport = (element) => {
11507
+ return this.dynascaleManager.setViewport(element);
11508
+ };
11509
+ /**
11510
+ * Binds a DOM <video> element to the given session id.
11511
+ * This method will make sure that the video element will play
11512
+ * the correct video stream for the given session id.
11513
+ *
11514
+ * Under the hood, it would also keep track of the video element dimensions
11515
+ * and update the subscription accordingly in order to optimize the bandwidth.
11516
+ *
11517
+ * If a "viewport" is configured, the video element will be automatically
11518
+ * tracked for visibility and the subscription will be updated accordingly.
11519
+ *
11520
+ * @param videoElement the video element to bind to.
11521
+ * @param sessionId the session id.
11522
+ * @param trackType the kind of video.
11523
+ */
11524
+ this.bindVideoElement = (videoElement, sessionId, trackType) => {
11525
+ const unbind = this.dynascaleManager.bindVideoElement(videoElement, sessionId, trackType);
11526
+ if (!unbind)
11527
+ return;
11528
+ this.leaveCallHooks.add(unbind);
11529
+ return () => {
11530
+ this.leaveCallHooks.delete(unbind);
11531
+ unbind();
11532
+ };
11533
+ };
11534
+ /**
11535
+ * Binds a DOM <audio> element to the given session id.
11536
+ *
11537
+ * This method will make sure that the audio element will
11538
+ * play the correct audio stream for the given session id.
11539
+ *
11540
+ * @param audioElement the audio element to bind to.
11541
+ * @param sessionId the session id.
11542
+ */
11543
+ this.bindAudioElement = (audioElement, sessionId) => {
11544
+ const unbind = this.dynascaleManager.bindAudioElement(audioElement, sessionId);
11545
+ if (!unbind)
11546
+ return;
11547
+ this.leaveCallHooks.add(unbind);
11548
+ return () => {
11549
+ this.leaveCallHooks.delete(unbind);
11550
+ unbind();
11551
+ };
11552
+ };
11228
11553
  this.type = type;
11229
11554
  this.id = id;
11230
11555
  this.cid = `${type}:${id}`;
@@ -11246,20 +11571,21 @@ class Call {
11246
11571
  // update state with the latest event data
11247
11572
  this.state.updateFromEvent(event);
11248
11573
  });
11249
- this.leaveCallHooks.push(registerEventHandlers(this, this.state, this.dispatcher));
11574
+ this.leaveCallHooks.add(registerEventHandlers(this, this.state, this.dispatcher));
11250
11575
  this.registerEffects();
11251
- this.leaveCallHooks.push(createSubscription(this.trackSubscriptionsSubject.pipe(rxjs.debounce((v) => rxjs.timer(v.type)), rxjs.map((v) => v.data)), (subscriptions) => { var _a; return (_a = this.sfuClient) === null || _a === void 0 ? void 0 : _a.updateSubscriptions(subscriptions); }));
11576
+ this.leaveCallHooks.add(createSubscription(this.trackSubscriptionsSubject.pipe(rxjs.debounce((v) => rxjs.timer(v.type)), rxjs.map((v) => v.data)), (subscriptions) => { var _a; return (_a = this.sfuClient) === null || _a === void 0 ? void 0 : _a.updateSubscriptions(subscriptions); }));
11252
11577
  this.camera = new CameraManager(this);
11253
11578
  this.microphone = new MicrophoneManager(this);
11254
11579
  }
11255
11580
  registerEffects() {
11256
- this.leaveCallHooks.push(
11581
+ this.leaveCallHooks.add(
11257
11582
  // handles updating the permissions context when the settings change.
11258
11583
  createSubscription(this.state.settings$, (settings) => {
11259
11584
  if (!settings)
11260
11585
  return;
11261
11586
  this.permissionsContext.setCallSettings(settings);
11262
- }),
11587
+ }));
11588
+ this.leaveCallHooks.add(
11263
11589
  // handle the case when the user permissions are modified.
11264
11590
  createSubscription(this.state.ownCapabilities$, (ownCapabilities) => {
11265
11591
  // update the permission context.
@@ -11280,7 +11606,8 @@ class Call {
11280
11606
  });
11281
11607
  }
11282
11608
  }
11283
- }),
11609
+ }));
11610
+ this.leaveCallHooks.add(
11284
11611
  // handles the case when the user is blocked by the call owner.
11285
11612
  createSubscription(this.state.blockedUserIds$, (blockedUserIds) => __awaiter(this, void 0, void 0, function* () {
11286
11613
  if (!blockedUserIds)
@@ -11290,7 +11617,8 @@ class Call {
11290
11617
  this.logger('info', 'Leaving call because of being blocked');
11291
11618
  yield this.leave();
11292
11619
  }
11293
- })),
11620
+ })));
11621
+ this.leaveCallHooks.add(
11294
11622
  // watch for auto drop cancellation
11295
11623
  createSubscription(this.state.callingState$, (callingState) => {
11296
11624
  if (!this.ringing)
@@ -11301,7 +11629,8 @@ class Call {
11301
11629
  clearTimeout(this.dropTimeout);
11302
11630
  this.dropTimeout = undefined;
11303
11631
  }
11304
- }),
11632
+ }));
11633
+ this.leaveCallHooks.add(
11305
11634
  // "ringing" mode effects and event handlers
11306
11635
  createSubscription(this.ringingSubject, (isRinging) => {
11307
11636
  if (!isRinging)
@@ -11310,7 +11639,7 @@ class Call {
11310
11639
  if (this.state.callingState === exports.CallingState.IDLE) {
11311
11640
  this.state.setCallingState(exports.CallingState.RINGING);
11312
11641
  }
11313
- this.leaveCallHooks.push(registerRingingCallEventHandlers(this));
11642
+ this.leaveCallHooks.add(registerRingingCallEventHandlers(this));
11314
11643
  }));
11315
11644
  }
11316
11645
  on(eventName, fn) {
@@ -12537,7 +12866,7 @@ class WSConnectionFallback {
12537
12866
  }
12538
12867
  }
12539
12868
 
12540
- const version = '0.3.8';
12869
+ const version = '0.3.10';
12541
12870
 
12542
12871
  const logger = getLogger(['location']);
12543
12872
  const HINT_URL = `https://hint.stream-io-video.com/`;
@@ -13611,6 +13940,7 @@ exports.CallTypes = CallTypes;
13611
13940
  exports.CameraManager = CameraManager;
13612
13941
  exports.CameraManagerState = CameraManagerState;
13613
13942
  exports.CreateDeviceRequestPushProviderEnum = CreateDeviceRequestPushProviderEnum;
13943
+ exports.DynascaleManager = DynascaleManager;
13614
13944
  exports.ErrorFromResponse = ErrorFromResponse;
13615
13945
  exports.InputMediaDeviceManager = InputMediaDeviceManager;
13616
13946
  exports.InputMediaDeviceManagerState = InputMediaDeviceManagerState;