@stream-io/video-client 0.6.7 → 0.6.9

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
@@ -5825,7 +5825,7 @@ class SignalServerClient {
5825
5825
  const defaultOptions = {
5826
5826
  baseUrl: '',
5827
5827
  sendJson: true,
5828
- timeout: 5 * 1000,
5828
+ timeout: 5 * 1000, // ms.
5829
5829
  jsonOptions: {
5830
5830
  ignoreUnknownFields: true,
5831
5831
  },
@@ -6231,7 +6231,7 @@ const findOptimalScreenSharingLayers = (videoTrack, preferences) => {
6231
6231
  return [
6232
6232
  {
6233
6233
  active: true,
6234
- rid: 'q',
6234
+ rid: 'q', // single track, start from 'q'
6235
6235
  width: settings.width || 0,
6236
6236
  height: settings.height || 0,
6237
6237
  scaleResolutionDownBy: 1,
@@ -7084,9 +7084,14 @@ class CallState {
7084
7084
  this.thumbnails$ = this.thumbnailsSubject.asObservable();
7085
7085
  this.eventHandlers = {
7086
7086
  // these events are not updating the call state:
7087
+ 'call.closed_caption': undefined,
7087
7088
  'call.permission_request': undefined,
7088
7089
  'call.recording_failed': undefined,
7089
7090
  'call.recording_ready': undefined,
7091
+ 'call.transcription_started': undefined,
7092
+ 'call.transcription_stopped': undefined,
7093
+ 'call.transcription_ready': undefined,
7094
+ 'call.transcription_failed': undefined,
7090
7095
  'call.user_muted': undefined,
7091
7096
  'connection.error': undefined,
7092
7097
  'connection.ok': undefined,
@@ -9919,7 +9924,6 @@ class DynascaleManager {
9919
9924
  // play audio through the system's default device
9920
9925
  const { selectedDevice } = this.call.speaker.state;
9921
9926
  if (selectedDevice && 'setSinkId' in audioElement) {
9922
- // @ts-expect-error setSinkId is not yet in the lib
9923
9927
  audioElement.setSinkId(selectedDevice);
9924
9928
  }
9925
9929
  }
@@ -9929,7 +9933,6 @@ class DynascaleManager {
9929
9933
  ? null
9930
9934
  : this.call.speaker.state.selectedDevice$.subscribe((deviceId) => {
9931
9935
  if (deviceId) {
9932
- // @ts-expect-error setSinkId is not yet in the lib
9933
9936
  audioElement.setSinkId(deviceId);
9934
9937
  }
9935
9938
  });
@@ -10329,8 +10332,14 @@ class InputMediaDeviceManager {
10329
10332
  this.stopOnLeave = true;
10330
10333
  this.subscriptions = [];
10331
10334
  this.isTrackStoppedDueToTrackEnd = false;
10332
- this.removeSubscriptions = () => {
10333
- this.subscriptions.forEach((s) => s.unsubscribe());
10335
+ this.filters = [];
10336
+ /**
10337
+ * Disposes the manager.
10338
+ *
10339
+ * @internal
10340
+ */
10341
+ this.dispose = () => {
10342
+ this.subscriptions.forEach((s) => s());
10334
10343
  };
10335
10344
  this.logger = getLogger([`${TrackType[trackType].toLowerCase()} manager`]);
10336
10345
  if (deviceIds$ &&
@@ -10407,6 +10416,23 @@ class InputMediaDeviceManager {
10407
10416
  return this.enable();
10408
10417
  }
10409
10418
  }
10419
+ /**
10420
+ * Registers a filter that will be applied to the stream.
10421
+ *
10422
+ * The registered filter will get the existing stream, and it should return
10423
+ * a new stream with the applied filter.
10424
+ *
10425
+ * @param filter the filter to register.
10426
+ * @returns a function that will unregister the filter.
10427
+ */
10428
+ async registerFilter(filter) {
10429
+ this.filters.push(filter);
10430
+ await this.applySettingsToStream();
10431
+ return async () => {
10432
+ this.filters = this.filters.filter((f) => f !== filter);
10433
+ await this.applySettingsToStream();
10434
+ };
10435
+ }
10410
10436
  /**
10411
10437
  * Will set the default constraints for the device.
10412
10438
  *
@@ -10502,7 +10528,62 @@ class InputMediaDeviceManager {
10502
10528
  ...defaultConstraints,
10503
10529
  deviceId: this.state.selectedDevice,
10504
10530
  };
10505
- stream = await this.getStream(constraints);
10531
+ /**
10532
+ * Chains two media streams together.
10533
+ *
10534
+ * In our case, filters MediaStreams are derived from their parent MediaStream.
10535
+ * However, once a child filter's track is stopped,
10536
+ * the tracks of the parent MediaStream aren't automatically stopped.
10537
+ * This leads to a situation where the camera indicator light is still on
10538
+ * even though the user stopped publishing video.
10539
+ *
10540
+ * This function works around this issue by stopping the parent MediaStream's tracks
10541
+ * as well once the child filter's tracks are stopped.
10542
+ *
10543
+ * It works by patching the stop() method of the child filter's tracks to also stop
10544
+ * the parent MediaStream's tracks of the same type. Here we assume that
10545
+ * the parent MediaStream has only one track of each type.
10546
+ *
10547
+ * @param parentStream the parent MediaStream. Omit for the root stream.
10548
+ */
10549
+ const chainWith = (parentStream) => async (filterStream) => {
10550
+ if (!parentStream)
10551
+ return filterStream;
10552
+ // TODO OL: take care of track.enabled property as well
10553
+ const parent = await parentStream;
10554
+ filterStream.getTracks().forEach((track) => {
10555
+ const originalStop = track.stop;
10556
+ track.stop = function stop() {
10557
+ originalStop.call(track);
10558
+ parent.getTracks().forEach((parentTrack) => {
10559
+ if (parentTrack.kind === track.kind) {
10560
+ parentTrack.stop();
10561
+ }
10562
+ });
10563
+ };
10564
+ });
10565
+ parent.getTracks().forEach((parentTrack) => {
10566
+ // When the parent stream abruptly ends, we propagate the event
10567
+ // to the filter stream.
10568
+ // This usually happens when the camera/microphone permissions
10569
+ // are revoked or when the device is disconnected.
10570
+ const handleParentTrackEnded = () => {
10571
+ filterStream.getTracks().forEach((track) => {
10572
+ if (parentTrack.kind !== track.kind)
10573
+ return;
10574
+ track.stop();
10575
+ track.dispatchEvent(new Event('ended')); // propagate the event
10576
+ });
10577
+ };
10578
+ parentTrack.addEventListener('ended', handleParentTrackEnded);
10579
+ this.subscriptions.push(() => {
10580
+ parentTrack.removeEventListener('ended', handleParentTrackEnded);
10581
+ });
10582
+ });
10583
+ return filterStream;
10584
+ };
10585
+ // we publish the last MediaStream of the chain
10586
+ stream = await this.filters.reduce((parent, filter) => parent.then(filter).then(chainWith(parent)), this.getStream(constraints));
10506
10587
  }
10507
10588
  if (this.call.state.callingState === exports.CallingState.JOINED) {
10508
10589
  await this.publishStream(stream);
@@ -10538,10 +10619,10 @@ class InputMediaDeviceManager {
10538
10619
  return '';
10539
10620
  }
10540
10621
  handleDisconnectedOrReplacedDevices() {
10541
- this.subscriptions.push(rxjs.combineLatest([
10622
+ this.subscriptions.push(createSubscription(rxjs.combineLatest([
10542
10623
  deviceIds$.pipe(rxjs.pairwise()),
10543
10624
  this.state.selectedDevice$,
10544
- ]).subscribe(async ([[prevDevices, currentDevices], deviceId]) => {
10625
+ ]), async ([[prevDevices, currentDevices], deviceId]) => {
10545
10626
  if (!deviceId) {
10546
10627
  return;
10547
10628
  }
@@ -11305,7 +11386,12 @@ class SpeakerManager {
11305
11386
  constructor(call) {
11306
11387
  this.state = new SpeakerState();
11307
11388
  this.subscriptions = [];
11308
- this.removeSubscriptions = () => {
11389
+ /**
11390
+ * Disposes the manager.
11391
+ *
11392
+ * @internal
11393
+ */
11394
+ this.dispose = () => {
11309
11395
  this.subscriptions.forEach((s) => s.unsubscribe());
11310
11396
  };
11311
11397
  this.call = call;
@@ -11500,10 +11586,10 @@ class Call {
11500
11586
  // Call all leave call hooks, e.g. to clean up global event handlers
11501
11587
  this.leaveCallHooks.forEach((hook) => hook());
11502
11588
  this.clientStore.unregisterCall(this);
11503
- this.camera.removeSubscriptions();
11504
- this.microphone.removeSubscriptions();
11505
- this.screenShare.removeSubscriptions();
11506
- this.speaker.removeSubscriptions();
11589
+ this.camera.dispose();
11590
+ this.microphone.dispose();
11591
+ this.screenShare.dispose();
11592
+ this.speaker.dispose();
11507
11593
  const stopOnLeavePromises = [];
11508
11594
  if (this.camera.stopOnLeave) {
11509
11595
  stopOnLeavePromises.push(this.camera.disable(true));
@@ -12305,6 +12391,20 @@ class Call {
12305
12391
  this.stopRecording = async () => {
12306
12392
  return this.streamClient.post(`${this.streamClientBasePath}/stop_recording`, {});
12307
12393
  };
12394
+ /**
12395
+ * Starts the transcription of the call.
12396
+ *
12397
+ * @param request the request data.
12398
+ */
12399
+ this.startTranscription = async (request) => {
12400
+ return this.streamClient.post(`${this.streamClientBasePath}/start_transcription`, request);
12401
+ };
12402
+ /**
12403
+ * Stops the transcription of the call.
12404
+ */
12405
+ this.stopTranscription = async () => {
12406
+ return this.streamClient.post(`${this.streamClientBasePath}/stop_transcription`);
12407
+ };
12308
12408
  /**
12309
12409
  * Sends a `call.permission_request` event to all users connected to the call. The call settings object contains infomration about which permissions can be requested during a call (for example a user might be allowed to request permission to publish audio, but not video).
12310
12410
  */
@@ -12513,6 +12613,14 @@ class Call {
12513
12613
  }
12514
12614
  return this.streamClient.get(`${endpoint}/recordings`);
12515
12615
  };
12616
+ /**
12617
+ * Retrieves the list of transcriptions for the current call.
12618
+ *
12619
+ * @returns the list of transcriptions.
12620
+ */
12621
+ this.queryTranscriptions = async () => {
12622
+ return this.streamClient.get(`${this.streamClientBasePath}/transcriptions`);
12623
+ };
12516
12624
  /**
12517
12625
  * Retrieve call statistics for a particular call session (historical).
12518
12626
  * Here `callSessionID` is mandatory.
@@ -12524,6 +12632,19 @@ class Call {
12524
12632
  const endpoint = `${this.streamClientBasePath}/stats/${callSessionID}`;
12525
12633
  return this.streamClient.get(endpoint);
12526
12634
  };
12635
+ /**
12636
+ * Submit feedback on call experience
12637
+ *
12638
+ * @returns
12639
+ */
12640
+ this.submitFeedback = async (feedback) => {
12641
+ const callSessionId = this.state.session?.id;
12642
+ if (!callSessionId) {
12643
+ return;
12644
+ }
12645
+ const endpoint = `${this.streamClientBasePath}/feedback/${callSessionId}`;
12646
+ return this.streamClient.post(endpoint, feedback);
12647
+ };
12527
12648
  /**
12528
12649
  * Sends a custom event to all call participants.
12529
12650
  *
@@ -13581,7 +13702,7 @@ function UserFromToken(token) {
13581
13702
  */
13582
13703
  function DevToken(userId) {
13583
13704
  return [
13584
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
13705
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', //{"alg": "HS256", "typ": "JWT"}
13585
13706
  encodeBase64(JSON.stringify({ user_id: userId })),
13586
13707
  'devtoken', // hardcoded signature
13587
13708
  ].join('.');
@@ -14393,7 +14514,7 @@ class StreamClient {
14393
14514
  });
14394
14515
  };
14395
14516
  this.getUserAgent = () => {
14396
- const version = "0.6.7" ;
14517
+ const version = "0.6.9" ;
14397
14518
  return (this.userAgent ||
14398
14519
  `stream-video-javascript-client-${this.node ? 'node' : 'browser'}-${version}`);
14399
14520
  };
@@ -14508,7 +14629,7 @@ class StreamClient {
14508
14629
  }
14509
14630
  this.options = {
14510
14631
  timeout: 5000,
14511
- withCredentials: false,
14632
+ withCredentials: false, // making sure cookies are not sent
14512
14633
  warmUp: false,
14513
14634
  ...inputOptions,
14514
14635
  };