@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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ### [0.4.7](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.4.6...@stream-io/video-client-0.4.7) (2023-11-13)
6
+
7
+
8
+ ### Features
9
+
10
+ * **device-api:** Browser Permissions API ([#1184](https://github.com/GetStream/stream-video-js/issues/1184)) ([a0b3573](https://github.com/GetStream/stream-video-js/commit/a0b3573b630ff8450953cdf1102fe722aea83f6f))
11
+
12
+ ### [0.4.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.4.5...@stream-io/video-client-0.4.6) (2023-11-13)
13
+
14
+
15
+ ### Features
16
+
17
+ * handle device disconnection ([#1174](https://github.com/GetStream/stream-video-js/issues/1174)) ([ae3779f](https://github.com/GetStream/stream-video-js/commit/ae3779fbfd820d8ef85ad58dafb698e06c00a3e3))
18
+
5
19
  ### [0.4.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-0.4.4...@stream-io/video-client-0.4.5) (2023-11-07)
6
20
 
7
21
 
@@ -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, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, filter, pairwise, merge, Observable, debounceTime, concatMap, from, of, tap, debounce, timer } from 'rxjs';
7
+ import { ReplaySubject, combineLatest, BehaviorSubject, map as map$1, shareReplay, distinctUntilChanged, takeWhile, distinctUntilKeyChanged, merge, from, Observable, debounceTime, concatMap, pairwise, of, filter, 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';
@@ -9978,8 +9978,7 @@ const getDevices = (constraints, kind) => {
9978
9978
  /**
9979
9979
  * [Tells if the browser supports audio output change on 'audio' elements](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId).
9980
9980
  *
9981
- * @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.
9982
- */
9981
+ * */
9983
9982
  const checkIfAudioOutputChangeSupported = () => {
9984
9983
  if (typeof document === 'undefined')
9985
9984
  return false;
@@ -10139,90 +10138,10 @@ const getScreenShareStream = async (options) => {
10139
10138
  throw e;
10140
10139
  }
10141
10140
  };
10142
- const watchForDisconnectedDevice = (kind, deviceId$) => {
10143
- let devices$;
10144
- switch (kind) {
10145
- case 'audioinput':
10146
- devices$ = getAudioDevices();
10147
- break;
10148
- case 'videoinput':
10149
- devices$ = getVideoDevices();
10150
- break;
10151
- case 'audiooutput':
10152
- devices$ = getAudioOutputDevices();
10153
- break;
10154
- }
10155
- return combineLatest([devices$, deviceId$]).pipe(filter(([devices, deviceId]) => !!deviceId && !devices.find((d) => d.deviceId === deviceId)), map$1(() => true));
10156
- };
10157
- /**
10158
- * Notifies the subscriber if a given 'audioinput' device is disconnected
10159
- *
10160
- * @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.
10161
- * @param deviceId$ an Observable that specifies which device to watch for
10162
- * @returns
10163
- */
10164
- const watchForDisconnectedAudioDevice = (deviceId$) => {
10165
- return watchForDisconnectedDevice('audioinput', deviceId$);
10166
- };
10167
- /**
10168
- * Notifies the subscriber if a given 'videoinput' device is disconnected
10169
- *
10170
- * @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.
10171
- * @param deviceId$ an Observable that specifies which device to watch for
10172
- * @returns
10173
- */
10174
- const watchForDisconnectedVideoDevice = (deviceId$) => {
10175
- return watchForDisconnectedDevice('videoinput', deviceId$);
10176
- };
10177
- /**
10178
- * Notifies the subscriber if a given 'audiooutput' device is disconnected
10179
- *
10180
- * @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.
10181
- * @param deviceId$ an Observable that specifies which device to watch for
10182
- * @returns
10183
- */
10184
- const watchForDisconnectedAudioOutputDevice = (deviceId$) => {
10185
- return watchForDisconnectedDevice('audiooutput', deviceId$);
10186
- };
10187
- const watchForAddedDefaultDevice = (kind) => {
10188
- let devices$;
10189
- switch (kind) {
10190
- case 'audioinput':
10191
- devices$ = getAudioDevices();
10192
- break;
10193
- case 'videoinput':
10194
- devices$ = getVideoDevices();
10195
- break;
10196
- case 'audiooutput':
10197
- devices$ = getAudioOutputDevices();
10198
- break;
10199
- default:
10200
- throw new Error('Unknown MediaDeviceKind', kind);
10201
- }
10202
- return devices$.pipe(pairwise(), filter(([prev, current]) => {
10203
- const prevDefault = prev.find((device) => device.deviceId === 'default');
10204
- const currentDefault = current.find((device) => device.deviceId === 'default');
10205
- return !!(current.length > prev.length &&
10206
- prevDefault &&
10207
- currentDefault &&
10208
- prevDefault.groupId !== currentDefault.groupId);
10209
- }), map$1(() => true));
10210
- };
10211
- /**
10212
- * Notifies the subscriber about newly added default audio input device.
10213
- * @returns Observable<boolean>
10214
- */
10215
- const watchForAddedDefaultAudioDevice = () => watchForAddedDefaultDevice('audioinput');
10216
- /**
10217
- * Notifies the subscriber about newly added default audio output device.
10218
- * @returns Observable<boolean>
10219
- */
10220
- const watchForAddedDefaultAudioOutputDevice = () => watchForAddedDefaultDevice('audiooutput');
10221
- /**
10222
- * Notifies the subscriber about newly added default video input device.
10223
- * @returns Observable<boolean>
10224
- */
10225
- const watchForAddedDefaultVideoDevice = () => watchForAddedDefaultDevice('videoinput');
10141
+ const deviceIds$ = typeof navigator !== 'undefined' &&
10142
+ typeof navigator.mediaDevices !== 'undefined'
10143
+ ? memoizedObservable(() => merge(from(navigator.mediaDevices.enumerateDevices()), getDeviceChangeObserver()).pipe(shareReplay(1)))()
10144
+ : undefined;
10226
10145
  /**
10227
10146
  * Deactivates MediaStream (stops and removes tracks) to be later garbage collected
10228
10147
  *
@@ -10248,7 +10167,17 @@ class InputMediaDeviceManager {
10248
10167
  this.call = call;
10249
10168
  this.state = state;
10250
10169
  this.trackType = trackType;
10170
+ this.subscriptions = [];
10171
+ this.isTrackStoppedDueToTrackEnd = false;
10172
+ this.removeSubscriptions = () => {
10173
+ this.subscriptions.forEach((s) => s.unsubscribe());
10174
+ };
10251
10175
  this.logger = getLogger([`${TrackType[trackType].toLowerCase()} manager`]);
10176
+ if (deviceIds$ &&
10177
+ !isReactNative() &&
10178
+ (this.trackType === TrackType.AUDIO || this.trackType === TrackType.VIDEO)) {
10179
+ this.handleDisconnectedOrReplacedDevices();
10180
+ }
10252
10181
  }
10253
10182
  /**
10254
10183
  * Lists the available audio/video devices
@@ -10326,11 +10255,10 @@ class InputMediaDeviceManager {
10326
10255
  this.state.setDefaultConstraints(constraints);
10327
10256
  }
10328
10257
  /**
10329
- * Select device
10258
+ * Selects a device.
10330
10259
  *
10331
10260
  * Note: this method is not supported in React Native
10332
- *
10333
- * @param deviceId
10261
+ * @param deviceId the device id to select.
10334
10262
  */
10335
10263
  async select(deviceId) {
10336
10264
  if (isReactNative()) {
@@ -10408,9 +10336,6 @@ class InputMediaDeviceManager {
10408
10336
  this.unmuteTracks();
10409
10337
  }
10410
10338
  else {
10411
- if (this.state.mediaStream) {
10412
- this.stopTracks();
10413
- }
10414
10339
  const defaultConstraints = this.state.defaultConstraints;
10415
10340
  const constraints = {
10416
10341
  ...defaultConstraints,
@@ -10423,13 +10348,93 @@ class InputMediaDeviceManager {
10423
10348
  }
10424
10349
  if (this.state.mediaStream !== stream) {
10425
10350
  this.state.setMediaStream(stream);
10351
+ this.getTracks().forEach((track) => {
10352
+ track.addEventListener('ended', async () => {
10353
+ if (this.enablePromise) {
10354
+ await this.enablePromise;
10355
+ }
10356
+ if (this.disablePromise) {
10357
+ await this.disablePromise;
10358
+ }
10359
+ if (this.state.status === 'enabled') {
10360
+ this.isTrackStoppedDueToTrackEnd = true;
10361
+ setTimeout(() => {
10362
+ this.isTrackStoppedDueToTrackEnd = false;
10363
+ }, 2000);
10364
+ await this.disable();
10365
+ }
10366
+ });
10367
+ });
10368
+ }
10369
+ }
10370
+ get mediaDeviceKind() {
10371
+ if (this.trackType === TrackType.AUDIO) {
10372
+ return 'audioinput';
10426
10373
  }
10374
+ if (this.trackType === TrackType.VIDEO) {
10375
+ return 'videoinput';
10376
+ }
10377
+ return '';
10378
+ }
10379
+ handleDisconnectedOrReplacedDevices() {
10380
+ this.subscriptions.push(combineLatest([
10381
+ deviceIds$.pipe(pairwise()),
10382
+ this.state.selectedDevice$,
10383
+ ]).subscribe(async ([[prevDevices, currentDevices], deviceId]) => {
10384
+ if (!deviceId) {
10385
+ return;
10386
+ }
10387
+ if (this.enablePromise) {
10388
+ await this.enablePromise;
10389
+ }
10390
+ if (this.disablePromise) {
10391
+ await this.disablePromise;
10392
+ }
10393
+ let isDeviceDisconnected = false;
10394
+ let isDeviceReplaced = false;
10395
+ const currentDevice = this.findDeviceInList(currentDevices, deviceId);
10396
+ const prevDevice = this.findDeviceInList(prevDevices, deviceId);
10397
+ if (!currentDevice && prevDevice) {
10398
+ isDeviceDisconnected = true;
10399
+ }
10400
+ else if (currentDevice &&
10401
+ prevDevice &&
10402
+ currentDevice.deviceId === prevDevice.deviceId &&
10403
+ currentDevice.groupId !== prevDevice.groupId) {
10404
+ isDeviceReplaced = true;
10405
+ }
10406
+ if (isDeviceDisconnected) {
10407
+ await this.disable();
10408
+ this.select(undefined);
10409
+ }
10410
+ if (isDeviceReplaced) {
10411
+ if (this.isTrackStoppedDueToTrackEnd &&
10412
+ this.state.status === 'disabled') {
10413
+ await this.enable();
10414
+ this.isTrackStoppedDueToTrackEnd = false;
10415
+ }
10416
+ else {
10417
+ await this.applySettingsToStream();
10418
+ }
10419
+ }
10420
+ }));
10421
+ }
10422
+ findDeviceInList(devices, deviceId) {
10423
+ return devices.find((d) => d.deviceId === deviceId && d.kind === this.mediaDeviceKind);
10427
10424
  }
10428
10425
  }
10429
10426
 
10430
10427
  class InputMediaDeviceManagerState {
10431
- constructor(disableMode = 'stop-tracks') {
10428
+ /**
10429
+ * Constructs new InputMediaDeviceManagerState instance.
10430
+ *
10431
+ * @param disableMode the disable mode to use.
10432
+ * @param permissionName the permission name to use for querying.
10433
+ * `undefined` means no permission is required.
10434
+ */
10435
+ constructor(disableMode = 'stop-tracks', permissionName = undefined) {
10432
10436
  this.disableMode = disableMode;
10437
+ this.permissionName = permissionName;
10433
10438
  this.statusSubject = new BehaviorSubject(undefined);
10434
10439
  this.mediaStreamSubject = new BehaviorSubject(undefined);
10435
10440
  this.selectedDeviceSubject = new BehaviorSubject(undefined);
@@ -10453,6 +10458,33 @@ class InputMediaDeviceManagerState {
10453
10458
  * The default constraints for the device.
10454
10459
  */
10455
10460
  this.defaultConstraints$ = this.defaultConstraintsSubject.asObservable();
10461
+ /**
10462
+ * An observable that will emit `true` if browser/system permission
10463
+ * is granted, `false` otherwise.
10464
+ */
10465
+ this.hasBrowserPermission$ = new Observable((subscriber) => {
10466
+ const notifyGranted = () => subscriber.next(true);
10467
+ if (isReactNative() || !this.permissionName)
10468
+ return notifyGranted();
10469
+ let permissionState;
10470
+ const notify = () => subscriber.next(permissionState.state === 'granted');
10471
+ navigator.permissions
10472
+ .query({ name: this.permissionName })
10473
+ .then((permissionStatus) => {
10474
+ permissionState = permissionStatus;
10475
+ permissionState.addEventListener('change', notify);
10476
+ notify();
10477
+ })
10478
+ .catch(() => {
10479
+ // permission doesn't exist or can't be queried -> assume it's granted
10480
+ // an example would be Firefox,
10481
+ // where neither camera microphone permission can be queried
10482
+ notifyGranted();
10483
+ });
10484
+ return () => {
10485
+ permissionState?.removeEventListener('change', notify);
10486
+ };
10487
+ }).pipe(shareReplay(1));
10456
10488
  /**
10457
10489
  * Gets the current value of an observable, or undefined if the observable has
10458
10490
  * not emitted a value yet.
@@ -10534,7 +10566,10 @@ class InputMediaDeviceManagerState {
10534
10566
 
10535
10567
  class CameraManagerState extends InputMediaDeviceManagerState {
10536
10568
  constructor() {
10537
- super('stop-tracks');
10569
+ super('stop-tracks',
10570
+ // `camera` is not in the W3C standard yet,
10571
+ // but it's supported by Chrome and Safari.
10572
+ 'camera');
10538
10573
  this.directionSubject = new BehaviorSubject(undefined);
10539
10574
  this.direction$ = this.directionSubject
10540
10575
  .asObservable()
@@ -10664,7 +10699,10 @@ class CameraManager extends InputMediaDeviceManager {
10664
10699
 
10665
10700
  class MicrophoneManagerState extends InputMediaDeviceManagerState {
10666
10701
  constructor() {
10667
- super('disable-tracks');
10702
+ super('disable-tracks',
10703
+ // `microphone` is not in the W3C standard yet,
10704
+ // but it's supported by Chrome and Safari.
10705
+ 'microphone');
10668
10706
  this.speakingWhileMutedSubject = new BehaviorSubject(false);
10669
10707
  this.speakingWhileMuted$ = this.speakingWhileMutedSubject
10670
10708
  .asObservable()
@@ -10991,6 +11029,21 @@ class SpeakerState {
10991
11029
  class SpeakerManager {
10992
11030
  constructor() {
10993
11031
  this.state = new SpeakerState();
11032
+ this.subscriptions = [];
11033
+ this.removeSubscriptions = () => {
11034
+ this.subscriptions.forEach((s) => s.unsubscribe());
11035
+ };
11036
+ if (deviceIds$ && !isReactNative()) {
11037
+ this.subscriptions.push(combineLatest([deviceIds$, this.state.selectedDevice$]).subscribe(([devices, deviceId]) => {
11038
+ if (!deviceId) {
11039
+ return;
11040
+ }
11041
+ const device = devices.find((d) => d.deviceId === deviceId && d.kind === 'audiooutput');
11042
+ if (!device) {
11043
+ this.select('');
11044
+ }
11045
+ }));
11046
+ }
10994
11047
  }
10995
11048
  /**
10996
11049
  * Lists the available audio output devices
@@ -11108,6 +11161,10 @@ class Call {
11108
11161
  this.leaveCallHooks.forEach((hook) => hook());
11109
11162
  this.clientStore.unregisterCall(this);
11110
11163
  this.state.setCallingState(CallingState.LEFT);
11164
+ this.camera.removeSubscriptions();
11165
+ this.microphone.removeSubscriptions();
11166
+ this.screenShare.removeSubscriptions();
11167
+ this.speaker.removeSubscriptions();
11111
11168
  };
11112
11169
  /**
11113
11170
  * Loads the information about the call.
@@ -11481,7 +11538,7 @@ class Call {
11481
11538
  await this.initMic({ setStatus: true });
11482
11539
  }
11483
11540
  catch (error) {
11484
- this.logger('warn', 'Camera and/or mic init failed during join call');
11541
+ this.logger('warn', 'Camera and/or mic init failed during join call', error);
11485
11542
  }
11486
11543
  // 3. once we have the "joinResponse", and possibly reconciled the local state
11487
11544
  // we schedule a fast subscription update for all remote participants
@@ -13953,7 +14010,7 @@ class StreamClient {
13953
14010
  });
13954
14011
  };
13955
14012
  this.getUserAgent = () => {
13956
- const version = "0.4.5" ;
14013
+ const version = "0.4.7" ;
13957
14014
  return (this.userAgent ||
13958
14015
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
13959
14016
  };
@@ -14488,5 +14545,5 @@ class StreamVideoServerClient extends StreamVideoClient {
14488
14545
  }
14489
14546
  }
14490
14547
 
14491
- export { AudioSettingsDefaultDeviceEnum, AudioSettingsRequestDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DynascaleManager, ErrorFromResponse, InputMediaDeviceManager, InputMediaDeviceManagerState, LayoutSettingsNameEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, OwnCapability, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, models as SfuModels, SpeakerManager, SpeakerState, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoServerClient, StreamVideoWriteableStateStore, TranscriptionSettingsModeEnum, TranscriptionSettingsRequestModeEnum, VideoSettingsCameraFacingEnum, VideoSettingsRequestCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, disposeOfMediaStream, dominantSpeaker, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceInfo, getLogger, getOSInfo, getScreenShareStream, getSdkInfo, getVideoDevices, getVideoStream, livestreamOrAudioRoomSortPreset, logLevels, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, role, screenSharing, setDeviceInfo, setLogLevel, setLogger, setOSInfo, setSdkInfo, speakerLayoutSortPreset, speaking, watchForAddedDefaultAudioDevice, watchForAddedDefaultAudioOutputDevice, watchForAddedDefaultVideoDevice, watchForDisconnectedAudioDevice, watchForDisconnectedAudioOutputDevice, watchForDisconnectedVideoDevice };
14548
+ export { AudioSettingsDefaultDeviceEnum, AudioSettingsRequestDefaultDeviceEnum, browsers as Browsers, Call, CallState, CallType, CallTypes, CallingState, CameraManager, CameraManagerState, CreateDeviceRequestPushProviderEnum, DebounceType, DynascaleManager, ErrorFromResponse, InputMediaDeviceManager, InputMediaDeviceManagerState, LayoutSettingsNameEnum, LayoutSettingsRequestNameEnum, MicrophoneManager, MicrophoneManagerState, OwnCapability, RecordSettingsRequestModeEnum, RecordSettingsRequestQualityEnum, rxUtils as RxUtils, ScreenShareManager, ScreenShareState, events as SfuEvents, models as SfuModels, SpeakerManager, SpeakerState, StreamSfuClient, StreamVideoClient, StreamVideoReadOnlyStateStore, StreamVideoServerClient, StreamVideoWriteableStateStore, TranscriptionSettingsModeEnum, TranscriptionSettingsRequestModeEnum, VideoSettingsCameraFacingEnum, VideoSettingsRequestCameraFacingEnum, ViewportTracker, VisibilityState, checkIfAudioOutputChangeSupported, combineComparators, conditional, createSoundDetector, defaultSortPreset, descending, deviceIds$, disposeOfMediaStream, dominantSpeaker, getAudioDevices, getAudioOutputDevices, getAudioStream, getClientDetails, getDeviceInfo, getLogger, getOSInfo, getScreenShareStream, getSdkInfo, getVideoDevices, getVideoStream, livestreamOrAudioRoomSortPreset, logLevels, logToConsole, name, noopComparator, paginatedLayoutSortPreset, pinned, publishingAudio, publishingVideo, reactionType, role, screenSharing, setDeviceInfo, setLogLevel, setLogger, setOSInfo, setSdkInfo, speakerLayoutSortPreset, speaking };
14492
14549
  //# sourceMappingURL=index.browser.es.js.map