@stream-io/video-client 0.4.5 → 0.4.7

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
@@ -10001,8 +10001,7 @@ const getDevices = (constraints, kind) => {
10001
10001
  /**
10002
10002
  * [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
10003
10003
  *
10004
- * @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.
10005
- */
10004
+ * */
10006
10005
  const checkIfAudioOutputChangeSupported = () => {
10007
10006
  if (typeof document === 'undefined')
10008
10007
  return false;
@@ -10162,90 +10161,10 @@ const getScreenShareStream = async (options) => {
10162
10161
  throw e;
10163
10162
  }
10164
10163
  };
10165
- const watchForDisconnectedDevice = (kind, deviceId$) => {
10166
- let devices$;
10167
- switch (kind) {
10168
- case 'audioinput':
10169
- devices$ = getAudioDevices();
10170
- break;
10171
- case 'videoinput':
10172
- devices$ = getVideoDevices();
10173
- break;
10174
- case 'audiooutput':
10175
- devices$ = getAudioOutputDevices();
10176
- break;
10177
- }
10178
- return rxjs.combineLatest([devices$, deviceId$]).pipe(rxjs.filter(([devices, deviceId]) => !!deviceId && !devices.find((d) => d.deviceId === deviceId)), rxjs.map(() => true));
10179
- };
10180
- /**
10181
- * Notifies the subscriber if a given 'audioinput' device is disconnected
10182
- *
10183
- * @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.
10184
- * @param deviceId$ an Observable that specifies which device to watch for
10185
- * @returns
10186
- */
10187
- const watchForDisconnectedAudioDevice = (deviceId$) => {
10188
- return watchForDisconnectedDevice('audioinput', deviceId$);
10189
- };
10190
- /**
10191
- * Notifies the subscriber if a given 'videoinput' device is disconnected
10192
- *
10193
- * @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.
10194
- * @param deviceId$ an Observable that specifies which device to watch for
10195
- * @returns
10196
- */
10197
- const watchForDisconnectedVideoDevice = (deviceId$) => {
10198
- return watchForDisconnectedDevice('videoinput', deviceId$);
10199
- };
10200
- /**
10201
- * Notifies the subscriber if a given 'audiooutput' device is disconnected
10202
- *
10203
- * @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.
10204
- * @param deviceId$ an Observable that specifies which device to watch for
10205
- * @returns
10206
- */
10207
- const watchForDisconnectedAudioOutputDevice = (deviceId$) => {
10208
- return watchForDisconnectedDevice('audiooutput', deviceId$);
10209
- };
10210
- const watchForAddedDefaultDevice = (kind) => {
10211
- let devices$;
10212
- switch (kind) {
10213
- case 'audioinput':
10214
- devices$ = getAudioDevices();
10215
- break;
10216
- case 'videoinput':
10217
- devices$ = getVideoDevices();
10218
- break;
10219
- case 'audiooutput':
10220
- devices$ = getAudioOutputDevices();
10221
- break;
10222
- default:
10223
- throw new Error('Unknown MediaDeviceKind', kind);
10224
- }
10225
- return devices$.pipe(rxjs.pairwise(), rxjs.filter(([prev, current]) => {
10226
- const prevDefault = prev.find((device) => device.deviceId === 'default');
10227
- const currentDefault = current.find((device) => device.deviceId === 'default');
10228
- return !!(current.length > prev.length &&
10229
- prevDefault &&
10230
- currentDefault &&
10231
- prevDefault.groupId !== currentDefault.groupId);
10232
- }), rxjs.map(() => true));
10233
- };
10234
- /**
10235
- * Notifies the subscriber about newly added default audio input device.
10236
- * @returns Observable<boolean>
10237
- */
10238
- const watchForAddedDefaultAudioDevice = () => watchForAddedDefaultDevice('audioinput');
10239
- /**
10240
- * Notifies the subscriber about newly added default audio output device.
10241
- * @returns Observable<boolean>
10242
- */
10243
- const watchForAddedDefaultAudioOutputDevice = () => watchForAddedDefaultDevice('audiooutput');
10244
- /**
10245
- * Notifies the subscriber about newly added default video input device.
10246
- * @returns Observable<boolean>
10247
- */
10248
- const watchForAddedDefaultVideoDevice = () => watchForAddedDefaultDevice('videoinput');
10164
+ const deviceIds$ = typeof navigator !== 'undefined' &&
10165
+ typeof navigator.mediaDevices !== 'undefined'
10166
+ ? memoizedObservable(() => rxjs.merge(rxjs.from(navigator.mediaDevices.enumerateDevices()), getDeviceChangeObserver()).pipe(rxjs.shareReplay(1)))()
10167
+ : undefined;
10249
10168
  /**
10250
10169
  * Deactivates MediaStream (stops and removes tracks) to be later garbage collected
10251
10170
  *
@@ -10271,7 +10190,17 @@ class InputMediaDeviceManager {
10271
10190
  this.call = call;
10272
10191
  this.state = state;
10273
10192
  this.trackType = trackType;
10193
+ this.subscriptions = [];
10194
+ this.isTrackStoppedDueToTrackEnd = false;
10195
+ this.removeSubscriptions = () => {
10196
+ this.subscriptions.forEach((s) => s.unsubscribe());
10197
+ };
10274
10198
  this.logger = getLogger([`${TrackType[trackType].toLowerCase()} manager`]);
10199
+ if (deviceIds$ &&
10200
+ !isReactNative() &&
10201
+ (this.trackType === TrackType.AUDIO || this.trackType === TrackType.VIDEO)) {
10202
+ this.handleDisconnectedOrReplacedDevices();
10203
+ }
10275
10204
  }
10276
10205
  /**
10277
10206
  * Lists the available audio/video devices
@@ -10349,11 +10278,10 @@ class InputMediaDeviceManager {
10349
10278
  this.state.setDefaultConstraints(constraints);
10350
10279
  }
10351
10280
  /**
10352
- * Select device
10281
+ * Selects a device.
10353
10282
  *
10354
10283
  * Note: this method is not supported in React Native
10355
- *
10356
- * @param deviceId
10284
+ * @param deviceId the device id to select.
10357
10285
  */
10358
10286
  async select(deviceId) {
10359
10287
  if (isReactNative()) {
@@ -10431,9 +10359,6 @@ class InputMediaDeviceManager {
10431
10359
  this.unmuteTracks();
10432
10360
  }
10433
10361
  else {
10434
- if (this.state.mediaStream) {
10435
- this.stopTracks();
10436
- }
10437
10362
  const defaultConstraints = this.state.defaultConstraints;
10438
10363
  const constraints = {
10439
10364
  ...defaultConstraints,
@@ -10446,13 +10371,93 @@ class InputMediaDeviceManager {
10446
10371
  }
10447
10372
  if (this.state.mediaStream !== stream) {
10448
10373
  this.state.setMediaStream(stream);
10374
+ this.getTracks().forEach((track) => {
10375
+ track.addEventListener('ended', async () => {
10376
+ if (this.enablePromise) {
10377
+ await this.enablePromise;
10378
+ }
10379
+ if (this.disablePromise) {
10380
+ await this.disablePromise;
10381
+ }
10382
+ if (this.state.status === 'enabled') {
10383
+ this.isTrackStoppedDueToTrackEnd = true;
10384
+ setTimeout(() => {
10385
+ this.isTrackStoppedDueToTrackEnd = false;
10386
+ }, 2000);
10387
+ await this.disable();
10388
+ }
10389
+ });
10390
+ });
10391
+ }
10392
+ }
10393
+ get mediaDeviceKind() {
10394
+ if (this.trackType === TrackType.AUDIO) {
10395
+ return 'audioinput';
10449
10396
  }
10397
+ if (this.trackType === TrackType.VIDEO) {
10398
+ return 'videoinput';
10399
+ }
10400
+ return '';
10401
+ }
10402
+ handleDisconnectedOrReplacedDevices() {
10403
+ this.subscriptions.push(rxjs.combineLatest([
10404
+ deviceIds$.pipe(rxjs.pairwise()),
10405
+ this.state.selectedDevice$,
10406
+ ]).subscribe(async ([[prevDevices, currentDevices], deviceId]) => {
10407
+ if (!deviceId) {
10408
+ return;
10409
+ }
10410
+ if (this.enablePromise) {
10411
+ await this.enablePromise;
10412
+ }
10413
+ if (this.disablePromise) {
10414
+ await this.disablePromise;
10415
+ }
10416
+ let isDeviceDisconnected = false;
10417
+ let isDeviceReplaced = false;
10418
+ const currentDevice = this.findDeviceInList(currentDevices, deviceId);
10419
+ const prevDevice = this.findDeviceInList(prevDevices, deviceId);
10420
+ if (!currentDevice && prevDevice) {
10421
+ isDeviceDisconnected = true;
10422
+ }
10423
+ else if (currentDevice &&
10424
+ prevDevice &&
10425
+ currentDevice.deviceId === prevDevice.deviceId &&
10426
+ currentDevice.groupId !== prevDevice.groupId) {
10427
+ isDeviceReplaced = true;
10428
+ }
10429
+ if (isDeviceDisconnected) {
10430
+ await this.disable();
10431
+ this.select(undefined);
10432
+ }
10433
+ if (isDeviceReplaced) {
10434
+ if (this.isTrackStoppedDueToTrackEnd &&
10435
+ this.state.status === 'disabled') {
10436
+ await this.enable();
10437
+ this.isTrackStoppedDueToTrackEnd = false;
10438
+ }
10439
+ else {
10440
+ await this.applySettingsToStream();
10441
+ }
10442
+ }
10443
+ }));
10444
+ }
10445
+ findDeviceInList(devices, deviceId) {
10446
+ return devices.find((d) => d.deviceId === deviceId && d.kind === this.mediaDeviceKind);
10450
10447
  }
10451
10448
  }
10452
10449
 
10453
10450
  class InputMediaDeviceManagerState {
10454
- constructor(disableMode = 'stop-tracks') {
10451
+ /**
10452
+ * Constructs new InputMediaDeviceManagerState instance.
10453
+ *
10454
+ * @param disableMode the disable mode to use.
10455
+ * @param permissionName the permission name to use for querying.
10456
+ * `undefined` means no permission is required.
10457
+ */
10458
+ constructor(disableMode = 'stop-tracks', permissionName = undefined) {
10455
10459
  this.disableMode = disableMode;
10460
+ this.permissionName = permissionName;
10456
10461
  this.statusSubject = new rxjs.BehaviorSubject(undefined);
10457
10462
  this.mediaStreamSubject = new rxjs.BehaviorSubject(undefined);
10458
10463
  this.selectedDeviceSubject = new rxjs.BehaviorSubject(undefined);
@@ -10476,6 +10481,33 @@ class InputMediaDeviceManagerState {
10476
10481
  * The default constraints for the device.
10477
10482
  */
10478
10483
  this.defaultConstraints$ = this.defaultConstraintsSubject.asObservable();
10484
+ /**
10485
+ * An observable that will emit `true` if browser/system permission
10486
+ * is granted, `false` otherwise.
10487
+ */
10488
+ this.hasBrowserPermission$ = new rxjs.Observable((subscriber) => {
10489
+ const notifyGranted = () => subscriber.next(true);
10490
+ if (isReactNative() || !this.permissionName)
10491
+ return notifyGranted();
10492
+ let permissionState;
10493
+ const notify = () => subscriber.next(permissionState.state === 'granted');
10494
+ navigator.permissions
10495
+ .query({ name: this.permissionName })
10496
+ .then((permissionStatus) => {
10497
+ permissionState = permissionStatus;
10498
+ permissionState.addEventListener('change', notify);
10499
+ notify();
10500
+ })
10501
+ .catch(() => {
10502
+ // permission doesn't exist or can't be queried -> assume it's granted
10503
+ // an example would be Firefox,
10504
+ // where neither camera microphone permission can be queried
10505
+ notifyGranted();
10506
+ });
10507
+ return () => {
10508
+ permissionState?.removeEventListener('change', notify);
10509
+ };
10510
+ }).pipe(rxjs.shareReplay(1));
10479
10511
  /**
10480
10512
  * Gets the current value of an observable, or undefined if the observable has
10481
10513
  * not emitted a value yet.
@@ -10557,7 +10589,10 @@ class InputMediaDeviceManagerState {
10557
10589
 
10558
10590
  class CameraManagerState extends InputMediaDeviceManagerState {
10559
10591
  constructor() {
10560
- super('stop-tracks');
10592
+ super('stop-tracks',
10593
+ // `camera` is not in the W3C standard yet,
10594
+ // but it's supported by Chrome and Safari.
10595
+ 'camera');
10561
10596
  this.directionSubject = new rxjs.BehaviorSubject(undefined);
10562
10597
  this.direction$ = this.directionSubject
10563
10598
  .asObservable()
@@ -10687,7 +10722,10 @@ class CameraManager extends InputMediaDeviceManager {
10687
10722
 
10688
10723
  class MicrophoneManagerState extends InputMediaDeviceManagerState {
10689
10724
  constructor() {
10690
- super('disable-tracks');
10725
+ super('disable-tracks',
10726
+ // `microphone` is not in the W3C standard yet,
10727
+ // but it's supported by Chrome and Safari.
10728
+ 'microphone');
10691
10729
  this.speakingWhileMutedSubject = new rxjs.BehaviorSubject(false);
10692
10730
  this.speakingWhileMuted$ = this.speakingWhileMutedSubject
10693
10731
  .asObservable()
@@ -11014,6 +11052,21 @@ class SpeakerState {
11014
11052
  class SpeakerManager {
11015
11053
  constructor() {
11016
11054
  this.state = new SpeakerState();
11055
+ this.subscriptions = [];
11056
+ this.removeSubscriptions = () => {
11057
+ this.subscriptions.forEach((s) => s.unsubscribe());
11058
+ };
11059
+ if (deviceIds$ && !isReactNative()) {
11060
+ this.subscriptions.push(rxjs.combineLatest([deviceIds$, this.state.selectedDevice$]).subscribe(([devices, deviceId]) => {
11061
+ if (!deviceId) {
11062
+ return;
11063
+ }
11064
+ const device = devices.find((d) => d.deviceId === deviceId && d.kind === 'audiooutput');
11065
+ if (!device) {
11066
+ this.select('');
11067
+ }
11068
+ }));
11069
+ }
11017
11070
  }
11018
11071
  /**
11019
11072
  * Lists the available audio output devices
@@ -11131,6 +11184,10 @@ class Call {
11131
11184
  this.leaveCallHooks.forEach((hook) => hook());
11132
11185
  this.clientStore.unregisterCall(this);
11133
11186
  this.state.setCallingState(exports.CallingState.LEFT);
11187
+ this.camera.removeSubscriptions();
11188
+ this.microphone.removeSubscriptions();
11189
+ this.screenShare.removeSubscriptions();
11190
+ this.speaker.removeSubscriptions();
11134
11191
  };
11135
11192
  /**
11136
11193
  * Loads the information about the call.
@@ -11504,7 +11561,7 @@ class Call {
11504
11561
  await this.initMic({ setStatus: true });
11505
11562
  }
11506
11563
  catch (error) {
11507
- this.logger('warn', 'Camera and/or mic init failed during join call');
11564
+ this.logger('warn', 'Camera and/or mic init failed during join call', error);
11508
11565
  }
11509
11566
  // 3. once we have the "joinResponse", and possibly reconciled the local state
11510
11567
  // we schedule a fast subscription update for all remote participants
@@ -13977,7 +14034,7 @@ class StreamClient {
13977
14034
  });
13978
14035
  };
13979
14036
  this.getUserAgent = () => {
13980
- const version = "0.4.5" ;
14037
+ const version = "0.4.7" ;
13981
14038
  return (this.userAgent ||
13982
14039
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
13983
14040
  };
@@ -14560,6 +14617,7 @@ exports.conditional = conditional;
14560
14617
  exports.createSoundDetector = createSoundDetector;
14561
14618
  exports.defaultSortPreset = defaultSortPreset;
14562
14619
  exports.descending = descending;
14620
+ exports.deviceIds$ = deviceIds$;
14563
14621
  exports.disposeOfMediaStream = disposeOfMediaStream;
14564
14622
  exports.dominantSpeaker = dominantSpeaker;
14565
14623
  exports.getAudioDevices = getAudioDevices;
@@ -14592,10 +14650,4 @@ exports.setOSInfo = setOSInfo;
14592
14650
  exports.setSdkInfo = setSdkInfo;
14593
14651
  exports.speakerLayoutSortPreset = speakerLayoutSortPreset;
14594
14652
  exports.speaking = speaking;
14595
- exports.watchForAddedDefaultAudioDevice = watchForAddedDefaultAudioDevice;
14596
- exports.watchForAddedDefaultAudioOutputDevice = watchForAddedDefaultAudioOutputDevice;
14597
- exports.watchForAddedDefaultVideoDevice = watchForAddedDefaultVideoDevice;
14598
- exports.watchForDisconnectedAudioDevice = watchForDisconnectedAudioDevice;
14599
- exports.watchForDisconnectedAudioOutputDevice = watchForDisconnectedAudioOutputDevice;
14600
- exports.watchForDisconnectedVideoDevice = watchForDisconnectedVideoDevice;
14601
14653
  //# sourceMappingURL=index.cjs.js.map