@stream-io/video-client 0.3.13 → 0.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -6412,6 +6412,14 @@ class Publisher {
6412
6412
  [TrackType.SCREEN_SHARE_AUDIO]: undefined,
6413
6413
  [TrackType.UNSPECIFIED]: undefined,
6414
6414
  };
6415
+ /**
6416
+ * A map keeping track of track types that were published to the SFU.
6417
+ * This map shouldn't be cleared when unpublishing a track, as it is used
6418
+ * to determine whether a track was published before.
6419
+ *
6420
+ * @private
6421
+ */
6422
+ this.trackTypePublishHistory = new Map();
6415
6423
  this.isIceRestarting = false;
6416
6424
  this.createPeerConnection = (connectionConfig) => {
6417
6425
  const pc = new RTCPeerConnection(connectionConfig);
@@ -6493,6 +6501,9 @@ class Publisher {
6493
6501
  // by an external factor as permission revokes, device disconnected, etc.
6494
6502
  // keep in mind that `track.stop()` doesn't trigger this event.
6495
6503
  track.addEventListener('ended', handleTrackEnded);
6504
+ if (!track.enabled) {
6505
+ track.enabled = true;
6506
+ }
6496
6507
  transceiver = this.pc.addTransceiver(track, {
6497
6508
  direction: 'sendonly',
6498
6509
  streams: trackType === TrackType.VIDEO || trackType === TrackType.SCREEN_SHARE
@@ -6503,6 +6514,7 @@ class Publisher {
6503
6514
  logger$3('debug', `Added ${TrackType[trackType]} transceiver`);
6504
6515
  this.transceiverInitOrder.push(trackType);
6505
6516
  this.transceiverRegistry[trackType] = transceiver;
6517
+ this.trackTypePublishHistory.set(trackType, true);
6506
6518
  if ('setCodecPreferences' in transceiver && codecPreferences) {
6507
6519
  logger$3('info', `Setting ${TrackType[trackType]} codec preferences`, codecPreferences);
6508
6520
  transceiver.setCodecPreferences(codecPreferences);
@@ -6530,16 +6542,25 @@ class Publisher {
6530
6542
  * @param stopTrack specifies whether track should be stopped or just disabled
6531
6543
  */
6532
6544
  this.unpublishStream = (trackType, stopTrack) => __awaiter(this, void 0, void 0, function* () {
6545
+ var _b;
6533
6546
  const transceiver = this.pc
6534
6547
  .getTransceivers()
6535
6548
  .find((t) => t === this.transceiverRegistry[trackType] && t.sender.track);
6536
6549
  if (transceiver &&
6537
6550
  transceiver.sender.track &&
6538
- transceiver.sender.track.readyState === 'live') {
6551
+ (stopTrack
6552
+ ? transceiver.sender.track.readyState === 'live'
6553
+ : transceiver.sender.track.enabled)) {
6539
6554
  stopTrack
6540
6555
  ? transceiver.sender.track.stop()
6541
6556
  : (transceiver.sender.track.enabled = false);
6542
- return this.notifyTrackMuteStateChanged(undefined, transceiver.sender.track, trackType, true);
6557
+ // We don't need to notify SFU if unpublishing in response to remote soft mute
6558
+ if (!((_b = this.state.localParticipant) === null || _b === void 0 ? void 0 : _b.publishedTracks.includes(trackType))) {
6559
+ return;
6560
+ }
6561
+ else {
6562
+ return this.notifyTrackMuteStateChanged(undefined, transceiver.sender.track, trackType, true);
6563
+ }
6543
6564
  }
6544
6565
  });
6545
6566
  /**
@@ -6548,6 +6569,32 @@ class Publisher {
6548
6569
  * @param trackType the track type to check.
6549
6570
  */
6550
6571
  this.isPublishing = (trackType) => {
6572
+ const transceiverForTrackType = this.transceiverRegistry[trackType];
6573
+ if (transceiverForTrackType && transceiverForTrackType.sender) {
6574
+ const sender = transceiverForTrackType.sender;
6575
+ return (!!sender.track &&
6576
+ sender.track.readyState === 'live' &&
6577
+ sender.track.enabled);
6578
+ }
6579
+ return false;
6580
+ };
6581
+ /**
6582
+ * Returns true if the given track type was ever published to the SFU.
6583
+ * Contrary to `isPublishing`, this method returns true if a certain
6584
+ * track type was published before, even if it is currently unpublished.
6585
+ *
6586
+ * @param trackType the track type to check.
6587
+ */
6588
+ this.hasEverPublished = (trackType) => {
6589
+ var _a;
6590
+ return (_a = this.trackTypePublishHistory.get(trackType)) !== null && _a !== void 0 ? _a : false;
6591
+ };
6592
+ /**
6593
+ * Returns true if the given track type is currently live
6594
+ *
6595
+ * @param trackType the track type to check.
6596
+ */
6597
+ this.isLive = (trackType) => {
6551
6598
  const transceiverForTrackType = this.transceiverRegistry[trackType];
6552
6599
  if (transceiverForTrackType && transceiverForTrackType.sender) {
6553
6600
  const sender = transceiverForTrackType.sender;
@@ -6588,9 +6635,9 @@ class Publisher {
6588
6635
  });
6589
6636
  };
6590
6637
  this.updateVideoPublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
6591
- var _b;
6638
+ var _c;
6592
6639
  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;
6640
+ const videoSender = (_c = this.transceiverRegistry[TrackType.VIDEO]) === null || _c === void 0 ? void 0 : _c.sender;
6594
6641
  if (!videoSender) {
6595
6642
  logger$3('warn', 'Update publish quality, no video sender found.');
6596
6643
  return;
@@ -6688,8 +6735,8 @@ class Publisher {
6688
6735
  * @param options the optional offer options to use.
6689
6736
  */
6690
6737
  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;
6738
+ var _d;
6739
+ this.isIceRestarting = (_d = options === null || options === void 0 ? void 0 : options.iceRestart) !== null && _d !== void 0 ? _d : false;
6693
6740
  const offer = yield this.pc.createOffer(options);
6694
6741
  offer.sdp = this.mungeCodecs(offer.sdp);
6695
6742
  const trackInfos = this.getCurrentTrackInfos(offer.sdp);
@@ -7477,6 +7524,9 @@ const retryable = (rpc, logger) => __awaiter(void 0, void 0, void 0, function* (
7477
7524
  retryAttempt++;
7478
7525
  } while (((_a = rpcCallResult.response.error) === null || _a === void 0 ? void 0 : _a.shouldRetry) &&
7479
7526
  retryAttempt < MAX_RETRIES);
7527
+ if (rpcCallResult.response.error) {
7528
+ throw rpcCallResult.response.error;
7529
+ }
7480
7530
  return rpcCallResult;
7481
7531
  });
7482
7532
 
@@ -9800,6 +9850,190 @@ class CameraManagerState extends InputMediaDeviceManagerState {
9800
9850
  }
9801
9851
  }
9802
9852
 
9853
+ class InputMediaDeviceManager {
9854
+ constructor(call, state, trackType) {
9855
+ this.call = call;
9856
+ this.state = state;
9857
+ this.trackType = trackType;
9858
+ this.logger = getLogger([`${TrackType[trackType].toLowerCase()} manager`]);
9859
+ }
9860
+ /**
9861
+ * Lists the available audio/video devices
9862
+ *
9863
+ * Note: It prompts the user for a permission to use devices (if not already granted)
9864
+ *
9865
+ * @returns an Observable that will be updated if a device is connected or disconnected
9866
+ */
9867
+ listDevices() {
9868
+ return this.getDevices();
9869
+ }
9870
+ /**
9871
+ * Starts camera/microphone
9872
+ */
9873
+ enable() {
9874
+ return __awaiter(this, void 0, void 0, function* () {
9875
+ if (this.state.status === 'enabled') {
9876
+ return;
9877
+ }
9878
+ this.enablePromise = this.unmuteStream();
9879
+ try {
9880
+ yield this.enablePromise;
9881
+ this.state.setStatus('enabled');
9882
+ }
9883
+ catch (error) {
9884
+ this.enablePromise = undefined;
9885
+ throw error;
9886
+ }
9887
+ });
9888
+ }
9889
+ /**
9890
+ * Stops camera/microphone
9891
+ *
9892
+ * @returns
9893
+ */
9894
+ disable() {
9895
+ return __awaiter(this, void 0, void 0, function* () {
9896
+ this.state.prevStatus = this.state.status;
9897
+ if (this.state.status === 'disabled') {
9898
+ return;
9899
+ }
9900
+ this.disablePromise = this.muteStream(this.state.disableMode === 'stop-tracks');
9901
+ try {
9902
+ yield this.disablePromise;
9903
+ this.state.setStatus('disabled');
9904
+ this.disablePromise = undefined;
9905
+ }
9906
+ catch (error) {
9907
+ this.disablePromise = undefined;
9908
+ throw error;
9909
+ }
9910
+ });
9911
+ }
9912
+ /**
9913
+ * If status was previously enabled, it will reenable the device.
9914
+ */
9915
+ resume() {
9916
+ return __awaiter(this, void 0, void 0, function* () {
9917
+ if (this.state.prevStatus === 'enabled' &&
9918
+ this.state.status === 'disabled') {
9919
+ this.enable();
9920
+ }
9921
+ });
9922
+ }
9923
+ /**
9924
+ * If current device statis is disabled, it will enable the device, else it will disable it.
9925
+ *
9926
+ * @returns
9927
+ */
9928
+ toggle() {
9929
+ return __awaiter(this, void 0, void 0, function* () {
9930
+ if (this.state.status === 'enabled') {
9931
+ return this.disable();
9932
+ }
9933
+ else {
9934
+ return this.enable();
9935
+ }
9936
+ });
9937
+ }
9938
+ /**
9939
+ * Select device
9940
+ *
9941
+ * Note: this method is not supported in React Native
9942
+ *
9943
+ * @param deviceId
9944
+ */
9945
+ select(deviceId) {
9946
+ return __awaiter(this, void 0, void 0, function* () {
9947
+ if (isReactNative()) {
9948
+ throw new Error('This method is not supported in React Native');
9949
+ }
9950
+ if (deviceId === this.state.selectedDevice) {
9951
+ return;
9952
+ }
9953
+ this.state.setDevice(deviceId);
9954
+ yield this.applySettingsToStream();
9955
+ });
9956
+ }
9957
+ applySettingsToStream() {
9958
+ return __awaiter(this, void 0, void 0, function* () {
9959
+ if (this.state.status === 'enabled') {
9960
+ yield this.muteStream();
9961
+ yield this.unmuteStream();
9962
+ }
9963
+ });
9964
+ }
9965
+ muteStream(stopTracks = true) {
9966
+ var _a;
9967
+ return __awaiter(this, void 0, void 0, function* () {
9968
+ if (!this.state.mediaStream) {
9969
+ return;
9970
+ }
9971
+ this.logger('debug', `${stopTracks ? 'Stopping' : 'Disabling'} stream`);
9972
+ if (this.call.state.callingState === CallingState.JOINED) {
9973
+ yield this.stopPublishStream(stopTracks);
9974
+ }
9975
+ this.muteLocalStream(stopTracks);
9976
+ if (((_a = this.getTrack()) === null || _a === void 0 ? void 0 : _a.readyState) === 'ended') {
9977
+ // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
9978
+ if (typeof this.state.mediaStream.release === 'function') {
9979
+ // @ts-expect-error
9980
+ this.state.mediaStream.release();
9981
+ }
9982
+ this.state.setMediaStream(undefined);
9983
+ }
9984
+ });
9985
+ }
9986
+ muteTrack() {
9987
+ const track = this.getTrack();
9988
+ if (!track || !track.enabled) {
9989
+ return;
9990
+ }
9991
+ track.enabled = false;
9992
+ }
9993
+ unmuteTrack() {
9994
+ const track = this.getTrack();
9995
+ if (!track || track.enabled) {
9996
+ return;
9997
+ }
9998
+ track.enabled = true;
9999
+ }
10000
+ stopTrack() {
10001
+ const track = this.getTrack();
10002
+ if (!track || track.readyState === 'ended') {
10003
+ return;
10004
+ }
10005
+ track.stop();
10006
+ }
10007
+ muteLocalStream(stopTracks) {
10008
+ if (!this.state.mediaStream) {
10009
+ return;
10010
+ }
10011
+ stopTracks ? this.stopTrack() : this.muteTrack();
10012
+ }
10013
+ unmuteStream() {
10014
+ var _a;
10015
+ return __awaiter(this, void 0, void 0, function* () {
10016
+ this.logger('debug', 'Starting stream');
10017
+ let stream;
10018
+ if (this.state.mediaStream && ((_a = this.getTrack()) === null || _a === void 0 ? void 0 : _a.readyState) === 'live') {
10019
+ stream = this.state.mediaStream;
10020
+ this.unmuteTrack();
10021
+ }
10022
+ else {
10023
+ if (this.state.mediaStream) {
10024
+ this.stopTrack();
10025
+ }
10026
+ const constraints = { deviceId: this.state.selectedDevice };
10027
+ stream = yield this.getStream(constraints);
10028
+ }
10029
+ if (this.call.state.callingState === CallingState.JOINED) {
10030
+ yield this.publishStream(stream);
10031
+ }
10032
+ this.state.setMediaStream(stream);
10033
+ });
10034
+ }
10035
+ }
10036
+
9803
10037
  const getDevices = (constraints) => {
9804
10038
  return new Observable((subscriber) => {
9805
10039
  navigator.mediaDevices
@@ -10048,156 +10282,9 @@ const disposeOfMediaStream = (stream) => {
10048
10282
  }
10049
10283
  };
10050
10284
 
10051
- class InputMediaDeviceManager {
10052
- constructor(call, state) {
10053
- this.call = call;
10054
- this.state = state;
10055
- }
10056
- /**
10057
- * Lists the available audio/video devices
10058
- *
10059
- * Note: It prompts the user for a permission to use devices (if not already granted)
10060
- *
10061
- * @returns an Observable that will be updated if a device is connected or disconnected
10062
- */
10063
- listDevices() {
10064
- return this.getDevices();
10065
- }
10066
- /**
10067
- * Starts camera/microphone
10068
- */
10069
- enable() {
10070
- return __awaiter(this, void 0, void 0, function* () {
10071
- if (this.state.status === 'enabled') {
10072
- return;
10073
- }
10074
- this.enablePromise = this.unmuteStream();
10075
- try {
10076
- yield this.enablePromise;
10077
- this.state.setStatus('enabled');
10078
- }
10079
- catch (error) {
10080
- this.enablePromise = undefined;
10081
- throw error;
10082
- }
10083
- });
10084
- }
10085
- /**
10086
- * Stops camera/microphone
10087
- *
10088
- * @returns
10089
- */
10090
- disable() {
10091
- return __awaiter(this, void 0, void 0, function* () {
10092
- this.state.prevStatus = this.state.status;
10093
- if (this.state.status === 'disabled') {
10094
- return;
10095
- }
10096
- this.disablePromise = this.muteStream(this.state.disableMode === 'stop-tracks');
10097
- try {
10098
- yield this.disablePromise;
10099
- this.state.setStatus('disabled');
10100
- this.disablePromise = undefined;
10101
- }
10102
- catch (error) {
10103
- this.disablePromise = undefined;
10104
- throw error;
10105
- }
10106
- });
10107
- }
10108
- /**
10109
- * If status was previously enabled, it will reenable the device.
10110
- */
10111
- resume() {
10112
- return __awaiter(this, void 0, void 0, function* () {
10113
- if (this.state.prevStatus === 'enabled' &&
10114
- this.state.status === 'disabled') {
10115
- this.enable();
10116
- }
10117
- });
10118
- }
10119
- /**
10120
- * If current device statis is disabled, it will enable the device, else it will disable it.
10121
- *
10122
- * @returns
10123
- */
10124
- toggle() {
10125
- return __awaiter(this, void 0, void 0, function* () {
10126
- if (this.state.status === 'enabled') {
10127
- return this.disable();
10128
- }
10129
- else {
10130
- return this.enable();
10131
- }
10132
- });
10133
- }
10134
- /**
10135
- * Select device
10136
- *
10137
- * Note: this method is not supported in React Native
10138
- *
10139
- * @param deviceId
10140
- */
10141
- select(deviceId) {
10142
- return __awaiter(this, void 0, void 0, function* () {
10143
- if (isReactNative()) {
10144
- throw new Error('This method is not supported in React Native');
10145
- }
10146
- if (deviceId === this.state.selectedDevice) {
10147
- return;
10148
- }
10149
- this.state.setDevice(deviceId);
10150
- yield this.applySettingsToStream();
10151
- });
10152
- }
10153
- applySettingsToStream() {
10154
- return __awaiter(this, void 0, void 0, function* () {
10155
- if (this.state.status === 'enabled') {
10156
- yield this.muteStream();
10157
- yield this.unmuteStream();
10158
- }
10159
- });
10160
- }
10161
- muteStream(stopTracks = true) {
10162
- return __awaiter(this, void 0, void 0, function* () {
10163
- if (!this.state.mediaStream) {
10164
- return;
10165
- }
10166
- if (this.call.state.callingState === CallingState.JOINED) {
10167
- yield this.stopPublishStream(stopTracks);
10168
- }
10169
- else if (this.state.mediaStream) {
10170
- stopTracks
10171
- ? disposeOfMediaStream(this.state.mediaStream)
10172
- : this.muteTracks();
10173
- }
10174
- if (stopTracks) {
10175
- this.state.setMediaStream(undefined);
10176
- }
10177
- });
10178
- }
10179
- unmuteStream() {
10180
- return __awaiter(this, void 0, void 0, function* () {
10181
- let stream;
10182
- if (this.state.mediaStream) {
10183
- stream = this.state.mediaStream;
10184
- this.unmuteTracks();
10185
- }
10186
- else {
10187
- const constraints = { deviceId: this.state.selectedDevice };
10188
- stream = yield this.getStream(constraints);
10189
- }
10190
- if (this.call.state.callingState === CallingState.JOINED) {
10191
- yield this.publishStream(stream);
10192
- }
10193
- this.state.setMediaStream(stream);
10194
- });
10195
- }
10196
- }
10197
-
10198
10285
  class CameraManager extends InputMediaDeviceManager {
10199
10286
  constructor(call) {
10200
- super(call, new CameraManagerState());
10287
+ super(call, new CameraManagerState(), TrackType.VIDEO);
10201
10288
  this.targetResolution = {
10202
10289
  width: 1280,
10203
10290
  height: 720,
@@ -10249,6 +10336,7 @@ class CameraManager extends InputMediaDeviceManager {
10249
10336
  if (width !== this.targetResolution.width ||
10250
10337
  height !== this.targetResolution.height)
10251
10338
  yield this.applySettingsToStream();
10339
+ this.logger('debug', `${width}x${height} target resolution applied to media stream`);
10252
10340
  }
10253
10341
  });
10254
10342
  }
@@ -10272,13 +10360,9 @@ class CameraManager extends InputMediaDeviceManager {
10272
10360
  stopPublishStream(stopTracks) {
10273
10361
  return this.call.stopPublish(TrackType.VIDEO, stopTracks);
10274
10362
  }
10275
- muteTracks() {
10276
- var _a;
10277
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().forEach((t) => (t.enabled = false));
10278
- }
10279
- unmuteTracks() {
10363
+ getTrack() {
10280
10364
  var _a;
10281
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().forEach((t) => (t.enabled = true));
10365
+ return (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks()[0];
10282
10366
  }
10283
10367
  }
10284
10368
 
@@ -10294,7 +10378,7 @@ class MicrophoneManagerState extends InputMediaDeviceManagerState {
10294
10378
 
10295
10379
  class MicrophoneManager extends InputMediaDeviceManager {
10296
10380
  constructor(call) {
10297
- super(call, new MicrophoneManagerState());
10381
+ super(call, new MicrophoneManagerState(), TrackType.AUDIO);
10298
10382
  }
10299
10383
  getDevices() {
10300
10384
  return getAudioDevices();
@@ -10308,13 +10392,9 @@ class MicrophoneManager extends InputMediaDeviceManager {
10308
10392
  stopPublishStream(stopTracks) {
10309
10393
  return this.call.stopPublish(TrackType.AUDIO, stopTracks);
10310
10394
  }
10311
- muteTracks() {
10312
- var _a;
10313
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach((t) => (t.enabled = false));
10314
- }
10315
- unmuteTracks() {
10395
+ getTrack() {
10316
10396
  var _a;
10317
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach((t) => (t.enabled = true));
10397
+ return (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
10318
10398
  }
10319
10399
  }
10320
10400
 
@@ -11000,7 +11080,7 @@ class Call {
11000
11080
  */
11001
11081
  this.stopPublish = (trackType, stopTrack = true) => __awaiter(this, void 0, void 0, function* () {
11002
11082
  var _j;
11003
- this.logger('info', `stopPublish ${TrackType[trackType]}`);
11083
+ this.logger('info', `stopPublish ${TrackType[trackType]}, stop tracks: ${stopTrack}`);
11004
11084
  yield ((_j = this.publisher) === null || _j === void 0 ? void 0 : _j.unpublishStream(trackType, stopTrack));
11005
11085
  });
11006
11086
  /**
@@ -11578,6 +11658,40 @@ class Call {
11578
11658
  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); }));
11579
11659
  this.camera = new CameraManager(this);
11580
11660
  this.microphone = new MicrophoneManager(this);
11661
+ // FIXME OL: disable soft-mutes as they are not working properly
11662
+ // this.state.localParticipant$.subscribe(async (p) => {
11663
+ // if (!this.publisher) return;
11664
+ // // Mute via device manager
11665
+ // // If integrator doesn't use device manager, we mute using stopPublish
11666
+ // if (
11667
+ // this.publisher.hasEverPublished(TrackType.VIDEO) &&
11668
+ // this.publisher.isPublishing(TrackType.VIDEO) &&
11669
+ // !p?.publishedTracks.includes(TrackType.VIDEO)
11670
+ // ) {
11671
+ // this.logger(
11672
+ // 'info',
11673
+ // `Local participant's video track is muted remotely`,
11674
+ // );
11675
+ // await this.camera.disable();
11676
+ // if (this.publisher.isPublishing(TrackType.VIDEO)) {
11677
+ // await this.stopPublish(TrackType.VIDEO);
11678
+ // }
11679
+ // }
11680
+ // if (
11681
+ // this.publisher.hasEverPublished(TrackType.AUDIO) &&
11682
+ // this.publisher.isPublishing(TrackType.AUDIO) &&
11683
+ // !p?.publishedTracks.includes(TrackType.AUDIO)
11684
+ // ) {
11685
+ // this.logger(
11686
+ // 'info',
11687
+ // `Local participant's audio track is muted remotely`,
11688
+ // );
11689
+ // await this.microphone.disable();
11690
+ // if (this.publisher.isPublishing(TrackType.AUDIO)) {
11691
+ // await this.stopPublish(TrackType.AUDIO);
11692
+ // }
11693
+ // }
11694
+ // });
11581
11695
  this.speaker = new SpeakerManager();
11582
11696
  }
11583
11697
  registerEffects() {
@@ -11603,9 +11717,27 @@ class Call {
11603
11717
  };
11604
11718
  for (const [permission, trackType] of Object.entries(permissionToTrackType)) {
11605
11719
  const hasPermission = this.permissionsContext.hasPermission(permission);
11606
- if (!hasPermission && this.publisher.isPublishing(trackType)) {
11607
- this.stopPublish(trackType).catch((err) => {
11720
+ if (!hasPermission &&
11721
+ (this.publisher.isPublishing(trackType) ||
11722
+ this.publisher.isLive(trackType))) {
11723
+ // Stop tracks, then notify device manager
11724
+ this.stopPublish(trackType)
11725
+ .catch((err) => {
11608
11726
  this.logger('error', `Error stopping publish ${trackType}`, err);
11727
+ })
11728
+ .then(() => {
11729
+ if (trackType === TrackType.VIDEO &&
11730
+ this.camera.state.status === 'enabled') {
11731
+ this.camera
11732
+ .disable()
11733
+ .catch((err) => this.logger('error', `Error disabling camera after pemission revoked`, err));
11734
+ }
11735
+ if (trackType === TrackType.AUDIO &&
11736
+ this.microphone.state.status === 'enabled') {
11737
+ this.microphone
11738
+ .disable()
11739
+ .catch((err) => this.logger('error', `Error disabling microphone after pemission revoked`, err));
11740
+ }
11609
11741
  });
11610
11742
  }
11611
11743
  }
@@ -12869,11 +13001,11 @@ class WSConnectionFallback {
12869
13001
  }
12870
13002
  }
12871
13003
 
12872
- const version = '0.3.13';
13004
+ const version = '0.3.15';
12873
13005
 
12874
13006
  const logger = getLogger(['location']);
12875
13007
  const HINT_URL = `https://hint.stream-io-video.com/`;
12876
- const getLocationHint = (hintUrl = HINT_URL, timeout = 1500) => __awaiter(void 0, void 0, void 0, function* () {
13008
+ const getLocationHint = (hintUrl = HINT_URL, timeout = 2000) => __awaiter(void 0, void 0, void 0, function* () {
12877
13009
  const abortController = new AbortController();
12878
13010
  const timeoutId = setTimeout(() => abortController.abort(), timeout);
12879
13011
  try {
@@ -12886,7 +13018,7 @@ const getLocationHint = (hintUrl = HINT_URL, timeout = 1500) => __awaiter(void 0
12886
13018
  return awsPop.substring(0, 3); // AMS1-P2 -> AMS
12887
13019
  }
12888
13020
  catch (e) {
12889
- logger('error', `Failed to get location hint from ${HINT_URL}`, e);
13021
+ logger('warn', `Failed to get location hint from ${HINT_URL}`, e);
12890
13022
  return 'ERR';
12891
13023
  }
12892
13024
  finally {