@100mslive/hms-video-store 0.12.19-alpha.21 → 0.12.19-alpha.23

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.
@@ -14,6 +14,12 @@ export declare class HMSLocalAudioTrack extends HMSAudioTrack {
14
14
  private pluginsManager;
15
15
  private processedTrack?;
16
16
  private manuallySelectedDeviceId?;
17
+ /**
18
+ * This is to keep track of all the tracks created so far and stop and clear them when creating new tracks to release microphone
19
+ * This is needed because when replaceTrackWith is called before updating native track, there is no way that track is available
20
+ * for you to stop, which leads to the microphone not released even after leave is called.
21
+ */
22
+ private tracksCreated;
17
23
  audioLevelMonitor?: TrackAudioLevelMonitor;
18
24
  /**
19
25
  * see the doc in HMSLocalVideoTrack
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.12.19-alpha.21",
2
+ "version": "0.12.19-alpha.23",
3
3
  "license": "MIT",
4
4
  "repository": {
5
5
  "type": "git",
@@ -73,5 +73,5 @@
73
73
  "conferencing",
74
74
  "100ms"
75
75
  ],
76
- "gitHead": "5201ce15a559d9a81939b5752b9af73cde22c552"
76
+ "gitHead": "6d6c172062d69b0bd667798f32ce3a726dd796c5"
77
77
  }
@@ -39,8 +39,6 @@ export class AudioSinkManager {
39
39
  private volume = 100;
40
40
  private state = { ...INITIAL_STATE };
41
41
  private listener?: HMSUpdateListener;
42
- private timer: ReturnType<typeof setInterval> | null = null;
43
- private earpieceSelected = false;
44
42
 
45
43
  constructor(private store: Store, private deviceManager: DeviceManager, private eventBus: EventBus) {
46
44
  this.eventBus.audioTrackAdded.subscribe(this.handleTrackAdd);
@@ -48,7 +46,6 @@ export class AudioSinkManager {
48
46
  this.eventBus.audioTrackUpdate.subscribe(this.handleTrackUpdate);
49
47
  this.eventBus.deviceChange.subscribe(this.handleAudioDeviceChange);
50
48
  this.eventBus.localVideoUnmutedNatively.subscribe(this.unpauseAudioTracks);
51
- this.startPollingForDevices();
52
49
  }
53
50
 
54
51
  setListener(listener?: HMSUpdateListener) {
@@ -95,12 +92,7 @@ export class AudioSinkManager {
95
92
 
96
93
  cleanup() {
97
94
  this.audioSink?.remove();
98
- this.earpieceSelected = false;
99
95
  this.audioSink = undefined;
100
- if (this.timer) {
101
- clearInterval(this.timer);
102
- this.timer = null;
103
- }
104
96
  this.eventBus.audioTrackAdded.unsubscribe(this.handleTrackAdd);
105
97
  this.eventBus.audioTrackRemoved.unsubscribe(this.handleTrackRemove);
106
98
  this.eventBus.audioTrackUpdate.unsubscribe(this.handleTrackUpdate);
@@ -185,11 +177,6 @@ export class AudioSinkManager {
185
177
  };
186
178
 
187
179
  private handleAudioDeviceChange = (event: HMSDeviceChangeEvent) => {
188
- // this means the initial load
189
- if (!event.selection) {
190
- HMSLogger.d(this.TAG, 'device change called');
191
- this.autoSelectAudioOutput();
192
- }
193
180
  // if there is no selection that means this is an init request. No need to do anything
194
181
  if (event.isUserSelection || event.error || !event.selection || event.type === 'video') {
195
182
  return;
@@ -260,50 +247,4 @@ export class AudioSinkManager {
260
247
  track.setAudioElement(null);
261
248
  }
262
249
  };
263
-
264
- private startPollingForDevices = () => {
265
- // device change supported, no polling needed
266
- if ('ondevicechange' in navigator.mediaDevices) {
267
- return;
268
- }
269
- this.timer = setInterval(() => {
270
- (async () => {
271
- await this.deviceManager.init(true, false);
272
- await this.autoSelectAudioOutput();
273
- this.unpauseAudioTracks();
274
- })();
275
- }, 5000);
276
- };
277
-
278
- /**
279
- * Mweb is not able to play via call channel by default, this is to switch from media channel to call channel
280
- */
281
- // eslint-disable-next-line complexity
282
- private autoSelectAudioOutput = async () => {
283
- if ('ondevicechange' in navigator.mediaDevices) {
284
- return;
285
- }
286
- const { bluetoothDevice, earpiece, speakerPhone, wired } = this.deviceManager.categorizeAudioInputDevices();
287
- const localAudioTrack = this.store.getLocalPeer()?.audioTrack;
288
- if (localAudioTrack && earpiece) {
289
- const manualSelection = this.deviceManager.getManuallySelectedAudioDevice();
290
- const externalDeviceID =
291
- manualSelection?.deviceId || bluetoothDevice?.deviceId || wired?.deviceId || speakerPhone?.deviceId;
292
- HMSLogger.d(this.TAG, 'externalDeviceID', externalDeviceID);
293
- // already selected appropriate device
294
- if (localAudioTrack.settings.deviceId === externalDeviceID && this.earpieceSelected) {
295
- return;
296
- }
297
- if (!this.earpieceSelected && bluetoothDevice?.deviceId !== externalDeviceID) {
298
- await localAudioTrack.setSettings({ deviceId: earpiece?.deviceId }, true);
299
- this.earpieceSelected = true;
300
- }
301
- await localAudioTrack.setSettings(
302
- {
303
- deviceId: externalDeviceID,
304
- },
305
- true,
306
- );
307
- }
308
- };
309
250
  }
