@stream-io/video-client 1.61.1 → 1.62.0

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 (62) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/index.browser.es.js +525 -178
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +525 -178
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +525 -178
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +21 -1
  9. package/dist/src/coordinator/connection/types.d.ts +1 -1
  10. package/dist/src/devices/DeviceManager.d.ts +6 -0
  11. package/dist/src/devices/DeviceManagerState.d.ts +17 -0
  12. package/dist/src/devices/MicrophoneManager.d.ts +11 -0
  13. package/dist/src/devices/devices.d.ts +5 -0
  14. package/dist/src/gen/video/sfu/signal_rpc/signal.d.ts +0 -27
  15. package/dist/src/helpers/AudioBindingsWatchdog.d.ts +7 -2
  16. package/dist/src/helpers/DynascaleManager.d.ts +15 -0
  17. package/dist/src/helpers/MediaPlaybackWatchdog.d.ts +18 -0
  18. package/dist/src/helpers/mediaInterruptions.d.ts +14 -0
  19. package/dist/src/reporting/ClientEventReporter.d.ts +1 -1
  20. package/dist/src/ringing/reconcileRingState.d.ts +8 -7
  21. package/dist/src/rtc/BasePeerConnection.d.ts +13 -1
  22. package/dist/src/rtc/types.d.ts +7 -0
  23. package/dist/src/stats/SfuStatsReporter.d.ts +1 -13
  24. package/dist/src/stats/rtc/StatsTracer.d.ts +6 -6
  25. package/dist/src/stats/rtc/types.d.ts +0 -11
  26. package/package.json +1 -1
  27. package/src/Call.ts +110 -9
  28. package/src/StreamVideoClient.ts +8 -0
  29. package/src/__tests__/StreamVideoClient.rewatch.test.ts +136 -0
  30. package/src/coordinator/connection/types.ts +1 -1
  31. package/src/devices/DeviceManager.ts +26 -1
  32. package/src/devices/DeviceManagerState.ts +24 -0
  33. package/src/devices/MicrophoneManager.ts +79 -17
  34. package/src/devices/SpeakerManager.ts +1 -1
  35. package/src/devices/__tests__/CameraManager.test.ts +50 -1
  36. package/src/devices/__tests__/DeviceManager.test.ts +29 -0
  37. package/src/devices/__tests__/MicrophoneManager.test.ts +235 -0
  38. package/src/devices/__tests__/SpeakerManager.test.ts +27 -0
  39. package/src/devices/__tests__/devices.test.ts +10 -14
  40. package/src/devices/__tests__/mediaStreamTestHelpers.ts +11 -6
  41. package/src/devices/__tests__/mocks.ts +1 -0
  42. package/src/devices/devices.ts +7 -1
  43. package/src/gen/video/sfu/signal_rpc/signal.ts +0 -53
  44. package/src/helpers/AudioBindingsWatchdog.ts +91 -35
  45. package/src/helpers/DynascaleManager.ts +86 -11
  46. package/src/helpers/MediaPlaybackWatchdog.ts +55 -6
  47. package/src/helpers/__tests__/DynascaleManager.test.ts +253 -13
  48. package/src/helpers/__tests__/MediaPlaybackWatchdog.test.ts +140 -0
  49. package/src/helpers/__tests__/mediaInterruptions.test.ts +131 -0
  50. package/src/helpers/mediaInterruptions.ts +37 -0
  51. package/src/reporting/ClientEventReporter.ts +1 -1
  52. package/src/ringing/RingStatePoller.ts +1 -1
  53. package/src/ringing/__tests__/RingStatePoller.test.ts +1 -1
  54. package/src/ringing/__tests__/reconcileRingState.test.ts +33 -0
  55. package/src/ringing/reconcileRingState.ts +18 -10
  56. package/src/rtc/BasePeerConnection.ts +47 -9
  57. package/src/rtc/__tests__/Call.reconnect.test.ts +474 -0
  58. package/src/rtc/types.ts +8 -0
  59. package/src/stats/SfuStatsReporter.ts +19 -93
  60. package/src/stats/__tests__/SfuStatsReporter.test.ts +2 -18
  61. package/src/stats/rtc/StatsTracer.ts +6 -6
  62. package/src/stats/rtc/types.ts +0 -13
