@stream-io/video-client 0.3.0 → 0.3.2

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/index.browser.es.js +863 -455
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +867 -453
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +863 -455
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +18 -1
  9. package/dist/src/devices/CameraManager.d.ts +31 -0
  10. package/dist/src/devices/CameraManagerState.d.ts +28 -0
  11. package/dist/src/devices/InputMediaDeviceManager.d.ts +47 -0
  12. package/dist/src/devices/InputMediaDeviceManagerState.d.ts +69 -0
  13. package/dist/src/devices/MicrophoneManager.d.ts +19 -0
  14. package/dist/src/devices/MicrophoneManagerState.d.ts +4 -0
  15. package/dist/src/devices/__tests__/CameraManager.test.d.ts +1 -0
  16. package/dist/src/devices/__tests__/InputMediaDeviceManager.test.d.ts +1 -0
  17. package/dist/src/devices/__tests__/MicrophoneManager.test.d.ts +1 -0
  18. package/dist/src/devices/__tests__/mocks.d.ts +13 -0
  19. package/dist/src/devices/index.d.ts +6 -0
  20. package/dist/src/types.d.ts +4 -0
  21. package/dist/version.d.ts +1 -1
  22. package/package.json +1 -1
  23. package/src/Call.ts +100 -3
  24. package/src/__tests__/StreamVideoClient.test.ts +3 -0
  25. package/src/devices/CameraManager.ts +73 -0
  26. package/src/devices/CameraManagerState.ts +61 -0
  27. package/src/devices/InputMediaDeviceManager.ts +121 -0
  28. package/src/devices/InputMediaDeviceManagerState.ts +111 -0
  29. package/src/devices/MicrophoneManager.ts +45 -0
  30. package/src/devices/MicrophoneManagerState.ts +9 -0
  31. package/src/devices/__tests__/CameraManager.test.ts +150 -0
  32. package/src/devices/__tests__/InputMediaDeviceManager.test.ts +159 -0
  33. package/src/devices/__tests__/MicrophoneManager.test.ts +103 -0
  34. package/src/devices/__tests__/mocks.ts +98 -0
  35. package/src/devices/index.ts +6 -0
  36. package/src/rtc/Publisher.ts +11 -19
  37. package/src/rtc/videoLayers.ts +7 -2
  38. package/src/types.ts +4 -0
package/dist/index.cjs.js CHANGED
@@ -7,9 +7,9 @@ var axios = require('axios');
7
7
  var twirpTransport = require('@protobuf-ts/twirp-transport');
8
8
  var rxjs = require('rxjs');
9
9
  var SDP = require('sdp-transform');
10
+ var uaParserJs = require('ua-parser-js');
10
11
  var WebSocket = require('isomorphic-ws');
11
12
  var operators = require('rxjs/operators');
12
- var uaParserJs = require('ua-parser-js');
13
13
  var https = require('https');
14
14
  var jwt = require('jsonwebtoken');
15
15
  require('crypto');
@@ -6289,6 +6289,56 @@ function getIceCandidate(candidate) {
6289
6289
  }
6290
6290
  }
6291
6291
 
6292
+ let sdkInfo;
6293
+ let osInfo;
6294
+ let deviceInfo;
6295
+ const setSdkInfo = (info) => {
6296
+ sdkInfo = info;
6297
+ };
6298
+ const getSdkInfo = () => {
6299
+ return sdkInfo;
6300
+ };
6301
+ const setOSInfo = (info) => {
6302
+ osInfo = info;
6303
+ };
6304
+ const getOSInfo = () => {
6305
+ return osInfo;
6306
+ };
6307
+ const setDeviceInfo = (info) => {
6308
+ deviceInfo = info;
6309
+ };
6310
+ const getDeviceInfo = () => {
6311
+ return deviceInfo;
6312
+ };
6313
+ const getClientDetails = () => {
6314
+ if (isReactNative()) {
6315
+ // Since RN doesn't support web, sharing browser info is not required
6316
+ return {
6317
+ sdk: getSdkInfo(),
6318
+ os: getOSInfo(),
6319
+ device: getDeviceInfo(),
6320
+ };
6321
+ }
6322
+ const userAgent = new uaParserJs.UAParser(navigator.userAgent);
6323
+ const { browser, os, device, cpu } = userAgent.getResult();
6324
+ return {
6325
+ sdk: getSdkInfo(),
6326
+ browser: {
6327
+ name: browser.name || navigator.userAgent,
6328
+ version: browser.version || '',
6329
+ },
6330
+ os: {
6331
+ name: os.name || '',
6332
+ version: os.version || '',
6333
+ architecture: cpu.architecture || '',
6334
+ },
6335
+ device: {
6336
+ name: `${device.vendor || ''} ${device.model || ''} ${device.type || ''}`,
6337
+ version: '',
6338
+ },
6339
+ };
6340
+ };
6341
+
6292
6342
  const DEFAULT_BITRATE = 1250000;
