@stream-io/video-client 1.52.1-beta.0 → 1.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/index.browser.es.js +819 -123
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +819 -122
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +819 -123
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +6 -14
- package/dist/src/StreamVideoClient.d.ts +2 -0
- package/dist/src/coordinator/connection/client.d.ts +1 -0
- package/dist/src/devices/MicrophoneManager.d.ts +6 -0
- package/dist/src/errors/SfuTimeoutError.d.ts +8 -0
- package/dist/src/errors/index.d.ts +1 -0
- package/dist/src/gen/google/protobuf/struct.d.ts +1 -3
- package/dist/src/gen/google/protobuf/timestamp.d.ts +1 -3
- package/dist/src/gen/video/sfu/event/events.d.ts +1 -22
- package/dist/src/gen/video/sfu/models/models.d.ts +0 -4
- package/dist/src/gen/video/sfu/signal_rpc/signal.client.d.ts +2 -23
- package/dist/src/helpers/firstVideoFrame.d.ts +7 -0
- package/dist/src/reporting/ClientEventReporter.d.ts +84 -0
- package/dist/src/reporting/index.d.ts +1 -0
- package/dist/src/rtc/BasePeerConnection.d.ts +5 -2
- package/dist/src/rtc/Publisher.d.ts +1 -4
- package/dist/src/rtc/Subscriber.d.ts +0 -7
- package/dist/src/rtc/types.d.ts +24 -1
- package/dist/src/types.d.ts +16 -0
- package/package.json +1 -1
- package/src/Call.ts +185 -106
- package/src/StreamSfuClient.ts +3 -3
- package/src/StreamVideoClient.ts +18 -3
- package/src/__tests__/Call.autodrop.test.ts +4 -1
- package/src/__tests__/Call.lifecycle.test.ts +4 -1
- package/src/__tests__/Call.publishing.test.ts +4 -1
- package/src/__tests__/Call.test.ts +23 -0
- package/src/coordinator/connection/client.ts +5 -0
- package/src/devices/MicrophoneManager.ts +16 -0
- package/src/devices/__tests__/CameraManager.test.ts +10 -1
- package/src/devices/__tests__/DeviceManager.test.ts +10 -1
- package/src/devices/__tests__/DeviceManagerFilters.test.ts +4 -1
- package/src/devices/__tests__/MicrophoneManager.test.ts +10 -1
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +78 -2
- package/src/devices/__tests__/ScreenShareManager.test.ts +4 -1
- package/src/devices/__tests__/SpeakerManager.test.ts +13 -4
- package/src/errors/SfuTimeoutError.ts +7 -0
- package/src/errors/index.ts +1 -0
- package/src/events/__tests__/call.test.ts +2 -0
- package/src/events/__tests__/mutes.test.ts +4 -1
- package/src/events/call.ts +8 -0
- package/src/gen/google/protobuf/struct.ts +12 -7
- package/src/gen/google/protobuf/timestamp.ts +7 -6
- package/src/gen/video/sfu/event/events.ts +25 -23
- package/src/gen/video/sfu/models/models.ts +1 -11
- package/src/gen/video/sfu/signal_rpc/signal.client.ts +29 -25
- package/src/gen/video/sfu/signal_rpc/signal.ts +0 -1
- package/src/helpers/__tests__/AudioBindingsWatchdog.test.ts +6 -3
- package/src/helpers/__tests__/DynascaleManager.test.ts +6 -3
- package/src/helpers/__tests__/ViewportTracker.test.ts +6 -3
- package/src/helpers/__tests__/firstVideoFrame.test.ts +91 -0
- package/src/helpers/client-details.ts +1 -1
- package/src/helpers/firstVideoFrame.ts +38 -0
- package/src/reporting/ClientEventReporter.ts +864 -0
- package/src/reporting/__tests__/ClientEventReporter.test.ts +620 -0
- package/src/reporting/index.ts +1 -0
- package/src/rtc/BasePeerConnection.ts +30 -0
- package/src/rtc/Publisher.ts +0 -4
- package/src/rtc/Subscriber.ts +2 -28
- package/src/rtc/__tests__/Call.reconnect.test.ts +3 -0
- package/src/rtc/types.ts +34 -0
- package/src/types.ts +18 -0
package/src/rtc/Publisher.ts
CHANGED
|
@@ -39,7 +39,6 @@ export class Publisher extends BasePeerConnection {
|
|
|
39
39
|
private readonly transceiverCache = new TransceiverCache();
|
|
40
40
|
private readonly clonedTracks = new Set<MediaStreamTrack>();
|
|
41
41
|
private publishOptions: PublishOption[];
|
|
42
|
-
private readonly selfSubEnabled: boolean;
|
|
43
42
|
|
|
44
43
|
/**
|
|
45
44
|
* Constructs a new `Publisher` instance.
|
|
@@ -47,11 +46,9 @@ export class Publisher extends BasePeerConnection {
|
|
|
47
46
|
constructor(
|
|
48
47
|
baseOptions: BasePeerConnectionOpts,
|
|
49
48
|
publishOptions: PublishOption[],
|
|
50
|
-
opts: { selfSubEnabled?: boolean } = {},
|
|
51
49
|
) {
|
|
52
50
|
super(PeerType.PUBLISHER_UNSPECIFIED, baseOptions);
|
|
53
51
|
this.publishOptions = publishOptions;
|
|
54
|
-
this.selfSubEnabled = opts.selfSubEnabled ?? false;
|
|
55
52
|
|
|
56
53
|
this.on('iceRestart', (iceRestart) => {
|
|
57
54
|
if (iceRestart.peerType !== PeerType.PUBLISHER_UNSPECIFIED) return;
|
|
@@ -579,7 +576,6 @@ export class Publisher extends BasePeerConnection {
|
|
|
579
576
|
muted: !isTrackLive,
|
|
580
577
|
codec: publishOption.codec,
|
|
581
578
|
publishOptionId: publishOption.id,
|
|
582
|
-
selfSubAudioVideo: this.selfSubEnabled,
|
|
583
579
|
};
|
|
584
580
|
};
|
|
585
581
|
|
package/src/rtc/Subscriber.ts
CHANGED
|
@@ -14,14 +14,6 @@ import { enableStereo, removeCodecsExcept } from './helpers/sdp';
|
|
|
14
14
|
* @internal
|
|
15
15
|
*/
|
|
16
16
|
export class Subscriber extends BasePeerConnection {
|
|
17
|
-
/**
|
|
18
|
-
* Remote streams received from the SFU. For a self-sub case
|
|
19
|
-
* we need to be able to distinguish between the local capture stream.
|
|
20
|
-
* The map will never contain local streams so we can safely use it to
|
|
21
|
-
* check if the stream is remote and dispose it when needed.
|
|
22
|
-
*/
|
|
23
|
-
private trackedStreams: WeakSet<MediaStream> = new WeakSet();
|
|
24
|
-
|
|
25
17
|
/**
|
|
26
18
|
* Constructs a new `Subscriber` instance.
|
|
27
19
|
*/
|
|
@@ -83,7 +75,6 @@ export class Subscriber extends BasePeerConnection {
|
|
|
83
75
|
const participantToUpdate = this.state.participants.find(
|
|
84
76
|
(p) => p.trackLookupPrefix === trackId,
|
|
85
77
|
);
|
|
86
|
-
const isSelfSub = !!participantToUpdate?.isLocalParticipant;
|
|
87
78
|
this.logger.debug(
|
|
88
79
|
`[onTrack]: Got remote ${rawTrackType} track for userId: ${participantToUpdate?.userId}`,
|
|
89
80
|
track.id,
|
|
@@ -103,6 +94,7 @@ export class Subscriber extends BasePeerConnection {
|
|
|
103
94
|
track.addEventListener('unmute', () => {
|
|
104
95
|
this.logger.info(`[onTrack]: Track unmuted: ${trackDebugInfo}`);
|
|
105
96
|
this.setRemoteTrackInterrupted(trackId, trackType, false);
|
|
97
|
+
this.onRemoteTrackUnmute?.(trackType, track.id);
|
|
106
98
|
});
|
|
107
99
|
track.addEventListener('ended', () => {
|
|
108
100
|
this.logger.info(`[onTrack]: Track ended: ${trackDebugInfo}`);
|
|
@@ -116,10 +108,6 @@ export class Subscriber extends BasePeerConnection {
|
|
|
116
108
|
|
|
117
109
|
this.trackIdToTrackType.set(track.id, trackType);
|
|
118
110
|
|
|
119
|
-
if (isSelfSub) {
|
|
120
|
-
this.trackedStreams.add(primaryStream);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
111
|
if (!participantToUpdate) {
|
|
124
112
|
this.logger.warn(
|
|
125
113
|
`[onTrack]: Received track for unknown participant: ${trackId}`,
|
|
@@ -140,13 +128,6 @@ export class Subscriber extends BasePeerConnection {
|
|
|
140
128
|
return;
|
|
141
129
|
}
|
|
142
130
|
|
|
143
|
-
// Self-sub loopback audio routes to the speaker by default, which
|
|
144
|
-
// would echo the local user's voice. Default-mute here; consumers
|
|
145
|
-
// (the loopback recording hook) re-enable explicitly when needed.
|
|
146
|
-
if (isSelfSub && e.track.kind === 'audio') {
|
|
147
|
-
e.track.enabled = false;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
131
|
// get the previous stream to dispose it later
|
|
151
132
|
// usually this happens during migration, when the stream is replaced
|
|
152
133
|
// with a new one but the old one is still in the state
|
|
@@ -157,15 +138,8 @@ export class Subscriber extends BasePeerConnection {
|
|
|
157
138
|
[streamKindProp]: primaryStream,
|
|
158
139
|
});
|
|
159
140
|
|
|
141
|
+
// now, dispose the previous stream if it exists
|
|
160
142
|
if (previousStream) {
|
|
161
|
-
if (isSelfSub && !this.trackedStreams.has(previousStream)) {
|
|
162
|
-
// this is the local capture stream, we don't want to dispose it
|
|
163
|
-
this.logger.debug(
|
|
164
|
-
`[onTrack]: Skipping cleanup of previous ${e.track.kind} stream for userId: ${participantToUpdate.userId} because it is not tracked`,
|
|
165
|
-
);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
143
|
this.logger.info(
|
|
170
144
|
`[onTrack]: Cleaning up previous remote ${track.kind} tracks for userId: ${participantToUpdate.userId}`,
|
|
171
145
|
);
|
|
@@ -3,6 +3,7 @@ import './mocks/webrtc.mocks';
|
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import { Call } from '../../Call';
|
|
5
5
|
import { StreamClient } from '../../coordinator/connection/client';
|
|
6
|
+
import { ClientEventReporter } from '../../reporting';
|
|
6
7
|
import { StreamVideoWriteableStateStore } from '../../store';
|
|
7
8
|
import { CallingState } from '../../store';
|
|
8
9
|
import { NegotiationError } from '../NegotiationError';
|
|
@@ -30,6 +31,7 @@ const makeCall = () => {
|
|
|
30
31
|
type: 'default',
|
|
31
32
|
id: 'test-call',
|
|
32
33
|
streamClient,
|
|
34
|
+
clientEventReporter: new ClientEventReporter({ streamClient }),
|
|
33
35
|
clientStore,
|
|
34
36
|
ringing: false,
|
|
35
37
|
watching: false,
|
|
@@ -585,6 +587,7 @@ describe('Call reconnect wiring (PC event → leave)', () => {
|
|
|
585
587
|
type: 'default',
|
|
586
588
|
id: 'test-call',
|
|
587
589
|
streamClient,
|
|
590
|
+
clientEventReporter: new ClientEventReporter({ streamClient }),
|
|
588
591
|
clientStore,
|
|
589
592
|
ringing: false,
|
|
590
593
|
watching: false,
|
package/src/rtc/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
AudioBitrateProfile,
|
|
3
3
|
PeerType,
|
|
4
4
|
PublishOption,
|
|
5
|
+
TrackType,
|
|
5
6
|
WebsocketReconnectStrategy,
|
|
6
7
|
} from '../gen/video/sfu/models/models';
|
|
7
8
|
import { StreamSfuClient } from '../StreamSfuClient';
|
|
@@ -53,6 +54,37 @@ export type OnReconnectionNeeded = (
|
|
|
53
54
|
*/
|
|
54
55
|
export type OnIceConnected = (peerType: PeerType) => void;
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Snapshot of the peer connection's ICE and DTLS state surfaced to telemetry
|
|
59
|
+
* consumers (e.g. `ClientEventReporter`). Fired on every transition of
|
|
60
|
+
* either `iceConnectionState` or `peerConnectionState`.
|
|
61
|
+
*/
|
|
62
|
+
export type PeerConnectionStateChangeEvent =
|
|
63
|
+
| {
|
|
64
|
+
peerType: PeerType;
|
|
65
|
+
stateType: 'ice';
|
|
66
|
+
state: RTCIceConnectionState;
|
|
67
|
+
}
|
|
68
|
+
| {
|
|
69
|
+
peerType: PeerType;
|
|
70
|
+
stateType: 'peerConnection';
|
|
71
|
+
state: RTCPeerConnectionState;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type OnPeerConnectionStateChange = (
|
|
75
|
+
event: PeerConnectionStateChangeEvent,
|
|
76
|
+
) => void;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Fired when a remote track starts receiving media (`unmute`). Used by
|
|
80
|
+
* telemetry to report the `FirstVideoFrame` / `FirstAudioFrame` stage; the
|
|
81
|
+
* consumer decides which track types are relevant.
|
|
82
|
+
*/
|
|
83
|
+
export type OnRemoteTrackUnmute = (
|
|
84
|
+
trackType: TrackType,
|
|
85
|
+
trackId: string,
|
|
86
|
+
) => void;
|
|
87
|
+
|
|
56
88
|
export type BasePeerConnectionOpts = {
|
|
57
89
|
sfuClient: StreamSfuClient;
|
|
58
90
|
state: CallState;
|
|
@@ -60,6 +92,8 @@ export type BasePeerConnectionOpts = {
|
|
|
60
92
|
dispatcher: Dispatcher;
|
|
61
93
|
onReconnectionNeeded?: OnReconnectionNeeded;
|
|
62
94
|
onIceConnected?: OnIceConnected;
|
|
95
|
+
onPeerConnectionStateChange?: OnPeerConnectionStateChange;
|
|
96
|
+
onRemoteTrackUnmute?: OnRemoteTrackUnmute;
|
|
63
97
|
tag: string;
|
|
64
98
|
enableTracing: boolean;
|
|
65
99
|
iceRestartDelay?: number;
|
package/src/types.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
StartRecordingResponse,
|
|
15
15
|
} from './gen/coordinator';
|
|
16
16
|
import type { StreamClient } from './coordinator/connection/client';
|
|
17
|
+
import type { ClientEventReporter } from './reporting';
|
|
17
18
|
import type {
|
|
18
19
|
RejectReason,
|
|
19
20
|
StreamClientOptions,
|
|
@@ -311,6 +312,11 @@ export type CallConstructor = {
|
|
|
311
312
|
*/
|
|
312
313
|
streamClient: StreamClient;
|
|
313
314
|
|
|
315
|
+
/**
|
|
316
|
+
* The shared client event reporter, owned by `StreamVideoClient`.
|
|
317
|
+
*/
|
|
318
|
+
clientEventReporter: ClientEventReporter;
|
|
319
|
+
|
|
314
320
|
/**
|
|
315
321
|
* The Call type.
|
|
316
322
|
*/
|
|
@@ -474,6 +480,18 @@ export type StreamRNVideoSDKGlobals = {
|
|
|
474
480
|
* Stops the in call manager.
|
|
475
481
|
*/
|
|
476
482
|
stop({ isRingingTypeCall }: StreamRNVideoSDKCallManagerRingingParams): void;
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* iOS-only. Keeps the audio engine's microphone-input (voice-processing)
|
|
486
|
+
* chain prepared while the mic is muted, so the `AVAudioEngine` stays full-duplex
|
|
487
|
+
* meaning: mic going out and speaker coming in, simultaneously
|
|
488
|
+
*
|
|
489
|
+
* Without this, joining muted builds an output-only engine under the
|
|
490
|
+
* `PlayAndRecord`/`VoiceChat` (VPIO) session, which makes the remote audio
|
|
491
|
+
* not audible when joining muted. As echo cancellation expects the mic to be
|
|
492
|
+
* prepared.
|
|
493
|
+
*/
|
|
494
|
+
setMutedRecordingPrepared?(enabled: boolean): void;
|
|
477
495
|
};
|
|
478
496
|
permissions: {
|
|
479
497
|
/**
|