@stream-io/video-client 1.20.0 → 1.20.1

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
@@ -5645,7 +5645,7 @@ const aggregate = (stats) => {
5645
5645
  return report;
5646
5646
  };
5647
5647
 
5648
- const version = "1.20.0";
5648
+ const version = "1.20.1";
5649
5649
  const [major, minor, patch] = version.split('.');
5650
5650
  let sdkInfo = {
5651
5651
  type: SdkType.PLAIN_JAVASCRIPT,
@@ -5786,9 +5786,9 @@ class SfuStatsReporter {
5786
5786
  this.logger = getLogger(['SfuStatsReporter']);
5787
5787
  this.inputDevices = new Map();
5788
5788
  this.observeDevice = (device, kind) => {
5789
- const { hasBrowserPermission$ } = device.state;
5789
+ const { browserPermissionState$ } = device.state;
5790
5790
  this.unsubscribeDevicePermissionsSubscription?.();
5791
- this.unsubscribeDevicePermissionsSubscription = createSubscription(rxjs.combineLatest([hasBrowserPermission$, this.state.ownCapabilities$]), ([hasPermission, ownCapabilities]) => {
5791
+ this.unsubscribeDevicePermissionsSubscription = createSubscription(rxjs.combineLatest([browserPermissionState$, this.state.ownCapabilities$]), ([browserPermissionState, ownCapabilities]) => {
5792
5792
  // cleanup the previous listDevices() subscription in case
5793
5793
  // permissions or capabilities have changed.
5794
5794
  // we will subscribe again if everything is in order.
@@ -5796,7 +5796,7 @@ class SfuStatsReporter {
5796
5796
  const hasCapability = kind === 'mic'
5797
5797
  ? ownCapabilities.includes(OwnCapability.SEND_AUDIO)
5798
5798
  : ownCapabilities.includes(OwnCapability.SEND_VIDEO);
5799
- if (!hasPermission || !hasCapability) {
5799
+ if (browserPermissionState !== 'granted' || !hasCapability) {
5800
5800
  this.inputDevices.set(kind, {
5801
5801
  currentDevice: '',
5802
5802
  availableDevices: [],
@@ -8730,6 +8730,9 @@ class BrowserPermission {
8730
8730
  // Instead of checking if a permission is granted, we check if it isn't denied
8731
8731
  rxjs.map((state) => state !== 'denied'));
8732
8732
  }
8733
+ asStateObservable() {
8734
+ return this.getStateObservable();
8735
+ }
8733
8736
  getIsPromptingObservable() {
8734
8737
  return this.getStateObservable().pipe(rxjs.map((state) => state === 'prompting'));
8735
8738
  }
@@ -9256,122 +9259,130 @@ class InputMediaDeviceManager {
9256
9259
  this.logger('debug', 'Starting stream');
9257
9260
  let stream;
9258
9261
  let rootStream;
9259
- if (this.state.mediaStream &&
9260
- this.getTracks().every((t) => t.readyState === 'live')) {
9261
- stream = this.state.mediaStream;
9262
- this.enableTracks();
9263
- }
9264
- else {
9265
- const defaultConstraints = this.state.defaultConstraints;
9266
- const constraints = {
9267
- ...defaultConstraints,
9268
- deviceId: this.state.selectedDevice
9269
- ? { exact: this.state.selectedDevice }
9270
- : undefined,
9271
- };
9272
- /**
9273
- * Chains two media streams together.
9274
- *
9275
- * In our case, filters MediaStreams are derived from their parent MediaStream.
9276
- * However, once a child filter's track is stopped,
9277
- * the tracks of the parent MediaStream aren't automatically stopped.
9278
- * This leads to a situation where the camera indicator light is still on
9279
- * even though the user stopped publishing video.
9280
- *
9281
- * This function works around this issue by stopping the parent MediaStream's tracks
9282
- * as well once the child filter's tracks are stopped.
9283
- *
9284
- * It works by patching the stop() method of the child filter's tracks to also stop
9285
- * the parent MediaStream's tracks of the same type. Here we assume that
9286
- * the parent MediaStream has only one track of each type.
9287
- *
9288
- * @param parentStream the parent MediaStream. Omit for the root stream.
9289
- */
9290
- const chainWith = (parentStream) => async (filterStream) => {
9291
- if (!parentStream)
9292
- return filterStream;
9293
- // TODO OL: take care of track.enabled property as well
9294
- const parent = await parentStream;
9295
- filterStream.getTracks().forEach((track) => {
9296
- const originalStop = track.stop;
9297
- track.stop = function stop() {
9298
- originalStop.call(track);
9299
- parent.getTracks().forEach((parentTrack) => {
9300
- if (parentTrack.kind === track.kind) {
9301
- parentTrack.stop();
9302
- }
9303
- });
9304
- };
9305
- });
9306
- parent.getTracks().forEach((parentTrack) => {
9307
- // When the parent stream abruptly ends, we propagate the event
9308
- // to the filter stream.
9309
- // This usually happens when the camera/microphone permissions
9310
- // are revoked or when the device is disconnected.
9311
- const handleParentTrackEnded = () => {
9312
- filterStream.getTracks().forEach((track) => {
9313
- if (parentTrack.kind !== track.kind)
9314
- return;
9315
- track.stop();
9316
- track.dispatchEvent(new Event('ended')); // propagate the event
9262
+ try {
9263
+ if (this.state.mediaStream &&
9264
+ this.getTracks().every((t) => t.readyState === 'live')) {
9265
+ stream = this.state.mediaStream;
9266
+ this.enableTracks();
9267
+ }
9268
+ else {
9269
+ const defaultConstraints = this.state.defaultConstraints;
9270
+ const constraints = {
9271
+ ...defaultConstraints,
9272
+ deviceId: this.state.selectedDevice
9273
+ ? { exact: this.state.selectedDevice }
9274
+ : undefined,
9275
+ };
9276
+ /**
9277
+ * Chains two media streams together.
9278
+ *
9279
+ * In our case, filters MediaStreams are derived from their parent MediaStream.
9280
+ * However, once a child filter's track is stopped,
9281
+ * the tracks of the parent MediaStream aren't automatically stopped.
9282
+ * This leads to a situation where the camera indicator light is still on
9283
+ * even though the user stopped publishing video.
9284
+ *
9285
+ * This function works around this issue by stopping the parent MediaStream's tracks
9286
+ * as well once the child filter's tracks are stopped.
9287
+ *
9288
+ * It works by patching the stop() method of the child filter's tracks to also stop
9289
+ * the parent MediaStream's tracks of the same type. Here we assume that
9290
+ * the parent MediaStream has only one track of each type.
9291
+ *
9292
+ * @param parentStream the parent MediaStream. Omit for the root stream.
9293
+ */
9294
+ const chainWith = (parentStream) => async (filterStream) => {
9295
+ if (!parentStream)
9296
+ return filterStream;
9297
+ // TODO OL: take care of track.enabled property as well
9298
+ const parent = await parentStream;
9299
+ filterStream.getTracks().forEach((track) => {
9300
+ const originalStop = track.stop;
9301
+ track.stop = function stop() {
9302
+ originalStop.call(track);
9303
+ parent.getTracks().forEach((parentTrack) => {
9304
+ if (parentTrack.kind === track.kind) {
9305
+ parentTrack.stop();
9306
+ }
9307
+ });
9308
+ };
9309
+ });
9310
+ parent.getTracks().forEach((parentTrack) => {
9311
+ // When the parent stream abruptly ends, we propagate the event
9312
+ // to the filter stream.
9313
+ // This usually happens when the camera/microphone permissions
9314
+ // are revoked or when the device is disconnected.
9315
+ const handleParentTrackEnded = () => {
9316
+ filterStream.getTracks().forEach((track) => {
9317
+ if (parentTrack.kind !== track.kind)
9318
+ return;
9319
+ track.stop();
9320
+ track.dispatchEvent(new Event('ended')); // propagate the event
9321
+ });
9322
+ };
9323
+ parentTrack.addEventListener('ended', handleParentTrackEnded);
9324
+ this.subscriptions.push(() => {
9325
+ parentTrack.removeEventListener('ended', handleParentTrackEnded);
9317
9326
  });
9318
- };
9319
- parentTrack.addEventListener('ended', handleParentTrackEnded);
9327
+ });
9328
+ return filterStream;
9329
+ };
9330
+ // the rootStream represents the stream coming from the actual device
9331
+ // e.g. camera or microphone stream
9332
+ rootStream = this.getStream(constraints);
9333
+ // we publish the last MediaStream of the chain
9334
+ stream = await this.filters.reduce((parent, entry) => parent
9335
+ .then((inputStream) => {
9336
+ const { stop, output } = entry.start(inputStream);
9337
+ entry.stop = stop;
9338
+ return output;
9339
+ })
9340
+ .then(chainWith(parent), (error) => {
9341
+ this.logger('warn', 'Filter failed to start and will be ignored', error);
9342
+ return parent;
9343
+ }), rootStream);
9344
+ }
9345
+ if (this.call.state.callingState === exports.CallingState.JOINED) {
9346
+ await this.publishStream(stream);
9347
+ }
9348
+ if (this.state.mediaStream !== stream) {
9349
+ this.state.setMediaStream(stream, await rootStream);
9350
+ const handleTrackEnded = async () => {
9351
+ await this.statusChangeSettled();
9352
+ if (this.enabled) {
9353
+ this.isTrackStoppedDueToTrackEnd = true;
9354
+ setTimeout(() => {
9355
+ this.isTrackStoppedDueToTrackEnd = false;
9356
+ }, 2000);
9357
+ await this.disable();
9358
+ }
9359
+ };
9360
+ const createTrackMuteHandler = (muted) => () => {
9361
+ if (!isMobile() || this.trackType !== TrackType.VIDEO)
9362
+ return;
9363
+ this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
9364
+ this.logger('warn', 'Error while notifying track mute state', err);
9365
+ });
9366
+ };
9367
+ stream.getTracks().forEach((track) => {
9368
+ const muteHandler = createTrackMuteHandler(true);
9369
+ const unmuteHandler = createTrackMuteHandler(false);
9370
+ track.addEventListener('mute', muteHandler);
9371
+ track.addEventListener('unmute', unmuteHandler);
9372
+ track.addEventListener('ended', handleTrackEnded);
9320
9373
  this.subscriptions.push(() => {
9321
- parentTrack.removeEventListener('ended', handleParentTrackEnded);
9374
+ track.removeEventListener('mute', muteHandler);
9375
+ track.removeEventListener('unmute', unmuteHandler);
9376
+ track.removeEventListener('ended', handleTrackEnded);
9322
9377
  });
9323
9378
  });
9324
- return filterStream;
9325
- };
9326
- // the rootStream represents the stream coming from the actual device
9327
- // e.g. camera or microphone stream
9328
- rootStream = this.getStream(constraints);
9329
- // we publish the last MediaStream of the chain
9330
- stream = await this.filters.reduce((parent, entry) => parent
9331
- .then((inputStream) => {
9332
- const { stop, output } = entry.start(inputStream);
9333
- entry.stop = stop;
9334
- return output;
9335
- })
9336
- .then(chainWith(parent), (error) => {
9337
- this.logger('warn', 'Filter failed to start and will be ignored', error);
9338
- return parent;
9339
- }), rootStream);
9340
- }
9341
- if (this.call.state.callingState === exports.CallingState.JOINED) {
9342
- await this.publishStream(stream);
9379
+ }
9343
9380
  }
9344
- if (this.state.mediaStream !== stream) {
9345
- this.state.setMediaStream(stream, await rootStream);
9346
- const handleTrackEnded = async () => {
9347
- await this.statusChangeSettled();
9348
- if (this.enabled) {
9349
- this.isTrackStoppedDueToTrackEnd = true;
9350
- setTimeout(() => {
9351
- this.isTrackStoppedDueToTrackEnd = false;
9352
- }, 2000);
9353
- await this.disable();
9354
- }
9355
- };
9356
- const createTrackMuteHandler = (muted) => () => {
9357
- if (!isMobile() || this.trackType !== TrackType.VIDEO)
9358
- return;
9359
- this.call.notifyTrackMuteState(muted, this.trackType).catch((err) => {
9360
- this.logger('warn', 'Error while notifying track mute state', err);
9361
- });
9362
- };
9363
- stream.getTracks().forEach((track) => {
9364
- const muteHandler = createTrackMuteHandler(true);
9365
- const unmuteHandler = createTrackMuteHandler(false);
9366
- track.addEventListener('mute', muteHandler);
9367
- track.addEventListener('unmute', unmuteHandler);
9368
- track.addEventListener('ended', handleTrackEnded);
9369
- this.subscriptions.push(() => {
9370
- track.removeEventListener('mute', muteHandler);
9371
- track.removeEventListener('unmute', unmuteHandler);
9372
- track.removeEventListener('ended', handleTrackEnded);
9373
- });
9374
- });
9381
+ catch (err) {
9382
+ if (rootStream) {
9383
+ disposeOfMediaStream(await rootStream);
9384
+ }
9385
+ throw err;
9375
9386
  }
9376
9387
  }
9377
9388
  get mediaDeviceKind() {
@@ -9493,6 +9504,9 @@ class InputMediaDeviceManagerState {
9493
9504
  this.hasBrowserPermission$ = permission
9494
9505
  ? permission.asObservable().pipe(rxjs.shareReplay(1))
9495
9506
  : rxjs.of(true);
9507
+ this.browserPermissionState$ = permission
9508
+ ? permission.asStateObservable().pipe(rxjs.shareReplay(1))
9509
+ : rxjs.of('prompt');
9496
9510
  this.isPromptingPermission$ = permission
9497
9511
  ? permission.getIsPromptingObservable().pipe(rxjs.shareReplay(1))
9498
9512
  : rxjs.of(false);
@@ -13458,7 +13472,7 @@ class StreamClient {
13458
13472
  this.getUserAgent = () => {
13459
13473
  if (!this.cachedUserAgent) {
13460
13474
  const { clientAppIdentifier = {} } = this.options;
13461
- const { sdkName = 'js', sdkVersion = "1.20.0", ...extras } = clientAppIdentifier;
13475
+ const { sdkName = 'js', sdkVersion = "1.20.1", ...extras } = clientAppIdentifier;
13462
13476
  this.cachedUserAgent = [
13463
13477
  `stream-video-${sdkName}-v${sdkVersion}`,
13464
13478
  ...Object.entries(extras).map(([key, value]) => `${key}=${value}`),