6293
6343
  const defaultTargetResolution = {
6294
6344
  bitrate: DEFAULT_BITRATE,
@@ -6303,9 +6353,11 @@ const defaultTargetResolution = {
6303
6353
  * @param targetResolution the expected target resolution.
6304
6354
  */
6305
6355
  const findOptimalVideoLayers = (videoTrack, targetResolution = defaultTargetResolution) => {
6356
+ var _a;
6306
6357
  const optimalVideoLayers = [];
6307
6358
  const settings = videoTrack.getSettings();
6308
6359
  const { width: w = 0, height: h = 0 } = settings;
6360
+ const isRNIos = isReactNative() && ((_a = getOSInfo()) === null || _a === void 0 ? void 0 : _a.name.toLowerCase()) === 'ios';
6309
6361
  const maxBitrate = getComputedMaxBitrate(targetResolution, w, h);
6310
6362
  let downscaleFactor = 1;
6311
6363
  ['f', 'h', 'q'].forEach((rid) => {
@@ -6319,10 +6371,11 @@ const findOptimalVideoLayers = (videoTrack, targetResolution = defaultTargetReso
6319
6371
  height: Math.round(h / downscaleFactor),
6320
6372
  maxBitrate: Math.round(maxBitrate / downscaleFactor),
6321
6373
  scaleResolutionDownBy: downscaleFactor,
6374
+ // Simulcast on iOS React-Native requires all encodings to share the same framerate
6322
6375
  maxFramerate: {
6323
6376
  f: 30,
6324
- h: 25,
6325
- q: 20,
6377
+ h: isRNIos ? 30 : 25,
6378
+ q: isRNIos ? 30 : 20,
6326
6379
  }[rid],
6327
6380
  });
6328
6381
  downscaleFactor *= 2;
@@ -6521,6 +6574,7 @@ class Publisher {
6521
6574
  * @param opts
6522
6575
  */
6523
6576
  this.publishStream = (mediaStream, track, trackType, opts = {}) => __awaiter(this, void 0, void 0, function* () {
6577
+ var _a;
6524
6578
  if (track.readyState === 'ended') {
6525
6579
  throw new Error(`Can't publish a track that has ended already.`);
6526
6580
  }
@@ -6548,7 +6602,14 @@ class Publisher {
6548
6602
  const videoEncodings = trackType === TrackType.VIDEO
6549
6603
  ? findOptimalVideoLayers(track, targetResolution)
6550
6604
  : undefined;
6551
- const codecPreferences = this.getCodecPreferences(trackType, opts.preferredCodec);
6605
+ let preferredCodec = opts.preferredCodec;
6606
+ if (!preferredCodec && trackType === TrackType.VIDEO) {
6607
+ const isRNAndroid = isReactNative() && ((_a = getOSInfo()) === null || _a === void 0 ? void 0 : _a.name.toLowerCase()) === 'android';
6608
+ if (isRNAndroid) {
6609
+ preferredCodec = 'VP8';
6610
+ }
6611
+ }
6612
+ const codecPreferences = this.getCodecPreferences(trackType, preferredCodec);
6552
6613
  // listen for 'ended' event on the track as it might be ended abruptly
6553
6614
  // by an external factor as permission revokes, device disconnected, etc.
6554
6615
  // keep in mind that `track.stop()` doesn't trigger this event.
@@ -6642,9 +6703,9 @@ class Publisher {
6642
6703
  });
6643
6704
  };
6644
6705
  this.updateVideoPublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
6645
- var _a;
6706
+ var _b;
6646
6707
  logger$3('info', 'Update publish quality, requested rids by SFU:', enabledRids);
6647
- const videoSender = (_a = this.transceiverRegistry[TrackType.VIDEO]) === null || _a === void 0 ? void 0 : _a.sender;
6708
+ const videoSender = (_b = this.transceiverRegistry[TrackType.VIDEO]) === null || _b === void 0 ? void 0 : _b.sender;
6648
6709
  if (!videoSender) {
6649
6710
  logger$3('warn', 'Update publish quality, no video sender found.');
6650
6711
  return;
@@ -6742,8 +6803,8 @@ class Publisher {
6742
6803
  * @param options the optional offer options to use.
6743
6804
  */
6744
6805
  this.negotiate = (options) => __awaiter(this, void 0, void 0, function* () {
6745
- var _b;
6746
- this.isIceRestarting = (_b = options === null || options === void 0 ? void 0 : options.iceRestart) !== null && _b !== void 0 ? _b : false;
6806
+ var _c;
6807
+ this.isIceRestarting = (_c = options === null || options === void 0 ? void 0 : options.iceRestart) !== null && _c !== void 0 ? _c : false;
6747
6808
  const offer = yield this.pc.createOffer(options);
6748
6809
  offer.sdp = this.mungeCodecs(offer.sdp);
6749
6810
  const trackInfos = this.getCurrentTrackInfos(offer.sdp);
@@ -6781,15 +6842,6 @@ class Publisher {
6781
6842
  this.mungeCodecs = (sdp) => {
6782
6843
  if (sdp) {
6783
6844
  sdp = toggleDtx(sdp, this.isDtxEnabled);
6784
- if (isReactNative()) {
6785
- if (this.preferredVideoCodec) {
6786
- sdp = setPreferredCodec(sdp, 'video', this.preferredVideoCodec);
6787
- }
6788
- sdp = setPreferredCodec(sdp, 'audio', this.isRedEnabled ? 'red' : 'opus');
6789
- if (!this.isRedEnabled) {
6790
- sdp = removeCodec(sdp, 'audio', 'red');
6791
- }
6792
- }
6793
6845
  }
6794
6846
  return sdp;
6795
6847
  };
@@ -9487,195 +9539,730 @@ const CallTypes = new CallTypesRegistry([
9487
9539
  }),
9488
9540
  ]);
9489
9541
 
9490
- let sdkInfo;
9491
- let osInfo;
9492
- let deviceInfo;
9493
- const setSdkInfo = (info) => {
9494
- sdkInfo = info;
9495
- };
9496
- const getSdkInfo = () => {
9497
- return sdkInfo;
9498
- };
9499
- const setOSInfo = (info) => {
9500
- osInfo = info;
9501
- };
9502
- const getOSInfo = () => {
9503
- return osInfo;
9504
- };
9505
- const setDeviceInfo = (info) => {
9506
- deviceInfo = info;
9507
- };
9508
- const getDeviceInfo = () => {
9509
- return deviceInfo;
9510
- };
9511
- const getClientDetails = () => {
9512
- if (isReactNative()) {
9513
- // Since RN doesn't support web, sharing browser info is not required
9514
- return {
9515
- sdk: getSdkInfo(),
9516
- os: getOSInfo(),
9517
- device: getDeviceInfo(),
9518
- };
9519
- }
9520
- const userAgent = new uaParserJs.UAParser(navigator.userAgent);
9521
- const { browser, os, device, cpu } = userAgent.getResult();
9522
- return {
9523
- sdk: getSdkInfo(),
9524
- browser: {
9525
- name: browser.name || navigator.userAgent,
9526
- version: browser.version || '',
9527
- },
9528
- os: {
9529
- name: os.name || '',
9530
- version: os.version || '',
9531
- architecture: cpu.architecture || '',
9532
- },
9533
- device: {
9534
- name: `${device.vendor || ''} ${device.model || ''} ${device.type || ''}`,
9535
- version: '',
9536
- },
9537
- };
9538
- };
9539
-
9540
- /**
9541
- * An object representation of a `Call`.
9542
- */
9543
- class Call {
9544
- /**
9545
- * Constructs a new `Call` instance.
9546
- *
9547
- * NOTE: Don't call the constructor directly, instead
9548
- * Use the [`StreamVideoClient.call`](./StreamVideoClient.md/#call)
9549
- * method to construct a `Call` instance.
9550
- */
9551
- constructor({ type, id, streamClient, members, ownCapabilities, sortParticipantsBy, clientStore, ringing = false, watching = false, }) {
9552
- /**
9553
- * ViewportTracker instance
9554
- */
9555
- this.viewportTracker = new ViewportTracker();
9556
- /**
9557
- * The state of this call.
9558
- */
9559
- this.state = new CallState();
9560
- /**
9561
- * The permissions context of this call.
9562
- */
9563
- this.permissionsContext = new PermissionsContext();
9564
- /**
9565
- * The event dispatcher instance dedicated to this Call instance.
9566
- * @private
9567
- */
9568
- this.dispatcher = new Dispatcher();
9569
- this.trackSubscriptionsSubject = new rxjs.BehaviorSubject({ type: exports.DebounceType.MEDIUM, data: [] });
9570
- this.reconnectAttempts = 0;
9571
- this.maxReconnectAttempts = 10;
9572
- /**
9573
- * A list hooks/functions to invoke when the call is left.
9574
- * A typical use case is to clean up some global event handlers.
9575
- * @private
9576
- */
9577
- this.leaveCallHooks = [];
9578
- this.streamClientEventHandlers = new Map();
9579
- /**
9580
- * Leave the call and stop the media streams that were published by the call.
9581
- */
9582
- this.leave = ({ reject = false } = {}) => __awaiter(this, void 0, void 0, function* () {
9583
- var _a, _b, _c, _d;
9584
- const callingState = this.state.callingState;
9585
- if (callingState === exports.CallingState.LEFT) {
9586
- throw new Error('Cannot leave call that has already been left.');
9587
- }
9588
- if (callingState === exports.CallingState.JOINING) {
9589
- yield this.assertCallJoined();
9590
- }
9591
- if (this.ringing) {
9592
- // I'm the one who started the call, so I should cancel it.
9593
- const hasOtherParticipants = this.state.remoteParticipants.length > 0;
9594
- if (this.isCreatedByMe && !hasOtherParticipants) {
9595
- // Signals other users that I have cancelled my call to them
9596
- // before they accepted it.
9597
- yield this.reject();
9598
- }
9599
- else if (reject && callingState === exports.CallingState.RINGING) {
9600
- // Signals other users that I have rejected the incoming call.
9601
- yield this.reject();
9602
- }
9603
- }
9604
- (_a = this.statsReporter) === null || _a === void 0 ? void 0 : _a.stop();
9605
- this.statsReporter = undefined;
9606
- (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.close();
9607
- this.subscriber = undefined;
9608
- (_c = this.publisher) === null || _c === void 0 ? void 0 : _c.close();
9609
- this.publisher = undefined;
9610
- (_d = this.sfuClient) === null || _d === void 0 ? void 0 : _d.close();
9611
- this.sfuClient = undefined;
9612
- this.dispatcher.offAll();
9613
- // Call all leave call hooks, e.g. to clean up global event handlers
9614
- this.leaveCallHooks.forEach((hook) => hook());
9615
- this.clientStore.unregisterCall(this);
9616
- this.state.setCallingState(exports.CallingState.LEFT);
9617
- });
9542
+ class InputMediaDeviceManagerState {
9543
+ constructor() {
9544
+ this.statusSubject = new rxjs.BehaviorSubject(undefined);
9545
+ this.mediaStreamSubject = new rxjs.BehaviorSubject(undefined);
9546
+ this.selectedDeviceSubject = new rxjs.BehaviorSubject(undefined);
9618
9547
  /**
9619
- * Loads the information about the call.
9548
+ * Gets the current value of an observable, or undefined if the observable has
9549
+ * not emitted a value yet.
9620
9550
  *
9621
- * @param params.ring if set to true, a `call.ring` event will be sent to the call members.
9622
- * @param params.notify if set to true, a `call.notification` event will be sent to the call members.
9623
- * @param params.members_limit the members limit.
9551
+ * @param observable$ the observable to get the value from.
9624
9552
  */
9625
- this.get = (params) => __awaiter(this, void 0, void 0, function* () {
9626
- const response = yield this.streamClient.get(this.streamClientBasePath, params);
9627
- if ((params === null || params === void 0 ? void 0 : params.ring) && !this.ringing) {
9628
- this.ringingSubject.next(true);
9629
- }
9630
- this.state.updateFromCallResponse(response.call);
9631
- this.state.setMembers(response.members);
9632
- this.state.setOwnCapabilities(response.own_capabilities);
9633
- if (this.streamClient._hasConnectionID()) {
9634
- this.watching = true;
9635
- this.clientStore.registerCall(this);
9636
- }
9637
- return response;
9638
- });
9553
+ this.getCurrentValue = getCurrentValue;
9639
9554
  /**
9640
- * Loads the information about the call and creates it if it doesn't exist.
9555
+ * Updates the value of the provided Subject.
9556
+ * An `update` can either be a new value or a function which takes
9557
+ * the current value and returns a new value.
9641
9558
  *
9642
- * @param data the data to create the call with.
9643
- */
9644
- this.getOrCreate = (data) => __awaiter(this, void 0, void 0, function* () {
9645
- const response = yield this.streamClient.post(this.streamClientBasePath, data);
9646
- if ((data === null || data === void 0 ? void 0 : data.ring) && !this.ringing) {
9647
- this.ringingSubject.next(true);
9648
- }
9649
- this.state.updateFromCallResponse(response.call);
9650
- this.state.setMembers(response.members);
9651
- this.state.setOwnCapabilities(response.own_capabilities);
9652
- if (this.streamClient._hasConnectionID()) {
9653
- this.watching = true;
9654
- this.clientStore.registerCall(this);
9655
- }
9656
- return response;
9657
- });
9658
- /**
9659
- * Creates a call
9559
+ * @internal
9660
9560
  *
9661
- * @param data the data to create the call with.
9662
- */
9663
- this.create = (data) => __awaiter(this, void 0, void 0, function* () {
9664
- return this.getOrCreate(data);
9665
- });
9666
- /**
9667
- * A shortcut for {@link Call.get} with `ring` parameter set to `true`.
9668
- * Will send a `call.ring` event to the call members.
9669
- */
9670
- this.ring = () => __awaiter(this, void 0, void 0, function* () {
9671
- return yield this.get({ ring: true });
9672
- });
9673
- /**
9674
- * A shortcut for {@link Call.get} with `notify` parameter set to `true`.
9675
- * Will send a `call.notification` event to the call members.
9561
+ * @param subject the subject to update.
9562
+ * @param update the update to apply to the subject.
9563
+ * @return the updated value.
9676
9564
  */
9677
- this.notify = () => __awaiter(this, void 0, void 0, function* () {
9678
- return yield this.get({ notify: true });
9565
+ this.setCurrentValue = setCurrentValue;
9566
+ this.mediaStream$ = this.mediaStreamSubject.asObservable();
9567
+ this.selectedDevice$ = this.selectedDeviceSubject
9568
+ .asObservable()
9569
+ .pipe(rxjs.distinctUntilChanged());
9570
+ this.status$ = this.statusSubject.asObservable();
9571
+ }
9572
+ /**
9573
+ * The device status
9574
+ */
9575
+ get status() {
9576
+ return this.getCurrentValue(this.status$);
9577
+ }
9578
+ /**
9579
+ * The currently selected device
9580
+ */
9581
+ get selectedDevice() {
9582
+ return this.getCurrentValue(this.selectedDevice$);
9583
+ }
9584
+ /**
9585
+ * The current media stream, or `undefined` if the device is currently disabled.
9586
+ */
9587
+ get mediaStream() {
9588
+ return this.getCurrentValue(this.mediaStream$);
9589
+ }
9590
+ /**
9591
+ * @internal
9592
+ * @param status
9593
+ */
9594
+ setStatus(status) {
9595
+ this.setCurrentValue(this.statusSubject, status);
9596
+ }
9597
+ /**
9598
+ * @internal
9599
+ * @param stream
9600
+ */
9601
+ setMediaStream(stream) {
9602
+ this.setCurrentValue(this.mediaStreamSubject, stream);
9603
+ if (stream) {
9604
+ this.setDevice(this.getDeviceIdFromStream(stream));
9605
+ }
9606
+ }
9607
+ /**
9608
+ * @internal
9609
+ * @param stream
9610
+ */
9611
+ setDevice(deviceId) {
9612
+ this.setCurrentValue(this.selectedDeviceSubject, deviceId);
9613
+ }
9614
+ }
9615
+
9616
+ class CameraManagerState extends InputMediaDeviceManagerState {
9617
+ constructor() {
9618
+ super();
9619
+ this.directionSubject = new rxjs.BehaviorSubject(undefined);
9620
+ this.direction$ = this.directionSubject
9621
+ .asObservable()
9622
+ .pipe(rxjs.distinctUntilChanged());
9623
+ }
9624
+ /**
9625
+ * The preferred camera direction
9626
+ * front - means the camera facing the user
9627
+ * back - means the camera facing the environment
9628
+ */
9629
+ get direction() {
9630
+ return this.getCurrentValue(this.direction$);
9631
+ }
9632
+ /**
9633
+ * @internal
9634
+ */
9635
+ setDirection(direction) {
9636
+ this.setCurrentValue(this.directionSubject, direction);
9637
+ }
9638
+ /**
9639
+ * @internal
9640
+ */
9641
+ setMediaStream(stream) {
9642
+ var _a;
9643
+ super.setMediaStream(stream);
9644
+ if (stream) {
9645
+ // RN getSettings() doesn't return facingMode, so we don't verify camera direction
9646
+ const direction = isReactNative()
9647
+ ? this.direction
9648
+ : ((_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().facingMode) === 'environment'
9649
+ ? 'back'
9650
+ : 'front';
9651
+ this.setDirection(direction);
9652
+ }
9653
+ }
9654
+ getDeviceIdFromStream(stream) {
9655
+ var _a;
9656
+ return (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
9657
+ }
9658
+ }
9659
+
9660
+ const getDevices = (constraints) => {
9661
+ return new rxjs.Observable((subscriber) => {
9662
+ navigator.mediaDevices
9663
+ .getUserMedia(constraints)
9664
+ .then((media) => {
9665
+ // in Firefox, devices can be enumerated after userMedia is requested
9666
+ // and permissions granted. Otherwise, device labels are empty
9667
+ navigator.mediaDevices.enumerateDevices().then((devices) => {
9668
+ subscriber.next(devices);
9669
+ // If we stop the tracks before enumerateDevices -> the labels won't show up in Firefox
9670
+ disposeOfMediaStream(media);
9671
+ subscriber.complete();
9672
+ });
9673
+ })
9674
+ .catch((error) => {
9675
+ getLogger(['devices'])('error', 'Failed to get devices', error);
9676
+ subscriber.error(error);
9677
+ });
9678
+ });
9679
+ };
9680
+ /**
9681
+ * [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
9682
+ *
9683
+ * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
9684
+ */
9685
+ const checkIfAudioOutputChangeSupported = () => {
9686
+ if (typeof document === 'undefined')
9687
+ return false;
9688
+ const element = document.createElement('audio');
9689
+ return element.sinkId !== undefined;
9690
+ };
9691
+ /**
9692
+ * The default constraints used to request audio devices.
9693
+ */
9694
+ const audioDeviceConstraints = {
9695
+ audio: {
9696
+ autoGainControl: true,
9697
+ noiseSuppression: true,
9698
+ echoCancellation: true,
9699
+ },
9700
+ };
9701
+ /**
9702
+ * The default constraints used to request video devices.
9703
+ */
9704
+ const videoDeviceConstraints = {
9705
+ video: {
9706
+ width: 1280,
9707
+ height: 720,
9708
+ },
9709
+ };
9710
+ // Audio and video devices are requested in two separate requests: that way users will be presented with two separate prompts -> they can give access to just camera, or just microphone
9711
+ const deviceChange$ = new rxjs.Observable((subscriber) => {
9712
+ var _a, _b;
9713
+ const deviceChangeHandler = () => subscriber.next();
9714
+ (_b = (_a = navigator.mediaDevices).addEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
9715
+ return () => {
9716
+ var _a, _b;
9717
+ return (_b = (_a = navigator.mediaDevices).removeEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
9718
+ };
9719
+ }).pipe(rxjs.debounceTime(500), rxjs.concatMap(() => rxjs.from(navigator.mediaDevices.enumerateDevices())), rxjs.shareReplay(1));
9720
+ const audioDevices$ = rxjs.merge(getDevices(audioDeviceConstraints), deviceChange$).pipe(rxjs.shareReplay(1));
9721
+ const videoDevices$ = rxjs.merge(getDevices(videoDeviceConstraints), deviceChange$).pipe(rxjs.shareReplay(1));
9722
+ /**
9723
+ * Prompts the user for a permission to use audio devices (if not already granted) and lists the available 'audioinput' devices, if devices are added/removed the list is updated.
9724
+ *
9725
+ * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
9726
+ * @returns
9727
+ */
9728
+ const getAudioDevices = () => audioDevices$.pipe(rxjs.map((values) => values.filter((d) => d.kind === 'audioinput')));
9729
+ /**
9730
+ * Prompts the user for a permission to use video devices (if not already granted) and lists the available 'videoinput' devices, if devices are added/removed the list is updated.
9731
+ *
9732
+ * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
9733
+ * @returns
9734
+ */
9735
+ const getVideoDevices = () => videoDevices$.pipe(rxjs.map((values) => values.filter((d) => d.kind === 'videoinput' && d.deviceId.length)));
9736
+ /**
9737
+ * Prompts the user for a permission to use audio devices (if not already granted) and lists the available 'audiooutput' devices, if devices are added/removed the list is updated. Selecting 'audiooutput' device only makes sense if [the browser has support for changing audio output on 'audio' elements](#checkifaudiooutputchangesupported)
9738
+ *
9739
+ * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
9740
+ * @returns
9741
+ */
9742
+ const getAudioOutputDevices = () => {
9743
+ return audioDevices$.pipe(rxjs.map((values) => values.filter((d) => d.kind === 'audiooutput')));
9744
+ };
9745
+ const getStream = (constraints) => __awaiter(void 0, void 0, void 0, function* () {
9746
+ try {
9747
+ return yield navigator.mediaDevices.getUserMedia(constraints);
9748
+ }
9749
+ catch (e) {
9750
+ getLogger(['devices'])('error', `Failed get user media`, {
9751
+ error: e,
9752
+ constraints: constraints,
9753
+ });
9754
+ throw e;
9755
+ }
9756
+ });
9757
+ /**
9758
+ * Returns an audio media stream that fulfills the given constraints.
9759
+ * If no constraints are provided, it uses the browser's default ones.
9760
+ *
9761
+ * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
9762
+ * @param trackConstraints the constraints to use when requesting the stream.
9763
+ * @returns the new `MediaStream` fulfilling the given constraints.
9764
+ */
9765
+ const getAudioStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
9766
+ const constraints = {
9767
+ audio: Object.assign(Object.assign({}, audioDeviceConstraints.audio), trackConstraints),
9768
+ };
9769
+ return getStream(constraints);
9770
+ });
9771
+ /**
9772
+ * Returns a video media stream that fulfills the given constraints.
9773
+ * If no constraints are provided, it uses the browser's default ones.
9774
+ *
9775
+ * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
9776
+ * @param trackConstraints the constraints to use when requesting the stream.
9777
+ * @returns a new `MediaStream` fulfilling the given constraints.
9778
+ */
9779
+ const getVideoStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
9780
+ const constraints = {
9781
+ video: Object.assign(Object.assign({}, videoDeviceConstraints.video), trackConstraints),
9782
+ };
9783
+ return getStream(constraints);
9784
+ });
9785
+ /**
9786
+ * Prompts the user for a permission to share a screen.
9787
+ * If the user grants the permission, a screen sharing stream is returned. Throws otherwise.
9788
+ *
9789
+ * The callers of this API are responsible to handle the possible errors.
9790
+ *
9791
+ * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
9792
+ *
9793
+ * @param options any additional options to pass to the [`getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) API.
9794
+ */
9795
+ const getScreenShareStream = (options) => __awaiter(void 0, void 0, void 0, function* () {
9796
+ try {
9797
+ return yield navigator.mediaDevices.getDisplayMedia(Object.assign({ video: true, audio: false }, options));
9798
+ }
9799
+ catch (e) {
9800
+ getLogger(['devices'])('error', 'Failed to get screen share stream', e);
9801
+ throw e;
9802
+ }
9803
+ });
9804
+ const watchForDisconnectedDevice = (kind, deviceId$) => {
9805
+ let devices$;
9806
+ switch (kind) {
9807
+ case 'audioinput':
9808
+ devices$ = getAudioDevices();
9809
+ break;
9810
+ case 'videoinput':
9811
+ devices$ = getVideoDevices();
9812
+ break;
9813
+ case 'audiooutput':
9814
+ devices$ = getAudioOutputDevices();
9815
+ break;
9816
+ }
9817
+ return rxjs.combineLatest([devices$, deviceId$]).pipe(rxjs.filter(([devices, deviceId]) => !!deviceId && !devices.find((d) => d.deviceId === deviceId)), rxjs.map(() => true));
9818
+ };
9819
+ /**
9820
+ * Notifies the subscriber if a given 'audioinput' device is disconnected
9821
+ *
9822
+ * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
9823
+ * @param deviceId$ an Observable that specifies which device to watch for
9824
+ * @returns
9825
+ */
9826
+ const watchForDisconnectedAudioDevice = (deviceId$) => {
9827
+ return watchForDisconnectedDevice('audioinput', deviceId$);
9828
+ };
9829
+ /**
9830
+ * Notifies the subscriber if a given 'videoinput' device is disconnected
9831
+ *
9832
+ * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
9833
+ * @param deviceId$ an Observable that specifies which device to watch for
9834
+ * @returns
9835
+ */
9836
+ const watchForDisconnectedVideoDevice = (deviceId$) => {
9837
+ return watchForDisconnectedDevice('videoinput', deviceId$);
9838
+ };
9839
+ /**
9840
+ * Notifies the subscriber if a given 'audiooutput' device is disconnected
9841
+ *
9842
+ * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
9843
+ * @param deviceId$ an Observable that specifies which device to watch for
9844
+ * @returns
9845
+ */
9846
+ const watchForDisconnectedAudioOutputDevice = (deviceId$) => {
9847
+ return watchForDisconnectedDevice('audiooutput', deviceId$);
9848
+ };
9849
+ const watchForAddedDefaultDevice = (kind) => {
9850
+ let devices$;
9851
+ switch (kind) {
9852
+ case 'audioinput':
9853
+ devices$ = getAudioDevices();
9854
+ break;
9855
+ case 'videoinput':
9856
+ devices$ = getVideoDevices();
9857
+ break;
9858
+ case 'audiooutput':
9859
+ devices$ = getAudioOutputDevices();
9860
+ break;
9861
+ default:
9862
+ throw new Error('Unknown MediaDeviceKind', kind);
9863
+ }
9864
+ return devices$.pipe(rxjs.pairwise(), rxjs.filter(([prev, current]) => {
9865
+ const prevDefault = prev.find((device) => device.deviceId === 'default');
9866
+ const currentDefault = current.find((device) => device.deviceId === 'default');
9867
+ return !!(current.length > prev.length &&
9868
+ prevDefault &&
9869
+ currentDefault &&
9870
+ prevDefault.groupId !== currentDefault.groupId);
9871
+ }), rxjs.map(() => true));
9872
+ };
9873
+ /**
9874
+ * Notifies the subscriber about newly added default audio input device.
9875
+ * @returns Observable<boolean>
9876
+ */
9877
+ const watchForAddedDefaultAudioDevice = () => watchForAddedDefaultDevice('audioinput');
9878
+ /**
9879
+ * Notifies the subscriber about newly added default audio output device.
9880
+ * @returns Observable<boolean>
9881
+ */
9882
+ const watchForAddedDefaultAudioOutputDevice = () => watchForAddedDefaultDevice('audiooutput');
9883
+ /**
9884
+ * Notifies the subscriber about newly added default video input device.
9885
+ * @returns Observable<boolean>
9886
+ */
9887
+ const watchForAddedDefaultVideoDevice = () => watchForAddedDefaultDevice('videoinput');
9888
+ /**
9889
+ * Deactivates MediaStream (stops and removes tracks) to be later garbage collected
9890
+ *
9891
+ * @param stream MediaStream
9892
+ * @returns void
9893
+ */
9894
+ const disposeOfMediaStream = (stream) => {
9895
+ if (!stream.active)
9896
+ return;
9897
+ stream.getTracks().forEach((track) => {
9898
+ track.stop();
9899
+ stream.removeTrack(track);
9900
+ });
9901
+ // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
9902
+ if (typeof stream.release === 'function') {
9903
+ // @ts-expect-error
9904
+ stream.release();
9905
+ }
9906
+ };
9907
+
9908
+ class InputMediaDeviceManager {
9909
+ constructor(call, state) {
9910
+ this.call = call;
9911
+ this.state = state;
9912
+ }
9913
+ /**
9914
+ * Lists the available audio/video devices
9915
+ *
9916
+ * Note: It prompts the user for a permission to use devices (if not already granted)
9917
+ *
9918
+ * @returns an Observable that will be updated if a device is connected or disconnected
9919
+ */
9920
+ listDevices() {
9921
+ return this.getDevices();
9922
+ }
9923
+ /**
9924
+ * Starts camera/microphone
9925
+ */
9926
+ enable() {
9927
+ return __awaiter(this, void 0, void 0, function* () {
9928
+ if (this.state.status === 'enabled') {
9929
+ return;
9930
+ }
9931
+ yield this.startStream();
9932
+ this.state.setStatus('enabled');
9933
+ });
9934
+ }
9935
+ /**
9936
+ * Stops camera/microphone
9937
+ * @returns
9938
+ */
9939
+ disable() {
9940
+ return __awaiter(this, void 0, void 0, function* () {
9941
+ if (this.state.status === 'disabled') {
9942
+ return;
9943
+ }
9944
+ yield this.stopStream();
9945
+ this.state.setStatus('disabled');
9946
+ });
9947
+ }
9948
+ /**
9949
+ * If current device statis is disabled, it will enable the device, else it will disable it.
9950
+ * @returns
9951
+ */
9952
+ toggle() {
9953
+ return __awaiter(this, void 0, void 0, function* () {
9954
+ if (this.state.status === 'enabled') {
9955
+ return this.disable();
9956
+ }
9957
+ else {
9958
+ return this.enable();
9959
+ }
9960
+ });
9961
+ }
9962
+ /**
9963
+ * Select device
9964
+ *
9965
+ * Note: this method is not supported in React Native
9966
+ *
9967
+ * @param deviceId
9968
+ */
9969
+ select(deviceId) {
9970
+ return __awaiter(this, void 0, void 0, function* () {
9971
+ if (isReactNative()) {
9972
+ throw new Error('This method is not supported in React Native');
9973
+ }
9974
+ if (deviceId === this.state.selectedDevice) {
9975
+ return;
9976
+ }
9977
+ this.state.setDevice(deviceId);
9978
+ yield this.applySettingsToStream();
9979
+ });
9980
+ }
9981
+ applySettingsToStream() {
9982
+ return __awaiter(this, void 0, void 0, function* () {
9983
+ if (this.state.status === 'enabled') {
9984
+ yield this.stopStream();
9985
+ yield this.startStream();
9986
+ }
9987
+ });
9988
+ }
9989
+ stopStream() {
9990
+ return __awaiter(this, void 0, void 0, function* () {
9991
+ if (!this.state.mediaStream) {
9992
+ return;
9993
+ }
9994
+ if (this.call.state.callingState === exports.CallingState.JOINED) {
9995
+ yield this.stopPublishStream();
9996
+ }
9997
+ else if (this.state.mediaStream) {
9998
+ disposeOfMediaStream(this.state.mediaStream);
9999
+ }
10000
+ this.state.setMediaStream(undefined);
10001
+ });
10002
+ }
10003
+ startStream() {
10004
+ return __awaiter(this, void 0, void 0, function* () {
10005
+ if (this.state.mediaStream) {
10006
+ return;
10007
+ }
10008
+ const constraints = { deviceId: this.state.selectedDevice };
10009
+ const stream = yield this.getStream(constraints);
10010
+ if (this.call.state.callingState === exports.CallingState.JOINED) {
10011
+ yield this.publishStream(stream);
10012
+ }
10013
+ this.state.setMediaStream(stream);
10014
+ });
10015
+ }
10016
+ }
10017
+
10018
+ class CameraManager extends InputMediaDeviceManager {
10019
+ constructor(call) {
10020
+ super(call, new CameraManagerState());
10021
+ }
10022
+ /**
10023
+ * Select the camera direaction
10024
+ * @param direction
10025
+ */
10026
+ selectDirection(direction) {
10027
+ return __awaiter(this, void 0, void 0, function* () {
10028
+ this.state.setDirection(direction);
10029
+ // Providing both device id and direction doesn't work, so we deselect the device
10030
+ this.state.setDevice(undefined);
10031
+ yield this.applySettingsToStream();
10032
+ });
10033
+ }
10034
+ /**
10035
+ * Flips the camera direction: if it's front it will change to back, if it's back, it will change to front.
10036
+ *
10037
+ * Note: if there is no available camera with the desired direction, this method will do nothing.
10038
+ * @returns
10039
+ */
10040
+ flip() {
10041
+ return __awaiter(this, void 0, void 0, function* () {
10042
+ const newDirection = this.state.direction === 'front' ? 'back' : 'front';
10043
+ this.selectDirection(newDirection);
10044
+ });
10045
+ }
10046
+ getDevices() {
10047
+ return getVideoDevices();
10048
+ }
10049
+ getStream(constraints) {
10050
+ // We can't set both device id and facing mode
10051
+ // Device id has higher priority
10052
+ if (!constraints.deviceId && this.state.direction) {
10053
+ constraints.facingMode =
10054
+ this.state.direction === 'front' ? 'user' : 'environment';
10055
+ }
10056
+ return getVideoStream(constraints);
10057
+ }
10058
+ publishStream(stream) {
10059
+ return this.call.publishVideoStream(stream);
10060
+ }
10061
+ stopPublishStream() {
10062
+ return this.call.stopPublish(TrackType.VIDEO);
10063
+ }
10064
+ /**
10065
+ * Disables the video tracks of the camera
10066
+ */
10067
+ pause() {
10068
+ var _a;
10069
+ (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().forEach((track) => {
10070
+ track.enabled = false;
10071
+ });
10072
+ }
10073
+ /**
10074
+ * (Re)enables the video tracks of the camera
10075
+ */
10076
+ resume() {
10077
+ var _a;
10078
+ (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().forEach((track) => {
10079
+ track.enabled = true;
10080
+ });
10081
+ }
10082
+ }
10083
+
10084
+ class MicrophoneManagerState extends InputMediaDeviceManagerState {
10085
+ getDeviceIdFromStream(stream) {
10086
+ var _a;
10087
+ return (_a = stream.getAudioTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
10088
+ }
10089
+ }
10090
+
10091
+ class MicrophoneManager extends InputMediaDeviceManager {
10092
+ constructor(call) {
10093
+ super(call, new MicrophoneManagerState());
10094
+ }
10095
+ getDevices() {
10096
+ return getAudioDevices();
10097
+ }
10098
+ getStream(constraints) {
10099
+ return getAudioStream(constraints);
10100
+ }
10101
+ publishStream(stream) {
10102
+ return this.call.publishAudioStream(stream);
10103
+ }
10104
+ stopPublishStream() {
10105
+ return this.call.stopPublish(TrackType.AUDIO);
10106
+ }
10107
+ /**
10108
+ * Disables the audio tracks of the microphone
10109
+ */
10110
+ pause() {
10111
+ var _a;
10112
+ (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach((track) => {
10113
+ track.enabled = false;
10114
+ });
10115
+ }
10116
+ /**
10117
+ * (Re)enables the audio tracks of the microphone
10118
+ */
10119
+ resume() {
10120
+ var _a;
10121
+ (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach((track) => {
10122
+ track.enabled = true;
10123
+ });
10124
+ }
10125
+ }
10126
+
10127
+ /**
10128
+ * An object representation of a `Call`.
10129
+ */
10130
+ class Call {
10131
+ /**
10132
+ * Constructs a new `Call` instance.
10133
+ *
10134
+ * NOTE: Don't call the constructor directly, instead
10135
+ * Use the [`StreamVideoClient.call`](./StreamVideoClient.md/#call)
10136
+ * method to construct a `Call` instance.
10137
+ */
10138
+ constructor({ type, id, streamClient, members, ownCapabilities, sortParticipantsBy, clientStore, ringing = false, watching = false, }) {
10139
+ /**
10140
+ * ViewportTracker instance
10141
+ */
10142
+ this.viewportTracker = new ViewportTracker();
10143
+ /**
10144
+ * The state of this call.
10145
+ */
10146
+ this.state = new CallState();
10147
+ /**
10148
+ * The permissions context of this call.
10149
+ */
10150
+ this.permissionsContext = new PermissionsContext();
10151
+ /**
10152
+ * The event dispatcher instance dedicated to this Call instance.
10153
+ * @private
10154
+ */
10155
+ this.dispatcher = new Dispatcher();
10156
+ this.trackSubscriptionsSubject = new rxjs.BehaviorSubject({ type: exports.DebounceType.MEDIUM, data: [] });
10157
+ this.reconnectAttempts = 0;
10158
+ this.maxReconnectAttempts = 10;
10159
+ /**
10160
+ * A list hooks/functions to invoke when the call is left.
10161
+ * A typical use case is to clean up some global event handlers.
10162
+ * @private
10163
+ */
10164
+ this.leaveCallHooks = [];
10165
+ this.streamClientEventHandlers = new Map();
10166
+ /**
10167
+ * Leave the call and stop the media streams that were published by the call.
10168
+ */
10169
+ this.leave = ({ reject = false } = {}) => __awaiter(this, void 0, void 0, function* () {
10170
+ var _a, _b, _c, _d;
10171
+ const callingState = this.state.callingState;
10172
+ if (callingState === exports.CallingState.LEFT) {
10173
+ throw new Error('Cannot leave call that has already been left.');
10174
+ }
10175
+ if (callingState === exports.CallingState.JOINING) {
10176
+ yield this.assertCallJoined();
10177
+ }
10178
+ if (this.ringing) {
10179
+ // I'm the one who started the call, so I should cancel it.
10180
+ const hasOtherParticipants = this.state.remoteParticipants.length > 0;
10181
+ if (this.isCreatedByMe && !hasOtherParticipants) {
10182
+ // Signals other users that I have cancelled my call to them
10183
+ // before they accepted it.
10184
+ yield this.reject();
10185
+ }
10186
+ else if (reject && callingState === exports.CallingState.RINGING) {
10187
+ // Signals other users that I have rejected the incoming call.
10188
+ yield this.reject();
10189
+ }
10190
+ }
10191
+ (_a = this.statsReporter) === null || _a === void 0 ? void 0 : _a.stop();
10192
+ this.statsReporter = undefined;
10193
+ (_b = this.subscriber) === null || _b === void 0 ? void 0 : _b.close();
10194
+ this.subscriber = undefined;
10195
+ (_c = this.publisher) === null || _c === void 0 ? void 0 : _c.close();
10196
+ this.publisher = undefined;
10197
+ (_d = this.sfuClient) === null || _d === void 0 ? void 0 : _d.close();
10198
+ this.sfuClient = undefined;
10199
+ this.dispatcher.offAll();
10200
+ // Call all leave call hooks, e.g. to clean up global event handlers
10201
+ this.leaveCallHooks.forEach((hook) => hook());
10202
+ this.clientStore.unregisterCall(this);
10203
+ this.state.setCallingState(exports.CallingState.LEFT);
10204
+ });
10205
+ /**
10206
+ * Loads the information about the call.
10207
+ *
10208
+ * @param params.ring if set to true, a `call.ring` event will be sent to the call members.
10209
+ * @param params.notify if set to true, a `call.notification` event will be sent to the call members.
10210
+ * @param params.members_limit the members limit.
10211
+ */
10212
+ this.get = (params) => __awaiter(this, void 0, void 0, function* () {
10213
+ const response = yield this.streamClient.get(this.streamClientBasePath, params);
10214
+ if ((params === null || params === void 0 ? void 0 : params.ring) && !this.ringing) {
10215
+ this.ringingSubject.next(true);
10216
+ }
10217
+ this.state.updateFromCallResponse(response.call);
10218
+ this.state.setMembers(response.members);
10219
+ this.state.setOwnCapabilities(response.own_capabilities);
10220
+ if (this.streamClient._hasConnectionID()) {
10221
+ this.watching = true;
10222
+ this.clientStore.registerCall(this);
10223
+ }
10224
+ return response;
10225
+ });
10226
+ /**
10227
+ * Loads the information about the call and creates it if it doesn't exist.
10228
+ *
10229
+ * @param data the data to create the call with.
10230
+ */
10231
+ this.getOrCreate = (data) => __awaiter(this, void 0, void 0, function* () {
10232
+ const response = yield this.streamClient.post(this.streamClientBasePath, data);
10233
+ if ((data === null || data === void 0 ? void 0 : data.ring) && !this.ringing) {
10234
+ this.ringingSubject.next(true);
10235
+ }
10236
+ this.state.updateFromCallResponse(response.call);
10237
+ this.state.setMembers(response.members);
10238
+ this.state.setOwnCapabilities(response.own_capabilities);
10239
+ if (this.streamClient._hasConnectionID()) {
10240
+ this.watching = true;
10241
+ this.clientStore.registerCall(this);
10242
+ }
10243
+ return response;
10244
+ });
10245
+ /**
10246
+ * Creates a call
10247
+ *
10248
+ * @param data the data to create the call with.
10249
+ */
10250
+ this.create = (data) => __awaiter(this, void 0, void 0, function* () {
10251
+ return this.getOrCreate(data);
10252
+ });
10253
+ /**
10254
+ * A shortcut for {@link Call.get} with `ring` parameter set to `true`.
10255
+ * Will send a `call.ring` event to the call members.
10256
+ */
10257
+ this.ring = () => __awaiter(this, void 0, void 0, function* () {
10258
+ return yield this.get({ ring: true });
10259
+ });
10260
+ /**
10261
+ * A shortcut for {@link Call.get} with `notify` parameter set to `true`.
10262
+ * Will send a `call.notification` event to the call members.
10263
+ */
10264
+ this.notify = () => __awaiter(this, void 0, void 0, function* () {
10265
+ return yield this.get({ notify: true });
9679
10266
  });
9680
10267
  /**
9681
10268
  * Marks the incoming call as accepted.
@@ -9703,7 +10290,7 @@ class Call {
9703
10290
  * @returns a promise which resolves once the call join-flow has finished.
9704
10291
  */
9705
10292
  this.join = (data) => __awaiter(this, void 0, void 0, function* () {
9706
- var _e, _f, _g;
10293
+ var _e, _f, _g, _h;
9707
10294
  const callingState = this.state.callingState;
9708
10295
  if ([exports.CallingState.JOINED, exports.CallingState.JOINING].includes(callingState)) {
9709
10296
  this.logger('warn', 'Join method called twice, you should only call this once');
@@ -9972,6 +10559,11 @@ class Call {
9972
10559
  this.state.setServerSidePins(pins);
9973
10560
  this.reconnectAttempts = 0; // reset the reconnect attempts counter
9974
10561
  this.state.setCallingState(exports.CallingState.JOINED);
10562
+ // React uses a different device management for now
10563
+ if (((_h = getSdkInfo()) === null || _h === void 0 ? void 0 : _h.type) !== SdkType.REACT) {
10564
+ this.initCamera();
10565
+ this.initMic();
10566
+ }
9975
10567
  // 3. once we have the "joinResponse", and possibly reconciled the local state
9976
10568
  // we schedule a fast subscription update for all remote participants
9977
10569
  // that were visible before we reconnected or migrated to a new SFU.
@@ -10094,9 +10686,9 @@ class Call {
10094
10686
  * @param trackType the track type to stop publishing.
10095
10687
  */
10096
10688
  this.stopPublish = (trackType) => __awaiter(this, void 0, void 0, function* () {
10097
- var _h;
10689
+ var _j;
10098
10690
  this.logger('info', `stopPublish ${TrackType[trackType]}`);
10099
- yield ((_h = this.publisher) === null || _h === void 0 ? void 0 : _h.unpublishStream(trackType));
10691
+ yield ((_j = this.publisher) === null || _j === void 0 ? void 0 : _j.unpublishStream(trackType));
10100
10692
  });
10101
10693
  /**
10102
10694
  * Update track subscription configuration for one or more participants.
@@ -10197,6 +10789,8 @@ class Call {
10197
10789
  *
10198
10790
  *
10199
10791
  * @param deviceId the selected device, pass `undefined` to clear the device selection
10792
+ *
10793
+ * @deprecated use call.microphone.select
10200
10794
  */
10201
10795
  this.setAudioDevice = (deviceId) => {
10202
10796
  if (!this.sfuClient)
@@ -10211,6 +10805,8 @@ class Call {
10211
10805
  * This method only stores the selection, if you want to start publishing a media stream call the [`publishVideoStream` method](#publishvideostream) that will set `videoDeviceId` as well.
10212
10806
  *
10213
10807
  * @param deviceId the selected device, pass `undefined` to clear the device selection
10808
+ *
10809
+ * @deprecated use call.camera.select
10214
10810
  */
10215
10811
  this.setVideoDevice = (deviceId) => {
10216
10812
  if (!this.sfuClient)
@@ -10243,8 +10839,8 @@ class Call {
10243
10839
  * @returns
10244
10840
  */
10245
10841
  this.updatePublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
10246
- var _j;
10247
- return (_j = this.publisher) === null || _j === void 0 ? void 0 : _j.updateVideoPublishQuality(enabledRids);
10842
+ var _k;
10843
+ return (_k = this.publisher) === null || _k === void 0 ? void 0 : _k.updateVideoPublishQuality(enabledRids);
10248
10844
  });
10249
10845
  this.assertCallJoined = () => {
10250
10846
  return new Promise((resolve) => {
@@ -10587,6 +11183,8 @@ class Call {
10587
11183
  this.leaveCallHooks.push(registerEventHandlers(this, this.state, this.dispatcher));
10588
11184
  this.registerEffects();
10589
11185
  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); }));
11186
+ this.camera = new CameraManager(this);
11187
+ this.microphone = new MicrophoneManager(this);
10590
11188
  }
10591
11189
  registerEffects() {
10592
11190
  this.leaveCallHooks.push(
@@ -10694,6 +11292,64 @@ class Call {
10694
11292
  var _a;
10695
11293
  return ((_a = this.state.createdBy) === null || _a === void 0 ? void 0 : _a.id) === this.currentUserId;
10696
11294
  }
11295
+ initCamera() {
11296
+ var _a, _b, _c;
11297
+ if (((_a = this.state.localParticipant) === null || _a === void 0 ? void 0 : _a.videoStream) ||
11298
+ !this.permissionsContext.hasPermission('send-video')) {
11299
+ return;
11300
+ }
11301
+ // Set camera direction if it's not yet set
11302
+ // This will also start publishing if camera is enabled
11303
+ if (!this.camera.state.direction && !this.camera.state.selectedDevice) {
11304
+ let defaultDirection = 'front';
11305
+ const backendSetting = (_b = this.state.settings) === null || _b === void 0 ? void 0 : _b.video.camera_facing;
11306
+ if (backendSetting) {
11307
+ defaultDirection = backendSetting === 'front' ? 'front' : 'back';
11308
+ }
11309
+ this.camera.selectDirection(defaultDirection);
11310
+ }
11311
+ else if (this.camera.state.status === 'enabled') {
11312
+ // Publish already started media streams (this is the case if there is a lobby screen before join)
11313
+ // Wait for media stream
11314
+ this.camera.state.mediaStream$
11315
+ .pipe(rxjs.takeWhile((s) => s === undefined, true))
11316
+ .subscribe((stream) => {
11317
+ var _a;
11318
+ if (!((_a = this.state.localParticipant) === null || _a === void 0 ? void 0 : _a.videoStream)) {
11319
+ this.publishVideoStream(stream);
11320
+ }
11321
+ });
11322
+ }
11323
+ // Apply backend config (this is the case if there is no lobby screen before join)
11324
+ if (this.camera.state.status === undefined &&
11325
+ ((_c = this.state.settings) === null || _c === void 0 ? void 0 : _c.video.camera_default_on)) {
11326
+ void this.camera.enable();
11327
+ }
11328
+ }
11329
+ initMic() {
11330
+ var _a, _b;
11331
+ if (((_a = this.state.localParticipant) === null || _a === void 0 ? void 0 : _a.audioStream) ||
11332
+ !this.permissionsContext.hasPermission('send-audio')) {
11333
+ return;
11334
+ }
11335
+ // Publish already started media streams (this is the case if there is a lobby screen before join)
11336
+ if (this.microphone.state.status === 'enabled') {
11337
+ // Wait for media stream
11338
+ this.microphone.state.mediaStream$
11339
+ .pipe(rxjs.takeWhile((s) => s === undefined, true))
11340
+ .subscribe((stream) => {
11341
+ var _a;
11342
+ if (!((_a = this.state.localParticipant) === null || _a === void 0 ? void 0 : _a.audioStream)) {
11343
+ this.publishAudioStream(stream);
11344
+ }
11345
+ });
11346
+ }
11347
+ // Apply backend config (this is the case if there is no lobby screen before join)
11348
+ if (this.microphone.state.status === undefined &&
11349
+ ((_b = this.state.settings) === null || _b === void 0 ? void 0 : _b.audio.mic_default_on)) {
11350
+ void this.microphone.enable();
11351
+ }
11352
+ }
10697
11353
  }
10698
11354
 
10699
11355
  class InsightMetrics {
@@ -11805,7 +12461,7 @@ class WSConnectionFallback {
11805
12461
  }
11806
12462
  }
11807
12463
 
11808
- const version = '0.3.0';
12464
+ const version = '0.3.2';
11809
12465
 
11810
12466
  const logger = getLogger(['location']);
11811
12467
  const HINT_URL = `https://hint.stream-io-video.com/`;
@@ -12779,254 +13435,6 @@ class StreamVideoServerClient extends StreamVideoClient {
12779
13435
  }
12780
13436
  }
12781
13437
 
12782
- const getDevices = (constraints) => {
12783
- return new rxjs.Observable((subscriber) => {
12784
- navigator.mediaDevices
12785
- .getUserMedia(constraints)
12786
- .then((media) => {
12787
- // in Firefox, devices can be enumerated after userMedia is requested
12788
- // and permissions granted. Otherwise, device labels are empty
12789
- navigator.mediaDevices.enumerateDevices().then((devices) => {
12790
- subscriber.next(devices);
12791
- // If we stop the tracks before enumerateDevices -> the labels won't show up in Firefox
12792
- disposeOfMediaStream(media);
12793
- subscriber.complete();
12794
- });
12795
- })
12796
- .catch((error) => {
12797
- getLogger(['devices'])('error', 'Failed to get devices', error);
12798
- subscriber.error(error);
12799
- });
12800
- });
12801
- };
12802
- /**
12803
- * [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
12804
- *
12805
- * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
12806
- */
12807
- const checkIfAudioOutputChangeSupported = () => {
12808
- if (typeof document === 'undefined')
12809
- return false;
12810
- const element = document.createElement('audio');
12811
- return element.sinkId !== undefined;
12812
- };
12813
- /**
12814
- * The default constraints used to request audio devices.
12815
- */
12816
- const audioDeviceConstraints = {
12817
- audio: {
12818
- autoGainControl: true,
12819
- noiseSuppression: true,
12820
- echoCancellation: true,
12821
- },
12822
- };
12823
- /**
12824
- * The default constraints used to request video devices.
12825
- */
12826
- const videoDeviceConstraints = {
12827
- video: {
12828
- width: 1280,
12829
- height: 720,
12830
- },
12831
- };
12832
- // Audio and video devices are requested in two separate requests: that way users will be presented with two separate prompts -> they can give access to just camera, or just microphone
12833
- const deviceChange$ = new rxjs.Observable((subscriber) => {
12834
- var _a, _b;
12835
- const deviceChangeHandler = () => subscriber.next();
12836
- (_b = (_a = navigator.mediaDevices).addEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
12837
- return () => {
12838
- var _a, _b;
12839
- return (_b = (_a = navigator.mediaDevices).removeEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
12840
- };
12841
- }).pipe(rxjs.debounceTime(500), rxjs.concatMap(() => rxjs.from(navigator.mediaDevices.enumerateDevices())), rxjs.shareReplay(1));
12842
- const audioDevices$ = rxjs.merge(getDevices(audioDeviceConstraints), deviceChange$).pipe(rxjs.shareReplay(1));
12843
- const videoDevices$ = rxjs.merge(getDevices(videoDeviceConstraints), deviceChange$).pipe(rxjs.shareReplay(1));
12844
- /**
12845
- * Prompts the user for a permission to use audio devices (if not already granted) and lists the available 'audioinput' devices, if devices are added/removed the list is updated.
12846
- *
12847
- * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
12848
- * @returns
12849
- */
12850
- const getAudioDevices = () => audioDevices$.pipe(rxjs.map((values) => values.filter((d) => d.kind === 'audioinput')));
12851
- /**
12852
- * Prompts the user for a permission to use video devices (if not already granted) and lists the available 'videoinput' devices, if devices are added/removed the list is updated.
12853
- *
12854
- * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
12855
- * @returns
12856
- */
12857
- const getVideoDevices = () => videoDevices$.pipe(rxjs.map((values) => values.filter((d) => d.kind === 'videoinput' && d.deviceId.length)));
12858
- /**
12859
- * Prompts the user for a permission to use audio devices (if not already granted) and lists the available 'audiooutput' devices, if devices are added/removed the list is updated. Selecting 'audiooutput' device only makes sense if [the browser has support for changing audio output on 'audio' elements](#checkifaudiooutputchangesupported)
12860
- *
12861
- * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
12862
- * @returns
12863
- */
12864
- const getAudioOutputDevices = () => {
12865
- return audioDevices$.pipe(rxjs.map((values) => values.filter((d) => d.kind === 'audiooutput')));
12866
- };
12867
- const getStream = (constraints) => __awaiter(void 0, void 0, void 0, function* () {
12868
- try {
12869
- return yield navigator.mediaDevices.getUserMedia(constraints);
12870
- }
12871
- catch (e) {
12872
- getLogger(['devices'])('error', `Failed get user media`, {
12873
- error: e,
12874
- constraints: constraints,
12875
- });
12876
- throw e;
12877
- }
12878
- });
12879
- /**
12880
- * Returns an audio media stream that fulfills the given constraints.
12881
- * If no constraints are provided, it uses the browser's default ones.
12882
- *
12883
- * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
12884
- * @param trackConstraints the constraints to use when requesting the stream.
12885
- * @returns the new `MediaStream` fulfilling the given constraints.
12886
- */
12887
- const getAudioStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
12888
- const constraints = {
12889
- audio: Object.assign(Object.assign({}, audioDeviceConstraints.audio), trackConstraints),
12890
- };
12891
- return getStream(constraints);
12892
- });
12893
- /**
12894
- * Returns a video media stream that fulfills the given constraints.
12895
- * If no constraints are provided, it uses the browser's default ones.
12896
- *
12897
- * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
12898
- * @param trackConstraints the constraints to use when requesting the stream.
12899
- * @returns a new `MediaStream` fulfilling the given constraints.
12900
- */
12901
- const getVideoStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
12902
- const constraints = {
12903
- video: Object.assign(Object.assign({}, videoDeviceConstraints.video), trackConstraints),
12904
- };
12905
- return getStream(constraints);
12906
- });
12907
- /**
12908
- * Prompts the user for a permission to share a screen.
12909
- * If the user grants the permission, a screen sharing stream is returned. Throws otherwise.
12910
- *
12911
- * The callers of this API are responsible to handle the possible errors.
12912
- *
12913
- * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
12914
- *
12915
- * @param options any additional options to pass to the [`getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) API.
12916
- */
12917
- const getScreenShareStream = (options) => __awaiter(void 0, void 0, void 0, function* () {
12918
- try {
12919
- return yield navigator.mediaDevices.getDisplayMedia(Object.assign({ video: true, audio: false }, options));
12920
- }
12921
- catch (e) {
12922
- getLogger(['devices'])('error', 'Failed to get screen share stream', e);
12923
- throw e;
12924
- }
12925
- });
12926
- const watchForDisconnectedDevice = (kind, deviceId$) => {
12927
- let devices$;
12928
- switch (kind) {
12929
- case 'audioinput':
12930
- devices$ = getAudioDevices();
12931
- break;
12932
- case 'videoinput':
12933
- devices$ = getVideoDevices();
12934
- break;
12935
- case 'audiooutput':
12936
- devices$ = getAudioOutputDevices();
12937
- break;
12938
- }
12939
- return rxjs.combineLatest([devices$, deviceId$]).pipe(rxjs.filter(([devices, deviceId]) => !!deviceId && !devices.find((d) => d.deviceId === deviceId)), rxjs.map(() => true));
12940
- };
12941
- /**
12942
- * Notifies the subscriber if a given 'audioinput' device is disconnected
12943
- *
12944
- * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
12945
- * @param deviceId$ an Observable that specifies which device to watch for
12946
- * @returns
12947
- */
12948
- const watchForDisconnectedAudioDevice = (deviceId$) => {
12949
- return watchForDisconnectedDevice('audioinput', deviceId$);
12950
- };
12951
- /**
12952
- * Notifies the subscriber if a given 'videoinput' device is disconnected
12953
- *
12954
- * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
12955
- * @param deviceId$ an Observable that specifies which device to watch for
12956
- * @returns
12957
- */
12958
- const watchForDisconnectedVideoDevice = (deviceId$) => {
12959
- return watchForDisconnectedDevice('videoinput', deviceId$);
12960
- };
12961
- /**
12962
- * Notifies the subscriber if a given 'audiooutput' device is disconnected
12963
- *
12964
- * @angular It's recommended to use the [`DeviceManagerService`](./DeviceManagerService.md) for a higher level API, use this low-level method only if the `DeviceManagerService` doesn't suit your requirements.
12965
- * @param deviceId$ an Observable that specifies which device to watch for
12966
- * @returns
12967
- */
12968
- const watchForDisconnectedAudioOutputDevice = (deviceId$) => {
12969
- return watchForDisconnectedDevice('audiooutput', deviceId$);
12970
- };
12971
- const watchForAddedDefaultDevice = (kind) => {
12972
- let devices$;
12973
- switch (kind) {
12974
- case 'audioinput':
12975
- devices$ = getAudioDevices();
12976
- break;
12977
- case 'videoinput':
12978
- devices$ = getVideoDevices();
12979
- break;
12980
- case 'audiooutput':
12981
- devices$ = getAudioOutputDevices();
12982
- break;
12983
- default:
12984
- throw new Error('Unknown MediaDeviceKind', kind);
12985
- }
12986
- return devices$.pipe(rxjs.pairwise(), rxjs.filter(([prev, current]) => {
12987
- const prevDefault = prev.find((device) => device.deviceId === 'default');
12988
- const currentDefault = current.find((device) => device.deviceId === 'default');
12989
- return !!(current.length > prev.length &&
12990
- prevDefault &&
12991
- currentDefault &&
12992
- prevDefault.groupId !== currentDefault.groupId);
12993
- }), rxjs.map(() => true));
12994
- };
12995
- /**
12996
- * Notifies the subscriber about newly added default audio input device.
12997
- * @returns Observable<boolean>
12998
- */
12999
- const watchForAddedDefaultAudioDevice = () => watchForAddedDefaultDevice('audioinput');
13000
- /**
13001
- * Notifies the subscriber about newly added default audio output device.
13002
- * @returns Observable<boolean>
13003
- */
13004
- const watchForAddedDefaultAudioOutputDevice = () => watchForAddedDefaultDevice('audiooutput');
13005
- /**
13006
- * Notifies the subscriber about newly added default video input device.
13007
- * @returns Observable<boolean>
13008
- */
13009
- const watchForAddedDefaultVideoDevice = () => watchForAddedDefaultDevice('videoinput');
13010
- /**
13011
- * Deactivates MediaStream (stops and removes tracks) to be later garbage collected
13012
- *
13013
- * @param stream MediaStream
13014
- * @returns void
13015
- */
13016
- const disposeOfMediaStream = (stream) => {
13017
- if (!stream.active)
13018
- return;
13019
- stream.getTracks().forEach((track) => {
13020
- track.stop();
13021
- stream.removeTrack(track);
13022
- });
13023
- // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
13024
- if (typeof stream.release === 'function') {
13025
- // @ts-expect-error
13026
- stream.release();
13027
- }
13028
- };
13029
-
13030
13438
  const DETECTION_FREQUENCY_IN_MS = 500;
13031
13439
  const AUDIO_LEVEL_THRESHOLD = 150;
13032
13440
  const FFT_SIZE = 128;
@@ -13118,8 +13526,14 @@ exports.Call = Call;
13118
13526
  exports.CallState = CallState;
13119
13527
  exports.CallType = CallType;
13120
13528
  exports.CallTypes = CallTypes;
13529
+ exports.CameraManager = CameraManager;
13530
+ exports.CameraManagerState = CameraManagerState;
13121
13531
  exports.CreateDeviceRequestPushProviderEnum = CreateDeviceRequestPushProviderEnum;
13122
13532
  exports.ErrorFromResponse = ErrorFromResponse;
13533
+ exports.InputMediaDeviceManager = InputMediaDeviceManager;
13534
+ exports.InputMediaDeviceManagerState = InputMediaDeviceManagerState;
13535
+ exports.MicrophoneManager = MicrophoneManager;
13536
+ exports.MicrophoneManagerState = MicrophoneManagerState;
13123
13537
  exports.OwnCapability = OwnCapability;
13124
13538
  exports.RecordSettingsModeEnum = RecordSettingsModeEnum;
13125
13539
  exports.RecordSettingsQualityEnum = RecordSettingsQualityEnum;