@stream-io/video-client 1.54.0 → 1.55.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.
- package/CHANGELOG.md +14 -0
- package/dist/index.browser.es.js +9641 -8767
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +9638 -8764
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +9639 -8765
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +13 -1
- package/dist/src/StreamSfuClient.d.ts +11 -3
- package/dist/src/coordinator/connection/connection.d.ts +1 -1
- package/dist/src/gen/google/protobuf/struct.d.ts +3 -1
- package/dist/src/gen/google/protobuf/timestamp.d.ts +3 -1
- package/dist/src/gen/video/sfu/event/events.d.ts +22 -1
- package/dist/src/gen/video/sfu/models/models.d.ts +4 -0
- package/dist/src/gen/video/sfu/signal_rpc/signal.client.d.ts +23 -2
- package/dist/src/reporting/ClientEventReporter.d.ts +1 -0
- package/dist/src/rtc/BasePeerConnection.d.ts +2 -12
- package/dist/src/rtc/IceTrickleBuffer.d.ts +41 -3
- package/dist/src/rtc/Publisher.d.ts +5 -2
- package/dist/src/rtc/Subscriber.d.ts +8 -0
- package/dist/src/rtc/helpers/iceCandiates.d.ts +12 -0
- package/dist/src/rtc/types.d.ts +2 -0
- package/dist/src/stats/SfuStatsReporter.d.ts +32 -1
- package/dist/src/stats/rtc/StatsTracer.d.ts +38 -8
- package/dist/src/stats/rtc/Tracer.d.ts +9 -2
- package/dist/src/stats/rtc/types.d.ts +10 -4
- package/package.json +5 -3
- package/src/Call.ts +83 -35
- package/src/StreamSfuClient.ts +36 -21
- package/src/__tests__/StreamSfuClient.test.ts +159 -1
- package/src/__tests__/StreamVideoClient.api.test.ts +123 -97
- package/src/coordinator/connection/__tests__/connection.test.ts +22 -0
- package/src/coordinator/connection/connection.ts +8 -5
- package/src/gen/google/protobuf/struct.ts +7 -12
- package/src/gen/google/protobuf/timestamp.ts +6 -7
- package/src/gen/video/sfu/event/events.ts +22 -25
- package/src/gen/video/sfu/models/models.ts +10 -1
- package/src/gen/video/sfu/signal_rpc/signal.client.ts +24 -29
- package/src/helpers/MediaPlaybackWatchdog.ts +1 -0
- package/src/helpers/__tests__/browsers.test.ts +12 -12
- package/src/helpers/browsers.ts +5 -5
- package/src/reporting/ClientEventReporter.ts +17 -12
- package/src/reporting/__tests__/ClientEventReporter.test.ts +52 -0
- package/src/rtc/BasePeerConnection.ts +15 -34
- package/src/rtc/IceTrickleBuffer.ts +105 -12
- package/src/rtc/Publisher.ts +23 -19
- package/src/rtc/Subscriber.ts +97 -36
- package/src/rtc/__tests__/Call.reconnect.test.ts +45 -45
- package/src/rtc/__tests__/IceTrickleBuffer.test.ts +127 -0
- package/src/rtc/__tests__/Publisher.test.ts +2 -31
- package/src/rtc/__tests__/Subscriber.test.ts +271 -20
- package/src/rtc/helpers/__tests__/iceCandiates.test.ts +88 -0
- package/src/rtc/helpers/degradationPreference.ts +1 -0
- package/src/rtc/helpers/iceCandiates.ts +35 -0
- package/src/rtc/helpers/sdp.ts +3 -2
- package/src/rtc/helpers/tracks.ts +2 -0
- package/src/rtc/types.ts +2 -0
- package/src/stats/SfuStatsReporter.ts +149 -49
- package/src/stats/__tests__/SfuStatsReporter.test.ts +235 -0
- package/src/stats/rtc/StatsTracer.ts +90 -32
- package/src/stats/rtc/Tracer.ts +23 -2
- package/src/stats/rtc/__tests__/StatsTracer.test.ts +213 -6
- package/src/stats/rtc/__tests__/Tracer.test.ts +34 -0
- package/src/stats/rtc/types.ts +11 -4
package/dist/src/Call.d.ts
CHANGED
|
@@ -129,6 +129,7 @@ export declare class Call {
|
|
|
129
129
|
private joinResponseTimeout?;
|
|
130
130
|
private rpcRequestTimeout?;
|
|
131
131
|
private joinCallData?;
|
|
132
|
+
private allowOwnTracksLoopback;
|
|
132
133
|
private hasJoinedOnce;
|
|
133
134
|
private deviceSettingsAppliedOnce;
|
|
134
135
|
private credentials?;
|
|
@@ -192,6 +193,16 @@ export declare class Call {
|
|
|
192
193
|
* Retrieves the current user ID.
|
|
193
194
|
*/
|
|
194
195
|
get currentUserId(): string | undefined;
|
|
196
|
+
/**
|
|
197
|
+
* A flag indicating whether self-subscription is enabled for the call.
|
|
198
|
+
*/
|
|
199
|
+
get isOwnTracksLoopbackAllowed(): boolean;
|
|
200
|
+
/**
|
|
201
|
+
* The largest video publish dimension across the current publish options.
|
|
202
|
+
*
|
|
203
|
+
* @internal
|
|
204
|
+
*/
|
|
205
|
+
getMaxVideoPublishDimension: () => VideoDimension | undefined;
|
|
195
206
|
/**
|
|
196
207
|
* A flag indicating whether the call was created by the current user.
|
|
197
208
|
*/
|
|
@@ -264,10 +275,11 @@ export declare class Call {
|
|
|
264
275
|
*
|
|
265
276
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
266
277
|
*/
|
|
267
|
-
join: ({ maxJoinRetries, joinResponseTimeout, rpcRequestTimeout, ...data }?: JoinCallData & {
|
|
278
|
+
join: ({ maxJoinRetries, joinResponseTimeout, rpcRequestTimeout, allowOwnTracksLoopback, ...data }?: JoinCallData & {
|
|
268
279
|
maxJoinRetries?: number;
|
|
269
280
|
joinResponseTimeout?: number;
|
|
270
281
|
rpcRequestTimeout?: number;
|
|
282
|
+
allowOwnTracksLoopback?: boolean;
|
|
271
283
|
}) => Promise<void>;
|
|
272
284
|
/**
|
|
273
285
|
* Will make a single attempt to watch for call related WebSocket events
|
|
@@ -87,12 +87,20 @@ export declare class StreamSfuClient {
|
|
|
87
87
|
* trigger a reconnection attempt.
|
|
88
88
|
*/
|
|
89
89
|
isClosingClean: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* One-shot latch guarding `onSignalClose`. The signal connection can be
|
|
92
|
+
* detected as dead by more than one source (the health watchdog and the
|
|
93
|
+
* WebSocket `close` event, which on a wedged socket can arrive seconds
|
|
94
|
+
* apart). This ensures revival is triggered at most once per client.
|
|
95
|
+
*/
|
|
96
|
+
private signalClosed;
|
|
90
97
|
private readonly rpc;
|
|
91
98
|
private keepAliveInterval?;
|
|
92
|
-
private
|
|
99
|
+
private connectionCheckInterval?;
|
|
93
100
|
private migrateAwayTimeout?;
|
|
94
101
|
private readonly pingIntervalInMs;
|
|
95
102
|
private readonly unhealthyTimeoutInMs;
|
|
103
|
+
private readonly connectionCheckIntervalInMs;
|
|
96
104
|
private lastMessageTimestamp?;
|
|
97
105
|
private readonly tracer?;
|
|
98
106
|
private readonly unsubscribeIceTrickle;
|
|
@@ -127,7 +135,7 @@ export declare class StreamSfuClient {
|
|
|
127
135
|
/**
|
|
128
136
|
* The error code used when the SFU connection is unhealthy.
|
|
129
137
|
* Usually, this means that no message has been received from the SFU for
|
|
130
|
-
* a certain amount of time (`
|
|
138
|
+
* a certain amount of time (`unhealthyTimeoutInMs`).
|
|
131
139
|
*/
|
|
132
140
|
static ERROR_CONNECTION_UNHEALTHY: number;
|
|
133
141
|
/**
|
|
@@ -154,7 +162,7 @@ export declare class StreamSfuClient {
|
|
|
154
162
|
private createWebSocket;
|
|
155
163
|
get isHealthy(): boolean;
|
|
156
164
|
get joinTask(): Promise<JoinResponse>;
|
|
157
|
-
private
|
|
165
|
+
private notifySignalClose;
|
|
158
166
|
close: (code?: number, reason?: string) => void;
|
|
159
167
|
private dispose;
|
|
160
168
|
getTrace: () => TraceSlice | undefined;
|
|
@@ -44,7 +44,7 @@ export declare class StableWSConnection {
|
|
|
44
44
|
pingInterval: number;
|
|
45
45
|
healthCheckTimeoutRef?: number;
|
|
46
46
|
connectionCheckTimeout: number;
|
|
47
|
-
connectionCheckTimeoutRef?:
|
|
47
|
+
connectionCheckTimeoutRef?: number;
|
|
48
48
|
/** Store the last event time for health checks */
|
|
49
49
|
lastEvent: Date | null;
|
|
50
50
|
constructor(client: StreamClient);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { JsonValue } from '@protobuf-ts/runtime';
|
|
2
|
+
import type { JsonReadOptions } from '@protobuf-ts/runtime';
|
|
3
|
+
import type { JsonWriteOptions } from '@protobuf-ts/runtime';
|
|
2
4
|
import { MessageType } from '@protobuf-ts/runtime';
|
|
3
5
|
/**
|
|
4
6
|
* `Struct` represents a structured data value, consisting of fields
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { JsonValue } from '@protobuf-ts/runtime';
|
|
2
|
+
import type { JsonReadOptions } from '@protobuf-ts/runtime';
|
|
3
|
+
import type { JsonWriteOptions } from '@protobuf-ts/runtime';
|
|
2
4
|
import { MessageType } from '@protobuf-ts/runtime';
|
|
3
5
|
/**
|
|
4
6
|
* A Timestamp represents a point in time independent of any time zone or local
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import { MessageType } from '@protobuf-ts/runtime';
|
|
2
|
-
import { CallEndedReason
|
|
2
|
+
import { CallEndedReason } from '../models/models';
|
|
3
|
+
import { GoAwayReason } from '../models/models';
|
|
4
|
+
import { CallGrants } from '../models/models';
|
|
5
|
+
import { DegradationPreference } from '../models/models';
|
|
6
|
+
import { Codec } from '../models/models';
|
|
7
|
+
import { ConnectionQuality } from '../models/models';
|
|
8
|
+
import { CallState } from '../models/models';
|
|
3
9
|
import { TrackSubscriptionDetails } from '../signal_rpc/signal';
|
|
10
|
+
import { TrackInfo } from '../models/models';
|
|
11
|
+
import { ParticipantSource } from '../models/models';
|
|
12
|
+
import { ClientCapability } from '../models/models';
|
|
13
|
+
import { SubscribeOption } from '../models/models';
|
|
14
|
+
import { ClientDetails } from '../models/models';
|
|
15
|
+
import { TrackUnpublishReason } from '../models/models';
|
|
16
|
+
import { Participant } from '../models/models';
|
|
17
|
+
import { TrackType } from '../models/models';
|
|
18
|
+
import { ParticipantCount } from '../models/models';
|
|
19
|
+
import { PeerType } from '../models/models';
|
|
20
|
+
import { WebsocketReconnectStrategy } from '../models/models';
|
|
21
|
+
import { Error as Error$ } from '../models/models';
|
|
22
|
+
import { Pin } from '../models/models';
|
|
23
|
+
import { PublishOption } from '../models/models';
|
|
24
|
+
import { ICETrickle as ICETrickle$ } from '../models/models';
|
|
4
25
|
/**
|
|
5
26
|
* SFUEvent is a message that is sent from the SFU to the client.
|
|
6
27
|
*
|
|
@@ -411,6 +411,10 @@ export interface TrackInfo {
|
|
|
411
411
|
* @generated from protobuf field: int32 publish_option_id = 12;
|
|
412
412
|
*/
|
|
413
413
|
publishOptionId: number;
|
|
414
|
+
/**
|
|
415
|
+
* @generated from protobuf field: bool self_sub_audio_video = 13;
|
|
416
|
+
*/
|
|
417
|
+
selfSubAudioVideo: boolean;
|
|
414
418
|
}
|
|
415
419
|
/**
|
|
416
420
|
* @generated from protobuf message stream.video.sfu.models.Error
|
|
@@ -1,6 +1,27 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { RpcTransport } from '@protobuf-ts/runtime-rpc';
|
|
2
|
+
import type { ServiceInfo } from '@protobuf-ts/runtime-rpc';
|
|
3
|
+
import type { StopNoiseCancellationResponse } from './signal';
|
|
4
|
+
import type { StopNoiseCancellationRequest } from './signal';
|
|
5
|
+
import type { StartNoiseCancellationResponse } from './signal';
|
|
6
|
+
import type { StartNoiseCancellationRequest } from './signal';
|
|
7
|
+
import type { SendMetricsResponse } from './signal';
|
|
8
|
+
import type { SendMetricsRequest } from './signal';
|
|
9
|
+
import type { SendStatsResponse } from './signal';
|
|
10
|
+
import type { SendStatsRequest } from './signal';
|
|
11
|
+
import type { ICERestartResponse } from './signal';
|
|
12
|
+
import type { ICERestartRequest } from './signal';
|
|
13
|
+
import type { UpdateMuteStatesResponse } from './signal';
|
|
14
|
+
import type { UpdateMuteStatesRequest } from './signal';
|
|
15
|
+
import type { UpdateSubscriptionsResponse } from './signal';
|
|
16
|
+
import type { UpdateSubscriptionsRequest } from './signal';
|
|
17
|
+
import type { ICETrickleResponse } from './signal';
|
|
3
18
|
import type { ICETrickle } from '../models/models';
|
|
19
|
+
import type { SendAnswerResponse } from './signal';
|
|
20
|
+
import type { SendAnswerRequest } from './signal';
|
|
21
|
+
import type { SetPublisherResponse } from './signal';
|
|
22
|
+
import type { SetPublisherRequest } from './signal';
|
|
23
|
+
import type { UnaryCall } from '@protobuf-ts/runtime-rpc';
|
|
24
|
+
import type { RpcOptions } from '@protobuf-ts/runtime-rpc';
|
|
4
25
|
/**
|
|
5
26
|
* @generated from protobuf service stream.video.sfu.signal.SignalServer
|
|
6
27
|
*/
|
|
@@ -5,7 +5,7 @@ import { PeerType, TrackType } from '../gen/video/sfu/models/models';
|
|
|
5
5
|
import { StreamSfuClient } from '../StreamSfuClient';
|
|
6
6
|
import { AllSfuEvents, Dispatcher } from './Dispatcher';
|
|
7
7
|
import { StatsTracer, Tracer } from '../stats';
|
|
8
|
-
import { BasePeerConnectionOpts, OnRemoteTrackUnmute } from './types';
|
|
8
|
+
import { BasePeerConnectionOpts, OnReconnectionNeeded, OnRemoteTrackUnmute } from './types';
|
|
9
9
|
import type { ClientPublishOptions } from '../types';
|
|
10
10
|
/**
|
|
11
11
|
* A base class for the `Publisher` and `Subscriber` classes.
|
|
@@ -20,7 +20,7 @@ export declare abstract class BasePeerConnection {
|
|
|
20
20
|
protected readonly clientPublishOptions?: ClientPublishOptions;
|
|
21
21
|
protected tag: string;
|
|
22
22
|
protected sfuClient: StreamSfuClient;
|
|
23
|
-
|
|
23
|
+
protected onReconnectionNeeded?: OnReconnectionNeeded;
|
|
24
24
|
private onIceConnected?;
|
|
25
25
|
private onPeerConnectionStateChange?;
|
|
26
26
|
protected onRemoteTrackUnmute?: OnRemoteTrackUnmute;
|
|
@@ -95,21 +95,11 @@ export declare abstract class BasePeerConnection {
|
|
|
95
95
|
* it returns `false`, otherwise it returns `true`.
|
|
96
96
|
*/
|
|
97
97
|
isHealthy: () => boolean;
|
|
98
|
-
/**
|
|
99
|
-
* Returns true only when the peer connection is currently fully established
|
|
100
|
-
* (ICE `connected`/`completed` AND connection state `connected`).
|
|
101
|
-
* Transient states like `disconnected`, `checking`, or `new` return false.
|
|
102
|
-
*/
|
|
103
|
-
isStable: () => boolean;
|
|
104
98
|
/**
|
|
105
99
|
* Handles the ICECandidate event and
|
|
106
100
|
* Initiates an ICE Trickle process with the SFU.
|
|
107
101
|
*/
|
|
108
102
|
private onIceCandidate;
|
|
109
|
-
/**
|
|
110
|
-
* Converts the ICE candidate to a JSON string.
|
|
111
|
-
*/
|
|
112
|
-
private asJSON;
|
|
113
103
|
/**
|
|
114
104
|
* Handles the ConnectionStateChange event.
|
|
115
105
|
*/
|
|
@@ -1,12 +1,50 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
2
|
import { ICETrickle } from '../gen/video/sfu/event/events';
|
|
3
|
+
import { PeerType } from '../gen/video/sfu/models/models';
|
|
3
4
|
/**
|
|
4
5
|
* A buffer for ICE Candidates. Used for ICE Trickle:
|
|
5
6
|
* - https://bloggeek.me/webrtcglossary/trickle-ice/
|
|
7
|
+
*
|
|
8
|
+
* The buffer is generation-aware: each peer connection tells it which ICE
|
|
9
|
+
* generation is current via `updateActiveGeneration` (whenever it applies an
|
|
10
|
+
* offer/answer). Candidate streams then emit only candidates of the active
|
|
11
|
+
* generation, hold candidates of a not-yet-applied (future) generation until
|
|
12
|
+
* it becomes active, and drop candidates of a superseded generation so they
|
|
13
|
+
* are never replayed. Candidates with no detectable generation, or before any
|
|
14
|
+
* generation is set, are emitted as-is (fail open).
|
|
6
15
|
*/
|
|
7
16
|
export declare class IceTrickleBuffer {
|
|
8
|
-
readonly
|
|
9
|
-
readonly
|
|
17
|
+
readonly subscriber: CandidateGenerationBuffer;
|
|
18
|
+
readonly publisher: CandidateGenerationBuffer;
|
|
10
19
|
push: (iceTrickle: ICETrickle) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Declares the ICE generation that is now current for the given peer type,
|
|
22
|
+
* derived from the `ice-ufrag` of the just-applied remote description.
|
|
23
|
+
* Candidates of superseded generations are evicted; candidates of the active
|
|
24
|
+
* generation flow to subscribers.
|
|
25
|
+
*/
|
|
26
|
+
updateActiveGeneration: (peerType: PeerType, sdp: string | undefined) => void;
|
|
11
27
|
dispose: () => void;
|
|
12
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Per-peer-connection generation-aware candidate store. Retains trickled
|
|
31
|
+
* candidates and replays the active generation to each new subscriber, then
|
|
32
|
+
* forwards matching live candidates.
|
|
33
|
+
*/
|
|
34
|
+
declare class CandidateGenerationBuffer {
|
|
35
|
+
private readonly store;
|
|
36
|
+
private readonly live;
|
|
37
|
+
private readonly seenUfrags;
|
|
38
|
+
private activeUfrag;
|
|
39
|
+
readonly candidates: Observable<RTCIceCandidateInit>;
|
|
40
|
+
push: (candidate: RTCIceCandidateInit) => void;
|
|
41
|
+
updateActiveGeneration: (ufrag: string | undefined) => void;
|
|
42
|
+
dispose: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* A candidate belongs to the current generation when its ufrag matches the
|
|
45
|
+
* active one. Fail open when either the candidate's generation or the active
|
|
46
|
+
* generation is unknown, so untagged candidates are never withheld.
|
|
47
|
+
*/
|
|
48
|
+
private isCurrent;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { PublishOption, TrackInfo, TrackType } from '../gen/video/sfu/models/models';
|
|
1
2
|
import { BasePeerConnection } from './BasePeerConnection';
|
|
2
3
|
import type { BasePeerConnectionOpts, TrackPublishOptions } from './types';
|
|
3
|
-
import { PublishOption, TrackInfo, TrackType } from '../gen/video/sfu/models/models';
|
|
4
4
|
/**
|
|
5
5
|
* The `Publisher` is responsible for publishing/unpublishing media streams to/from the SFU
|
|
6
6
|
*
|
|
@@ -10,10 +10,13 @@ export declare class Publisher extends BasePeerConnection {
|
|
|
10
10
|
private readonly transceiverCache;
|
|
11
11
|
private readonly clonedTracks;
|
|
12
12
|
private publishOptions;
|
|
13
|
+
private readonly selfSubEnabled;
|
|
13
14
|
/**
|
|
14
15
|
* Constructs a new `Publisher` instance.
|
|
15
16
|
*/
|
|
16
|
-
constructor(baseOptions: BasePeerConnectionOpts, publishOptions: PublishOption[]
|
|
17
|
+
constructor(baseOptions: BasePeerConnectionOpts, publishOptions: PublishOption[], opts?: {
|
|
18
|
+
selfSubEnabled?: boolean;
|
|
19
|
+
});
|
|
17
20
|
/**
|
|
18
21
|
* Disposes this Publisher instance.
|
|
19
22
|
*/
|
|
@@ -7,6 +7,14 @@ import { BasePeerConnectionOpts } from './types';
|
|
|
7
7
|
* @internal
|
|
8
8
|
*/
|
|
9
9
|
export declare class Subscriber extends BasePeerConnection {
|
|
10
|
+
/**
|
|
11
|
+
* Remote streams received from the SFU. For a self-sub case
|
|
12
|
+
* we need to be able to distinguish between the local capture stream.
|
|
13
|
+
* The map will never contain local streams so we can safely use it to
|
|
14
|
+
* check if the stream is remote and dispose it when needed.
|
|
15
|
+
*/
|
|
16
|
+
private trackedStreams?;
|
|
17
|
+
private negotiationFailures;
|
|
10
18
|
/**
|
|
11
19
|
* Constructs a new `Subscriber` instance.
|
|
12
20
|
*/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts the ICE candidate to a JSON string.
|
|
3
|
+
*/
|
|
4
|
+
export declare const toJSON: (candidate: RTCIceCandidate) => string;
|
|
5
|
+
/**
|
|
6
|
+
* Extracts the ICE ufrag from an SDP, or `undefined` when absent.
|
|
7
|
+
*/
|
|
8
|
+
export declare const parseIceUfrag: (sdp: string | undefined) => string | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Extracts the ICE ufrag (generation) a trickled candidate was gathered under.
|
|
11
|
+
*/
|
|
12
|
+
export declare const getCandidateUfrag: (ice: RTCIceCandidateInit) => string | undefined;
|
package/dist/src/rtc/types.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export declare const ReconnectReason: {
|
|
|
21
21
|
readonly CONNECTION_FAILED: "connection_failed";
|
|
22
22
|
/** `restartIce()` rejected. */
|
|
23
23
|
readonly RESTART_ICE_FAILED: "restart_ice_failed";
|
|
24
|
+
/** Subscriber renegotiation kept failing, escalate to REJOIN. */
|
|
25
|
+
readonly SUBSCRIBER_NEGOTIATION_FAILED: "subscriber_negotiation_failed";
|
|
24
26
|
/** SFU `goAway` event, migrate to a new SFU. */
|
|
25
27
|
readonly GO_AWAY: "go_away";
|
|
26
28
|
/** Network came back online after going offline. */
|
|
@@ -36,14 +36,45 @@ export declare class SfuStatsReporter {
|
|
|
36
36
|
private readonly sdkVersion;
|
|
37
37
|
private readonly webRTCVersion;
|
|
38
38
|
private readonly inputDevices;
|
|
39
|
+
private readonly statsConcurrencyTag;
|
|
40
|
+
private isStopped;
|
|
39
41
|
constructor(sfuClient: StreamSfuClient, { options, clientDetails, subscriber, publisher, microphone, camera, state, tracer, unifiedSessionId, }: SfuStatsReporterOptions);
|
|
40
42
|
private observeDevice;
|
|
41
43
|
sendConnectionTime: (connectionTimeSeconds: number) => void;
|
|
42
44
|
sendReconnectionTime: (strategy: WebsocketReconnectStrategy, timeSeconds: number) => void;
|
|
43
45
|
private sendTelemetryData;
|
|
46
|
+
/**
|
|
47
|
+
* Samples both peer connections. Each `StatsTracer.takeSample()` is serialized
|
|
48
|
+
* internally, so this is safe even if it overlaps another sample (e.g. the
|
|
49
|
+
* connection-state-change handler). Kept separate from `send()` so an
|
|
50
|
+
* explicit flush can capture the sample from live peer connections before
|
|
51
|
+
* they are disposed, without waiting for an in-flight send.
|
|
52
|
+
*/
|
|
53
|
+
private sample;
|
|
54
|
+
private send;
|
|
55
|
+
/**
|
|
56
|
+
* Samples and sends one report. Used by the scheduler and the telemetry path.
|
|
57
|
+
* Bails if the reporter has been stopped so it never samples disposed peer
|
|
58
|
+
* connections.
|
|
59
|
+
*/
|
|
44
60
|
private run;
|
|
45
61
|
private scheduleNextReport;
|
|
46
62
|
start: () => void;
|
|
47
63
|
stop: () => void;
|
|
48
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Explicit/final flush (leave, migration, re-init). Time-boxes the sampling
|
|
66
|
+
* step and swallows its failures, so a slow or failing `getStats()` on a
|
|
67
|
+
* degraded or closing peer connection can never block or reject call teardown
|
|
68
|
+
* or reconnect setup. On a successful sample it fires the send best-effort;
|
|
69
|
+
* the returned promise resolves once the sample is taken (or the time-box
|
|
70
|
+
* elapses / sampling fails), never when the sending completes. No-op once the
|
|
71
|
+
* reporter has been stopped.
|
|
72
|
+
*/
|
|
73
|
+
flush: () => Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Flush entry for the periodic scheduler. Skips when the reporter is stopped
|
|
76
|
+
* or a send is already in flight so ticks can't pile up under slow sends (the
|
|
77
|
+
* next sample's delta spans the skipped interval).
|
|
78
|
+
*/
|
|
79
|
+
private scheduledFlush;
|
|
49
80
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PeerType, TrackType } from '../../gen/video/sfu/models/models';
|
|
2
|
-
import type { ComputedStats } from './types';
|
|
2
|
+
import type { ComputedStats, 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
|
|
@@ -13,23 +13,53 @@ export declare class StatsTracer {
|
|
|
13
13
|
private readonly peerType;
|
|
14
14
|
private readonly trackIdToTrackType;
|
|
15
15
|
private readonly driftThresholdMs;
|
|
16
|
+
private readonly maxPendingDeltas;
|
|
17
|
+
private readonly sampleTag;
|
|
16
18
|
private costOverrides?;
|
|
17
|
-
private
|
|
19
|
+
private previousSample;
|
|
18
20
|
private frameTimeHistory;
|
|
19
21
|
private fpsHistory;
|
|
22
|
+
/**
|
|
23
|
+
* The un-acked, delta-compressed samples awaiting delivery confirmation.
|
|
24
|
+
* Each entry's delta is computed against the immediately preceding sample,
|
|
25
|
+
* so the list forms a chain the server applies in order. The delivery
|
|
26
|
+
* baseline advances only when the reporter calls `commitDeltas` after a
|
|
27
|
+
* successful send; until then the chain is re-sent in full.
|
|
28
|
+
*/
|
|
29
|
+
private pendingDeltas;
|
|
20
30
|
/**
|
|
21
31
|
* Creates a new StatsTracer instance.
|
|
22
32
|
*/
|
|
23
|
-
constructor(pc: RTCPeerConnection, peerType: PeerType, trackIdToTrackType: Map<string, TrackType>, statsTimestampDriftThresholdMs?: number);
|
|
33
|
+
constructor(pc: RTCPeerConnection, peerType: PeerType, trackIdToTrackType: Map<string, TrackType>, statsTimestampDriftThresholdMs?: number, maxPendingDeltas?: number);
|
|
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`).
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
takeSample: () => Promise<ComputedStats>;
|
|
42
|
+
/**
|
|
43
|
+
* Returns a stable copy of the un-acked delta chain to transmit, oldest first.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
getPendingDeltas: () => PendingDelta[];
|
|
48
|
+
/**
|
|
49
|
+
* Advances the delivery baseline by dropping exactly the deltas that were
|
|
50
|
+
* confirmed delivered. Matching is by object identity, so a stale commit
|
|
51
|
+
* that arrives after a re-anchor (which replaced the chain) is a safe no-op.
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
commitDeltas: (sent: PendingDelta[]) => void;
|
|
24
56
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* It will also return the delta between the current stats and the previous stats.
|
|
28
|
-
* This is used to track the performance of the connection.
|
|
57
|
+
* Drops the un-acked chain without sending it. Used when delta reporting is
|
|
58
|
+
* disabled so the chain can't grow unbounded.
|
|
29
59
|
*
|
|
30
60
|
* @internal
|
|
31
61
|
*/
|
|
32
|
-
|
|
62
|
+
clearPendingDeltas: () => void;
|
|
33
63
|
/**
|
|
34
64
|
* Collects encode stats from the RTCPeerConnection.
|
|
35
65
|
*/
|
|
@@ -2,13 +2,20 @@ import type { RTCStatsDataType, Trace, TraceKey, TraceSlice } from './types';
|
|
|
2
2
|
export declare class Tracer {
|
|
3
3
|
private buffer;
|
|
4
4
|
private enabled;
|
|
5
|
-
|
|
5
|
+
readonly id: string | null;
|
|
6
|
+
private readonly maxBuffer;
|
|
6
7
|
private keys?;
|
|
7
|
-
constructor(id: string | null);
|
|
8
|
+
constructor(id: string | null, maxBuffer?: number);
|
|
8
9
|
setEnabled: (enabled: boolean) => void;
|
|
9
10
|
trace: Trace;
|
|
10
11
|
traceOnce: (key: TraceKey, tag: string, data: RTCStatsDataType) => void;
|
|
11
12
|
resetTrace: (key: TraceKey) => void;
|
|
12
13
|
take: () => TraceSlice;
|
|
13
14
|
dispose: () => void;
|
|
15
|
+
/**
|
|
16
|
+
* Bounds the buffer to 2500 records by dropping the oldest ones,
|
|
17
|
+
* leaving a single `traceBufferOverflow` breadcrumb at the front so the
|
|
18
|
+
* consumer knows records were dropped.
|
|
19
|
+
*/
|
|
20
|
+
private capBuffer;
|
|
14
21
|
}
|
|
@@ -17,12 +17,18 @@ export type ComputedStats = {
|
|
|
17
17
|
* Current stats from the RTCPeerConnection.
|
|
18
18
|
*/
|
|
19
19
|
stats: RTCStatsReport;
|
|
20
|
-
/**
|
|
21
|
-
* Delta between the current stats and the previous stats.
|
|
22
|
-
*/
|
|
23
|
-
delta: Record<any, any>;
|
|
24
20
|
/**
|
|
25
21
|
* The current iteration of the stats.
|
|
26
22
|
*/
|
|
27
23
|
performanceStats: PerformanceStats[];
|
|
28
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* A single, not-yet-delivered delta-compressed stats sample.
|
|
27
|
+
* The `delta` is computed against the immediately preceding sample, so a
|
|
28
|
+
* sequence of `PendingDelta`s forms a chain that the server applies in order
|
|
29
|
+
* onto its running accumulator. `ts` is the wall-clock time the sample was taken.
|
|
30
|
+
*/
|
|
31
|
+
export type PendingDelta = {
|
|
32
|
+
delta: Record<any, any>;
|
|
33
|
+
ts: number;
|
|
34
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.55.0",
|
|
4
4
|
"main": "dist/index.cjs.js",
|
|
5
5
|
"module": "dist/index.es.js",
|
|
6
6
|
"browser": "dist/index.browser.es.js",
|
|
@@ -46,9 +46,11 @@
|
|
|
46
46
|
"@openapitools/openapi-generator-cli": "^2.34.0",
|
|
47
47
|
"@rollup/plugin-replace": "^6.0.3",
|
|
48
48
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
49
|
-
"@stream-io/audio-filters-web": "^0.
|
|
49
|
+
"@stream-io/audio-filters-web": "^0.9.0",
|
|
50
50
|
"@stream-io/node-sdk": "^0.7.59",
|
|
51
|
+
"@stream-io/typescript-config": "^0.0.0",
|
|
51
52
|
"@total-typescript/shoehorn": "^0.1.2",
|
|
53
|
+
"@types/node": "^25.9.3",
|
|
52
54
|
"@types/sdp-transform": "^2.15.0",
|
|
53
55
|
"@types/ua-parser-js": "^0.7.39",
|
|
54
56
|
"@vitest/coverage-v8": "^4.1.7",
|
|
@@ -57,7 +59,7 @@
|
|
|
57
59
|
"prettier": "^3.8.3",
|
|
58
60
|
"rimraf": "^6.1.3",
|
|
59
61
|
"rollup": "^4.60.4",
|
|
60
|
-
"typescript": "^
|
|
62
|
+
"typescript": "^6.0.3",
|
|
61
63
|
"vitest": "^4.1.7",
|
|
62
64
|
"vitest-mock-extended": "^4.0.0"
|
|
63
65
|
}
|