@stream-io/video-client 0.4.5 → 0.4.6

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,13 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ### [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)
6
+
7
+
8
+ ### Features
9
+
10
+ * handle device disconnection ([#1174](https://github.com/GetStream/stream-video-js/issues/1174)) ([ae3779f](https://github.com/GetStream/stream-video-js/commit/ae3779fbfd820d8ef85ad58dafb698e06c00a3e3))
11
+
5
12
  ### [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
13
 
7
14
 
@@ -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
@@ -10408,9 +10337,6 @@ class InputMediaDeviceManager {
10408
10337
  this.unmuteTracks();
10409
10338
  }
10410
10339
  else {
10411
- if (this.state.mediaStream) {
10412
- this.stopTracks();
10413
- }
10414
10340
  const defaultConstraints = this.state.defaultConstraints;
10415
10341
  const constraints = {
10416
10342
  ...defaultConstraints,
@@ -10423,7 +10349,79 @@ class InputMediaDeviceManager {
10423
10349
  }
10424
10350
  if (this.state.mediaStream !== stream) {
10425
10351
  this.state.setMediaStream(stream);
10352
+ this.getTracks().forEach((track) => {
10353
+ track.addEventListener('ended', async () => {
10354
+ if (this.enablePromise) {
10355
+ await this.enablePromise;
10356
+ }
10357
+ if (this.disablePromise) {
10358
+ await this.disablePromise;
10359
+ }
10360
+ if (this.state.status === 'enabled') {
10361
+ this.isTrackStoppedDueToTrackEnd = true;
10362
+ setTimeout(() => {
10363
+ this.isTrackStoppedDueToTrackEnd = false;
10364
+ }, 2000);
10365
+ await this.disable();
10366
+ }
10367
+ });
10368
+ });
10369
+ }
10370
+ }
10371
+ get mediaDeviceKind() {
10372
+ if (this.trackType === TrackType.AUDIO) {
10373
+ return 'audioinput';
10374
+ }
10375
+ if (this.trackType === TrackType.VIDEO) {
10376
+ return 'videoinput';
10426
10377
  }
10378
+ return '';
10379
+ }
10380
+ handleDisconnectedOrReplacedDevices() {
10381
+ this.subscriptions.push(combineLatest([
10382
+ deviceIds$.pipe(pairwise()),
10383
+ this.state.selectedDevice$,
10384
+ ]).subscribe(async ([[prevDevices, currentDevices], deviceId]) => {
10385
+ if (!deviceId) {
10386
+ return;
10387
+ }
10388
+ if (this.enablePromise) {
10389
+ await this.enablePromise;
10390
+ }
10391
+ if (this.disablePromise) {
10392
+ await this.disablePromise;
10393
+ }
10394
+ let isDeviceDisconnected = false;
10395
+ let isDeviceReplaced = false;
10396
+ const currentDevice = this.findDeviceInList(currentDevices, deviceId);
10397
+ const prevDevice = this.findDeviceInList(prevDevices, deviceId);
10398
+ if (!currentDevice && prevDevice) {
10399
+ isDeviceDisconnected = true;
10400
+ }
10401
+ else if (currentDevice &&
10402
+ prevDevice &&
10403
+ currentDevice.deviceId === prevDevice.deviceId &&
10404
+ currentDevice.groupId !== prevDevice.groupId) {
10405
+ isDeviceReplaced = true;
10406
+ }
10407
+ if (isDeviceDisconnected) {
10408
+ await this.disable();
10409
+ this.select(undefined);
10410
+ }
10411
+ if (isDeviceReplaced) {
10412
+ if (this.isTrackStoppedDueToTrackEnd &&
10413
+ this.state.status === 'disabled') {
10414
+ await this.enable();
10415
+ this.isTrackStoppedDueToTrackEnd = false;
10416
+ }
10417
+ else {
10418
+ await this.applySettingsToStream();
10419
+ }
10420
+ }
10421
+ }));
10422
+ }
10423
+ findDeviceInList(devices, deviceId) {
10424
+ return devices.find((d) => d.deviceId === deviceId && d.kind === this.mediaDeviceKind);
10427
10425
  }
10428
10426
  }
10429
10427
 
@@ -10991,6 +10989,21 @@ class SpeakerState {
10991
10989
  class SpeakerManager {
10992
10990
  constructor() {
10993
10991
  this.state = new SpeakerState();
10992
+ this.subscriptions = [];
10993
+ this.removeSubscriptions = () => {
10994
+ this.subscriptions.forEach((s) => s.unsubscribe());
10995
+ };
10996
+ if (deviceIds$ && !isReactNative()) {
10997
+ this.subscriptions.push(combineLatest([deviceIds$, this.state.selectedDevice$]).subscribe(([devices, deviceId]) => {
10998
+ if (!deviceId) {
10999
+ return;
11000
+ }
11001
+ const device = devices.find((d) => d.deviceId === deviceId && d.kind === 'audiooutput');
11002
+ if (!device) {
11003
+ this.select('');
11004
+ }
11005
+ }));
11006
+ }
10994
11007
  }
10995
11008
  /**
10996
11009
  * Lists the available audio output devices
@@ -11108,6 +11121,10 @@ class Call {
11108
11121
  this.leaveCallHooks.forEach((hook) => hook());
11109
11122
  this.clientStore.unregisterCall(this);
11110
11123
  this.state.setCallingState(CallingState.LEFT);
11124
+ this.camera.removeSubscriptions();
11125
+ this.microphone.removeSubscriptions();
11126
+ this.screenShare.removeSubscriptions();
11127
+ this.speaker.removeSubscriptions();
11111
11128
  };
11112
11129
  /**
11113
11130
  * Loads the information about the call.
@@ -11481,7 +11498,7 @@ class Call {
11481
11498
  await this.initMic({ setStatus: true });
11482
11499
  }
11483
11500
  catch (error) {
11484
- this.logger('warn', 'Camera and/or mic init failed during join call');
11501
+ this.logger('warn', 'Camera and/or mic init failed during join call', error);
11485
11502
  }
11486
11503
  // 3. once we have the "joinResponse", and possibly reconciled the local state
11487
11504
  // we schedule a fast subscription update for all remote participants
@@ -13953,7 +13970,7 @@ class StreamClient {
13953
13970
  });
13954
13971
  };
13955
13972
  this.getUserAgent = () => {
13956
- const version = "0.4.5" ;
13973
+ const version = "0.4.6" ;
13957
13974
  return (this.userAgent ||
13958
13975
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
13959
13976
  };
@@ -14488,5 +14505,5 @@ class StreamVideoServerClient extends StreamVideoClient {
14488
14505
  }
14489
14506
  }
14490
14507
 
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 };
14508
+ 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
14509
  //# sourceMappingURL=index.browser.es.js.map