@stream-io/video-client 0.3.9 → 0.3.11

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.
@@ -4,7 +4,7 @@ import { ServiceType, stackIntercept } from '@protobuf-ts/runtime-rpc';
4
4
  import axios, { AxiosHeaders } from 'axios';
5
5
  export { AxiosError } from 'axios';
6
6
  import { TwirpFetchTransport } from '@protobuf-ts/twirp-transport';
7
- import { ReplaySubject, BehaviorSubject, distinctUntilChanged as distinctUntilChanged$1, Observable, debounceTime, concatMap, from, shareReplay, merge, map as map$2, combineLatest, filter, pairwise, takeWhile, tap, debounce, timer } from 'rxjs';
7
+ import { ReplaySubject, BehaviorSubject, map as map$2, takeWhile, distinctUntilChanged as distinctUntilChanged$1, distinctUntilKeyChanged, Observable, debounceTime, concatMap, from, shareReplay, merge, combineLatest, filter, pairwise, tap, debounce, timer } from 'rxjs';
8
8
  import * as SDP from 'sdp-transform';
9
9
  import { UAParser } from 'ua-parser-js';
10
10
  import WebSocket from 'isomorphic-ws';
@@ -5711,7 +5711,7 @@ var VisibilityState;
5711
5711
  })(VisibilityState || (VisibilityState = {}));
5712
5712
  var DebounceType;
