@stream-io/video-client 1.21.0 → 1.22.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/index.browser.es.js +171 -143
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +171 -143
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.d.ts +0 -1
  7. package/dist/index.es.js +171 -143
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/src/Call.d.ts +2 -0
  10. package/dist/src/devices/InputMediaDeviceManager.d.ts +3 -3
  11. package/dist/src/devices/SpeakerState.d.ts +3 -1
  12. package/dist/src/devices/devices.d.ts +8 -8
  13. package/dist/src/events/call.d.ts +1 -1
  14. package/dist/src/gen/video/sfu/models/models.d.ts +8 -0
  15. package/dist/src/rtc/BasePeerConnection.d.ts +1 -0
  16. package/dist/src/stats/SfuStatsReporter.d.ts +5 -1
  17. package/dist/src/stats/utils.d.ts +14 -0
  18. package/index.ts +0 -4
  19. package/package.json +10 -10
  20. package/src/Call.ts +15 -8
  21. package/src/devices/CameraManager.ts +27 -23
  22. package/src/devices/InputMediaDeviceManager.ts +8 -5
  23. package/src/devices/MicrophoneManager.ts +1 -1
  24. package/src/devices/ScreenShareManager.ts +2 -2
  25. package/src/devices/SpeakerManager.ts +2 -1
  26. package/src/devices/SpeakerState.ts +6 -3
  27. package/src/devices/__tests__/CameraManager.test.ts +43 -27
  28. package/src/devices/__tests__/MicrophoneManager.test.ts +5 -3
  29. package/src/devices/__tests__/ScreenShareManager.test.ts +5 -1
  30. package/src/devices/__tests__/mocks.ts +2 -3
  31. package/src/devices/devices.ts +38 -16
  32. package/src/events/__tests__/call.test.ts +23 -0
  33. package/src/events/call.ts +12 -1
  34. package/src/gen/video/sfu/models/models.ts +14 -0
  35. package/src/rtc/BasePeerConnection.ts +11 -5
  36. package/src/rtc/Publisher.ts +5 -1
  37. package/src/rtc/__tests__/Publisher.test.ts +2 -2
  38. package/src/rtc/__tests__/Subscriber.test.ts +2 -2
  39. package/src/rtc/__tests__/mocks/webrtc.mocks.ts +1 -1
  40. package/src/rtc/__tests__/videoLayers.test.ts +44 -0
  41. package/src/rtc/videoLayers.ts +6 -3
  42. package/src/stats/SfuStatsReporter.ts +15 -5
  43. package/src/stats/utils.ts +15 -0
  44. package/dist/src/stats/rtc/mediaDevices.d.ts +0 -2
  45. package/src/stats/rtc/mediaDevices.ts +0 -43
@@ -3,6 +3,7 @@ import { CallState } from './store';
3
3
  import type { AcceptCallResponse, BlockUserResponse, CallRingEvent, CallSettingsResponse, CollectUserFeedbackRequest, CollectUserFeedbackResponse, DeleteCallRequest, DeleteCallResponse, EndCallResponse, GetCallReportResponse, GetCallResponse, GetOrCreateCallRequest, GetOrCreateCallResponse, GoLiveRequest, GoLiveResponse, JoinCallResponse, ListRecordingsResponse, ListTranscriptionsResponse, MuteUsersResponse, PinRequest, PinResponse, QueryCallMembersRequest, QueryCallMembersResponse, RejectCallResponse, RequestPermissionRequest, RequestPermissionResponse, SendCallEventResponse, SendReactionRequest, SendReactionResponse, StartClosedCaptionsRequest, StartClosedCaptionsResponse, StartFrameRecordingRequest, StartFrameRecordingResponse, StartHLSBroadcastingResponse, StartRecordingRequest, StartRecordingResponse, StartRTMPBroadcastsRequest, StartRTMPBroadcastsResponse, StartTranscriptionRequest, StartTranscriptionResponse, StopAllRTMPBroadcastsResponse, StopClosedCaptionsRequest, StopClosedCaptionsResponse, StopFrameRecordingResponse, StopHLSBroadcastingResponse, StopLiveRequest, StopLiveResponse, StopRecordingResponse, StopRTMPBroadcastsResponse, StopTranscriptionResponse, UnblockUserResponse, UnpinRequest, UnpinResponse, UpdateCallMembersRequest, UpdateCallMembersResponse, UpdateCallRequest, UpdateCallResponse, UpdateUserPermissionsRequest, UpdateUserPermissionsResponse, VideoDimension } from './gen/coordinator';