@@ -124,6 +124,9 @@ export declare class Call {
124
124
  private lastOfflineTimestamp;
125
125
  private networkAvailableTask;
126
126
  private readonly rejoinRateLimiter;
127
+ private rejoinLongWindowRateLimiter?;
128
+ private readonly joinTasks;
129
+ private noLocalCandidatesCheckScheduled;
127
130
  private maxIceFailuresWithoutConnect;
128
131
  private iceFailuresWithoutConnect;
129
132
  private maxConsecutiveNegotiationFailures;
@@ -321,6 +324,7 @@ export declare class Call {
321
324
  * @returns a promise which resolves once the call join-flow has finished.
322
325
  */
323
326
  private doJoin;
327
+ private performJoin;
324
328
  /**
325
329
  * Prepares Reconnect Details object.
326
330
  * @internal
@@ -354,6 +358,21 @@ export declare class Call {
354
358
  * @internal
355
359
  */
356
360
  private initPublisherAndSubscriber;
361
+ /**
362
+ * Escalates to REJOIN with `ICE_NEVER_CONNECTED` when the current peer
363
+ * connections never connected and one of them completed ICE gathering
364
+ * without a single local candidate, which means WebRTC networking is
365
+ * blocked on this device.
366
+ *
367
+ * `reconnect` ignores requests while a join or another reconnect is in
368
+ * flight, so the check is postponed until those settle and then re-run
369
+ * against the peer connections current at that time.
370
+ */
371
+ private checkNoLocalCandidates;
372
+ /**
373
+ * Resolves once no `doJoin` run and no reconnect is in flight.
374
+ */
375
+ private waitForJoinsAndReconnects;
357
376
  /**
358
377
  * Retrieves credentials for joining the call.
359
378
  *
@@ -996,7 +1015,8 @@ export declare class Call {
996
1015
  * `rejoin_attempt_limit_exceeded` leave message.
997
1016
  *
998
1017
  * Defaults: 10 attempts per 120 seconds (aligned with the Swift SDK).
999
- * Both arguments are clamped to a minimum of 1.
1018
+ * Both arguments are clamped to a minimum of 1. Unless this method is
1019
+ * called, an additional cap of 20 REJOIN attempts per 30 minutes applies.
1000
1020
  */
1001
1021
  setRejoinAttemptLimit: (maxAttempts: number, windowSeconds: number) => void;
1002
1022
  /**
@@ -138,7 +138,7 @@ export type CallEventListener<E extends keyof AllCallEvents> = (event: AllCallEv
138
138
  export type Logger = (logLevel: LogLevel, message: string, ...args: unknown[]) => void;
139
139
  export type RingStatePollingOptions = {
140
140
  /**
141
- * Quiet time after the ring starts, before the first poll. Defaults to 15_000.
141
+ * Quiet time after the ring starts, before the first poll. Defaults to 9_000.
142
142
  */
143
143
  startAfterMs?: number;
144
144
  /**
@@ -130,6 +130,12 @@ export declare abstract class DeviceManager<S extends DeviceManagerState<C>, C =
130
130
  protected findDevice(devices: MediaDeviceInfo[], deviceId: string): MediaDeviceInfo | undefined;
131
131
  private dispatchDeviceDisconnectedEvent;
132
132
  private persistPreference;
133
+ /**
134
+ * Returns the label of the active track if it was captured from the given
135
+ * device. Used as a fallback when the device list hasn't been enumerated yet,
136
+ * so the persisted preference still carries the device label.
137
+ */
138
+ private getActiveTrackLabel;
133
139
  protected applyPersistedPreferences(enabledInCallType: boolean, forceDisabled?: boolean): Promise<boolean>;
134
140
  private applyMutedState;
135
141
  }
@@ -9,6 +9,7 @@ export declare abstract class DeviceManagerState<C = MediaTrackConstraints> {
9
9
  protected rootMediaStreamSubject: BehaviorSubject<MediaStream | undefined>;
10
10
  protected selectedDeviceSubject: BehaviorSubject<string | undefined>;
11
11
  protected defaultConstraintsSubject: BehaviorSubject<C | undefined>;
12
+ protected errorSubject: BehaviorSubject<Error | undefined>;
12
13
  /**
13
14
  * @internal
14
15
  */
@@ -39,6 +40,13 @@ export declare abstract class DeviceManagerState<C = MediaTrackConstraints> {
39
40
  * The default constraints for the device.
40
41
  */
41
42
  defaultConstraints$: Observable<C | undefined>;
43
+ /**
44
+ * An Observable that emits the error of the last failed attempt to start
45
+ * the device (e.g. the device is in use by another app), or `undefined`.
46
+ * For screen share, this includes the user dismissing the picker.
47
+ * Cleared when the device is started again or disabled.
48
+ */
49
+ error$: Observable<Error | undefined>;
42
50
  /**
43
51
  * An observable that will emit `true` if browser/system permission
44
52
  * is granted (or at least hasn't been denied), `false` otherwise.
@@ -85,6 +93,15 @@ export declare abstract class DeviceManagerState<C = MediaTrackConstraints> {
85
93
  * same as `mediaStream`.
86
94
  */
87
95
  get rootMediaStream(): MediaStream | undefined;
96
+ /**
97
+ * The error of the last failed attempt to start the device, or `undefined`.
98
+ */
99
+ get error(): Error | undefined;
100
+ /**
101
+ * @internal
102
+ * @param error the error to set.
103
+ */
104
+ setError(error: Error | undefined): void;
88
105
  /**
89
106
  * @internal
90
107
  * @param status
@@ -74,6 +74,17 @@ export declare class MicrophoneManager extends AudioDeviceManager<MicrophoneMana
74
74
  apply(settings: AudioSettingsResponse, publish: boolean, forceDisabled?: boolean): Promise<void>;
75
75
  protected getDevices(): Observable<MediaDeviceInfo[]>;
76
76
  protected getStream(constraints: MediaTrackConstraints): Promise<MediaStream>;
77
+ /**
78
+ * On mute, hands a clone of the live mic track to the speaking while muted
79
+ * detection, so the device stays captured after the mic tracks are stopped.
80
+ * Releasing and re-acquiring the mic flips Bluetooth headsets between audio
81
+ * profiles.
82
+ */
83
+ protected muteStream(stopTracks?: boolean): Promise<void>;
84
+ /**
85
+ * Whether a mute can hand the mic over to the speaking while muted detection.
86
+ */
87
+ private canHandOffMic;
77
88
  protected doSetAudioBitrateProfile(profile: AudioBitrateProfile): void;
78
89
  private startSpeakingWhileMutedDetection;
79
90
  private stopSpeakingWhileMutedDetection;
@@ -3,6 +3,11 @@ import { Tracer } from '../stats';
3
3
  /**
4
4
  * Tells if the browser supports audio output change on 'audio' elements,
5
5
  * see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId.
6
+ *
7
+ * WebKit (Safari, iOS browsers, WKWebView) is reported as unsupported:
8
+ * setSinkId requires a user gesture, a per-device output can run at a
9
+ * different sample rate than the remote audio mixer (pitched/slowed audio),
10
+ * and audio goes silent when the selected device disconnects.
6
11
  */
7
12
  export declare const checkIfAudioOutputChangeSupported: () => boolean;
8
13
  /**
@@ -8,7 +8,6 @@ import { PerformanceStats } from '../models/models';
8
8
  import { RTMPIngress } from '../models/models';
9
9
  import { AppleState } from '../models/models';
10
10
  import { AndroidState } from '../models/models';
11
- import { InputDevices } from '../models/models';
12
11
  import { RemoteOutboundRtp } from '../models/models';
13
12
  import { RemoteInboundRtp } from '../models/models';
14
13
  import { OutboundRtp } from '../models/models';
@@ -129,14 +128,6 @@ export interface SendStatsRequest {
129
128
  * @generated from protobuf field: string session_id = 1;
130
129
  */
131
130
  sessionId: string;
132
- /**
133
- * @generated from protobuf field: string subscriber_stats = 2;
134
- */
135
- subscriberStats: string;
136
- /**
137
- * @generated from protobuf field: string publisher_stats = 3;
138
- */
139
- publisherStats: string;
140
131
  /**
141
132
  * @generated from protobuf field: string webrtc_version = 4;
142
133
  */
@@ -149,14 +140,6 @@ export interface SendStatsRequest {
149
140
  * @generated from protobuf field: string sdk_version = 6;
150
141
  */
151
142
  sdkVersion: string;
152
- /**
153
- * @generated from protobuf field: stream.video.sfu.models.InputDevices audio_devices = 7;
154
- */
155
- audioDevices?: InputDevices;
156
- /**
157
- * @generated from protobuf field: stream.video.sfu.models.InputDevices video_devices = 8;
158
- */
159
- videoDevices?: InputDevices;
160
143
  /**
161
144
  * @generated from protobuf oneof: device_state
162
145
  */
@@ -183,16 +166,6 @@ export interface SendStatsRequest {
183
166
  * @generated from protobuf field: stream.video.sfu.models.RTMPIngress rtmp = 12;
184
167
  */
185
168
  rtmp?: RTMPIngress;
186
- /**
187
- * @deprecated
188
- * @generated from protobuf field: string subscriber_rtc_stats = 13 [deprecated = true];
189
- */
190
- subscriberRtcStats: string;
191
- /**
192
- * @deprecated
193
- * @generated from protobuf field: string publisher_rtc_stats = 14 [deprecated = true];
194
- */
195
- publisherRtcStats: string;
196
169
  /**
197
170
  * @generated from protobuf field: string rtc_stats = 15;
198
171
  */
@@ -4,9 +4,12 @@ import { Tracer } from '../stats';
4
4
  /**
5
5
  * Tracks audio element bindings and periodically warns about
6
6
  * remote participants whose audio streams have no bound element.
7
+ * On the same tick, it traces the playback state of the bound elements,
8
+ * limited to the bindings whose state changed since the previous trace.
7
9
  */
8
10
  export declare class AudioBindingsWatchdog {
9
11
  private bindings;
12
+ private tracedStates;
10
13
  private enabled;
11
14
  private watchdogInterval?;
12
15
  private readonly unsubscribeCallingState;
@@ -24,8 +27,8 @@ export declare class AudioBindingsWatchdog {
24
27
  */
25
28
  unregister: (sessionId: string, trackType: AudioTrackType) => void;
26
29
  /**
27
- * Enables or disables the watchdog.
28
- * When disabled, the periodic check stops but bindings are still tracked.
30
+ * Enables or disables the dangling binding warnings.
31
+ * Bindings are still tracked and their playback state is still traced.
29
32
  */
30
33
  setEnabled: (enabled: boolean) => void;
31
34
  /**
@@ -33,5 +36,7 @@ export declare class AudioBindingsWatchdog {
33
36
  */
34
37
  dispose: () => void;
35
38
  private start;
39
+ private warnAboutDanglingBindings;
40
+ private traceBindingStates;
36
41
  private stop;
37
42
  }
@@ -17,6 +17,9 @@ export declare class DynascaleManager {
17
17
  private readonly tracer;
18
18
  private useWebAudio;
19
19
  private audioContext;
20
+ private playbackWatchdogs;
21
+ private stopWatchingInterruptions;
22
+ private stopTracingAudioSession;
20
23
  private trackSubscriptionManager;
21
24
  private blockedAudioTracker;
22
25
  /**
@@ -64,6 +67,18 @@ export declare class DynascaleManager {
64
67
  * @returns a cleanup function that will unbind the audio element.
65
68
  */
66
69
  bindAudioElement: (audioElement: HTMLAudioElement, sessionId: string, trackType: AudioTrackType) => (() => void) | undefined;
70
+ /**
71
+ * Creates a playback watchdog that is re-armed whenever a media
72
+ * interruption (backgrounding, phone call, Siri) ends.
73
+ */
74
+ private createPlaybackWatchdog;
75
+ private disposePlaybackWatchdog;
76
+ private rearmPlayback;
77
+ /**
78
+ * Traces the WebKit audio session state (initial value and changes) in
79
+ * both playback modes, as it explains most iOS playback interruptions.
80
+ */
81
+ private traceAudioSessionState;
67
82
  private getOrCreateAudioContext;
68
83
  private resumeAudioContext;
69
84
  }
@@ -4,7 +4,10 @@ export type MediaPlaybackWatchdogOptions = {
4
4
  element: HTMLMediaElement;
5
5
  kind: MediaKind;
6
6
  tracer: Tracer;
7
+ sessionId?: string;
8
+ trackType?: string;
7
9
  isBlocked?: () => boolean;
10
+ onGiveUp?: () => void;
8
11
  };
9
12
  /**
10
13
  * Watches a single audio or video element and attempts to recover playback
@@ -13,7 +16,9 @@ export type MediaPlaybackWatchdogOptions = {
13
16
  export declare class MediaPlaybackWatchdog {
14
17
  private logger;
15
18
  private readonly kind;
19
+ private readonly traceInfo;
16
20
  private readonly isBlocked;
21
+ private readonly onGiveUp;
17
22
  private element;
18
23
  private tracer;
19
24
  private controller;
@@ -25,9 +30,22 @@ export declare class MediaPlaybackWatchdog {
25
30
  constructor(opts: MediaPlaybackWatchdogOptions);
26
31
  private attach;
27
32
  dispose: () => void;
33
+ /**
34
+ * Starts a fresh recovery cycle, e.g. after a media interruption ended.
35
+ * Unlike a regular pause-triggered recovery, it also tries elements that
36
+ * were skipped as blocked or not ready, since those conditions may have
37
+ * cleared together with the interruption.
38
+ */
39
+ rearm: () => void;
28
40
  private clearTimers;
29
41
  private onPlaying;
30
42
  private onSettled;
43
+ /**
44
+ * Media elements backed by a MediaStream fire `suspend` routinely (after
45
+ * the initial load, on source swaps, on layer switches), so a `suspend`
46
+ * that needs no recovery is ignored without a trace. An explicit `pause`
47
+ * is rare and always traced, along with the reason recovery was skipped.
48
+ */
31
49
  private onPauseOrSuspend;
32
50
  private scheduleRecovery;
33
51
  private computeSkipReason;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Checks whether media playback and capture are likely interrupted: the page
3
+ * is hidden, or WebKit reports the audio session as interrupted (a phone
4
+ * call, Siri, or another app taking over audio).
5
+ */
6
+ export declare const isMediaInterrupted: () => boolean;
7
+ /**
8
+ * Invokes `onResumed` every time the page leaves a media interruption,
9
+ * as defined by {@link isMediaInterrupted}.
10
+ *
11
+ * @param onResumed the callback to invoke.
12
+ * @returns a function that stops watching.
13
+ */
14
+ export declare const watchMediaInterruptions: (onResumed: () => void) => (() => void);
@@ -9,7 +9,7 @@ export type JoinReason = 'first-attempt' | 'network-available' | 'migration' | '
9
9
  /**
10
10
  * What triggered an automatic join, reported as `source` on the call's
11
11
  * CoordinatorJoin events. `ring-ws` is a ring WebSocket event, `ring-poll-api`
12
- * is the ring state poller.
12
+ * is the ring state poller, or the call refresh after a WS reconnect.
13
13
  */
14
14
  export type JoinSource = 'ring-ws' | 'ring-poll-api';
15
15
  export type ClientEventStandardCode = 'CLIENT_ABORTED' | 'BACKEND_LEAVE' | 'REQUEST_TIMEOUT' | 'NETWORK_ERROR' | 'SFU_ERROR' | 'SFU_GO_AWAY' | 'ICE_CONNECTIVITY_FAILED' | 'DTLS_CONNECTIVITY_FAILED';
@@ -3,15 +3,16 @@ import type { JoinSource } from '../reporting';
3
3
  /**
4
4
  * Decides what a ringing call should do next, based on the current call state.
5
5
  *
6
- * The `call.accepted`, `call.rejected` and `call.missed` handlers and the ring
7
- * state poller both run this. They differ only in how the state got there: the
8
- * handlers rely on `CallState.updateFromEvent`, which runs before them, and the
9
- * poller applies the polled ring state itself.
6
+ * The `call.accepted` and `call.rejected` handlers, the ring state poller and
7
+ * the rewatch after a WS reconnect all run this. They differ only in how the
8
+ * state got there: the handlers rely on `CallState.updateFromEvent`, which runs
9
+ * before them, while the poller applies the polled ring state itself and the
10
+ * rewatch applies the queried call state.
10
11
  *
11
12
  * @param call the call to reconcile.
12
- * @param joinSource which of the two triggered this run, reported on the
13
- * caller's join: `ring-ws` for the event handlers, `ring-poll-api` for the
14
- * poller.
13
+ * @param joinSource what triggered this run, reported on the caller's join:
14
+ * `ring-ws` for the event handlers, `ring-poll-api` for the poller and the
15
+ * rewatch (both come from an API response).
15
16
  * @returns whether the ring reached a terminal state. A failed join is not
16
17
  * terminal: the caller should keep trying while the ring is open.
17
18
  */
@@ -24,10 +24,12 @@ export declare abstract class BasePeerConnection {
24
24
  protected sfuClient: StreamSfuClient;
25
25
  protected onReconnectionNeeded?: OnReconnectionNeeded;
26
26
  private onIceConnected?;
27
+ private onNoLocalCandidates?;
27
28
  private onPeerConnectionStateChange?;
28
29
  protected onRemoteTrackUnmute?: OnRemoteTrackUnmute;
29
30
  private readonly iceRestartDelay;
30
31
  private iceHasEverConnected;
32
+ private localCandidatesGathered;
31
33
  private iceRestartTimeout?;
32
34
  private preConnectStuckTimeout?;
33
35
  protected isIceRestarting: boolean;
@@ -41,7 +43,7 @@ export declare abstract class BasePeerConnection {
41
43
  /**
42
44
  * Constructs a new `BasePeerConnection` instance.
43
45
  */
44
- protected constructor(peerType: PeerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, onIceConnected, onPeerConnectionStateChange, onRemoteTrackUnmute, tag, enableTracing, clientPublishOptions, e2ee, iceRestartDelay, statsTimestampDriftThresholdMs, }: BasePeerConnectionOpts);
46
+ protected constructor(peerType: PeerType, { sfuClient, connectionConfig, state, dispatcher, onReconnectionNeeded, onIceConnected, onNoLocalCandidates, onPeerConnectionStateChange, onRemoteTrackUnmute, tag, enableTracing, clientPublishOptions, e2ee, iceRestartDelay, statsTimestampDriftThresholdMs, }: BasePeerConnectionOpts);
45
47
  private createPeerConnection;
46
48
  /**
47
49
  * Disposes the `RTCPeerConnection` instance.
@@ -90,6 +92,16 @@ export declare abstract class BasePeerConnection {
90
92
  * Maps the given track ID to the corresponding track type.
91
93
  */
92
94
  getTrackType: (trackId: string) => TrackType | undefined;
95
+ /**
96
+ * Returns `true` once ICE has reached `connected`/`completed` at least
97
+ * once during the lifetime of this peer connection.
98
+ */
99
+ hasIceEverConnected: () => boolean;
100
+ /**
101
+ * Returns `true` when the latest ICE gathering round completed without
102
+ * producing a single local candidate.
103
+ */
104
+ hasNoLocalCandidates: () => boolean;
93
105
  /**
94
106
  * Checks if the `RTCPeerConnection` is healthy.
95
107
  * It checks the ICE connection state and the peer connection state.
@@ -40,6 +40,12 @@ export type OnReconnectionNeeded = (kind: WebsocketReconnectStrategy, reason: Re
40
40
  * recovered, not merely when the SFU join handshake succeeded.
41
41
  */
42
42
  export type OnIceConnected = (peerType: PeerType) => void;
43
+ /**
44
+ * Fires when an ICE gathering round completes without producing a single
45
+ * local candidate on a peer connection that never connected. Used by `Call`
46
+ * to detect devices whose WebRTC networking is blocked.
47
+ */
48
+ export type OnNoLocalCandidates = (peerType: PeerType) => void;
43
49
  /**
44
50
  * Snapshot of the peer connection's ICE and DTLS state surfaced to telemetry
45
51
  * consumers (e.g. `ClientEventReporter`). Fired on every transition of
@@ -75,6 +81,7 @@ export type BasePeerConnectionOpts = {
75
81
  dispatcher: Dispatcher;
76
82
  onReconnectionNeeded?: OnReconnectionNeeded;
77
83
  onIceConnected?: OnIceConnected;
84
+ onNoLocalCandidates?: OnNoLocalCandidates;
78
85
  onPeerConnectionStateChange?: OnPeerConnectionStateChange;
79
86
  onRemoteTrackUnmute?: OnRemoteTrackUnmute;
80
87
  tag: string;
@@ -3,16 +3,11 @@ import { StatsOptions } from '../gen/coordinator';
3
3
  import { Publisher, Subscriber } from '../rtc';
4
4
  import { Tracer } from './rtc';
5
5
  import { ClientDetails, WebsocketReconnectStrategy } from '../gen/video/sfu/models/models';
6
- import { CameraManager, MicrophoneManager } from '../devices';
7
- import { CallState } from '../store';
8
6
  export type SfuStatsReporterOptions = {
9
7
  options: StatsOptions;
10
8
  clientDetails: ClientDetails;
11
9
  subscriber: Subscriber;
12
10
  publisher?: Publisher;
13
- microphone: MicrophoneManager;
14
- camera: CameraManager;
15
- state: CallState;
16
11
  tracer: Tracer;
17
12
  unifiedSessionId: string;
18
13
  };
@@ -22,24 +17,17 @@ export declare class SfuStatsReporter {
22
17
  private readonly sfuClient;
23
18
  private readonly subscriber;
24
19
  private readonly publisher?;
25
- private readonly microphone;
26
- private readonly camera;
27
- private readonly state;
28
20
  private readonly tracer;
29
21
  private readonly unifiedSessionId;
30
22
  private intervalId;
31
23
  private timeoutId;
32
24
  private reportCount;
33
- private unsubscribeDevicePermissionsSubscription?;
34
- private unsubscribeListDevicesSubscription?;
35
25
  private readonly sdkName;
36
26
  private readonly sdkVersion;
37
27
  private readonly webRTCVersion;
38
- private readonly inputDevices;
39
28
  private readonly statsConcurrencyTag;
40
29
  private isStopped;
41
- constructor(sfuClient: StreamSfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, tracer, unifiedSessionId, }: SfuStatsReporterOptions);
42
- private observeDevice;
30
+ constructor(sfuClient: StreamSfuClient, { options, clientDetails, subscriber, publisher, tracer, unifiedSessionId, }: SfuStatsReporterOptions);
43
31
  sendConnectionTime: (connectionTimeSeconds: number) => void;
44
32
  sendReconnectionTime: (strategy: WebsocketReconnectStrategy, timeSeconds: number) => void;
45
33
  private sendTelemetryData;
@@ -1,5 +1,5 @@
1
- import { PeerType, TrackType } from '../../gen/video/sfu/models/models';
2
- import type { ComputedStats, PendingDelta } from './types';
1
+ import { PeerType, PerformanceStats, TrackType } from '../../gen/video/sfu/models/models';
2
+ import type { PendingDelta } from './types';
3
3
  /**
4
4
  * StatsTracer is a class that collects and processes WebRTC stats.
5
5
  * It is used to track the performance of the WebRTC connection
@@ -32,13 +32,13 @@ export declare class StatsTracer {
32
32
  */
33
33
  constructor(pc: RTCPeerConnection, peerType: PeerType, trackIdToTrackType: Map<string, TrackType>, statsTimestampDriftThresholdMs?: number, maxPendingDeltas?: number);
34
34
  /**
35
- * Samples the RTCPeerConnection: returns the current stats report and the
36
- * derived performance stats, and appends the delta-compressed sample to the
37
- * un-acked delivery chain (retrieved via `getPendingDeltas`).
35
+ * Samples the RTCPeerConnection: returns the derived performance stats and
36
+ * appends the delta-compressed sample to the un-acked delivery chain
37
+ * (retrieved via `getPendingDeltas`).
38
38
  *
39
39
  * @internal
40
40
  */
41
- takeSample: () => Promise<ComputedStats>;
41
+ takeSample: () => Promise<PerformanceStats[]>;
42
42
  /**
43
43
  * Returns a stable copy of the un-acked delta chain to transmit, oldest first.
44
44
  *
@@ -1,4 +1,3 @@
1
- import type { PerformanceStats } from '../../gen/video/sfu/models/models';
2
1
  export type RTCStatsDataType = RTCConfiguration | RTCIceCandidate | RTCSignalingState | RTCIceConnectionState | RTCIceGatheringState | RTCPeerConnectionState | [number | null | string] | string | boolean | RTCOfferOptions | [string | RTCDataChannelInit | undefined] | (RTCOfferOptions | undefined) | RTCSessionDescriptionInit | (RTCIceCandidateInit | RTCIceCandidate) | object | number | null | undefined;
3
2
  export type TraceKey = 'device-enumeration' | (string & {});
4
3
  export type Trace = (tag: string, data: RTCStatsDataType) => void;
@@ -12,16 +11,6 @@ export type TraceSlice = {
12
11
  snapshot: TraceRecord[];
13
12
  rollback: () => void;
14
13
  };
15
- export type ComputedStats = {
16
- /**
17
- * Current stats from the RTCPeerConnection.
18
- */
19
- stats: RTCStatsReport;
20
- /**
21
- * The current iteration of the stats.
22
- */
23
- performanceStats: PerformanceStats[];
24
- };
25
14
  /**
26
15
  * A single, not-yet-delivered delta-compressed stats sample.
27
16
  * The `delta` is computed against the immediately preceding sample, so a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "1.61.1",
3
+ "version": "1.62.0",
4
4
  "main": "dist/index.cjs.js",
5
5
  "module": "dist/index.es.js",
6
6
  "browser": "dist/index.browser.es.js",