@stream-io/video-client 0.3.12 → 0.3.14

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 (35) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/index.browser.es.js +614 -391
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +613 -390
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +614 -391
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +6 -1
  9. package/dist/src/devices/CameraManager.d.ts +1 -2
  10. package/dist/src/devices/InputMediaDeviceManager.d.ts +10 -3
  11. package/dist/src/devices/MicrophoneManager.d.ts +1 -2
  12. package/dist/src/devices/SpeakerManager.d.ts +28 -0
  13. package/dist/src/devices/SpeakerState.d.ts +64 -0
  14. package/dist/src/devices/__tests__/SpeakerManager.test.d.ts +1 -0
  15. package/dist/src/rtc/Publisher.d.ts +6 -0
  16. package/dist/src/types.d.ts +2 -0
  17. package/dist/version.d.ts +1 -1
  18. package/package.json +1 -1
  19. package/src/Call.ts +91 -6
  20. package/src/StreamSfuClient.ts +4 -0
  21. package/src/coordinator/connection/location.ts +2 -2
  22. package/src/devices/CameraManager.ts +7 -8
  23. package/src/devices/InputMediaDeviceManager.ts +58 -12
  24. package/src/devices/MicrophoneManager.ts +3 -8
  25. package/src/devices/SpeakerManager.ts +50 -0
  26. package/src/devices/SpeakerState.ts +90 -0
  27. package/src/devices/__tests__/InputMediaDeviceManager.test.ts +5 -12
  28. package/src/devices/__tests__/SpeakerManager.test.ts +66 -0
  29. package/src/devices/__tests__/mocks.ts +4 -0
  30. package/src/helpers/DynascaleManager.ts +25 -7
  31. package/src/helpers/__tests__/DynascaleManager.test.ts +33 -2
  32. package/src/rtc/Publisher.ts +35 -7
  33. package/src/rtc/__tests__/Publisher.test.ts +1 -0
  34. package/src/rtc/codecs.ts +0 -2
  35. package/src/types.ts +2 -0