@@ -34,6 +34,8 @@ export class DeviceManager implements HMSDeviceManager {
34
34
  private initialized = false;
35
35
  private videoInputChanged = false;
36
36
  private audioInputChanged = false;
37
+ private earpieceSelected = false;
38
+ private timer: ReturnType<typeof setTimeout> | null = null;
37
39
 
38
40
  constructor(private store: Store, private eventBus: EventBus) {
39
41
  const isLocalTrackEnabled = ({ enabled, track }: { enabled: boolean; track: HMSLocalTrack }) =>
@@ -97,6 +99,8 @@ export class DeviceManager implements HMSDeviceManager {
97
99
  // do it only on initial load.
98
100
  if (!force) {
99
101
  await this.updateToActualDefaultDevice();
102
+ await this.autoSelectAudioOutput();
103
+ this.startPollingForDevices();
100
104
  }
101
105
  this.logDevices('Init');
102
106
  await this.setOutputDevice();
@@ -124,6 +128,11 @@ export class DeviceManager implements HMSDeviceManager {
124
128
 
125
129
  cleanup() {
126
130
  this.initialized = false;
131
+ this.earpieceSelected = false;
132
+ if (this.timer) {
133
+ clearTimeout(this.timer);
134
+ this.timer = null;
135
+ }
127
136
  this.audioInput = [];
128
137
  this.audioOutput = [];
129
138
  this.videoInput = [];
@@ -210,7 +219,10 @@ export class DeviceManager implements HMSDeviceManager {
210
219
  const nonIPhoneDevice = this.audioInput.find(device => !device.label.toLowerCase().includes('iphone'));
211
220
  return isIOS() && nonIPhoneDevice ? nonIPhoneDevice?.deviceId : this.getNewAudioInputDevice()?.deviceId;
212
221
  };
213
- await localPeer.audioTrack.setSettings({ deviceId: getInitialDeviceId() }, true);
222
+ const deviceIdToUpdate = getInitialDeviceId();
223
+ if (deviceIdToUpdate) {
224
+ await localPeer.audioTrack.setSettings({ deviceId: getInitialDeviceId() }, true);
225
+ }
214
226
  }
215
227
  };
216
228
 
@@ -446,6 +458,57 @@ export class DeviceManager implements HMSDeviceManager {
446
458
  return { bluetoothDevice, speakerPhone, wired, earpiece };
447
459
  }
448
460
 
461
+ private startPollingForDevices = () => {
462
+ // device change supported, no polling needed
463
+ if ('ondevicechange' in navigator.mediaDevices) {
464
+ return;
465
+ }
466
+ this.timer = setTimeout(() => {
467
+ (async () => {
468
+ await this.enumerateDevices();
469
+ await this.autoSelectAudioOutput();
470
+ this.startPollingForDevices();
471
+ })();
472
+ }, 5000);
473
+ };
474
+
475
+ /**
476
+ * Mweb is not able to play via call channel by default, this is to switch from media channel to call channel
477
+ */
478
+ // eslint-disable-next-line complexity
479
+ private autoSelectAudioOutput = async () => {
480
+ if ('ondevicechange' in navigator.mediaDevices) {
481
+ return;
482
+ }
483
+ const { bluetoothDevice, earpiece, speakerPhone, wired } = this.categorizeAudioInputDevices();
484
+ const localAudioTrack = this.store.getLocalPeer()?.audioTrack;
485
+ if (!localAudioTrack || !earpiece) {
486
+ return;
487
+ }
488
+ const manualSelection = this.getManuallySelectedAudioDevice();
489
+ const externalDeviceID =
490
+ manualSelection?.deviceId || bluetoothDevice?.deviceId || wired?.deviceId || speakerPhone?.deviceId;
491
+ HMSLogger.d(this.TAG, 'externalDeviceID', externalDeviceID);
492
+ // already selected appropriate device
493
+ if (localAudioTrack.settings.deviceId === externalDeviceID && this.earpieceSelected) {
494
+ return;
495
+ }
496
+ if (!this.earpieceSelected) {
497
+ if (bluetoothDevice?.deviceId === externalDeviceID) {
498
+ this.earpieceSelected = true;
499
+ return;
500
+ }
501
+ await localAudioTrack.setSettings({ deviceId: earpiece?.deviceId }, true);
502
+ this.earpieceSelected = true;
503
+ }
504
+ await localAudioTrack.setSettings(
505
+ {
506
+ deviceId: externalDeviceID,
507
+ },
508
+ true,
509
+ );
510
+ };
511
+
449
512
  // eslint-disable-next-line complexity
450
513
  private getAudioOutputDeviceMatchingInput(inputDevice?: MediaDeviceInfo) {
451
514
  const blacklist = this.store.getConfig()?.settings?.speakerAutoSelectionBlacklist || [];
@@ -26,6 +26,12 @@ export class HMSLocalAudioTrack extends HMSAudioTrack {
26
26
  private pluginsManager: HMSAudioPluginsManager;
27
27
  private processedTrack?: MediaStreamTrack;
28
28
  private manuallySelectedDeviceId?: string;
29
+ /**
30
+ * This is to keep track of all the tracks created so far and stop and clear them when creating new tracks to release microphone
31
+ * This is needed because when replaceTrackWith is called before updating native track, there is no way that track is available
32
+ * for you to stop, which leads to the microphone not released even after leave is called.
33
+ */
34
+ private tracksCreated = new Set<MediaStreamTrack>();
29
35
 
30
36
  audioLevelMonitor?: TrackAudioLevelMonitor;
31
37
 
@@ -127,6 +133,7 @@ export class HMSLocalAudioTrack extends HMSAudioTrack {
127
133
  * @param track
128
134
  */
129
135
  private async updateTrack(track: MediaStreamTrack) {
136
+ track.enabled = this.enabled;
130
137
  const localStream = this.stream as HMSLocalStream;
131
138
  await localStream.replaceStreamTrack(this.nativeTrack, track);
132
139
  // change nativeTrack so plugin can start its work
@@ -144,14 +151,17 @@ export class HMSLocalAudioTrack extends HMSAudioTrack {
144
151
  * no audio when the above getAudioTrack throws an error. ex: DeviceInUse error
145
152
  */
146
153
  prevTrack?.stop();
154
+ this.tracksCreated.forEach(track => track.stop());
155
+ this.tracksCreated.clear();
147
156
  try {
148
157
  const newTrack = await getAudioTrack(settings);
149
- newTrack.enabled = this.enabled;
158
+ this.tracksCreated.add(newTrack);
150
159
  HMSLogger.d(this.TAG, 'replaceTrack, Previous track stopped', prevTrack, 'newTrack', newTrack);
151
160
  await this.updateTrack(newTrack);
152
161
  } catch (e) {
153
162
  // Generate a new track from previous settings so there will be audio because previous track is stopped
154
163
  const newTrack = await getAudioTrack(this.settings);
164
+ this.tracksCreated.add(newTrack);
155
165
  await this.updateTrack(newTrack);
156
166
  if (this.isPublished) {
157
167
  this.eventBus.analytics.publish(
@@ -271,6 +281,8 @@ export class HMSLocalAudioTrack extends HMSAudioTrack {
271
281
  await this.pluginsManager.closeContext();
272
282
  this.transceiver = undefined;
273
283
  this.processedTrack?.stop();
284
+ this.tracksCreated.forEach(track => track.stop());
285
+ this.tracksCreated.clear();
274
286
  this.isPublished = false;
275
287
  this.destroyAudioLevelMonitor();
276
288
  document.removeEventListener('visibilitychange', this.handleVisibilityChange);
@@ -19,6 +19,7 @@ export class WakeLockManager {
19
19
  HMSLogger.w(this.TAG, 'Error while releasing wake lock', `name=${error.name}, message=${error.message}`);
20
20
  }
21
21
  }
22
+ document?.removeEventListener('visibilitychange', this.visibilityHandler);
22
23
  this.wakeLock = null;
23
24
  };
24
25