@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.cjs.js CHANGED
@@ -6432,6 +6432,14 @@ class Publisher {
6432
6432
  [TrackType.SCREEN_SHARE_AUDIO]: undefined,
6433
6433
  [TrackType.UNSPECIFIED]: undefined,
6434
6434
  };
6435
+ /**
6436
+ * A map keeping track of track types that were published to the SFU.
6437
+ * This map shouldn't be cleared when unpublishing a track, as it is used
6438
+ * to determine whether a track was published before.
6439
+ *
6440
+ * @private
6441
+ */
6442
+ this.trackTypePublishHistory = new Map();
6435
6443
  this.isIceRestarting = false;
6436
6444
  this.createPeerConnection = (connectionConfig) => {
6437
6445
  const pc = new RTCPeerConnection(connectionConfig);
@@ -6513,6 +6521,9 @@ class Publisher {
6513
6521
  // by an external factor as permission revokes, device disconnected, etc.
6514
6522
  // keep in mind that `track.stop()` doesn't trigger this event.
6515
6523
  track.addEventListener('ended', handleTrackEnded);
6524
+ if (!track.enabled) {
6525
+ track.enabled = true;
6526
+ }
6516
6527
  transceiver = this.pc.addTransceiver(track, {
6517
6528
  direction: 'sendonly',
6518
6529
  streams: trackType === TrackType.VIDEO || trackType === TrackType.SCREEN_SHARE
@@ -6523,6 +6534,7 @@ class Publisher {
6523
6534
  logger$3('debug', `Added ${TrackType[trackType]} transceiver`);
6524
6535
  this.transceiverInitOrder.push(trackType);
6525
6536
  this.transceiverRegistry[trackType] = transceiver;
6537
+ this.trackTypePublishHistory.set(trackType, true);
6526
6538
  if ('setCodecPreferences' in transceiver && codecPreferences) {
6527
6539
  logger$3('info', `Setting ${TrackType[trackType]} codec preferences`, codecPreferences);
6528
6540
  transceiver.setCodecPreferences(codecPreferences);
@@ -6550,16 +6562,25 @@ class Publisher {
6550
6562
  * @param stopTrack specifies whether track should be stopped or just disabled
6551
6563
  */
6552
6564
  this.unpublishStream = (trackType, stopTrack) => __awaiter(this, void 0, void 0, function* () {
6565
+ var _b;
6553
6566
  const transceiver = this.pc
6554
6567
  .getTransceivers()
6555
6568
  .find((t) => t === this.transceiverRegistry[trackType] && t.sender.track);
6556
6569
  if (transceiver &&
6557
6570
  transceiver.sender.track &&
6558
- transceiver.sender.track.readyState === 'live') {
6571
+ (stopTrack
6572
+ ? transceiver.sender.track.readyState === 'live'
6573
+ : transceiver.sender.track.enabled)) {
6559
6574
  stopTrack
6560
6575
  ? transceiver.sender.track.stop()
6561
6576
  : (transceiver.sender.track.enabled = false);
6562
- return this.notifyTrackMuteStateChanged(undefined, transceiver.sender.track, trackType, true);
6577
+ // We don't need to notify SFU if unpublishing in response to remote soft mute
6578
+ if (!((_b = this.state.localParticipant) === null || _b === void 0 ? void 0 : _b.publishedTracks.includes(trackType))) {
6579
+ return;
6580
+ }
6581
+ else {
6582
+ return this.notifyTrackMuteStateChanged(undefined, transceiver.sender.track, trackType, true);
6583
+ }
6563
6584
  }
6564
6585
  });
6565
6586
  /**
@@ -6568,6 +6589,32 @@ class Publisher {
6568
6589
  * @param trackType the track type to check.
6569
6590
  */
6570
6591
  this.isPublishing = (trackType) => {
6592
+ const transceiverForTrackType = this.transceiverRegistry[trackType];
6593
+ if (transceiverForTrackType && transceiverForTrackType.sender) {
6594
+ const sender = transceiverForTrackType.sender;
6595
+ return (!!sender.track &&
6596
+ sender.track.readyState === 'live' &&
6597
+ sender.track.enabled);
6598
+ }
6599
+ return false;
6600
+ };
6601
+ /**
6602
+ * Returns true if the given track type was ever published to the SFU.
6603
+ * Contrary to `isPublishing`, this method returns true if a certain
6604
+ * track type was published before, even if it is currently unpublished.
6605
+ *
6606
+ * @param trackType the track type to check.
6607
+ */
6608
+ this.hasEverPublished = (trackType) => {
6609
+ var _a;
6610
+ return (_a = this.trackTypePublishHistory.get(trackType)) !== null && _a !== void 0 ? _a : false;
6611
+ };
6612
+ /**
6613
+ * Returns true if the given track type is currently live
6614
+ *
6615
+ * @param trackType the track type to check.
6616
+ */
6617
+ this.isLive = (trackType) => {
6571
6618
  const transceiverForTrackType = this.transceiverRegistry[trackType];
6572
6619
  if (transceiverForTrackType && transceiverForTrackType.sender) {
6573
6620
  const sender = transceiverForTrackType.sender;
@@ -6608,9 +6655,9 @@ class Publisher {
6608
6655
  });
6609
6656
  };
6610
6657
  this.updateVideoPublishQuality = (enabledRids) => __awaiter(this, void 0, void 0, function* () {
6611
- var _b;
6658
+ var _c;
6612
6659
  logger$3('info', 'Update publish quality, requested rids by SFU:', enabledRids);
6613
- const videoSender = (_b = this.transceiverRegistry[TrackType.VIDEO]) === null || _b === void 0 ? void 0 : _b.sender;
6660
+ const videoSender = (_c = this.transceiverRegistry[TrackType.VIDEO]) === null || _c === void 0 ? void 0 : _c.sender;
6614
6661
  if (!videoSender) {
6615
6662
  logger$3('warn', 'Update publish quality, no video sender found.');
6616
6663
  return;
@@ -6708,8 +6755,8 @@ class Publisher {
6708
6755
  * @param options the optional offer options to use.
6709
6756
  */
6710
6757
  this.negotiate = (options) => __awaiter(this, void 0, void 0, function* () {
6711
- var _c;
6712
- this.isIceRestarting = (_c = options === null || options === void 0 ? void 0 : options.iceRestart) !== null && _c !== void 0 ? _c : false;
6758
+ var _d;
6759
+ this.isIceRestarting = (_d = options === null || options === void 0 ? void 0 : options.iceRestart) !== null && _d !== void 0 ? _d : false;
6713
6760
  const offer = yield this.pc.createOffer(options);
6714
6761
  offer.sdp = this.mungeCodecs(offer.sdp);
6715
6762
  const trackInfos = this.getCurrentTrackInfos(offer.sdp);
@@ -7497,6 +7544,9 @@ const retryable = (rpc, logger) => __awaiter(void 0, void 0, void 0, function* (
7497
7544
  retryAttempt++;
7498
7545
  } while (((_a = rpcCallResult.response.error) === null || _a === void 0 ? void 0 : _a.shouldRetry) &&
7499
7546
  retryAttempt < MAX_RETRIES);
7547
+ if (rpcCallResult.response.error) {
7548
+ throw rpcCallResult.response.error;
7549
+ }
7500
7550
  return rpcCallResult;
7501
7551
  });
7502
7552
 
@@ -9820,6 +9870,190 @@ class CameraManagerState extends InputMediaDeviceManagerState {
9820
9870
  }
9821
9871
  }
9822
9872
 
9873
+ class InputMediaDeviceManager {
9874
+ constructor(call, state, trackType) {
9875
+ this.call = call;
9876
+ this.state = state;
9877
+ this.trackType = trackType;
9878
+ this.logger = getLogger([`${TrackType[trackType].toLowerCase()} manager`]);
9879
+ }
9880
+ /**
9881
+ * Lists the available audio/video devices
9882
+ *
9883
+ * Note: It prompts the user for a permission to use devices (if not already granted)
9884
+ *
9885
+ * @returns an Observable that will be updated if a device is connected or disconnected
9886
+ */
9887
+ listDevices() {
9888
+ return this.getDevices();
9889
+ }
9890
+ /**
9891
+ * Starts camera/microphone
9892
+ */
9893
+ enable() {
9894
+ return __awaiter(this, void 0, void 0, function* () {
9895
+ if (this.state.status === 'enabled') {
9896
+ return;
9897
+ }
9898
+ this.enablePromise = this.unmuteStream();
9899
+ try {
9900
+ yield this.enablePromise;
9901
+ this.state.setStatus('enabled');
9902
+ }
9903
+ catch (error) {
9904
+ this.enablePromise = undefined;
9905
+ throw error;
9906
+ }
9907
+ });
9908
+ }
9909
+ /**
9910
+ * Stops camera/microphone
9911
+ *
9912
+ * @returns
9913
+ */
9914
+ disable() {
9915
+ return __awaiter(this, void 0, void 0, function* () {
9916
+ this.state.prevStatus = this.state.status;
9917
+ if (this.state.status === 'disabled') {
9918
+ return;
9919
+ }
9920
+ this.disablePromise = this.muteStream(this.state.disableMode === 'stop-tracks');
9921
+ try {
9922
+ yield this.disablePromise;
9923
+ this.state.setStatus('disabled');
9924
+ this.disablePromise = undefined;
9925
+ }
9926
+ catch (error) {
9927
+ this.disablePromise = undefined;
9928
+ throw error;
9929
+ }
9930
+ });
9931
+ }
9932
+ /**
9933
+ * If status was previously enabled, it will reenable the device.
9934
+ */
9935
+ resume() {
9936
+ return __awaiter(this, void 0, void 0, function* () {
9937
+ if (this.state.prevStatus === 'enabled' &&
9938
+ this.state.status === 'disabled') {
9939
+ this.enable();
9940
+ }
9941
+ });
9942
+ }
9943
+ /**
9944
+ * If current device statis is disabled, it will enable the device, else it will disable it.
9945
+ *
9946
+ * @returns
9947
+ */
9948
+ toggle() {
9949
+ return __awaiter(this, void 0, void 0, function* () {
9950
+ if (this.state.status === 'enabled') {
9951
+ return this.disable();
9952
+ }
9953
+ else {
9954
+ return this.enable();
9955
+ }
9956
+ });
9957
+ }
9958
+ /**
9959
+ * Select device
9960
+ *
9961
+ * Note: this method is not supported in React Native
9962
+ *
9963
+ * @param deviceId
9964
+ */
9965
+ select(deviceId) {
9966
+ return __awaiter(this, void 0, void 0, function* () {
9967
+ if (isReactNative()) {
9968
+ throw new Error('This method is not supported in React Native');
9969
+ }
9970
+ if (deviceId === this.state.selectedDevice) {
9971
+ return;
9972
+ }
9973
+ this.state.setDevice(deviceId);
9974
+ yield this.applySettingsToStream();
9975
+ });
9976
+ }
9977
+ applySettingsToStream() {
9978
+ return __awaiter(this, void 0, void 0, function* () {
9979
+ if (this.state.status === 'enabled') {
9980
+ yield this.muteStream();
9981
+ yield this.unmuteStream();
9982
+ }
9983
+ });
9984
+ }
9985
+ muteStream(stopTracks = true) {
9986
+ var _a;
9987
+ return __awaiter(this, void 0, void 0, function* () {
9988
+ if (!this.state.mediaStream) {
9989
+ return;
9990
+ }
9991
+ this.logger('debug', `${stopTracks ? 'Stopping' : 'Disabling'} stream`);
9992
+ if (this.call.state.callingState === exports.CallingState.JOINED) {
9993
+ yield this.stopPublishStream(stopTracks);
9994
+ }
9995
+ this.muteLocalStream(stopTracks);
9996
+ if (((_a = this.getTrack()) === null || _a === void 0 ? void 0 : _a.readyState) === 'ended') {
9997
+ // @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
9998
+ if (typeof this.state.mediaStream.release === 'function') {
9999
+ // @ts-expect-error
10000
+ this.state.mediaStream.release();
10001
+ }
10002
+ this.state.setMediaStream(undefined);
10003
+ }
10004
+ });
10005
+ }
10006
+ muteTrack() {
10007
+ const track = this.getTrack();
10008
+ if (!track || !track.enabled) {
10009
+ return;
10010
+ }
10011
+ track.enabled = false;
10012
+ }
10013
+ unmuteTrack() {
10014
+ const track = this.getTrack();
10015
+ if (!track || track.enabled) {
10016
+ return;
10017
+ }
10018
+ track.enabled = true;
10019
+ }
10020
+ stopTrack() {
10021
+ const track = this.getTrack();
10022
+ if (!track || track.readyState === 'ended') {
10023
+ return;
10024
+ }
10025
+ track.stop();
10026
+ }
10027
+ muteLocalStream(stopTracks) {
10028
+ if (!this.state.mediaStream) {
10029
+ return;
10030
+ }
10031
+ stopTracks ? this.stopTrack() : this.muteTrack();
10032
+ }
10033
+ unmuteStream() {
10034
+ var _a;
10035
+ return __awaiter(this, void 0, void 0, function* () {
10036
+ this.logger('debug', 'Starting stream');
10037
+ let stream;
10038
+ if (this.state.mediaStream && ((_a = this.getTrack()) === null || _a === void 0 ? void 0 : _a.readyState) === 'live') {
10039
+ stream = this.state.mediaStream;
10040
+ this.unmuteTrack();
10041
+ }
10042
+ else {
10043
+ if (this.state.mediaStream) {
10044
+ this.stopTrack();
10045
+ }
10046
+ const constraints = { deviceId: this.state.selectedDevice };
10047
+ stream = yield this.getStream(constraints);
10048
+ }
10049
+ if (this.call.state.callingState === exports.CallingState.JOINED) {
10050
+ yield this.publishStream(stream);
10051
+ }
10052
+ this.state.setMediaStream(stream);
10053
+ });
10054
+ }
10055
+ }
10056
+
9823
10057
  const getDevices = (constraints) => {
9824
10058
  return new rxjs.Observable((subscriber) => {
9825
10059
  navigator.mediaDevices
@@ -10068,156 +10302,9 @@ const disposeOfMediaStream = (stream) => {
10068
10302
  }
10069
10303
  };
10070
10304
 
10071
- class InputMediaDeviceManager {
10072
- constructor(call, state) {
10073
- this.call = call;
10074
- this.state = state;
10075
- }
10076
- /**
10077
- * Lists the available audio/video devices
10078
- *
10079
- * Note: It prompts the user for a permission to use devices (if not already granted)
10080
- *
10081
- * @returns an Observable that will be updated if a device is connected or disconnected
10082
- */
10083
- listDevices() {
10084
- return this.getDevices();
10085
- }
10086
- /**
10087
- * Starts camera/microphone
10088
- */
10089
- enable() {
10090
- return __awaiter(this, void 0, void 0, function* () {
10091
- if (this.state.status === 'enabled') {
10092
- return;
10093
- }
10094
- this.enablePromise = this.unmuteStream();
10095
- try {
10096
- yield this.enablePromise;
10097
- this.state.setStatus('enabled');
10098
- }
10099
- catch (error) {
10100
- this.enablePromise = undefined;
10101
- throw error;
10102
- }
10103
- });
10104
- }
10105
- /**
10106
- * Stops camera/microphone
10107
- *
10108
- * @returns
10109
- */
10110
- disable() {
10111
- return __awaiter(this, void 0, void 0, function* () {
10112
- this.state.prevStatus = this.state.status;
10113
- if (this.state.status === 'disabled') {
10114
- return;
10115
- }
10116
- this.disablePromise = this.muteStream(this.state.disableMode === 'stop-tracks');
10117
- try {
10118
- yield this.disablePromise;
10119
- this.state.setStatus('disabled');
10120
- this.disablePromise = undefined;
10121
- }
10122
- catch (error) {
10123
- this.disablePromise = undefined;
10124
- throw error;
10125
- }
10126
- });
10127
- }
10128
- /**
10129
- * If status was previously enabled, it will reenable the device.
10130
- */
10131
- resume() {
10132
- return __awaiter(this, void 0, void 0, function* () {
10133
- if (this.state.prevStatus === 'enabled' &&
10134
- this.state.status === 'disabled') {
10135
- this.enable();
10136
- }
10137
- });
10138
- }
10139
- /**
10140
- * If current device statis is disabled, it will enable the device, else it will disable it.
10141
- *
10142
- * @returns
10143
- */
10144
- toggle() {
10145
- return __awaiter(this, void 0, void 0, function* () {
10146
- if (this.state.status === 'enabled') {
10147
- return this.disable();
10148
- }
10149
- else {
10150
- return this.enable();
10151
- }
10152
- });
10153
- }
10154
- /**
10155
- * Select device
10156
- *
10157
- * Note: this method is not supported in React Native
10158
- *
10159
- * @param deviceId
10160
- */
10161
- select(deviceId) {
10162
- return __awaiter(this, void 0, void 0, function* () {
10163
- if (isReactNative()) {
10164
- throw new Error('This method is not supported in React Native');
10165
- }
10166
- if (deviceId === this.state.selectedDevice) {
10167
- return;
10168
- }
10169
- this.state.setDevice(deviceId);
10170
- yield this.applySettingsToStream();
10171
- });
10172
- }
10173
- applySettingsToStream() {
10174
- return __awaiter(this, void 0, void 0, function* () {
10175
- if (this.state.status === 'enabled') {
10176
- yield this.muteStream();
10177
- yield this.unmuteStream();
10178
- }
10179
- });
10180
- }
10181
- muteStream(stopTracks = true) {
10182
- return __awaiter(this, void 0, void 0, function* () {
10183
- if (!this.state.mediaStream) {
10184
- return;
10185
- }
10186
- if (this.call.state.callingState === exports.CallingState.JOINED) {
10187
- yield this.stopPublishStream(stopTracks);
10188
- }
10189
- else if (this.state.mediaStream) {
10190
- stopTracks
10191
- ? disposeOfMediaStream(this.state.mediaStream)
10192
- : this.muteTracks();
10193
- }
10194
- if (stopTracks) {
10195
- this.state.setMediaStream(undefined);
10196
- }
10197
- });
10198
- }
10199
- unmuteStream() {
10200
- return __awaiter(this, void 0, void 0, function* () {
10201
- let stream;
10202
- if (this.state.mediaStream) {
10203
- stream = this.state.mediaStream;
10204
- this.unmuteTracks();
10205
- }
10206
- else {
10207
- const constraints = { deviceId: this.state.selectedDevice };
10208
- stream = yield this.getStream(constraints);
10209
- }
10210
- if (this.call.state.callingState === exports.CallingState.JOINED) {
10211
- yield this.publishStream(stream);
10212
- }
10213
- this.state.setMediaStream(stream);
10214
- });
10215
- }
10216
- }
10217
-
10218
10305
  class CameraManager extends InputMediaDeviceManager {
10219
10306
  constructor(call) {
10220
- super(call, new CameraManagerState());
10307
+ super(call, new CameraManagerState(), TrackType.VIDEO);
10221
10308
  this.targetResolution = {
10222
10309
  width: 1280,
10223
10310
  height: 720,
@@ -10269,6 +10356,7 @@ class CameraManager extends InputMediaDeviceManager {
10269
10356
  if (width !== this.targetResolution.width ||
10270
10357
  height !== this.targetResolution.height)
10271
10358
  yield this.applySettingsToStream();
10359
+ this.logger('debug', `${width}x${height} target resolution applied to media stream`);
10272
10360
  }
10273
10361
  });
10274
10362
  }
@@ -10292,13 +10380,9 @@ class CameraManager extends InputMediaDeviceManager {
10292
10380
  stopPublishStream(stopTracks) {
10293
10381
  return this.call.stopPublish(TrackType.VIDEO, stopTracks);
10294
10382
  }
10295
- muteTracks() {
10296
- var _a;
10297
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().forEach((t) => (t.enabled = false));
10298
- }
10299
- unmuteTracks() {
10383
+ getTrack() {
10300
10384
  var _a;
10301
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks().forEach((t) => (t.enabled = true));
10385
+ return (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getVideoTracks()[0];
10302
10386
  }
10303
10387
  }
10304
10388
 
@@ -10314,7 +10398,7 @@ class MicrophoneManagerState extends InputMediaDeviceManagerState {
10314
10398
 
10315
10399
  class MicrophoneManager extends InputMediaDeviceManager {
10316
10400
  constructor(call) {
10317
- super(call, new MicrophoneManagerState());
10401
+ super(call, new MicrophoneManagerState(), TrackType.AUDIO);
10318
10402
  }
10319
10403
  getDevices() {
10320
10404
  return getAudioDevices();
@@ -10328,13 +10412,9 @@ class MicrophoneManager extends InputMediaDeviceManager {
10328
10412
  stopPublishStream(stopTracks) {
10329
10413
  return this.call.stopPublish(TrackType.AUDIO, stopTracks);
10330
10414
  }
10331
- muteTracks() {
10332
- var _a;
10333
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach((t) => (t.enabled = false));
10334
- }
10335
- unmuteTracks() {
10415
+ getTrack() {
10336
10416
  var _a;
10337
- (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks().forEach((t) => (t.enabled = true));
10417
+ return (_a = this.state.mediaStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];
10338
10418
  }
10339
10419
  }
10340
10420
 
@@ -11020,7 +11100,7 @@ class Call {
11020
11100
  */
11021
11101
  this.stopPublish = (trackType, stopTrack = true) => __awaiter(this, void 0, void 0, function* () {
11022
11102
  var _j;
11023
- this.logger('info', `stopPublish ${TrackType[trackType]}`);
11103
+ this.logger('info', `stopPublish ${TrackType[trackType]}, stop tracks: ${stopTrack}`);
11024
11104
  yield ((_j = this.publisher) === null || _j === void 0 ? void 0 : _j.unpublishStream(trackType, stopTrack));
11025
11105
  });
11026
11106
  /**
@@ -11598,6 +11678,40 @@ class Call {
11598
11678
  this.leaveCallHooks.add(createSubscription(this.trackSubscriptionsSubject.pipe(rxjs.debounce((v) => rxjs.timer(v.type)), rxjs.map((v) => v.data)), (subscriptions) => { var _a; return (_a = this.sfuClient) === null || _a === void 0 ? void 0 : _a.updateSubscriptions(subscriptions); }));
11599
11679
  this.camera = new CameraManager(this);
11600
11680
  this.microphone = new MicrophoneManager(this);
11681
+ // FIXME OL: disable soft-mutes as they are not working properly
11682
+ // this.state.localParticipant$.subscribe(async (p) => {
11683
+ // if (!this.publisher) return;
11684
+ // // Mute via device manager
11685
+ // // If integrator doesn't use device manager, we mute using stopPublish
11686
+ // if (
11687
+ // this.publisher.hasEverPublished(TrackType.VIDEO) &&
11688
+ // this.publisher.isPublishing(TrackType.VIDEO) &&
11689
+ // !p?.publishedTracks.includes(TrackType.VIDEO)
11690
+ // ) {
11691
+ // this.logger(
11692
+ // 'info',
11693
+ // `Local participant's video track is muted remotely`,
11694
+ // );
11695
+ // await this.camera.disable();
11696
+ // if (this.publisher.isPublishing(TrackType.VIDEO)) {
11697
+ // await this.stopPublish(TrackType.VIDEO);
11698
+ // }
11699
+ // }
11700
+ // if (
11701
+ // this.publisher.hasEverPublished(TrackType.AUDIO) &&
11702
+ // this.publisher.isPublishing(TrackType.AUDIO) &&
11703
+ // !p?.publishedTracks.includes(TrackType.AUDIO)
11704
+ // ) {
11705
+ // this.logger(
11706
+ // 'info',
11707
+ // `Local participant's audio track is muted remotely`,
11708
+ // );
11709
+ // await this.microphone.disable();
11710
+ // if (this.publisher.isPublishing(TrackType.AUDIO)) {
11711
+ // await this.stopPublish(TrackType.AUDIO);
11712
+ // }
11713
+ // }
11714
+ // });
11601
11715
  this.speaker = new SpeakerManager();
11602
11716
  }
11603
11717
  registerEffects() {
@@ -11623,9 +11737,27 @@ class Call {
11623
11737
  };
11624
11738
  for (const [permission, trackType] of Object.entries(permissionToTrackType)) {
11625
11739
  const hasPermission = this.permissionsContext.hasPermission(permission);
11626
- if (!hasPermission && this.publisher.isPublishing(trackType)) {
11627
- this.stopPublish(trackType).catch((err) => {
11740
+ if (!hasPermission &&
11741
+ (this.publisher.isPublishing(trackType) ||
11742
+ this.publisher.isLive(trackType))) {
11743
+ // Stop tracks, then notify device manager
11744
+ this.stopPublish(trackType)
11745
+ .catch((err) => {
11628
11746
  this.logger('error', `Error stopping publish ${trackType}`, err);
11747
+ })
11748
+ .then(() => {
11749
+ if (trackType === TrackType.VIDEO &&
11750
+ this.camera.state.status === 'enabled') {
11751
+ this.camera
11752
+ .disable()
11753
+ .catch((err) => this.logger('error', `Error disabling camera after pemission revoked`, err));
11754
+ }
11755
+ if (trackType === TrackType.AUDIO &&
11756
+ this.microphone.state.status === 'enabled') {
11757
+ this.microphone
11758
+ .disable()
11759
+ .catch((err) => this.logger('error', `Error disabling microphone after pemission revoked`, err));
11760
+ }
11629
11761
  });
11630
11762
  }
11631
11763
  }
@@ -12889,11 +13021,11 @@ class WSConnectionFallback {
12889
13021
  }
12890
13022
  }
12891
13023
 
12892
- const version = '0.3.13';
13024
+ const version = '0.3.15';
12893
13025
 
12894
13026
  const logger = getLogger(['location']);
12895
13027
  const HINT_URL = `https://hint.stream-io-video.com/`;
12896
- const getLocationHint = (hintUrl = HINT_URL, timeout = 1500) => __awaiter(void 0, void 0, void 0, function* () {
13028
+ const getLocationHint = (hintUrl = HINT_URL, timeout = 2000) => __awaiter(void 0, void 0, void 0, function* () {
12897
13029
  const abortController = new AbortController();
12898
13030
  const timeoutId = setTimeout(() => abortController.abort(), timeout);
12899
13031
  try {
@@ -12906,7 +13038,7 @@ const getLocationHint = (hintUrl = HINT_URL, timeout = 1500) => __awaiter(void 0
12906
13038
  return awsPop.substring(0, 3); // AMS1-P2 -> AMS
12907
13039
  }
12908
13040
  catch (e) {
12909
- logger('error', `Failed to get location hint from ${HINT_URL}`, e);
13041
+ logger('warn', `Failed to get location hint from ${HINT_URL}`, e);
12910
13042
  return 'ERR';
12911
13043
  }
12912
13044
  finally {