package/dist/index.es.js CHANGED
@@ -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, map as map$2, takeWhile, distinctUntilChanged as distinctUntilChanged$1, distinctUntilKeyChanged, Observable, debounceTime, concatMap, from, shareReplay, merge, combineLatest, filter, pairwise, tap, debounce, timer } from 'rxjs';
7
+ import { ReplaySubject, BehaviorSubject, map as map$2, takeWhile, distinctUntilChanged as distinctUntilChanged$1, distinctUntilKeyChanged, combineLatest, Observable, debounceTime, concatMap, from, shareReplay, merge, 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';
@@ -6493,6 +6493,9 @@ class Publisher {
6493
6493
  // by an external factor as permission revokes, device disconnected, etc.
6494
6494
  // keep in mind that `track.stop()` doesn't trigger this event.
6495
6495
  track.addEventListener('ended', handleTrackEnded);
6496
+ if (!track.enabled) {
6497
+ track.enabled = true;
6498
+ }
6496
6499
  transceiver = this.pc.addTransceiver(track, {
6497
6500
  direction: 'sendonly',
6498
6501
  streams: trackType === TrackType.VIDEO || trackType === TrackType.SCREEN_SHARE
@@ -6530,16 +6533,25 @@ class Publisher {
6530
6533
  * @param stopTrack specifies whether track should be stopped or just disabled
6531
6534
  */
6532
6535
  this.unpublishStream = (trackType, stopTrack) => __awaiter(this, void 0, void 0, function* () {
6536
+ var _b;
6533
6537
  const transceiver = this.pc
6534
6538
  .getTransceivers()
6535
6539
  .find((t) => t === this.transceiverRegistry[trackType] && t.sender.track);
6536
6540
  if (transceiver &&
6537
6541
  transceiver.sender.track &&
6538
- transceiver.sender.track.readyState === 'live') {
6542
+ (stopTrack
6543
+ ? transceiver.sender.track.readyState === 'live'
6544
+ : transceiver.sender.track.enabled)) {
6539
6545
  stopTrack
6540
6546
  ? transceiver.sender.track.stop()
6541
6547
  : (transceiver.sender.track.enabled = false);
6542
- return this.notifyTrackMuteStateChanged(undefined, transceiver.sender.track, trackType, true);
6548
+ // We don't need to notify SFU if unpublishing in response to remote soft mute
6549
+ if (!((_b = this.state.localParticipant) === null || _b === void 0 ? void 0 : _b.publishedTracks.includes(trackType))) {
6550
+ return;
6551
+ }
6552
+ else {
6553
+ return this.notifyTrackMuteStateChanged(undefined, transceiver.sender.track, trackType, true);
6554
+ }
6543
6555
  }
6544
6556
  });
6545
6557
  /**
@@ -6548,6 +6560,21 @@ class Publisher {
6548
6560
  * @param trackType the track type to check.
6549
6561
  */
6550
6562
  this.isPublishing = (trackType) => {
6563
+ const transceiverForTrackType = this.transceiverRegistry[trackType];
6564
+ if (transceiverForTrackType && transceiverForTrackType.sender) {
6565
+ const sender = transceiverForTrackType.sender;
6566
+ return (!!sender.track &&
6567
+ sender.track.readyState === 'live' &&
6568
+ sender.track.enabled);
6569
+ }
6570
+ return false;
6571
+ };
6572
+ /**
6573
+ * Returns true if the given track type is currently live
6574
+ *
6575
+ * @param trackType the track type to check.
6576
+ */
6577
+ this.isLive = (trackType) => {
6551
6578
  const transceiverForTrackType = this.transceiverRegistry[trackType];
6552
6579
  if (transceiverForTrackType && transceiverForTrackType.sender) {
6553
6580
  const sender = transceiverForTrackType.sender;
@@ -6588,9 +6615,9 @@ class Publisher {
6588
6615
  });
6589
6616
  };
6590
6617
  this.updateVideoPublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
6591
- var _b;
6618
+ var _c;
6592
6619
  logger$3('info', 'Update publish quality, requested rids by SFU:', enabledRids);
6593
- const videoSender = (_b = this.transceiverRegistry[TrackType.VIDEO]) === null || _b === void 0 ? void 0 : _b.sender;
6620
+ const videoSender = (_c = this.transceiverRegistry[TrackType.VIDEO]) === null || _c === void 0 ? void 0 : _c.sender;
6594
6621
  if (!videoSender) {
6595
6622
  logger$3('warn', 'Update publish quality, no video sender found.');
6596
6623
  return;
@@ -6688,8 +6715,8 @@ class Publisher {
6688
6715
  * @param options the optional offer options to use.
6689
6716
  */
6690
6717
  this.negotiate = (options) => __awaiter(this, void 0, void 0, function* () {
6691
- var _c;
6692
- this.isIceRestarting = (_c = options === null || options === void 0 ? void 0 : options.iceRestart) !== null && _c !== void 0 ? _c : false;
6718
+ var _d;
6719
+ this.isIceRestarting = (_d = options === null || options === void 0 ? void 0 : options.iceRestart) !== null && _d !== void 0 ? _d : false;
6693
6720
  const offer = yield this.pc.createOffer(options);
6694
6721
  offer.sdp = this.mungeCodecs(offer.sdp);
6695
6722
  const trackInfos = this.getCurrentTrackInfos(offer.sdp);
@@ -7477,6 +7504,9 @@ const retryable = (rpc, logger) => __awaiter(void 0, void 0, void 0, function* (
7477
7504
  retryAttempt++;
7478
7505
  } while (((_a = rpcCallResult.response.error) === null || _a === void 0 ? void 0 : _a.shouldRetry) &&
7479
7506
  retryAttempt < MAX_RETRIES);
7507
+ if (rpcCallResult.response.error) {
7508
+ throw rpcCallResult.response.error;
7509
+ }
7480
7510
  return rpcCallResult;
7481
7511
  });
7482
7512
 
@@ -9513,15 +9543,26 @@ class DynascaleManager {
9513
9543
  }
9514
9544
  });
9515
9545
  });
9516
- const sinkIdSubscription = this.call.state.localParticipant$.subscribe((p) => {
9517
- if (p && p.audioOutputDeviceId && 'setSinkId' in audioElement) {
9546
+ const sinkIdSubscription = combineLatest([
9547
+ this.call.state.localParticipant$,
9548
+ this.call.speaker.state.selectedDevice$,
9549
+ ]).subscribe(([p, selectedDevice]) => {
9550
+ var _a;
9551
+ const deviceId = ((_a = getSdkInfo()) === null || _a === void 0 ? void 0 : _a.type) === SdkType.REACT
9552
+ ? p === null || p === void 0 ? void 0 : p.audioOutputDeviceId
9553
+ : selectedDevice;
9554
+ if ('setSinkId' in audioElement) {
9518
9555
  // @ts-expect-error setSinkId is not yet in the lib
9519
- audioElement.setSinkId(p.audioOutputDeviceId);
9556
+ audioElement.setSinkId(deviceId);
9520
9557
  }
9521
9558
  });
9559
+ const volumeSubscription = this.call.speaker.state.volume$.subscribe((volume) => {
9560
+ audioElement.volume = volume;
9561
+ });
9522
9562
  audioElement.autoplay = true;
9523
9563
  return () => {
9524
9564
  sinkIdSubscription.unsubscribe();
9565
+ volumeSubscription.unsubscribe();
9525
9566
  updateMediaStreamSubscription.unsubscribe();
9526
9567
  };
9527
9568
  };
@@ -9668,258 +9709,133 @@ const CallTypes = new CallTypesRegistry([
9668
9709
  }),
9669
9710
  ]);
9670
9711
 
9671
- const getDevices = (constraints) => {
9672
- return new Observable((subscriber) => {
9673
- navigator.mediaDevices
9674
- .getUserMedia(constraints)
9675
- .then((media) => {
9676
- // in Firefox, devices can be enumerated after userMedia is requested
9677
- // and permissions granted. Otherwise, device labels are empty
9678
- navigator.mediaDevices.enumerateDevices().then((devices) => {
9679
- subscriber.next(devices);
9680
- // If we stop the tracks before enumerateDevices -> the labels won't show up in Firefox
9681
- disposeOfMediaStream(media);
9682
- subscriber.complete();
9683
- });
9684
- })
9685
- .catch((error) => {
9686
- getLogger(['devices'])('error', 'Failed to get devices', error);
9687
- subscriber.error(error);
9688
- });
9689
- });
9690
- };
9691
- /**
9692
- * [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
9693
- *
9694
- * @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.
9695
- */
9696
- const checkIfAudioOutputChangeSupported = () => {
9697
- if (typeof document === 'undefined')
9698
- return false;
9699
- const element = document.createElement('audio');
9700
- return element.sinkId !== undefined;
9701
- };
9702
- /**
9703
- * The default constraints used to request audio devices.
9704
- */
9705
- const audioDeviceConstraints = {
9706
- audio: {
9707
- autoGainControl: true,
9708
- noiseSuppression: true,
9709
- echoCancellation: true,
9710
- },
9711
- };
9712
- /**
9713
- * The default constraints used to request video devices.
9714
- */
9715
- const videoDeviceConstraints = {
9716
- video: {
9717
- width: 1280,
9718
- height: 720,
9719
- },
9720
- };
9721
- // 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
9722
- const deviceChange$ = new Observable((subscriber) => {
9723
- var _a, _b;
9724
- const deviceChangeHandler = () => subscriber.next();
9725
- (_b = (_a = navigator.mediaDevices).addEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
9726
- return () => {
9727
- var _a, _b;
9728
- return (_b = (_a = navigator.mediaDevices).removeEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
9729
- };
9730
- }).pipe(debounceTime(500), concatMap(() => from(navigator.mediaDevices.enumerateDevices())), shareReplay(1));
9731
- const audioDevices$ = merge(getDevices(audioDeviceConstraints), deviceChange$).pipe(shareReplay(1));
9732
- const videoDevices$ = merge(getDevices(videoDeviceConstraints), deviceChange$).pipe(shareReplay(1));
9733
- /**
9734
- * 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.
9735
- *
9736
- * @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.
9737
- * @returns
9738
- */
9739
- const getAudioDevices = () => audioDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'audioinput')));
9740
- /**
9741
- * 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.
9742
- *
9743
- * @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.
9744
- * @returns
9745
- */
9746
- const getVideoDevices = () => videoDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'videoinput' && d.deviceId.length)));
9747
- /**
9748
- * 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)
9749
- *
9750
- * @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.
9751
- * @returns
9752
- */
9753
- const getAudioOutputDevices = () => {
9754
- return audioDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'audiooutput')));
9755
- };
9756
- const getStream = (constraints) => __awaiter(void 0, void 0, void 0, function* () {
9757
- try {
9758
- return yield navigator.mediaDevices.getUserMedia(constraints);
9712
+ class InputMediaDeviceManagerState {
9713
+ constructor(disableMode = 'stop-tracks') {
9714
+ this.disableMode = disableMode;
9715
+ this.statusSubject = new BehaviorSubject(undefined);
9716
+ this.mediaStreamSubject = new BehaviorSubject(undefined);
9717
+ this.selectedDeviceSubject = new BehaviorSubject(undefined);
9718
+ /**
9719
+ * Gets the current value of an observable, or undefined if the observable has
9720
+ * not emitted a value yet.
9721
+ *
9722
+ * @param observable$ the observable to get the value from.
9723
+ */
9724
+ this.getCurrentValue = getCurrentValue;
9725
+ /**
9726
+ * Updates the value of the provided Subject.
9727
+ * An `update` can either be a new value or a function which takes
9728
+ * the current value and returns a new value.
9729
+ *
9730
+ * @internal
9731
+ *
9732
+ * @param subject the subject to update.
9733
+ * @param update the update to apply to the subject.
9734
+ * @return the updated value.
9735
+ */
9736
+ this.setCurrentValue = setCurrentValue;
9737
+ this.mediaStream$ = this.mediaStreamSubject.asObservable();
9738
+ this.selectedDevice$ = this.selectedDeviceSubject
9739
+ .asObservable()
9740
+ .pipe(distinctUntilChanged$1());
9741
+ this.status$ = this.statusSubject
9742
+ .asObservable()
9743
+ .pipe(distinctUntilChanged$1());
9759
9744
  }
9760
- catch (e) {
9761
- getLogger(['devices'])('error', `Failed get user media`, {
9762
- error: e,
9763
- constraints: constraints,
9764
- });
9765
- throw e;
9745
+ /**
9746
+ * The device status
9747
+ */
9748
+ get status() {
9749
+ return this.getCurrentValue(this.status$);
9766
9750
  }
9767
- });
9768
- /**
9769
- * Returns an audio media stream that fulfills the given constraints.
9770
- * If no constraints are provided, it uses the browser's default ones.
9771
- *
9772
- * @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.
9773
- * @param trackConstraints the constraints to use when requesting the stream.
9774
- * @returns the new `MediaStream` fulfilling the given constraints.
9775
- */
9776
- const getAudioStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
9777
- const constraints = {
9778
- audio: Object.assign(Object.assign({}, audioDeviceConstraints.audio), trackConstraints),
9779
- };
9780
- return getStream(constraints);
9781
- });
9782
- /**
9783
- * Returns a video media stream that fulfills the given constraints.
9784
- * If no constraints are provided, it uses the browser's default ones.
9785
- *
9786
- * @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.
9787
- * @param trackConstraints the constraints to use when requesting the stream.
9788
- * @returns a new `MediaStream` fulfilling the given constraints.
9789
- */
9790
- const getVideoStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
9791
- const constraints = {
9792
- video: Object.assign(Object.assign({}, videoDeviceConstraints.video), trackConstraints),
9793
- };
9794
- return getStream(constraints);
9795
- });
9796
- /**
9797
- * Prompts the user for a permission to share a screen.
9798
- * If the user grants the permission, a screen sharing stream is returned. Throws otherwise.
9799
- *
9800
- * The callers of this API are responsible to handle the possible errors.
9801
- *
9802
- * @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.
9803
- *
9804
- * @param options any additional options to pass to the [`getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) API.
9805
- */
9806
- const getScreenShareStream = (options) => __awaiter(void 0, void 0, void 0, function* () {
9807
- try {
9808
- return yield navigator.mediaDevices.getDisplayMedia(Object.assign({ video: true, audio: false }, options));
9751
+ /**
9752
+ * The currently selected device
9753
+ */
9754
+ get selectedDevice() {
9755
+ return this.getCurrentValue(this.selectedDevice$);
9809
9756
  }
9810
- catch (e) {
9811
- getLogger(['devices'])('error', 'Failed to get screen share stream', e);
9812
- throw e;
9757
+ /**
9758
+ * The current media stream, or `undefined` if the device is currently disabled.
9759
+ */
9760
+ get mediaStream() {
9761
+ return this.getCurrentValue(this.mediaStream$);
9813
9762
  }
9814
- });
9815
- const watchForDisconnectedDevice = (kind, deviceId$) => {
9816
- let devices$;
9817
- switch (kind) {
9818
- case 'audioinput':
9819
- devices$ = getAudioDevices();
9820
- break;
9821
- case 'videoinput':
9822
- devices$ = getVideoDevices();
9823
- break;
9824
- case 'audiooutput':
9825
- devices$ = getAudioOutputDevices();
9826
- break;
9763
+ /**
9764
+ * @internal
9765
+ * @param status
9766
+ */
9767
+ setStatus(status) {
9768
+ this.setCurrentValue(this.statusSubject, status);
9827
9769
  }
9828
- return combineLatest([devices$, deviceId$]).pipe(filter(([devices, deviceId]) => !!deviceId && !devices.find((d) => d.deviceId === deviceId)), map$2(() => true));
9829
- };
9830
- /**
9831
- * Notifies the subscriber if a given 'audioinput' device is disconnected
9832
- *
9833
- * @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.
9834
- * @param deviceId$ an Observable that specifies which device to watch for
9835
- * @returns
9836
- */
9837
- const watchForDisconnectedAudioDevice = (deviceId$) => {
9838
- return watchForDisconnectedDevice('audioinput', deviceId$);
9839
- };
9840
- /**
9841
- * Notifies the subscriber if a given 'videoinput' device is disconnected
9842
- *
9843
- * @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.
9844
- * @param deviceId$ an Observable that specifies which device to watch for
9845
- * @returns
9846
- */
9847
- const watchForDisconnectedVideoDevice = (deviceId$) => {
9848
- return watchForDisconnectedDevice('videoinput', deviceId$);
9849
- };
9850
- /**
9851
- * Notifies the subscriber if a given 'audiooutput' device is disconnected
9852
- *
9853
- * @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.
9854
- * @param deviceId$ an Observable that specifies which device to watch for
9855
- * @returns
9856
- */
9857
- const watchForDisconnectedAudioOutputDevice = (deviceId$) => {
9858
- return watchForDisconnectedDevice('audiooutput', deviceId$);
9859
- };
9860
- const watchForAddedDefaultDevice = (kind) => {
9861
- let devices$;
9862
- switch (kind) {
9863
- case 'audioinput':
9864
- devices$ = getAudioDevices();
9865
- break;
9866
- case 'videoinput':
9867
- devices$ = getVideoDevices();
9868
- break;
9869
- case 'audiooutput':
9870
- devices$ = getAudioOutputDevices();
9871
- break;
9872
- default:
9873
- throw new Error('Unknown MediaDeviceKind', kind);
9770
+ /**
9771
+ * @internal
9772
+ * @param stream
9773
+ */
9774
+ setMediaStream(stream) {
9775
+ this.setCurrentValue(this.mediaStreamSubject, stream);
9776
+ if (stream) {
9777
+ this.setDevice(this.getDeviceIdFromStream(stream));
9778
+ }
9874
9779
  }
9875
- return devices$.pipe(pairwise(), filter(([prev, current]) => {
9876
- const prevDefault = prev.find((device) => device.deviceId === 'default');
9877
- const currentDefault = current.find((device) => device.deviceId === 'default');
9878
- return !!(current.length > prev.length &&
9879
- prevDefault &&
9880
- currentDefault &&
9881
- prevDefault.groupId !== currentDefault.groupId);
9882
- }), map$2(() => true));
9883
- };
9884
- /**
9885
- * Notifies the subscriber about newly added default audio input device.
9886
- * @returns Observable<boolean>
9887
- */
9888
- const watchForAddedDefaultAudioDevice = () => watchForAddedDefaultDevice('audioinput');
9889
- /**
9890
- * Notifies the subscriber about newly added default audio output device.
9891
- * @returns Observable<boolean>
9892
- */
9893
- const watchForAddedDefaultAudioOutputDevice = () => watchForAddedDefaultDevice('audiooutput');
9894
- /**
9895
- * Notifies the subscriber about newly added default video input device.
9896
- * @returns Observable<boolean>
9897
- */
9898
- const watchForAddedDefaultVideoDevice = () => watchForAddedDefaultDevice('videoinput');
9899
- /**
9900
- * Deactivates MediaStream (stops and removes tracks) to be later garbage collected
9901
- *
9902
- * @param stream MediaStream
9903
- * @returns void
9904
- */
9905
- const disposeOfMediaStream = (stream) => {
9906
- if (!stream.active)
9907
- return;
9908
- stream.getTracks().forEach((track) => {
9909
- track.stop();
9910
- stream.removeTrack(track);
9911
- });
9912
- // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
9913
- if (typeof stream.release === 'function') {
9914
- // @ts-expect-error
9915
- stream.release();
9780
+ /**
9781
+ * @internal
9782
+ * @param stream
9783
+ */
9784
+ setDevice(deviceId) {
9785
+ this.setCurrentValue(this.selectedDeviceSubject, deviceId);
9916
9786
  }
9917
- };
9787
+ }
9788
+
9789
+ class CameraManagerState extends InputMediaDeviceManagerState {
9790
+ constructor() {
9791
+ super('stop-tracks');
9792
+ this.directionSubject = new BehaviorSubject(undefined);
9793
+ this.direction$ = this.directionSubject
9794
+ .asObservable()
9795
+ .pipe(distinctUntilChanged$1());
9796
+ }
9797
+ /**
9798
+ * The preferred camera direction
9799
+ * front - means the camera facing the user
9800
+ * back - means the camera facing the environment
9801
+ */
9802
+ get direction() {
9803
+ return this.getCurrentValue(this.direction$);
9804
+ }
9805
+ /**
9806
+ * @internal
9807
+ */
9808
+ setDirection(direction) {
9809
+ this.setCurrentValue(this.directionSubject, direction);
9810
+ }
9811
+ /**
9812
+ * @internal
9813
+ */
9814
+ setMediaStream(stream) {
9815
+ var _a;
9816
+ super.setMediaStream(stream);
9817
+ if (stream) {
9818
+ // RN getSettings() doesn't return facingMode, so we don't verify camera direction
9819
+ const direction = isReactNative()
9820
+ ? this.direction
9821
+ : ((_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().facingMode) === 'environment'
9822
+ ? 'back'
9823
+ : 'front';
9824
+ this.setDirection(direction);
9825
+ }
9826
+ }
9827
+ getDeviceIdFromStream(stream) {
9828
+ var _a;
9829
+ return (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
9830
+ }
9831
+ }
9918
9832
 
9919
9833
  class InputMediaDeviceManager {
9920
- constructor(call, state) {
9834
+ constructor(call, state, trackType) {
9921
9835
  this.call = call;
9922
9836
  this.state = state;
9837
+ this.trackType = trackType;
9838
+ this.logger = getLogger([`${TrackType[trackType].toLowerCase()} manager`]);
9923
9839
  }
9924
9840
  /**
9925
9841
  * Lists the available audio/video devices
@@ -10027,31 +9943,66 @@ class InputMediaDeviceManager {
10027
9943
  });
10028
9944
  }
10029
9945
  muteStream(stopTracks = true) {
9946
+ var _a;
10030
9947
  return __awaiter(this, void 0, void 0, function* () {
10031
9948
  if (!this.state.mediaStream) {
10032
9949
  return;
10033
9950
  }
9951
+ this.logger('debug', `${stopTracks ? 'Stopping' : 'Disabling'} stream`);
10034
9952
  if (this.call.state.callingState === CallingState.JOINED) {
10035
9953
  yield this.stopPublishStream(stopTracks);
10036
9954
  }
10037
- else if (this.state.mediaStream) {
10038
- stopTracks
10039
- ? disposeOfMediaStream(this.state.mediaStream)
10040
- : this.muteTracks();
10041
- }
10042
- if (stopTracks) {
9955
+ this.muteLocalStream(stopTracks);
9956
+ if (((_a = this.getTrack()) === null || _a === void 0 ? void 0 : _a.readyState) === 'ended') {
9957
+ // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
9958
+ if (typeof this.state.mediaStream.release === 'function') {
9959
+ // @ts-expect-error
9960
+ this.state.mediaStream.release();
9961
+ }
10043
9962
  this.state.setMediaStream(undefined);
10044
9963
  }
10045
9964
  });
10046
9965
  }
9966
+ muteTrack() {
9967
+ const track = this.getTrack();
9968
+ if (!track || !track.enabled) {
9969
+ return;
9970
+ }
9971
+ track.enabled = false;
9972
+ }
9973
+ unmuteTrack() {
9974
+ const track = this.getTrack();
9975
+ if (!track || track.enabled) {
9976
+ return;
9977
+ }
9978
+ track.enabled = true;
9979
+ }
9980
+ stopTrack() {
9981
+ const track = this.getTrack();
9982
+ if (!track || track.readyState === 'ended') {
9983
+ return;
9984
+ }
9985
+ track.stop();
9986
+ }
9987
+ muteLocalStream(stopTracks) {
9988
+ if (!this.state.mediaStream) {
9989
+ return;
9990
+ }
9991
+ stopTracks ? this.stopTrack() : this.muteTrack();
9992
+ }
10047
9993
  unmuteStream() {
9994
+ var _a;
10048
9995
  return __awaiter(this, void 0, void 0, function* () {
9996
+ this.logger('debug', 'Starting stream');
10049
9997
  let stream;
10050
- if (this.state.mediaStream) {
9998
+ if (this.state.mediaStream && ((_a = this.getTrack()) === null || _a === void 0 ? void 0 : _a.readyState) === 'live') {
10051
9999
  stream = this.state.mediaStream;
10052
- this.unmuteTracks();
10000
+ this.unmuteTrack();
10053
10001
  }
10054
10002
  else {
10003
+ if (this.state.mediaStream) {
10004
+ this.stopTrack();
10005
+ }
10055
10006
  const constraints = { deviceId: this.state.selectedDevice };
10056
10007
  stream = yield this.getStream(constraints);
10057
10008
  }
@@ -10063,130 +10014,257 @@ class InputMediaDeviceManager {
10063
10014
  }
10064
10015
  }
10065
10016
 
10066
- class InputMediaDeviceManagerState {
10067
- constructor(disableMode = 'stop-tracks') {
10068
- this.disableMode = disableMode;
10069
- this.statusSubject = new BehaviorSubject(undefined);
10070
- this.mediaStreamSubject = new BehaviorSubject(undefined);
10071
- this.selectedDeviceSubject = new BehaviorSubject(undefined);
10072
- /**
10073
- * Gets the current value of an observable, or undefined if the observable has
10074
- * not emitted a value yet.
10075
- *
10076
- * @param observable$ the observable to get the value from.
10077
- */
10078
- this.getCurrentValue = getCurrentValue;
10079
- /**
10080
- * Updates the value of the provided Subject.
10081
- * An `update` can either be a new value or a function which takes
10082
- * the current value and returns a new value.
10083
- *
10084
- * @internal
10085
- *
10086
- * @param subject the subject to update.
10087
- * @param update the update to apply to the subject.
10088
- * @return the updated value.
10089
- */
10090
- this.setCurrentValue = setCurrentValue;
10091
- this.mediaStream$ = this.mediaStreamSubject.asObservable();
10092
- this.selectedDevice$ = this.selectedDeviceSubject
10093
- .asObservable()
10094
- .pipe(distinctUntilChanged$1());
10095
- this.status$ = this.statusSubject
10096
- .asObservable()
10097
- .pipe(distinctUntilChanged$1());
10098
- }
10099
- /**
10100
- * The device status
10101
- */
10102
- get status() {
10103
- return this.getCurrentValue(this.status$);
10104
- }
10105
- /**
10106
- * The currently selected device
10107
- */
10108
- get selectedDevice() {
10109
- return this.getCurrentValue(this.selectedDevice$);
10110
- }
10111
- /**
10112
- * The current media stream, or `undefined` if the device is currently disabled.
10113
- */
10114
- get mediaStream() {
10115
- return this.getCurrentValue(this.mediaStream$);
10116
- }
10117
- /**
10118
- * @internal
10119
- * @param status
10120
- */
10121
- setStatus(status) {
10122
- this.setCurrentValue(this.statusSubject, status);
10123
- }
10124
- /**
10125
- * @internal
10126
- * @param stream
10127
- */
10128
- setMediaStream(stream) {
10129
- this.setCurrentValue(this.mediaStreamSubject, stream);
10130
- if (stream) {
10131
- this.setDevice(this.getDeviceIdFromStream(stream));
10132
- }
10017
+ const getDevices = (constraints) => {
10018
+ return new Observable((subscriber) => {
10019
+ navigator.mediaDevices
10020
+ .getUserMedia(constraints)
10021
+ .then((media) => {
10022
+ // in Firefox, devices can be enumerated after userMedia is requested
10023
+ // and permissions granted. Otherwise, device labels are empty
10024
+ navigator.mediaDevices.enumerateDevices().then((devices) => {
10025
+ subscriber.next(devices);
10026
+ // If we stop the tracks before enumerateDevices -> the labels won't show up in Firefox
10027
+ disposeOfMediaStream(media);
10028
+ subscriber.complete();
10029
+ });
10030
+ })
10031
+ .catch((error) => {
10032
+ getLogger(['devices'])('error', 'Failed to get devices', error);
10033
+ subscriber.error(error);
10034
+ });
10035
+ });
10036
+ };
10037
+ /**
10038
+ * [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
10039
+ *
10040
+ * @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.
10041
+ */
10042
+ const checkIfAudioOutputChangeSupported = () => {
10043
+ if (typeof document === 'undefined')
10044
+ return false;
10045
+ const element = document.createElement('audio');
10046
+ return element.sinkId !== undefined;
10047
+ };
10048
+ /**
10049
+ * The default constraints used to request audio devices.
10050
+ */
10051
+ const audioDeviceConstraints = {
10052
+ audio: {
10053
+ autoGainControl: true,
10054
+ noiseSuppression: true,
10055
+ echoCancellation: true,
10056
+ },
10057
+ };
10058
+ /**
10059
+ * The default constraints used to request video devices.
10060
+ */
10061
+ const videoDeviceConstraints = {
10062
+ video: {
10063
+ width: 1280,
10064
+ height: 720,
10065
+ },
10066
+ };
10067
+ // 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
10068
+ const deviceChange$ = new Observable((subscriber) => {
10069
+ var _a, _b;
10070
+ const deviceChangeHandler = () => subscriber.next();
10071
+ (_b = (_a = navigator.mediaDevices).addEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
10072
+ return () => {
10073
+ var _a, _b;
10074
+ return (_b = (_a = navigator.mediaDevices).removeEventListener) === null || _b === void 0 ? void 0 : _b.call(_a, 'devicechange', deviceChangeHandler);
10075
+ };
10076
+ }).pipe(debounceTime(500), concatMap(() => from(navigator.mediaDevices.enumerateDevices())), shareReplay(1));
10077
+ const audioDevices$ = merge(getDevices(audioDeviceConstraints), deviceChange$).pipe(shareReplay(1));
10078
+ const videoDevices$ = merge(getDevices(videoDeviceConstraints), deviceChange$).pipe(shareReplay(1));
10079
+ /**
10080
+ * 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.
10081
+ *
10082
+ * @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.
10083
+ * @returns
10084
+ */
10085
+ const getAudioDevices = () => audioDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'audioinput')));
10086
+ /**
10087
+ * 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.
10088
+ *
10089
+ * @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.
10090
+ * @returns
10091
+ */
10092
+ const getVideoDevices = () => videoDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'videoinput' && d.deviceId.length)));
10093
+ /**
10094
+ * 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)
10095
+ *
10096
+ * @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.
10097
+ * @returns
10098
+ */
10099
+ const getAudioOutputDevices = () => {
10100
+ return audioDevices$.pipe(map$2((values) => values.filter((d) => d.kind === 'audiooutput')));
10101
+ };
10102
+ const getStream = (constraints) => __awaiter(void 0, void 0, void 0, function* () {
10103
+ try {
10104
+ return yield navigator.mediaDevices.getUserMedia(constraints);
10133
10105
  }
10134
- /**
10135
- * @internal
10136
- * @param stream
10137
- */
10138
- setDevice(deviceId) {
10139
- this.setCurrentValue(this.selectedDeviceSubject, deviceId);
10106
+ catch (e) {
10107
+ getLogger(['devices'])('error', `Failed get user media`, {
10108
+ error: e,
10109
+ constraints: constraints,
10110
+ });
10111
+ throw e;
10140
10112
  }
10141
- }
10142
-
10143
- class CameraManagerState extends InputMediaDeviceManagerState {
10144
- constructor() {
10145
- super('stop-tracks');
10146
- this.directionSubject = new BehaviorSubject(undefined);
10147
- this.direction$ = this.directionSubject
10148
- .asObservable()
10149
- .pipe(distinctUntilChanged$1());
10113
+ });
10114
+ /**
10115
+ * Returns an audio media stream that fulfills the given constraints.
10116
+ * If no constraints are provided, it uses the browser's default ones.
10117
+ *
10118
+ * @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.
10119
+ * @param trackConstraints the constraints to use when requesting the stream.
10120
+ * @returns the new `MediaStream` fulfilling the given constraints.
10121
+ */
10122
+ const getAudioStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
10123
+ const constraints = {
10124
+ audio: Object.assign(Object.assign({}, audioDeviceConstraints.audio), trackConstraints),
10125
+ };
10126
+ return getStream(constraints);
10127
+ });
10128
+ /**
10129
+ * Returns a video media stream that fulfills the given constraints.
10130
+ * If no constraints are provided, it uses the browser's default ones.
10131
+ *
10132
+ * @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.
10133
+ * @param trackConstraints the constraints to use when requesting the stream.
10134
+ * @returns a new `MediaStream` fulfilling the given constraints.
10135
+ */
10136
+ const getVideoStream = (trackConstraints) => __awaiter(void 0, void 0, void 0, function* () {
10137
+ const constraints = {
10138
+ video: Object.assign(Object.assign({}, videoDeviceConstraints.video), trackConstraints),
10139
+ };
10140
+ return getStream(constraints);
10141
+ });
10142
+ /**
10143
+ * Prompts the user for a permission to share a screen.
10144
+ * If the user grants the permission, a screen sharing stream is returned. Throws otherwise.
10145
+ *
10146
+ * The callers of this API are responsible to handle the possible errors.
10147
+ *
10148
+ * @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.
10149
+ *
10150
+ * @param options any additional options to pass to the [`getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) API.
10151
+ */
10152
+ const getScreenShareStream = (options) => __awaiter(void 0, void 0, void 0, function* () {
10153
+ try {
10154
+ return yield navigator.mediaDevices.getDisplayMedia(Object.assign({ video: true, audio: false }, options));
10150
10155
  }
10151
- /**
10152
- * The preferred camera direction
10153
- * front - means the camera facing the user
10154
- * back - means the camera facing the environment
10155
- */
10156
- get direction() {
10157
- return this.getCurrentValue(this.direction$);
10156
+ catch (e) {
10157
+ getLogger(['devices'])('error', 'Failed to get screen share stream', e);
10158
+ throw e;
10158
10159
  }
10159
- /**
10160
- * @internal
10161
- */
10162
- setDirection(direction) {
10163
- this.setCurrentValue(this.directionSubject, direction);
10160
+ });
10161
+ const watchForDisconnectedDevice = (kind, deviceId$) => {
10162
+ let devices$;
10163
+ switch (kind) {
10164
+ case 'audioinput':
10165
+ devices$ = getAudioDevices();
10166
+ break;
10167
+ case 'videoinput':
10168
+ devices$ = getVideoDevices();
10169
+ break;
10170
+ case 'audiooutput':
10171
+ devices$ = getAudioOutputDevices();
10172
+ break;
10164
10173
  }
10165
- /**
10166
- * @internal
10167
- */
10168
- setMediaStream(stream) {
10169
- var _a;
10170
- super.setMediaStream(stream);
10171
- if (stream) {
10172
- // RN getSettings() doesn't return facingMode, so we don't verify camera direction
10173
- const direction = isReactNative()
10174
- ? this.direction
10175
- : ((_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().facingMode) === 'environment'
10176
- ? 'back'
10177
- : 'front';
10178
- this.setDirection(direction);
10179
- }
10174
+ return combineLatest([devices$, deviceId$]).pipe(filter(([devices, deviceId]) => !!deviceId && !devices.find((d) => d.deviceId === deviceId)), map$2(() => true));
10175
+ };
10176
+ /**
10177
+ * Notifies the subscriber if a given 'audioinput' device is disconnected
10178
+ *
10179
+ * @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.
10180
+ * @param deviceId$ an Observable that specifies which device to watch for
10181
+ * @returns
10182
+ */
10183
+ const watchForDisconnectedAudioDevice = (deviceId$) => {
10184
+ return watchForDisconnectedDevice('audioinput', deviceId$);
10185
+ };
10186
+ /**
10187
+ * Notifies the subscriber if a given 'videoinput' device is disconnected
10188
+ *
10189
+ * @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.
10190
+ * @param deviceId$ an Observable that specifies which device to watch for
10191
+ * @returns
10192
+ */
10193
+ const watchForDisconnectedVideoDevice = (deviceId$) => {
10194
+ return watchForDisconnectedDevice('videoinput', deviceId$);
10195
+ };
10196
+ /**
10197
+ * Notifies the subscriber if a given 'audiooutput' device is disconnected
10198
+ *
10199
+ * @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.
10200
+ * @param deviceId$ an Observable that specifies which device to watch for
10201
+ * @returns
10202
+ */
10203
+ const watchForDisconnectedAudioOutputDevice = (deviceId$) => {
10204
+ return watchForDisconnectedDevice('audiooutput', deviceId$);
10205
+ };
10206
+ const watchForAddedDefaultDevice = (kind) => {
10207
+ let devices$;
10208
+ switch (kind) {
10209
+ case 'audioinput':
10210
+ devices$ = getAudioDevices();
10211
+ break;
10212
+ case 'videoinput':
10213
+ devices$ = getVideoDevices();
10214
+ break;
10215
+ case 'audiooutput':
10216
+ devices$ = getAudioOutputDevices();
10217
+ break;
10218
+ default:
10219
+ throw new Error('Unknown MediaDeviceKind', kind);
10180
10220
  }
10181
- getDeviceIdFromStream(stream) {
10182
- var _a;
10183
- return (_a = stream.getVideoTracks()[0]) === null || _a === void 0 ? void 0 : _a.getSettings().deviceId;
10221
+ return devices$.pipe(pairwise(), filter(([prev, current]) => {
10222
+ const prevDefault = prev.find((device) => device.deviceId === 'default');
10223
+ const currentDefault = current.find((device) => device.deviceId === 'default');
10224
+ return !!(current.length > prev.length &&
10225
+ prevDefault &&
10226
+ currentDefault &&
10227
+ prevDefault.groupId !== currentDefault.groupId);
10228
+ }), map$2(() => true));
10229
+ };
10230
+ /**
10231
+ * Notifies the subscriber about newly added default audio input device.
10232
+ * @returns Observable<boolean>
10233
+ */
10234
+ const watchForAddedDefaultAudioDevice = () => watchForAddedDefaultDevice('audioinput');
10235
+ /**
10236
+ * Notifies the subscriber about newly added default audio output device.
10237
+ * @returns Observable<boolean>
10238
+ */
10239
+ const watchForAddedDefaultAudioOutputDevice = () => watchForAddedDefaultDevice('audiooutput');
10240
+ /**
10241
+ * Notifies the subscriber about newly added default video input device.
10242
+ * @returns Observable<boolean>
10243
+ */
10244
+ const watchForAddedDefaultVideoDevice = () => watchForAddedDefaultDevice('videoinput');
10245
+ /**
10246
+ * Deactivates MediaStream (stops and removes tracks) to be later garbage collected
10247
+ *
10248
+ * @param stream MediaStream
10249
+ * @returns void
10250
+ */
10251
+ const disposeOfMediaStream = (stream) => {
10252
+ if (!stream.active)
10253
+ return;
10254
+ stream.getTracks().forEach((track) => {
10255
+ track.stop();
10256
+ stream.removeTrack(track);
10257
+ });
10258
+ // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
10259
+ if (typeof stream.release === 'function') {
10260
+ // @ts-expect-error
10261
+ stream.release();
10184
10262
  }
10185
- }
10263
+ };
10186
10264
 
10187
10265
  class CameraManager extends InputMediaDeviceManager {
10188
10266
  constructor(call) {
10189
- super(call, new CameraManagerState());
10267
+ super(call, new CameraManagerState(), TrackType.VIDEO);
10190
10268
  this.targetResolution = {
10191
10269
  width: 1280,
10192
10270
  height: 720,
@@ -10238,6 +10316,7 @@ class CameraManager extends InputMediaDeviceManager {
10238
10316
  if (width !== this.targetResolution.width ||
10239
10317
  height !== this.targetResolution.height)
10240
10318
  yield this.applySettingsToStream();
10319
+ this.logger('debug', `${width}x${height} target resolution applied to media stream`);
10241
10320
  }
10242
10321
  });
10243
10322
  }
@@ -10261,13 +10340,9 @@ class CameraManager extends InputMediaDeviceManager {
10261
10340
  stopPublishStream(stopTracks) {
10262
10341
  return this.call.stopPublish(TrackType.VIDEO, stopTracks);
10263
10342
  }
10264
- muteTracks() {
10265
- var _a;
10266
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().forEach((t) => (t.enabled = false));
10267
- }
10268
- unmuteTracks() {
10343
+ getTrack() {
10269
10344
  var _a;
10270
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().forEach((t) => (t.enabled = true));
10345
+ return (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks()[0];
10271
10346
  }
10272
10347
  }
10273
10348
 
@@ -10283,7 +10358,7 @@ class MicrophoneManagerState extends InputMediaDeviceManagerState {
10283
10358
 
10284
10359
  class MicrophoneManager extends InputMediaDeviceManager {
10285
10360
  constructor(call) {
10286
- super(call, new MicrophoneManagerState());
10361
+ super(call, new MicrophoneManagerState(), TrackType.AUDIO);
10287
10362
  }
10288
10363
  getDevices() {
10289
10364
  return getAudioDevices();
@@ -10297,13 +10372,119 @@ class MicrophoneManager extends InputMediaDeviceManager {
10297
10372
  stopPublishStream(stopTracks) {
10298
10373
  return this.call.stopPublish(TrackType.AUDIO, stopTracks);
10299
10374
  }
10300
- muteTracks() {
10375
+ getTrack() {
10301
10376
  var _a;
10302
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach((t) => (t.enabled = false));
10377
+ return (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
10303
10378
  }
10304
- unmuteTracks() {
10305
- var _a;
10306
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach((t) => (t.enabled = true));
10379
+ }
10380
+
10381
+ class SpeakerState {
10382
+ constructor() {
10383
+ this.selectedDeviceSubject = new BehaviorSubject('');
10384
+ this.volumeSubject = new BehaviorSubject(1);
10385
+ /**
10386
+ * [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
10387
+ */
10388
+ this.isDeviceSelectionSupported = checkIfAudioOutputChangeSupported();
10389
+ /**
10390
+ * Gets the current value of an observable, or undefined if the observable has
10391
+ * not emitted a value yet.
10392
+ *
10393
+ * @param observable$ the observable to get the value from.
10394
+ */
10395
+ this.getCurrentValue = getCurrentValue;
10396
+ /**
10397
+ * Updates the value of the provided Subject.
10398
+ * An `update` can either be a new value or a function which takes
10399
+ * the current value and returns a new value.
10400
+ *
10401
+ * @internal
10402
+ *
10403
+ * @param subject the subject to update.
10404
+ * @param update the update to apply to the subject.
10405
+ * @return the updated value.
10406
+ */
10407
+ this.setCurrentValue = setCurrentValue;
10408
+ this.selectedDevice$ = this.selectedDeviceSubject
10409
+ .asObservable()
10410
+ .pipe(distinctUntilChanged$1());
10411
+ this.volume$ = this.volumeSubject
10412
+ .asObservable()
10413
+ .pipe(distinctUntilChanged$1());
10414
+ }
10415
+ /**
10416
+ * The currently selected device
10417
+ *
10418
+ * Note: this feature is not supported in React Native
10419
+ */
10420
+ get selectedDevice() {
10421
+ return this.getCurrentValue(this.selectedDevice$);
10422
+ }
10423
+ /**
10424
+ * The currently selected volume
10425
+ *
10426
+ * Note: this feature is not supported in React Native
10427
+ */
10428
+ get volume() {
10429
+ return this.getCurrentValue(this.volume$);
10430
+ }
10431
+ /**
10432
+ * @internal
10433
+ * @param deviceId
10434
+ */
10435
+ setDevice(deviceId) {
10436
+ this.setCurrentValue(this.selectedDeviceSubject, deviceId);
10437
+ }
10438
+ /**
10439
+ * @internal
10440
+ * @param volume
10441
+ */
10442
+ setVolume(volume) {
10443
+ this.setCurrentValue(this.volumeSubject, volume);
10444
+ }
10445
+ }
10446
+
10447
+ class SpeakerManager {
10448
+ constructor() {
10449
+ this.state = new SpeakerState();
10450
+ }
10451
+ /**
10452
+ * Lists the available audio output devices
10453
+ *
10454
+ * Note: It prompts the user for a permission to use devices (if not already granted)
10455
+ *
10456
+ * @returns an Observable that will be updated if a device is connected or disconnected
10457
+ */
10458
+ listDevices() {
10459
+ return getAudioOutputDevices();
10460
+ }
10461
+ /**
10462
+ * Select device
10463
+ *
10464
+ * Note: this method is not supported in React Native
10465
+ *
10466
+ * @param deviceId empty string means the system default
10467
+ */
10468
+ select(deviceId) {
10469
+ if (isReactNative()) {
10470
+ throw new Error('This feature is not supported in React Native');
10471
+ }
10472
+ this.state.setDevice(deviceId);
10473
+ }
10474
+ /**
10475
+ * Set the volume of the audio elements
10476
+ * @param volume a number between 0 and 1
10477
+ *
10478
+ * Note: this method is not supported in React Native
10479
+ */
10480
+ setVolume(volume) {
10481
+ if (isReactNative()) {
10482
+ throw new Error('This feature is not supported in React Native');
10483
+ }
10484
+ if (volume && (volume < 0 || volume > 1)) {
10485
+ throw new Error('Volume must be between 0 and 1');
10486
+ }
10487
+ this.state.setVolume(volume);
10307
10488
  }
10308
10489
  }
10309
10490
 
@@ -10879,7 +11060,7 @@ class Call {
10879
11060
  */
10880
11061
  this.stopPublish = (trackType, stopTrack = true) => __awaiter(this, void 0, void 0, function* () {
10881
11062
  var _j;
10882
- this.logger('info', `stopPublish ${TrackType[trackType]}`);
11063
+ this.logger('info', `stopPublish ${TrackType[trackType]}, stop tracks: ${stopTrack}`);
10883
11064
  yield ((_j = this.publisher) === null || _j === void 0 ? void 0 : _j.unpublishStream(trackType, stopTrack));
10884
11065
  });
10885
11066
  /**
@@ -10981,6 +11162,8 @@ class Call {
10981
11162
  *
10982
11163
  *
10983
11164
  * @param deviceId the selected device, `undefined` means the user wants to use the system's default audio output
11165
+ *
11166
+ * @deprecated use `call.speaker` instead
10984
11167
  */
10985
11168
  this.setAudioOutputDevice = (deviceId) => {
10986
11169
  if (!this.sfuClient)
@@ -11455,6 +11638,28 @@ class Call {
11455
11638
  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); }));
11456
11639
  this.camera = new CameraManager(this);
11457
11640
  this.microphone = new MicrophoneManager(this);
11641
+ this.state.localParticipant$.subscribe((p) => __awaiter(this, void 0, void 0, function* () {
11642
+ var _l, _m;
11643
+ // Mute via device manager
11644
+ // If integrator doesn't use device manager, we mute using stopPublish
11645
+ if (!(p === null || p === void 0 ? void 0 : p.publishedTracks.includes(TrackType.VIDEO)) &&
11646
+ ((_l = this.publisher) === null || _l === void 0 ? void 0 : _l.isPublishing(TrackType.VIDEO))) {
11647
+ this.logger('info', `Local participant's video track is muted remotely`);
11648
+ yield this.camera.disable();
11649
+ if (this.publisher.isPublishing(TrackType.VIDEO)) {
11650
+ this.stopPublish(TrackType.VIDEO);
11651
+ }
11652
+ }
11653
+ if (!(p === null || p === void 0 ? void 0 : p.publishedTracks.includes(TrackType.AUDIO)) &&
11654
+ ((_m = this.publisher) === null || _m === void 0 ? void 0 : _m.isPublishing(TrackType.AUDIO))) {
11655
+ this.logger('info', `Local participant's audio track is muted remotely`);
11656
+ yield this.microphone.disable();
11657
+ if (this.publisher.isPublishing(TrackType.AUDIO)) {
11658
+ this.stopPublish(TrackType.AUDIO);
11659
+ }
11660
+ }
11661
+ }));
11662
+ this.speaker = new SpeakerManager();
11458
11663
  }
11459
11664
  registerEffects() {
11460
11665
  this.leaveCallHooks.add(
@@ -11479,9 +11684,27 @@ class Call {
11479
11684
  };
11480
11685
  for (const [permission, trackType] of Object.entries(permissionToTrackType)) {
11481
11686
  const hasPermission = this.permissionsContext.hasPermission(permission);
11482
- if (!hasPermission && this.publisher.isPublishing(trackType)) {
11483
- this.stopPublish(trackType).catch((err) => {
11687
+ if (!hasPermission &&
11688
+ (this.publisher.isPublishing(trackType) ||
11689
+ this.publisher.isLive(trackType))) {
11690
+ // Stop tracks, then notify device manager
11691
+ this.stopPublish(trackType)
11692
+ .catch((err) => {
11484
11693
  this.logger('error', `Error stopping publish ${trackType}`, err);
11694
+ })
11695
+ .then(() => {
11696
+ if (trackType === TrackType.VIDEO &&
11697
+ this.camera.state.status === 'enabled') {
11698
+ this.camera
11699
+ .disable()
11700
+ .catch((err) => this.logger('error', `Error disabling camera after pemission revoked`, err));
11701
+ }
11702
+ if (trackType === TrackType.AUDIO &&
11703
+ this.microphone.state.status === 'enabled') {
11704
+ this.microphone
11705
+ .disable()
11706
+ .catch((err) => this.logger('error', `Error disabling microphone after pemission revoked`, err));
11707
+ }
11485
11708
  });
11486
11709
  }
11487
11710
  }
@@ -12745,11 +12968,11 @@ class WSConnectionFallback {
12745
12968
  }
12746
12969
  }
12747
12970
 
12748
- const version = '0.3.12';
12971
+ const version = '0.3.14';
12749
12972
 
12750
12973
  const logger = getLogger(['location']);
12751
12974
  const HINT_URL = `https://hint.stream-io-video.com/`;
12752
- const getLocationHint = (hintUrl = HINT_URL, timeout = 1500) => __awaiter(void 0, void 0, void 0, function* () {
12975
+ const getLocationHint = (hintUrl = HINT_URL, timeout = 2000) => __awaiter(void 0, void 0, void 0, function* () {
12753
12976
  const abortController = new AbortController();
12754
12977
  const timeoutId = setTimeout(() => abortController.abort(), timeout);
12755
12978
  try {
@@ -12762,7 +12985,7 @@ const getLocationHint = (hintUrl = HINT_URL, timeout = 1500) => __awaiter(void 0
12762
12985
  return awsPop.substring(0, 3); // AMS1-P2 -> AMS
12763
12986
  }
12764
12987
  catch (e) {
12765
- logger('error', `Failed to get location hint from ${HINT_URL}`, e);
12988
+ logger('warn', `Failed to get location hint from ${HINT_URL}`, e);
12766
12989
  return 'ERR';
12767
12990
  }
12768
12991
  finally {