5713
5713
  (function (DebounceType) {
5714
- DebounceType[DebounceType["IMMEDIATE"] = 0] = "IMMEDIATE";
5714
+ DebounceType[DebounceType["IMMEDIATE"] = 20] = "IMMEDIATE";
5715
5715
  DebounceType[DebounceType["FAST"] = 100] = "FAST";
5716
5716
  DebounceType[DebounceType["MEDIUM"] = 600] = "MEDIUM";
5717
5717
  DebounceType[DebounceType["SLOW"] = 1200] = "SLOW";
@@ -6100,10 +6100,10 @@ const getPreferredCodecs = (kind, preferredCodec, codecToRemove) => {
6100
6100
  cap.codecs.forEach((c) => {
6101
6101
  const codec = c.mimeType.toLowerCase();
6102
6102
  logger === null || logger === void 0 ? void 0 : logger('debug', `Found supported codec: ${codec}`);
6103
- const shouldRemoveCodec = codecToRemove && codec === `${kind}/${codecToRemove}`;
6103
+ const shouldRemoveCodec = codecToRemove && codec === `${kind}/${codecToRemove.toLowerCase()}`;
6104
6104
  if (shouldRemoveCodec)
6105
6105
  return;
6106
- const matchesCodec = codec === `${kind}/${preferredCodec}`;
6106
+ const matchesCodec = codec === `${kind}/${preferredCodec.toLowerCase()}`;
6107
6107
  if (!matchesCodec) {
6108
6108
  unmatched.push(c);
6109
6109
  return;
@@ -7586,10 +7586,20 @@ const retryable = (rpc, logger) => __awaiter(void 0, void 0, void 0, function* (
7586
7586
  */
7587
7587
  const getCurrentValue = (observable$) => {
7588
7588
  let value;
7589
+ let err = undefined;
7589
7590
  observable$
7590
7591
  .pipe(take(1))
7591
- .subscribe((v) => (value = v))
7592
+ .subscribe({
7593
+ next: (v) => {
7594
+ value = v;
7595
+ },
7596
+ error: (e) => {
7597
+ err = e;
7598
+ },
7599
+ })
7592
7600
  .unsubscribe();
7601
+ if (err)
7602
+ throw err;
7593
7603
  return value;
7594
7604
  };
7595
7605
  /**
@@ -7948,8 +7958,11 @@ const hasAudio = (p) => p.publishedTracks.includes(TrackType.AUDIO);
7948
7958
  // a comparator decorator which applies the decorated comparator only if the
7949
7959
  // participant is invisible.
7950
7960
  // This ensures stable sorting when all participants are visible.
7951
- const ifInvisibleBy = conditional((a, b) => a.viewportVisibilityState === VisibilityState.INVISIBLE ||
7952
- b.viewportVisibilityState === VisibilityState.INVISIBLE);
7961
+ const ifInvisibleBy = conditional((a, b) => {
7962
+ var _a, _b;
7963
+ return ((_a = a.viewportVisibilityState) === null || _a === void 0 ? void 0 : _a.videoTrack) === VisibilityState.INVISIBLE ||
7964
+ ((_b = b.viewportVisibilityState) === null || _b === void 0 ? void 0 : _b.videoTrack) === VisibilityState.INVISIBLE;
7965
+ });
7953
7966
  /**
7954
7967
  * The default sorting preset.
7955
7968
  */
@@ -8826,7 +8839,10 @@ const watchParticipantJoined = (state) => {
8826
8839
  // response and then follow up with a `participantJoined` event for
8827
8840
  // already announced participants.
8828
8841
  state.updateOrAddParticipant(participant.sessionId, Object.assign(participant, {
8829
- viewportVisibilityState: VisibilityState.UNKNOWN,
8842
+ viewportVisibilityState: {
8843
+ videoTrack: VisibilityState.UNKNOWN,
8844
+ screenShareTrack: VisibilityState.UNKNOWN,
8845
+ },
8830
8846
  }));
8831
8847
  };
8832
8848
  };
@@ -9383,6 +9399,234 @@ class ViewportTracker {
9383
9399
  }
9384
9400
  }
9385
9401
 
9402
+ const DEFAULT_VIEWPORT_VISIBILITY_STATE = {
9403
+ videoTrack: VisibilityState.UNKNOWN,
9404
+ screenShareTrack: VisibilityState.UNKNOWN,
9405
+ };
9406
+ /**
9407
+ * A manager class that handles dynascale related tasks like:
9408
+ *
9409
+ * - binding video elements to session ids
9410
+ * - binding audio elements to session ids
9411
+ * - tracking element visibility
9412
+ * - updating subscriptions based on viewport visibility
9413
+ * - updating subscriptions based on video element dimensions
9414
+ * - updating subscriptions based on published tracks
9415
+ */
9416
+ class DynascaleManager {
9417
+ /**
9418
+ * Creates a new DynascaleManager instance.
9419
+ *
9420
+ * @param call the call to manage.
9421
+ */
9422
+ constructor(call) {
9423
+ /**
9424
+ * The viewport tracker instance.
9425
+ */
9426
+ this.viewportTracker = new ViewportTracker();
9427
+ this.logger = getLogger(['DynascaleManager']);
9428
+ /**
9429
+ * Will begin tracking the given element for visibility changes within the
9430
+ * configured viewport element (`call.setViewport`).
9431
+ *
9432
+ * @param element the element to track.
9433
+ * @param sessionId the session id.
9434
+ * @param trackType the kind of video.
9435
+ * @returns Untrack.
9436
+ */
9437
+ this.trackElementVisibility = (element, sessionId, trackType) => {
9438
+ const cleanup = this.viewportTracker.observe(element, (entry) => {
9439
+ this.call.state.updateParticipant(sessionId, (participant) => {
9440
+ var _a;
9441
+ const previousVisibilityState = (_a = participant.viewportVisibilityState) !== null && _a !== void 0 ? _a : DEFAULT_VIEWPORT_VISIBILITY_STATE;
9442
+ // observer triggers when the element is "moved" to be a fullscreen element
9443
+ // keep it VISIBLE if that happens to prevent fullscreen with placeholder
9444
+ const isVisible = entry.isIntersecting || document.fullscreenElement === element
9445
+ ? VisibilityState.VISIBLE
9446
+ : VisibilityState.INVISIBLE;
9447
+ return Object.assign(Object.assign({}, participant), { viewportVisibilityState: Object.assign(Object.assign({}, previousVisibilityState), { [trackType]: isVisible }) });
9448
+ });
9449
+ });
9450
+ return () => {
9451
+ cleanup();
9452
+ // reset visibility state to UNKNOWN upon cleanup
9453
+ // so that the layouts that are not actively observed
9454
+ // can still function normally (runtime layout switching)
9455
+ this.call.state.updateParticipant(sessionId, (participant) => {
9456
+ var _a;
9457
+ const previousVisibilityState = (_a = participant.viewportVisibilityState) !== null && _a !== void 0 ? _a : DEFAULT_VIEWPORT_VISIBILITY_STATE;
9458
+ return Object.assign(Object.assign({}, participant), { viewportVisibilityState: Object.assign(Object.assign({}, previousVisibilityState), { [trackType]: VisibilityState.UNKNOWN }) });
9459
+ });
9460
+ };
9461
+ };
9462
+ /**
9463
+ * Sets the viewport element to track bound video elements for visibility.
9464
+ *
9465
+ * @param element the viewport element.
9466
+ */
9467
+ this.setViewport = (element) => {
9468
+ return this.viewportTracker.setViewport(element);
9469
+ };
9470
+ /**
9471
+ * Binds a DOM <video> element to the given session id.
9472
+ * This method will make sure that the video element will play
9473
+ * the correct video stream for the given session id.
9474
+ *
9475
+ * Under the hood, it would also keep track of the video element dimensions
9476
+ * and update the subscription accordingly in order to optimize the bandwidth.
9477
+ *
9478
+ * If a "viewport" is configured, the video element will be automatically
9479
+ * tracked for visibility and the subscription will be updated accordingly.
9480
+ *
9481
+ * @param videoElement the video element to bind to.
9482
+ * @param sessionId the session id.
9483
+ * @param trackType the kind of video.
9484
+ */
9485
+ this.bindVideoElement = (videoElement, sessionId, trackType) => {
9486
+ const boundParticipant = this.call.state.findParticipantBySessionId(sessionId);
9487
+ if (!boundParticipant)
9488
+ return;
9489
+ const requestTrackWithDimensions = (debounceType, dimension) => {
9490
+ this.call.updateSubscriptionsPartial(trackType, { [sessionId]: { dimension } }, debounceType);
9491
+ };
9492
+ const participant$ = this.call.state.participants$.pipe(map$2((participants) => participants.find((participant) => participant.sessionId === sessionId)), takeWhile((participant) => !!participant), distinctUntilChanged$1());
9493
+ // keep copy for resize observer handler
9494
+ let viewportVisibilityState;
9495
+ const viewportVisibilityStateSubscription = boundParticipant.isLocalParticipant
9496
+ ? null
9497
+ : participant$
9498
+ .pipe(map$2((p) => { var _a; return (_a = p.viewportVisibilityState) === null || _a === void 0 ? void 0 : _a[trackType]; }), distinctUntilChanged$1())
9499
+ .subscribe((nextViewportVisibilityState) => {
9500
+ // skip initial trigger
9501
+ if (!viewportVisibilityState) {
9502
+ viewportVisibilityState =
9503
+ nextViewportVisibilityState !== null && nextViewportVisibilityState !== void 0 ? nextViewportVisibilityState : VisibilityState.UNKNOWN;
9504
+ return;
9505
+ }
9506
+ viewportVisibilityState =
9507
+ nextViewportVisibilityState !== null && nextViewportVisibilityState !== void 0 ? nextViewportVisibilityState : VisibilityState.UNKNOWN;
9508
+ if (nextViewportVisibilityState === VisibilityState.INVISIBLE) {
9509
+ return requestTrackWithDimensions(DebounceType.MEDIUM, undefined);
9510
+ }
9511
+ requestTrackWithDimensions(DebounceType.MEDIUM, {
9512
+ width: videoElement.clientWidth,
9513
+ height: videoElement.clientHeight,
9514
+ });
9515
+ });
9516
+ let lastDimensions;
9517
+ const resizeObserver = boundParticipant.isLocalParticipant
9518
+ ? null
9519
+ : new ResizeObserver(() => {
9520
+ const currentDimensions = `${videoElement.clientWidth},${videoElement.clientHeight}`;
9521
+ // skip initial trigger
9522
+ if (!lastDimensions) {
9523
+ lastDimensions = currentDimensions;
9524
+ return;
9525
+ }
9526
+ if (lastDimensions === currentDimensions ||
9527
+ viewportVisibilityState === VisibilityState.INVISIBLE) {
9528
+ return;
9529
+ }
9530
+ requestTrackWithDimensions(DebounceType.SLOW, {
9531
+ width: videoElement.clientWidth,
9532
+ height: videoElement.clientHeight,
9533
+ });
9534
+ lastDimensions = currentDimensions;
9535
+ });
9536
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(videoElement);
9537
+ const publishedTracksSubscription = boundParticipant.isLocalParticipant
9538
+ ? null
9539
+ : participant$
9540
+ .pipe(distinctUntilKeyChanged('publishedTracks'), map$2((p) => p.publishedTracks.includes(trackType === 'videoTrack'
9541
+ ? TrackType.VIDEO
9542
+ : TrackType.SCREEN_SHARE)), distinctUntilChanged$1())
9543
+ .subscribe((isPublishing) => {
9544
+ if (isPublishing) {
9545
+ // the participant just started to publish a track
9546
+ requestTrackWithDimensions(DebounceType.IMMEDIATE, {
9547
+ width: videoElement.clientWidth,
9548
+ height: videoElement.clientHeight,
9549
+ });
9550
+ }
9551
+ else {
9552
+ // the participant just stopped publishing a track
9553
+ requestTrackWithDimensions(DebounceType.IMMEDIATE, undefined);
9554
+ }
9555
+ });
9556
+ const streamSubscription = participant$
9557
+ .pipe(distinctUntilKeyChanged(trackType === 'videoTrack' ? 'videoStream' : 'screenShareStream'))
9558
+ .subscribe((p) => {
9559
+ const source = trackType === 'videoTrack' ? p.videoStream : p.screenShareStream;
9560
+ if (videoElement.srcObject === source)
9561
+ return;
9562
+ setTimeout(() => {
9563
+ videoElement.srcObject = source !== null && source !== void 0 ? source : null;
9564
+ if (videoElement.srcObject) {
9565
+ videoElement.play().catch((e) => {
9566
+ this.logger('warn', `Failed to play stream`, e);
9567
+ });
9568
+ }
9569
+ }, 0);
9570
+ });
9571
+ videoElement.playsInline = true;
9572
+ videoElement.autoplay = true;
9573
+ // explicitly marking the element as muted will allow autoplay to work
9574
+ // without prior user interaction:
9575
+ // https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide
9576
+ videoElement.muted = true;
9577
+ return () => {
9578
+ viewportVisibilityStateSubscription === null || viewportVisibilityStateSubscription === void 0 ? void 0 : viewportVisibilityStateSubscription.unsubscribe();
9579
+ publishedTracksSubscription === null || publishedTracksSubscription === void 0 ? void 0 : publishedTracksSubscription.unsubscribe();
9580
+ streamSubscription.unsubscribe();
9581
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
9582
+ };
9583
+ };
9584
+ /**
9585
+ * Binds a DOM <audio> element to the given session id.
9586
+ *
9587
+ * This method will make sure that the audio element will
9588
+ * play the correct audio stream for the given session id.
9589
+ *
9590
+ * @param audioElement the audio element to bind to.
9591
+ * @param sessionId the session id.
9592
+ * @returns a cleanup function that will unbind the audio element.
9593
+ */
9594
+ this.bindAudioElement = (audioElement, sessionId) => {
9595
+ const participant = this.call.state.findParticipantBySessionId(sessionId);
9596
+ if (!participant || participant.isLocalParticipant)
9597
+ return;
9598
+ const participant$ = this.call.state.participants$.pipe(map$2((participants) => participants.find((p) => p.sessionId === sessionId)), takeWhile((p) => !!p), distinctUntilChanged$1());
9599
+ const updateMediaStreamSubscription = participant$
9600
+ .pipe(distinctUntilKeyChanged('audioStream'))
9601
+ .subscribe((p) => {
9602
+ const source = p.audioStream;
9603
+ if (audioElement.srcObject === source)
9604
+ return;
9605
+ setTimeout(() => {
9606
+ audioElement.srcObject = source !== null && source !== void 0 ? source : null;
9607
+ if (audioElement.srcObject) {
9608
+ audioElement.play().catch((e) => {
9609
+ this.logger('warn', `Failed to play stream`, e);
9610
+ });
9611
+ }
9612
+ });
9613
+ });
9614
+ const sinkIdSubscription = this.call.state.localParticipant$.subscribe((p) => {
9615
+ if (p && p.audioOutputDeviceId && 'setSinkId' in audioElement) {
9616
+ // @ts-expect-error setSinkId is not yet in the lib
9617
+ audioElement.setSinkId(p.audioOutputDeviceId);
9618
+ }
9619
+ });
9620
+ audioElement.autoplay = true;
9621
+ return () => {
9622
+ sinkIdSubscription.unsubscribe();
9623
+ updateMediaStreamSubscription.unsubscribe();
9624
+ };
9625
+ };
9626
+ this.call = call;
9627
+ }
9628
+ }
9629
+
9386
9630
  /**
9387
9631
  * Stores the permissions for the current user and exposes
9388
9632
  * a few helper methods which make it easier to work with permissions.
@@ -9522,127 +9766,6 @@ const CallTypes = new CallTypesRegistry([
9522
9766
  }),
9523
9767
  ]);
9524
9768
 
9525
- class InputMediaDeviceManagerState {
9526
- constructor(disableMode = 'stop-tracks') {
9527
- this.disableMode = disableMode;
9528
- this.statusSubject = new BehaviorSubject(undefined);
9529
- this.mediaStreamSubject = new BehaviorSubject(undefined);
9530
- this.selectedDeviceSubject = new BehaviorSubject(undefined);
9531
- /**
9532
- * Gets the current value of an observable, or undefined if the observable has
9533
- * not emitted a value yet.
9534
- *
9535
- * @param observable$ the observable to get the value from.
9536
- */
9537
- this.getCurrentValue = getCurrentValue;
9538
- /**
9539
- * Updates the value of the provided Subject.
9540
- * An `update` can either be a new value or a function which takes
9541
- * the current value and returns a new value.
9542
- *
9543
- * @internal
9544
- *
9545
- * @param subject the subject to update.
9546
- * @param update the update to apply to the subject.
9547
- * @return the updated value.
9548
- */
9549
- this.setCurrentValue = setCurrentValue;
9550
- this.mediaStream$ = this.mediaStreamSubject.asObservable();
9551
- this.selectedDevice$ = this.selectedDeviceSubject
9552
- .asObservable()
9553
- .pipe(distinctUntilChanged$1());
9554
- this.status$ = this.statusSubject
9555
- .asObservable()
9556
- .pipe(distinctUntilChanged$1());
9557
- }
9558
- /**
9559
- * The device status
9560
- */
9561
- get status() {
9562
- return this.getCurrentValue(this.status$);
9563
- }
9564
- /**
9565
- * The currently selected device
9566
- */
9567
- get selectedDevice() {
9568
- return this.getCurrentValue(this.selectedDevice$);
9569
- }
9570
- /**
9571
- * The current media stream, or `undefined` if the device is currently disabled.
9572
- */
9573
- get mediaStream() {
9574
- return this.getCurrentValue(this.mediaStream$);
9575
- }
9576
- /**
9577
- * @internal
9578
- * @param status
9579
- */
9580
- setStatus(status) {
9581
- this.setCurrentValue(this.statusSubject, status);
9582
- }
9583
- /**
9584
- * @internal
9585
- * @param stream
9586
- */
9587
- setMediaStream(stream) {
9588
- this.setCurrentValue(this.mediaStreamSubject, stream);
9589
- if (stream) {
9590
- this.setDevice(this.getDeviceIdFromStream(stream));
9591
- }
9592
- }
9593
- /**
9594
- * @internal
9595
- * @param stream
9596
- */
9597
- setDevice(deviceId) {
9598
- this.setCurrentValue(this.selectedDeviceSubject, deviceId);
9599
- }
9600
- }
9601
-
9602
- class CameraManagerState extends InputMediaDeviceManagerState {
9603
- constructor() {
9604
- super('stop-tracks');
9605
- this.directionSubject = new BehaviorSubject(undefined);
9606
- this.direction$ = this.directionSubject
9607
- .asObservable()
9608
- .pipe(distinctUntilChanged$1());
9609
- }
9610
- /**
9611
- * The preferred camera direction
9612
- * front - means the camera facing the user
9613
- * back - means the camera facing the environment
9614
- */
9615
- get direction() {
9616
- return this.getCurrentValue(this.direction$);
9617
- }
9618
- /**
9619
- * @internal
9620
- */
9621
- setDirection(direction) {
9622
- this.setCurrentValue(this.directionSubject, direction);
9623
- }
9624
- /**
9625
- * @internal
9626
- */
9627
- setMediaStream(stream) {
9628
- var _a;
9629
- super.setMediaStream(stream);
9630
- if (stream) {
9631
- // RN getSettings() doesn't return facingMode, so we don't verify camera direction
9632
- const direction = isReactNative()
9633
- ? this.direction
9634
- : ((_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().facingMode) === 'environment'
9635
- ? 'back'
9636
- : 'front';
9637
- this.setDirection(direction);
9638
- }
9639
- }
9640
- getDeviceIdFromStream(stream) {
9641
- var _a;
9642
- return (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
9643
- }
9644
- }
9645
-
9646
9769
  const getDevices = (constraints) => {
9647
9770
  return new Observable((subscriber) => {
9648
9771
  navigator.mediaDevices
@@ -10038,6 +10161,127 @@ class InputMediaDeviceManager {
10038
10161
  }
10039
10162
  }
10040
10163
 
10164
+ class InputMediaDeviceManagerState {
10165
+ constructor(disableMode = 'stop-tracks') {
10166
+ this.disableMode = disableMode;
10167
+ this.statusSubject = new BehaviorSubject(undefined);
10168
+ this.mediaStreamSubject = new BehaviorSubject(undefined);
10169
+ this.selectedDeviceSubject = new BehaviorSubject(undefined);
10170
+ /**
10171
+ * Gets the current value of an observable, or undefined if the observable has
10172
+ * not emitted a value yet.
10173
+ *
10174
+ * @param observable$ the observable to get the value from.
10175
+ */
10176
+ this.getCurrentValue = getCurrentValue;
10177
+ /**
10178
+ * Updates the value of the provided Subject.
10179
+ * An `update` can either be a new value or a function which takes
10180
+ * the current value and returns a new value.
10181
+ *
10182
+ * @internal
10183
+ *
10184
+ * @param subject the subject to update.
10185
+ * @param update the update to apply to the subject.
10186
+ * @return the updated value.
10187
+ */
10188
+ this.setCurrentValue = setCurrentValue;
10189
+ this.mediaStream$ = this.mediaStreamSubject.asObservable();
10190
+ this.selectedDevice$ = this.selectedDeviceSubject
10191
+ .asObservable()
10192
+ .pipe(distinctUntilChanged$1());
10193
+ this.status$ = this.statusSubject
10194
+ .asObservable()
10195
+ .pipe(distinctUntilChanged$1());
10196
+ }
10197
+ /**
10198
+ * The device status
10199
+ */
10200
+ get status() {
10201
+ return this.getCurrentValue(this.status$);
10202
+ }
10203
+ /**
10204
+ * The currently selected device
10205
+ */
10206
+ get selectedDevice() {
10207
+ return this.getCurrentValue(this.selectedDevice$);
10208
+ }
10209
+ /**
10210
+ * The current media stream, or `undefined` if the device is currently disabled.
10211
+ */
10212
+ get mediaStream() {
10213
+ return this.getCurrentValue(this.mediaStream$);
10214
+ }
10215
+ /**
10216
+ * @internal
10217
+ * @param status
10218
+ */
10219
+ setStatus(status) {
10220
+ this.setCurrentValue(this.statusSubject, status);
10221
+ }
10222
+ /**
10223
+ * @internal
10224
+ * @param stream
10225
+ */
10226
+ setMediaStream(stream) {
10227
+ this.setCurrentValue(this.mediaStreamSubject, stream);
10228
+ if (stream) {
10229
+ this.setDevice(this.getDeviceIdFromStream(stream));
10230
+ }
10231
+ }
10232
+ /**
10233
+ * @internal
10234
+ * @param stream
10235
+ */
10236
+ setDevice(deviceId) {
10237
+ this.setCurrentValue(this.selectedDeviceSubject, deviceId);
10238
+ }
10239
+ }
10240
+
10241
+ class CameraManagerState extends InputMediaDeviceManagerState {
10242
+ constructor() {
10243
+ super('stop-tracks');
10244
+ this.directionSubject = new BehaviorSubject(undefined);
10245
+ this.direction$ = this.directionSubject
10246
+ .asObservable()
10247
+ .pipe(distinctUntilChanged$1());
10248
+ }
10249
+ /**
10250
+ * The preferred camera direction
10251
+ * front - means the camera facing the user
10252
+ * back - means the camera facing the environment
10253
+ */
10254
+ get direction() {
10255
+ return this.getCurrentValue(this.direction$);
10256
+ }
10257
+ /**
10258
+ * @internal
10259
+ */
10260
+ setDirection(direction) {
10261
+ this.setCurrentValue(this.directionSubject, direction);
10262
+ }
10263
+ /**
10264
+ * @internal
10265
+ */
10266
+ setMediaStream(stream) {
10267
+ var _a;
10268
+ super.setMediaStream(stream);
10269
+ if (stream) {
10270
+ // RN getSettings() doesn't return facingMode, so we don't verify camera direction
10271
+ const direction = isReactNative()
10272
+ ? this.direction
10273
+ : ((_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().facingMode) === 'environment'
10274
+ ? 'back'
10275
+ : 'front';
10276
+ this.setDirection(direction);
10277
+ }
10278
+ }
10279
+ getDeviceIdFromStream(stream) {
10280
+ var _a;
10281
+ return (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
10282
+ }
10283
+ }
10284
+
10041
10285
  class CameraManager extends InputMediaDeviceManager {
10042
10286
  constructor(call) {
10043
10287
  super(call, new CameraManagerState());
@@ -10173,14 +10417,14 @@ class Call {
10173
10417
  * method to construct a `Call` instance.
10174
10418
  */
10175
10419
  constructor({ type, id, streamClient, members, ownCapabilities, sortParticipantsBy, clientStore, ringing = false, watching = false, }) {
10176
- /**
10177
- * ViewportTracker instance
10178
- */
10179
- this.viewportTracker = new ViewportTracker();
10180
10420
  /**
10181
10421
  * The state of this call.
10182
10422
  */
10183
10423
  this.state = new CallState();
10424
+ /**
10425
+ * The DynascaleManager instance.
10426
+ */
10427
+ this.dynascaleManager = new DynascaleManager(this);
10184
10428
  /**
10185
10429
  * The permissions context of this call.
10186
10430
  */
@@ -10198,7 +10442,7 @@ class Call {
10198
10442
  * A typical use case is to clean up some global event handlers.
10199
10443
  * @private
10200
10444
  */
10201
- this.leaveCallHooks = [];
10445
+ this.leaveCallHooks = new Set();
10202
10446
  this.streamClientEventHandlers = new Map();
10203
10447
  /**
10204
10448
  * Leave the call and stop the media streams that were published by the call.
@@ -10504,7 +10748,7 @@ class Call {
10504
10748
  unsubscribeOfflineEvent();
10505
10749
  this.state.setCallingState(CallingState.OFFLINE);
10506
10750
  });
10507
- this.leaveCallHooks.push(() => {
10751
+ this.leaveCallHooks.add(() => {
10508
10752
  unsubscribeOnlineEvent();
10509
10753
  unsubscribeOfflineEvent();
10510
10754
  });
@@ -10581,7 +10825,10 @@ class Call {
10581
10825
  return currentParticipants.map((p) => {
10582
10826
  const participant = Object.assign(p, {
10583
10827
  isLocalParticipant: p.sessionId === sfuClient.sessionId,
10584
- viewportVisibilityState: VisibilityState.UNKNOWN,
10828
+ viewportVisibilityState: {
10829
+ videoTrack: VisibilityState.UNKNOWN,
10830
+ screenShareTrack: VisibilityState.UNKNOWN,
10831
+ },
10585
10832
  });
10586
10833
  // We need to preserve the local state of the participant
10587
10834
  // (e.g. videoDimension, visibilityState, pinnedAt, etc.)
@@ -10738,11 +10985,19 @@ class Call {
10738
10985
  * You have to create a subscription for each participant for all the different kinds of tracks you want to receive.
10739
10986
  * You can only subscribe for tracks after the participant started publishing the given kind of track.
10740
10987
  *
10741
- * @param kind the kind of subscription to update.
10988
+ * @param trackType the kind of subscription to update.
10742
10989
  * @param changes the list of subscription changes to do.
10743
10990
  * @param type the debounce type to use for the update.
10744
10991
  */
10745
- this.updateSubscriptionsPartial = (kind, changes, type = DebounceType.SLOW) => {
10992
+ this.updateSubscriptionsPartial = (trackType, changes, type = DebounceType.SLOW) => {
10993
+ if (trackType === 'video') {
10994
+ this.logger('warn', `updateSubscriptionsPartial: ${trackType} is deprecated. Please switch to 'videoTrack'`);
10995
+ trackType = 'videoTrack';
10996
+ }
10997
+ else if (trackType === 'screen') {
10998
+ this.logger('warn', `updateSubscriptionsPartial: ${trackType} is deprecated. Please switch to 'screenShareTrack'`);
10999
+ trackType = 'screenShareTrack';
11000
+ }
10746
11001
  const participants = this.state.updateParticipants(Object.entries(changes).reduce((acc, [sessionId, change]) => {
10747
11002
  var _a, _b;
10748
11003
  if ((_a = change.dimension) === null || _a === void 0 ? void 0 : _a.height) {
@@ -10751,9 +11006,9 @@ class Call {
10751
11006
  if ((_b = change.dimension) === null || _b === void 0 ? void 0 : _b.width) {
10752
11007
  change.dimension.width = Math.ceil(change.dimension.width);
10753
11008
  }
10754
- const prop = kind === 'video'
11009
+ const prop = trackType === 'videoTrack'
10755
11010
  ? 'videoDimension'
10756
- : kind === 'screen'
11011
+ : trackType === 'screenShareTrack'
10757
11012
  ? 'screenShareDimension'
10758
11013
  : undefined;
10759
11014
  if (prop) {
@@ -10769,10 +11024,10 @@ class Call {
10769
11024
  };
10770
11025
  this.updateSubscriptions = (participants, type = DebounceType.SLOW) => {
10771
11026
  const subscriptions = [];
10772
- participants.forEach((p) => {
11027
+ for (const p of participants) {
10773
11028
  // we don't want to subscribe to our own tracks
10774
11029
  if (p.isLocalParticipant)
10775
- return;
11030
+ continue;
10776
11031
  // NOTE: audio tracks don't have to be requested explicitly
10777
11032
  // as the SFU will implicitly subscribe us to all of them,
10778
11033
  // once they become available.
@@ -10793,7 +11048,7 @@ class Call {
10793
11048
  dimension: p.screenShareDimension,
10794
11049
  });
10795
11050
  }
10796
- });
11051
+ }
10797
11052
  // schedule update
10798
11053
  this.trackSubscriptionsSubject.next({ type, data: subscriptions });
10799
11054
  };
@@ -11182,7 +11437,7 @@ class Call {
11182
11437
  this.dropTimeout = setTimeout(() => this.leave(), timeoutMs);
11183
11438
  }), takeWhile(() => !!this.clientStore.calls.find((call) => call.cid === this.cid)))
11184
11439
  .subscribe();
11185
- this.leaveCallHooks.push(() => {
11440
+ this.leaveCallHooks.add(() => {
11186
11441
  !subscription.closed && subscription.unsubscribe();
11187
11442
  });
11188
11443
  };
@@ -11209,6 +11464,69 @@ class Call {
11209
11464
  this.sendCustomEvent = (payload) => __awaiter(this, void 0, void 0, function* () {
11210
11465
  return this.streamClient.post(`${this.streamClientBasePath}/event`, { custom: payload });
11211
11466
  });
11467
+ /**
11468
+ * Will begin tracking the given element for visibility changes within the
11469
+ * configured viewport element (`call.setViewport`).
11470
+ *
11471
+ * @param element the element to track.
11472
+ * @param sessionId the session id.
11473
+ * @param trackType the video mode.
11474
+ */
11475
+ this.trackElementVisibility = (element, sessionId, trackType) => {
11476
+ return this.dynascaleManager.trackElementVisibility(element, sessionId, trackType);
11477
+ };
11478
+ /**
11479
+ * Sets the viewport element to track bound video elements for visibility.
11480
+ *
11481
+ * @param element the viewport element.
11482
+ */
11483
+ this.setViewport = (element) => {
11484
+ return this.dynascaleManager.setViewport(element);
11485
+ };
11486
+ /**
11487
+ * Binds a DOM <video> element to the given session id.
11488
+ * This method will make sure that the video element will play
11489
+ * the correct video stream for the given session id.
11490
+ *
11491
+ * Under the hood, it would also keep track of the video element dimensions
11492
+ * and update the subscription accordingly in order to optimize the bandwidth.
11493
+ *
11494
+ * If a "viewport" is configured, the video element will be automatically
11495
+ * tracked for visibility and the subscription will be updated accordingly.
11496
+ *
11497
+ * @param videoElement the video element to bind to.
11498
+ * @param sessionId the session id.
11499
+ * @param trackType the kind of video.
11500
+ */
11501
+ this.bindVideoElement = (videoElement, sessionId, trackType) => {
11502
+ const unbind = this.dynascaleManager.bindVideoElement(videoElement, sessionId, trackType);
11503
+ if (!unbind)
11504
+ return;
11505
+ this.leaveCallHooks.add(unbind);
11506
+ return () => {
11507
+ this.leaveCallHooks.delete(unbind);
11508
+ unbind();
11509
+ };
11510
+ };
11511
+ /**
11512
+ * Binds a DOM <audio> element to the given session id.
11513
+ *
11514
+ * This method will make sure that the audio element will
11515
+ * play the correct audio stream for the given session id.
11516
+ *
11517
+ * @param audioElement the audio element to bind to.
11518
+ * @param sessionId the session id.
11519
+ */
11520
+ this.bindAudioElement = (audioElement, sessionId) => {
11521
+ const unbind = this.dynascaleManager.bindAudioElement(audioElement, sessionId);
11522
+ if (!unbind)
11523
+ return;
11524
+ this.leaveCallHooks.add(unbind);
11525
+ return () => {
11526
+ this.leaveCallHooks.delete(unbind);
11527
+ unbind();
11528
+ };
11529
+ };
11212
11530
  this.type = type;
11213
11531
  this.id = id;
11214
11532
  this.cid = `${type}:${id}`;
@@ -11230,20 +11548,21 @@ class Call {
11230
11548
  // update state with the latest event data
11231
11549
  this.state.updateFromEvent(event);
11232
11550
  });
11233
- this.leaveCallHooks.push(registerEventHandlers(this, this.state, this.dispatcher));
11551
+ this.leaveCallHooks.add(registerEventHandlers(this, this.state, this.dispatcher));
11234
11552
  this.registerEffects();
11235
- this.leaveCallHooks.push(createSubscription(this.trackSubscriptionsSubject.pipe(debounce((v) => timer(v.type)), map$2((v) => v.data)), (subscriptions) => { var _a; return (_a = this.sfuClient) === null || _a === void 0 ? void 0 : _a.updateSubscriptions(subscriptions); }));
11553
+ this.leaveCallHooks.add(createSubscription(this.trackSubscriptionsSubject.pipe(debounce((v) => timer(v.type)), map$2((v) => v.data)), (subscriptions) => { var _a; return (_a = this.sfuClient) === null || _a === void 0 ? void 0 : _a.updateSubscriptions(subscriptions); }));
11236
11554
  this.camera = new CameraManager(this);
11237
11555
  this.microphone = new MicrophoneManager(this);
11238
11556
  }
11239
11557
  registerEffects() {
11240
- this.leaveCallHooks.push(
11558
+ this.leaveCallHooks.add(
11241
11559
  // handles updating the permissions context when the settings change.
11242
11560
  createSubscription(this.state.settings$, (settings) => {
11243
11561
  if (!settings)
11244
11562
  return;
11245
11563
  this.permissionsContext.setCallSettings(settings);
11246
- }),
11564
+ }));
11565
+ this.leaveCallHooks.add(
11247
11566
  // handle the case when the user permissions are modified.
11248
11567
  createSubscription(this.state.ownCapabilities$, (ownCapabilities) => {
11249
11568
  // update the permission context.
@@ -11264,7 +11583,8 @@ class Call {
11264
11583
  });
11265
11584
  }
11266
11585
  }
11267
- }),
11586
+ }));
11587
+ this.leaveCallHooks.add(
11268
11588
  // handles the case when the user is blocked by the call owner.
11269
11589
  createSubscription(this.state.blockedUserIds$, (blockedUserIds) => __awaiter(this, void 0, void 0, function* () {
11270
11590
  if (!blockedUserIds)
@@ -11274,7 +11594,8 @@ class Call {
11274
11594
  this.logger('info', 'Leaving call because of being blocked');
11275
11595
  yield this.leave();
11276
11596
  }
11277
- })),
11597
+ })));
11598
+ this.leaveCallHooks.add(
11278
11599
  // watch for auto drop cancellation
11279
11600
  createSubscription(this.state.callingState$, (callingState) => {
11280
11601
  if (!this.ringing)
@@ -11285,7 +11606,8 @@ class Call {
11285
11606
  clearTimeout(this.dropTimeout);
11286
11607
  this.dropTimeout = undefined;
11287
11608
  }
11288
- }),
11609
+ }));
11610
+ this.leaveCallHooks.add(
11289
11611
  // "ringing" mode effects and event handlers
11290
11612
  createSubscription(this.ringingSubject, (isRinging) => {
11291
11613
  if (!isRinging)
@@ -11294,7 +11616,7 @@ class Call {
11294
11616
  if (this.state.callingState === CallingState.IDLE) {
11295
11617
  this.state.setCallingState(CallingState.RINGING);
11296
11618
  }
11297
- this.leaveCallHooks.push(registerRingingCallEventHandlers(this));
11619
+ this.leaveCallHooks.add(registerRingingCallEventHandlers(this));
11298
11620
  }));
11299
11621
  }
11300
11622
  on(eventName, fn) {
@@ -12520,7 +12842,7 @@ class WSConnectionFallback {
12520
12842
  }
12521
12843
  }
12522
12844
 
12523
- const version = '0.3.9';
12845
+ const version = '0.3.11';
12524
12846
 
12525
12847
  const logger = getLogger(['location']);
12526
12848
  const HINT_URL = `https://hint.stream-io-video.com/`;
@@ -13580,5 +13902,5 @@ var browsers = /*#__PURE__*/Object.freeze({
13580
13902
  isSafari: isSafari
13581
13903
  });
13582
13904
 
13583
- export { AudioSettingsDefaultDeviceEnum, AudioSettingsRequestDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, ErrorFromResponse, InputMediaDeviceManager, InputMediaDeviceManagerState, MicrophoneManager, MicrophoneManagerState, OwnCapability, RecordSettingsModeEnum, RecordSettingsQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, events as SfuEvents, models as SfuModels, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoServerClient, StreamVideoWriteableStateStore, TranscriptionSettingsModeEnum, TranscriptionSettingsRequestModeEnum, VideoSettingsCameraFacingEnum, VideoSettingsRequestCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, disposeOfMediaStream, dominantSpeaker, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceInfo, getLogger, getOSInfo, getScreenShareStream, getSdkInfo, getVideoDevices, getVideoStream, isStreamVideoLocalParticipant, livestreamOrAudioRoomSortPreset, logLevels, logToConsole, name, noopComparator, pinned, publishingAudio, publishingVideo, reactionType, role, screenSharing, setDeviceInfo, setLogLevel, setLogger, setOSInfo, setSdkInfo, speakerLayoutSortPreset, speaking, watchForAddedDefaultAudioDevice, watchForAddedDefaultAudioOutputDevice, watchForAddedDefaultVideoDevice, watchForDisconnectedAudioDevice, watchForDisconnectedAudioOutputDevice, watchForDisconnectedVideoDevice };
13905
+ export { AudioSettingsDefaultDeviceEnum, AudioSettingsRequestDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DynascaleManager, ErrorFromResponse, InputMediaDeviceManager, InputMediaDeviceManagerState, MicrophoneManager, MicrophoneManagerState, OwnCapability, RecordSettingsModeEnum, RecordSettingsQualityEnum, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, events as SfuEvents, models as SfuModels, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoServerClient, StreamVideoWriteableStateStore, TranscriptionSettingsModeEnum, TranscriptionSettingsRequestModeEnum, VideoSettingsCameraFacingEnum, VideoSettingsRequestCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, disposeOfMediaStream, dominantSpeaker, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceInfo, getLogger, getOSInfo, getScreenShareStream, getSdkInfo, getVideoDevices, getVideoStream, isStreamVideoLocalParticipant, livestreamOrAudioRoomSortPreset, logLevels, logToConsole, name, noopComparator, pinned, publishingAudio, publishingVideo, reactionType, role, screenSharing, setDeviceInfo, setLogLevel, setLogger, setOSInfo, setSdkInfo, speakerLayoutSortPreset, speaking, watchForAddedDefaultAudioDevice, watchForAddedDefaultAudioOutputDevice, watchForAddedDefaultVideoDevice, watchForDisconnectedAudioDevice, watchForDisconnectedAudioOutputDevice, watchForDisconnectedVideoDevice };
13584
13906
  //# sourceMappingURL=index.browser.es.js.map