4
4
  import { AudioTrackType, CallConstructor, CallLeaveOptions, ClientPublishOptions, ClosedCaptionsSettings, JoinCallData, TrackMuteType, VideoTrackType } from './types';
5
5
  import { TrackType } from './gen/video/sfu/models/models';
6
+ import { Tracer } from './stats';
6
7
  import { DynascaleManager } from './helpers/DynascaleManager';
7
8
  import { PermissionsContext } from './permissions';
8
9
  import { StreamClient } from './coordinator/connection/client';
@@ -64,6 +65,7 @@ export declare class Call {
64
65
  * The permissions context of this call.
65
66
  */
66
67
  readonly permissionsContext: PermissionsContext;
68
+ readonly tracer: Tracer;
67
69
  readonly logger: Logger;
68
70
  /**
69
71
  * The event dispatcher instance dedicated to this Call instance.
@@ -5,14 +5,14 @@ import { Logger } from '../coordinator/connection/types';
5
5
  import { TrackType } from '../gen/video/sfu/models/models';
6
6
  import { MediaStreamFilter, MediaStreamFilterRegistrationResult } from './filters';
7
7
  export declare abstract class InputMediaDeviceManager<T extends InputMediaDeviceManagerState<C>, C = MediaTrackConstraints> {
8
- protected readonly call: Call;
9
- readonly state: T;
10
- protected readonly trackType: TrackType;
11
8
  /**
12
9
  * if true, stops the media stream when call is left
13
10
  */
14
11
  stopOnLeave: boolean;
15
12
  logger: Logger;
13
+ state: T;
14
+ protected readonly call: Call;
15
+ protected readonly trackType: TrackType;
16
16
  protected subscriptions: Function[];
17
17
  private isTrackStoppedDueToTrackEnd;
18
18
  private filters;
@@ -1,4 +1,5 @@
1
1
  import { BehaviorSubject, Observable } from 'rxjs';
2
+ import { Tracer } from '../stats';
2
3
  export declare class SpeakerState {
3
4
  protected selectedDeviceSubject: BehaviorSubject<string>;
4
5
  protected volumeSubject: BehaviorSubject<number>;
@@ -18,7 +19,8 @@ export declare class SpeakerState {
18
19
  * Note: this feature is not supported in React Native
19
20
  */
20
21
  volume$: Observable<number>;
21
- constructor();
22
+ private tracer;
23
+ constructor(tracer: Tracer);
22
24
  /**
23
25
  * The currently selected device
24
26
  *
@@ -1,4 +1,5 @@
1
1
  import { BrowserPermission } from './BrowserPermission';
2
+ import { Tracer } from '../stats';
2
3
  /**
3
4
  * Tells if the browser supports audio output change on 'audio' elements,
4
5
  * see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId.
@@ -39,31 +40,30 @@ export declare const getAudioOutputDevices: () => import("rxjs").Observable<Medi
39
40
  * Returns an audio media stream that fulfills the given constraints.
40
41
  * If no constraints are provided, it uses the browser's default ones.
41
42
  *
42
- * @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.
43
43
  * @param trackConstraints the constraints to use when requesting the stream.
44
- * @returns the new `MediaStream` fulfilling the given constraints.
44
+ * @param tracer the tracer to use for tracing the stream creation.
45
+ * @returns a new `MediaStream` fulfilling the given constraints.
45
46
  */
46
- export declare const getAudioStream: (trackConstraints?: MediaTrackConstraints) => Promise<MediaStream>;
47
+ export declare const getAudioStream: (trackConstraints?: MediaTrackConstraints, tracer?: Tracer) => Promise<MediaStream>;
47
48
  /**
48
49
  * Returns a video media stream that fulfills the given constraints.
49
50
  * If no constraints are provided, it uses the browser's default ones.
50
51
  *
51
- * @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.
52
52
  * @param trackConstraints the constraints to use when requesting the stream.
53
+ * @param tracer the tracer to use for tracing the stream creation.
53
54
  * @returns a new `MediaStream` fulfilling the given constraints.
54
55
  */
55
- export declare const getVideoStream: (trackConstraints?: MediaTrackConstraints) => Promise<MediaStream>;
56
+ export declare const getVideoStream: (trackConstraints?: MediaTrackConstraints, tracer?: Tracer) => Promise<MediaStream>;
56
57
  /**
57
58
  * Prompts the user for a permission to share a screen.
58
59
  * If the user grants the permission, a screen sharing stream is returned. Throws otherwise.
59
60
  *
60
61
  * The callers of this API are responsible to handle the possible errors.
61
62
  *
62
- * @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.
63
- *
64
63
  * @param options any additional options to pass to the [`getDisplayMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia) API.
64
+ * @param tracer the tracer to use for tracing the stream creation.
65
65
  */
66
- export declare const getScreenShareStream: (options?: DisplayMediaStreamOptions) => Promise<MediaStream>;
66
+ export declare const getScreenShareStream: (options?: DisplayMediaStreamOptions, tracer?: Tracer | undefined) => Promise<MediaStream>;
67
67
  export declare const deviceIds$: import("rxjs").Observable<MediaDeviceInfo[]> | undefined;
68
68
  /**
69
69
  * Deactivates MediaStream (stops and removes tracks) to be later garbage collected
@@ -1,5 +1,5 @@
1
1
  import { Call } from '../Call';
2
- import type { CallAcceptedEvent, CallRejectedEvent } from '../gen/coordinator';
2
+ import { CallAcceptedEvent, CallRejectedEvent } from '../gen/coordinator';
3
3
  /**
4
4
  * Event handler that watched the delivery of `call.accepted`.
5
5
  * Once the event is received, the call is joined.
@@ -285,6 +285,14 @@ export interface PublishOption {
285
285
  * @generated from protobuf field: int32 id = 8;
286
286
  */
287
287
  id: number;
288
+ /**
289
+ * If true, instructs the publisher to send only the highest available simulcast layer,
290
+ * disabling all lower layers. This applies to simulcast encodings.
291
+ * For SVC codecs, prefer using the L1T3 (single spatial, 3 temporal layers) mode instead.
292
+ *
293
+ * @generated from protobuf field: bool use_single_layer = 9;
294
+ */
295
+ useSingleLayer: boolean;
288
296
  }
289
297
  /**
290
298
  * @generated from protobuf message stream.video.sfu.models.Codec
@@ -32,6 +32,7 @@ export declare abstract class BasePeerConnection {
32
32
  readonly stats: StatsTracer;
33
33
  private readonly subscriptions;
34
34
  private unsubscribeIceTrickle?;
35
+ protected readonly lock: string;
35
36
  /**
36
37
  * Constructs a new `BasePeerConnection` instance.
37
38
  */
@@ -1,6 +1,7 @@
1
1
  import { StreamSfuClient } from '../StreamSfuClient';
2
2
  import { StatsOptions } from '../gen/coordinator';
3
3
  import { Publisher, Subscriber } from '../rtc';
4
+ import { Tracer } from './rtc';
4
5
  import { ClientDetails, WebsocketReconnectStrategy } from '../gen/video/sfu/models/models';
5
6
  import { CameraManager, MicrophoneManager } from '../devices';
6
7
  import { CallState } from '../store';
@@ -12,6 +13,7 @@ export type SfuStatsReporterOptions = {
12
13
  microphone: MicrophoneManager;
13
14
  camera: CameraManager;
14
15
  state: CallState;
16
+ tracer: Tracer;
15
17
  unifiedSessionId: string;
16
18
  };
17
19
  export declare class SfuStatsReporter {
@@ -23,6 +25,7 @@ export declare class SfuStatsReporter {
23
25
  private readonly microphone;
24
26
  private readonly camera;
25
27
  private readonly state;
28
+ private readonly tracer;
26
29
  private readonly unifiedSessionId;
27
30
  private intervalId;
28
31
  private timeoutId;
@@ -32,7 +35,7 @@ export declare class SfuStatsReporter {
32
35
  private readonly sdkVersion;
33
36
  private readonly webRTCVersion;
34
37
  private readonly inputDevices;
35
- constructor(sfuClient: StreamSfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, unifiedSessionId, }: SfuStatsReporterOptions);
38
+ constructor(sfuClient: StreamSfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, tracer, unifiedSessionId, }: SfuStatsReporterOptions);
36
39
  private observeDevice;
37
40
  sendConnectionTime: (connectionTimeSeconds: number) => void;
38
41
  sendReconnectionTime: (strategy: WebsocketReconnectStrategy, timeSeconds: number) => void;
@@ -40,5 +43,6 @@ export declare class SfuStatsReporter {
40
43
  private run;
41
44
  start: () => void;
42
45
  stop: () => void;
46
+ flush: () => void;
43
47
  scheduleOne: (timeout: number) => void;
44
48
  }
@@ -5,6 +5,20 @@ import { ClientDetails, Sdk } from '../gen/video/sfu/models/models';
5
5
  * @param report the report to flatten.
6
6
  */
7
7
  export declare const flatten: (report: RTCStatsReport) => RTCStats[];
8
+ /**
9
+ * Dump the provided MediaStream into a JSON object.
10
+ */
11
+ export declare const dumpStream: (stream: MediaStream) => {
12
+ id: string;
13
+ tracks: {
14
+ id: string;
15
+ kind: string;
16
+ label: string;
17
+ enabled: boolean;
18
+ muted: boolean;
19
+ readyState: MediaStreamTrackState;
20
+ }[];
21
+ };
8
22
  export declare const getSdkSignature: (clientDetails: ClientDetails) => {
9
23
  os?: import("../gen/video/sfu/models/models").OS;
10
24
  browser?: import("../gen/video/sfu/models/models").Browser;
package/index.ts CHANGED
@@ -1,9 +1,5 @@
1
1
  import 'webrtc-adapter';
2
2
 
3
- // side effect: we patch the mediaDevices APIs here
4
- // so we can intercept invocations and collect statistics
5
- import './src/stats/rtc/mediaDevices';
6
-
7
3
  export * from './src/gen/coordinator';
8
4
  export * from './src/coordinator/connection/types';
9
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "1.21.0",
3
+ "version": "1.22.1",
4
4
  "main": "dist/index.cjs.js",
5
5
  "module": "dist/index.es.js",
6
6
  "browser": "dist/index.browser.es.js",
@@ -40,19 +40,19 @@
40
40
  "@openapitools/openapi-generator-cli": "^2.13.4",
41
41
  "@rollup/plugin-replace": "^6.0.2",
42
42
  "@rollup/plugin-typescript": "^12.1.2",
43
- "@stream-io/audio-filters-web": "^0.2.3",
44
- "@stream-io/node-sdk": "^0.4.19",
43
+ "@stream-io/audio-filters-web": "^0.3.0",
44
+ "@stream-io/node-sdk": "^0.4.24",
45
45
  "@types/sdp-transform": "^2.4.9",
46
46
  "@types/ua-parser-js": "^0.7.39",
47
- "@vitest/coverage-v8": "^3.0.9",
48
- "dotenv": "^16.4.7",
47
+ "@vitest/coverage-v8": "^3.1.3",
48
+ "dotenv": "^16.5.0",
49
49
  "happy-dom": "^11.0.2",
50
50
  "prettier": "^3.5.3",
51
51
  "rimraf": "^6.0.1",
52
- "rollup": "^4.36.0",
53
- "typescript": "^5.8.2",
54
- "vite": "^6.2.2",
55
- "vitest": "^3.0.9",
56
- "vitest-mock-extended": "^3.0.1"
52
+ "rollup": "^4.40.2",
53
+ "typescript": "^5.8.3",
54
+ "vite": "^6.3.5",
55
+ "vitest": "^3.1.3",
56
+ "vitest-mock-extended": "^3.1.0"
57
57
  }
58
58
  }
package/src/Call.ts CHANGED
@@ -120,8 +120,8 @@ import {
120
120
  getSdkSignature,
121
121
  SfuStatsReporter,
122
122
  StatsReporter,
123
+ Tracer,
123
124
  } from './stats';
124
- import { tracer as mediaStatsTracer } from './stats/rtc/mediaDevices';
125
125
  import { DynascaleManager } from './helpers/DynascaleManager';
126
126
  import { PermissionsContext } from './permissions';
127
127
  import { CallTypes } from './CallType';
@@ -220,6 +220,7 @@ export class Call {
220
220
  * The permissions context of this call.
221
221
  */
222
222
  readonly permissionsContext = new PermissionsContext();
223
+ readonly tracer = new Tracer(null);
223
224
  readonly logger: Logger;
224
225
 
225
226
  /**
@@ -590,6 +591,7 @@ export class Call {
590
591
  this.statsReporter?.stop();
591
592
  this.statsReporter = undefined;
592
593
 
594
+ this.sfuStatsReporter?.flush();
593
595
  this.sfuStatsReporter?.stop();
594
596
  this.sfuStatsReporter = undefined;
595
597
 
@@ -1012,7 +1014,7 @@ export class Call {
1012
1014
  );
1013
1015
  }
1014
1016
 
1015
- if (performingRejoin) {
1017
+ if (performingRejoin && isWsHealthy) {
1016
1018
  const strategy = WebsocketReconnectStrategy[this.reconnectStrategy];
1017
1019
  await previousSfuClient?.leaveAndClose(
1018
1020
  `Closing previous WS after reconnect with strategy: ${strategy}`,
@@ -1225,7 +1227,6 @@ export class Call {
1225
1227
  });
1226
1228
  }
1227
1229
 
1228
- mediaStatsTracer.setEnabled(enableTracing);
1229
1230
  this.statsReporter?.stop();
1230
1231
  this.statsReporter = createStatsReporter({
1231
1232
  subscriber: this.subscriber,
@@ -1234,6 +1235,7 @@ export class Call {
1234
1235
  datacenter: sfuClient.edgeName,
1235
1236
  });
1236
1237
 
1238
+ this.tracer.setEnabled(enableTracing);
1237
1239
  this.sfuStatsReporter?.stop();
1238
1240
  if (statsOptions?.reporting_interval_ms > 0) {
1239
1241
  this.unifiedSessionId ??= sfuClient.sessionId;
@@ -1245,6 +1247,7 @@ export class Call {
1245
1247
  microphone: this.microphone,
1246
1248
  camera: this.camera,
1247
1249
  state: this.state,
1250
+ tracer: this.tracer,
1248
1251
  unifiedSessionId: this.unifiedSessionId,
1249
1252
  });
1250
1253
  this.sfuStatsReporter.start();
@@ -1336,16 +1339,12 @@ export class Call {
1336
1339
  ): Promise<void> => {
1337
1340
  if (
1338
1341
  this.state.callingState === CallingState.RECONNECTING ||
1342
+ this.state.callingState === CallingState.MIGRATING ||
1339
1343
  this.state.callingState === CallingState.RECONNECTING_FAILED
1340
1344
  )
1341
1345
  return;
1342
1346
 
1343
1347
  return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
1344
- this.logger(
1345
- 'info',
1346
- `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[strategy]}`,
1347
- );
1348
-
1349
1348
  const reconnectStartTime = Date.now();
1350
1349
  this.reconnectStrategy = strategy;
1351
1350
  this.reconnectReason = reason;
@@ -1372,6 +1371,12 @@ export class Call {
1372
1371
  try {
1373
1372
  // wait until the network is available
1374
1373
  await this.networkAvailableTask?.promise;
1374
+
1375
+ this.logger(
1376
+ 'info',
1377
+ `[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[this.reconnectStrategy]}`,
1378
+ );
1379
+
1375
1380
  switch (this.reconnectStrategy) {
1376
1381
  case WebsocketReconnectStrategy.UNSPECIFIED:
1377
1382
  case WebsocketReconnectStrategy.DISCONNECT:
@@ -1426,6 +1431,7 @@ export class Call {
1426
1431
  this.state.callingState !== CallingState.RECONNECTING_FAILED &&
1427
1432
  this.state.callingState !== CallingState.LEFT
1428
1433
  );
1434
+ this.logger('info', '[Reconnect] Reconnection flow finished');
1429
1435
  });
1430
1436
  };
1431
1437
 
@@ -1548,6 +1554,7 @@ export class Call {
1548
1554
  const unregisterNetworkChanged = this.streamClient.on(
1549
1555
  'network.changed',
1550
1556
  (e) => {
1557
+ this.tracer.trace('network.changed', e);
1551
1558
  if (!e.online) {
1552
1559
  this.logger('debug', '[Reconnect] Going offline');
1553
1560
  if (!this.hasJoinedOnce) return;
@@ -33,30 +33,34 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
33
33
  * @param direction the direction of the camera to select.
34
34
  */
35
35
  async selectDirection(direction: Exclude<CameraDirection, undefined>) {
36
- if (this.isDirectionSupportedByDevice()) {
37
- if (isReactNative()) {
38
- const videoTrack = this.getTracks()[0];
39
- if (!videoTrack) {
40
- this.logger('warn', 'No video track found to do direction selection');
41
- return;
42
- }
43
- await videoTrack.applyConstraints({
44
- facingMode: direction === 'front' ? 'user' : 'environment',
45
- });
46
- this.state.setDirection(direction);
47
- this.state.setDevice(undefined);
48
- } else {
49
- // web mobile
50
- this.state.setDirection(direction);
51
- // Providing both device id and direction doesn't work, so we deselect the device
52
- this.state.setDevice(undefined);
53
- this.getTracks().forEach((track) => {
54
- track.stop();
55
- });
36
+ if (!this.isDirectionSupportedByDevice()) {
37
+ this.logger('warn', 'Setting direction is not supported on this device');
38
+ return;
39
+ }
40
+
41
+ // providing both device id and direction doesn't work, so we deselect the device
42
+ this.state.setDirection(direction);
43
+ this.state.setDevice(undefined);
44
+
45
+ if (isReactNative()) {
46
+ const videoTrack = this.getTracks()[0] as MediaStreamTrack | undefined;
47
+ await videoTrack?.applyConstraints({
48
+ facingMode: direction === 'front' ? 'user' : 'environment',
49
+ });
50
+ return;
51
+ }
52
+
53
+ this.getTracks().forEach((track) => track.stop());
54
+ try {
55
+ await this.unmuteStream();
56
+ } catch (error) {
57
+ if (error instanceof Error && error.name === 'NotReadableError') {
58
+ // the camera is already in use, and the device can't use it unless it's released.
59
+ // in that case, we need to stop the stream and start it again.
60
+ await this.muteStream();
56
61
  await this.unmuteStream();
57
62
  }
58
- } else {
59
- this.logger('warn', 'Camera direction ignored for desktop devices');
63
+ throw error;
60
64
  }
61
65
  }
62
66
 
@@ -158,6 +162,6 @@ export class CameraManager extends InputMediaDeviceManager<CameraManagerState> {
158
162
  constraints.facingMode =
159
163
  this.state.direction === 'front' ? 'user' : 'environment';
160
164
  }
161
- return getVideoStream(constraints);
165
+ return getVideoStream(constraints, this.call.tracer);
162
166
  }
163
167
  }
@@ -30,6 +30,10 @@ export abstract class InputMediaDeviceManager<
30
30
  stopOnLeave = true;
31
31
  logger: Logger;
32
32
 
33
+ state: T;
34
+
35
+ protected readonly call: Call;
36
+ protected readonly trackType: TrackType;
33
37
  protected subscriptions: Function[] = [];
34
38
  private isTrackStoppedDueToTrackEnd = false;
35
39
  private filters: MediaStreamFilterEntry[] = [];
@@ -38,11 +42,10 @@ export abstract class InputMediaDeviceManager<
38
42
  'filterRegistrationConcurrencyTag',
39
43
  );
40
44
 
41
- protected constructor(
42
- protected readonly call: Call,
43
- public readonly state: T,
44
- protected readonly trackType: TrackType,
45
- ) {
45
+ protected constructor(call: Call, state: T, trackType: TrackType) {
46
+ this.call = call;
47
+ this.state = state;
48
+ this.trackType = trackType;
46
49
  this.logger = getLogger([`${TrackType[trackType].toLowerCase()} manager`]);
47
50
  if (
48
51
  deviceIds$ &&
@@ -235,7 +235,7 @@ export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManager
235
235
  protected getStream(
236
236
  constraints: MediaTrackConstraints,
237
237
  ): Promise<MediaStream> {
238
- return getAudioStream(constraints);
238
+ return getAudioStream(constraints, this.call.tracer);
239
239
  }
240
240
 
241
241
  private async startSpeakingWhileMutedDetection(deviceId?: string) {
@@ -76,7 +76,7 @@ export class ScreenShareManager extends InputMediaDeviceManager<
76
76
  if (!this.state.audioEnabled) {
77
77
  constraints.audio = false;
78
78
  }
79
- return getScreenShareStream(constraints);
79
+ return getScreenShareStream(constraints, this.call.tracer);
80
80
  }
81
81
 
82
82
  protected async stopPublishStream(): Promise<void> {
@@ -90,6 +90,6 @@ export class ScreenShareManager extends InputMediaDeviceManager<
90
90
  * Overrides the default `select` method to throw an error.
91
91
  */
92
92
  async select(): Promise<void> {
93
- throw new Error('This method is not supported in for Screen Share');
93
+ throw new Error('Not supported');
94
94
  }
95
95
  }
@@ -5,12 +5,13 @@ import { SpeakerState } from './SpeakerState';
5
5
  import { deviceIds$, getAudioOutputDevices } from './devices';
6
6
 
7
7
  export class SpeakerManager {
8
- public readonly state = new SpeakerState();
8
+ readonly state: SpeakerState;
9
9
  private subscriptions: Subscription[] = [];
10
10
  private readonly call: Call;
11
11
 
12
12
  constructor(call: Call) {
13
13
  this.call = call;
14
+ this.state = new SpeakerState(call.tracer);
14
15
  if (deviceIds$ && !isReactNative()) {
15
16
  this.subscriptions.push(
16
17
  combineLatest([deviceIds$!, this.state.selectedDevice$]).subscribe(
@@ -1,7 +1,7 @@
1
1
  import { BehaviorSubject, distinctUntilChanged, Observable } from 'rxjs';
2
2
  import { RxUtils } from '../store';
3
3
  import { checkIfAudioOutputChangeSupported } from './devices';
4
- import { tracer as mediaStatsTracer } from '../stats/rtc/mediaDevices';
4
+ import { Tracer } from '../stats';
5
5
 
6
6
  export class SpeakerState {
7
7
  protected selectedDeviceSubject = new BehaviorSubject<string>('');
@@ -25,7 +25,10 @@ export class SpeakerState {
25
25
  */
26
26
  volume$: Observable<number>;
27
27
 
28
- constructor() {
28
+ private tracer: Tracer;
29
+
30
+ constructor(tracer: Tracer) {
31
+ this.tracer = tracer;
29
32
  this.selectedDevice$ = this.selectedDeviceSubject
30
33
  .asObservable()
31
34
  .pipe(distinctUntilChanged());
@@ -58,7 +61,7 @@ export class SpeakerState {
58
61
  */
59
62
  setDevice(deviceId: string) {
60
63
  RxUtils.setCurrentValue(this.selectedDeviceSubject, deviceId);
61
- mediaStatsTracer.trace('navigator.mediaDevices.setSinkId', deviceId);
64
+ this.tracer.trace('navigator.mediaDevices.setSinkId', deviceId);
62
65
  }
63
66
 
64
67
  /**
@@ -14,6 +14,7 @@ import { TrackType } from '../../gen/video/sfu/models/models';
14
14
  import { CameraManager } from '../CameraManager';
15
15
  import { of } from 'rxjs';
16
16
  import { PermissionsContext } from '../../permissions';
17
+ import { Tracer } from '../../stats';
17
18
 
18
19
  const getVideoStream = vi.hoisted(() =>
19
20
  vi.fn(() => Promise.resolve(mockVideoStream())),
@@ -78,11 +79,14 @@ describe('CameraManager', () => {
78
79
  it('get stream', async () => {
79
80
  await manager.enable();
80
81
 
81
- expect(getVideoStream).toHaveBeenCalledWith({
82
- deviceId: undefined,
83
- width: 1280,
84
- height: 720,
85
- });
82
+ expect(getVideoStream).toHaveBeenCalledWith(
83
+ {
84
+ deviceId: undefined,
85
+ width: 1280,
86
+ height: 720,
87
+ },
88
+ expect.any(Tracer),
89
+ );
86
90
  });
87
91
 
88
92
  it('should get device id from stream', async () => {
@@ -133,29 +137,38 @@ describe('CameraManager', () => {
133
137
 
134
138
  await manager.enable();
135
139
 
136
- expect(getVideoStream).toHaveBeenCalledWith({
137
- deviceId: undefined,
138
- width: 1280,
139
- height: 720,
140
- });
140
+ expect(getVideoStream).toHaveBeenCalledWith(
141
+ {
142
+ deviceId: undefined,
143
+ width: 1280,
144
+ height: 720,
145
+ },
146
+ expect.any(Tracer),
147
+ );
141
148
 
142
149
  await manager.selectDirection('front');
143
150
 
144
- expect(getVideoStream).toHaveBeenCalledWith({
145
- deviceId: undefined,
146
- width: 1280,
147
- height: 720,
148
- facingMode: 'user',
149
- });
151
+ expect(getVideoStream).toHaveBeenCalledWith(
152
+ {
153
+ deviceId: undefined,
154
+ width: 1280,
155
+ height: 720,
156
+ facingMode: 'user',
157
+ },
158
+ expect.any(Tracer),
159
+ );
150
160
 
151
161
  await manager.selectDirection('back');
152
162
 
153
- expect(getVideoStream).toHaveBeenCalledWith({
154
- deviceId: undefined,
155
- facingMode: 'environment',
156
- width: 1280,
157
- height: 720,
158
- });
163
+ expect(getVideoStream).toHaveBeenCalledWith(
164
+ {
165
+ deviceId: undefined,
166
+ facingMode: 'environment',
167
+ width: 1280,
168
+ height: 720,
169
+ },
170
+ expect.any(Tracer),
171
+ );
159
172
  });
160
173
 
161
174
  it(`shouldn't set deviceId and facingMode at the same time`, async () => {
@@ -163,11 +176,14 @@ describe('CameraManager', () => {
163
176
 
164
177
  await manager.flip();
165
178
 
166
- expect(getVideoStream).toHaveBeenCalledWith({
167
- facingMode: 'environment',
168
- width: 1280,
169
- height: 720,
170
- });
179
+ expect(getVideoStream).toHaveBeenCalledWith(
180
+ {
181
+ facingMode: 'environment',
182
+ width: 1280,
183
+ height: 720,
184
+ },
185
+ expect.any(Tracer),
186
+ );
171
187
 
172
188
  const deviceId = mockVideoDevices[1].deviceId;
173
189
  await manager.select(deviceId);
@@ -24,6 +24,7 @@ import {
24
24
  SoundStateChangeHandler,
25
25
  } from '../../helpers/sound-detector';
26
26
  import { PermissionsContext } from '../../permissions';
27
+ import { Tracer } from '../../stats';
27
28
 
28
29
  vi.mock('../devices.ts', () => {
29
30
  console.log('MOCKING devices API');
@@ -92,9 +93,10 @@ describe('MicrophoneManager', () => {
92
93
  it('get stream', async () => {
93
94
  await manager.enable();
94
95
 
95
- expect(getAudioStream).toHaveBeenCalledWith({
96
- deviceId: undefined,
97
- });
96
+ expect(getAudioStream).toHaveBeenCalledWith(
97
+ { deviceId: undefined },
98
+ expect.any(Tracer),
99
+ );
98
100
  });
99
101
 
100
102
  it('should get device id from stream', async () => {