@stream-io/video-client 2.0.0-beta.0 → 2.0.0-beta.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 +18 -0
- package/dist/index.browser.es.js +1126 -791
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +1126 -791
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +1126 -791
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +37 -11
- package/dist/src/StreamVideoClient.d.ts +15 -21
- package/dist/src/coordinator/connection/types.d.ts +16 -0
- package/dist/src/events/call.d.ts +0 -11
- package/dist/src/gen/coordinator/index.d.ts +73 -0
- package/dist/src/reporting/ClientEventReporter.d.ts +11 -1
- package/dist/src/ringing/RingStatePoller.d.ts +46 -0
- package/dist/src/ringing/RingTimeout.d.ts +27 -0
- package/dist/src/ringing/index.d.ts +4 -0
- package/dist/src/ringing/reconcileRingState.d.ts +18 -0
- package/dist/src/ringing/resolveOwnRingOutcome.d.ts +32 -0
- package/dist/src/rtc/Publisher.d.ts +18 -0
- package/dist/src/rtc/TransceiverCache.d.ts +10 -0
- package/dist/src/rtc/e2ee/E2EEManager.d.ts +10 -1
- package/dist/src/stats/utils.d.ts +6 -0
- package/dist/src/store/CallState.d.ts +118 -267
- package/dist/src/store/ClientState.d.ts +62 -0
- package/dist/src/store/index.d.ts +1 -1
- package/dist/src/store/subjects.d.ts +15 -0
- package/dist/src/store/types.d.ts +22 -0
- package/dist/src/types.d.ts +33 -4
- package/index.ts +1 -0
- package/package.json +2 -2
- package/src/Call.ts +285 -125
- package/src/StreamSfuClient.ts +6 -2
- package/src/StreamVideoClient.ts +40 -47
- package/src/__tests__/Call.autodrop.test.ts +118 -66
- package/src/__tests__/Call.getRingState.test.ts +87 -0
- package/src/__tests__/Call.lifecycle.test.ts +499 -6
- package/src/__tests__/Call.publishing.test.ts +2 -2
- package/src/__tests__/Call.ringSettled.test.ts +71 -0
- package/src/__tests__/Call.ringingLifecycle.test.ts +242 -0
- package/src/__tests__/StreamSfuClient.test.ts +48 -0
- package/src/__tests__/StreamVideoClient.disconnect.test.ts +62 -0
- package/src/coordinator/connection/types.ts +19 -0
- package/src/devices/__tests__/CameraManager.test.ts +2 -2
- package/src/devices/__tests__/DeviceManager.test.ts +2 -2
- package/src/devices/__tests__/DeviceManagerFilters.test.ts +2 -2
- package/src/devices/__tests__/MicrophoneManager.test.ts +2 -2
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +2 -2
- package/src/devices/__tests__/ScreenShareManager.test.ts +3 -3
- package/src/devices/__tests__/SpeakerManager.test.ts +4 -4
- package/src/events/__tests__/call.test.ts +5 -220
- package/src/events/call.ts +1 -73
- package/src/events/callEventHandlers.ts +12 -23
- package/src/gen/coordinator/index.ts +67 -0
- package/src/helpers/__tests__/AudioBindingsWatchdog.test.ts +2 -2
- package/src/helpers/__tests__/DynascaleManager.test.ts +2 -2
- package/src/helpers/__tests__/ViewportTracker.test.ts +2 -2
- package/src/reporting/ClientEventReporter.ts +30 -4
- package/src/reporting/__tests__/ClientEventReporter.test.ts +184 -7
- package/src/ringing/RingStatePoller.ts +177 -0
- package/src/ringing/RingTimeout.ts +90 -0
- package/src/ringing/__tests__/RingStatePoller.test.ts +462 -0
- package/src/ringing/__tests__/RingTimeout.test.ts +199 -0
- package/src/ringing/__tests__/reconcileRingState.test.ts +330 -0
- package/src/ringing/__tests__/resolveOwnRingOutcome.test.ts +103 -0
- package/src/ringing/index.ts +4 -0
- package/src/ringing/reconcileRingState.ts +108 -0
- package/src/ringing/resolveOwnRingOutcome.ts +58 -0
- package/src/rtc/Publisher.ts +66 -12
- package/src/rtc/TransceiverCache.ts +17 -0
- package/src/rtc/__tests__/Call.reconnect.test.ts +5 -5
- package/src/rtc/__tests__/Publisher.test.ts +209 -0
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +1 -0
- package/src/rtc/e2ee/E2EEManager.ts +10 -1
- package/src/rtc/e2ee/EncryptionManager.ts +1 -1
- package/src/rtc/e2ee/SPEC.md +1 -1
- package/src/stats/__tests__/utils.test.ts +33 -0
- package/src/stats/utils.ts +15 -0
- package/src/store/CallState.ts +305 -550
- package/src/store/ClientState.ts +101 -0
- package/src/store/__tests__/CallState.test.ts +60 -1
- package/src/store/__tests__/ClientState.test.ts +35 -0
- package/src/store/__tests__/rxUtils.test.ts +67 -1
- package/src/store/index.ts +1 -1
- package/src/store/rxUtils.ts +8 -4
- package/src/store/subjects.ts +30 -0
- package/src/store/types.ts +45 -0
- package/src/types.ts +40 -4
- package/dist/src/store/stateStore.d.ts +0 -84
- package/src/store/stateStore.ts +0 -156
package/src/store/CallState.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
BehaviorSubject,
|
|
3
2
|
combineLatest,
|
|
4
3
|
distinctUntilChanged,
|
|
5
4
|
map,
|
|
6
|
-
Observable,
|
|
7
5
|
ReplaySubject,
|
|
8
|
-
shareReplay,
|
|
9
6
|
startWith,
|
|
10
7
|
} from 'rxjs';
|
|
11
|
-
import
|
|
12
|
-
|
|
8
|
+
import {
|
|
9
|
+
getCurrentValue,
|
|
10
|
+
isShallowArrayEqual,
|
|
11
|
+
type Patch,
|
|
12
|
+
setCurrentValue,
|
|
13
|
+
updateValue,
|
|
14
|
+
} from './rxUtils';
|
|
15
|
+
import { duc, shared, subject } from './subjects';
|
|
13
16
|
import { CallingState } from './CallingState';
|
|
14
17
|
import {
|
|
15
18
|
type CallRecordingType,
|
|
@@ -37,6 +40,7 @@ import {
|
|
|
37
40
|
CallSessionParticipantLeftEvent,
|
|
38
41
|
CallSessionResponse,
|
|
39
42
|
CallSettingsResponse,
|
|
43
|
+
GetCallRingStateResponse,
|
|
40
44
|
ClosedCaptionEvent,
|
|
41
45
|
EgressResponse,
|
|
42
46
|
MemberResponse,
|
|
@@ -59,10 +63,9 @@ import { Comparator, defaultSortPreset } from '../sorting';
|
|
|
59
63
|
import { ensureExhausted } from '../helpers/ensureExhausted';
|
|
60
64
|
import { hasScreenShare } from '../helpers/participantUtils';
|
|
61
65
|
import { videoLoggerSystem } from '../logger';
|
|
66
|
+
import type { AllEventHandlers, CallStateEventHandlers } from './types';
|
|
62
67
|
|
|
63
|
-
/**
|
|
64
|
-
* Returns the default egress object - when no egress data is available.
|
|
65
|
-
*/
|
|
68
|
+
/** Returns the default egress object - when no egress data is available. */
|
|
66
69
|
const defaultEgress: EgressResponse = {
|
|
67
70
|
broadcasting: false,
|
|
68
71
|
hls: { playlist_url: '', status: '' },
|
|
@@ -82,359 +85,188 @@ type OrphanedTrack = {
|
|
|
82
85
|
* @react You don't have to use this class directly, as we are exposing the state through Hooks.
|
|
83
86
|
*/
|
|
84
87
|
export class CallState {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
private
|
|
88
|
-
private endedAtSubject = new BehaviorSubject<Date | undefined>(undefined);
|
|
89
|
-
private startsAtSubject = new BehaviorSubject<Date | undefined>(undefined);
|
|
90
|
-
private updatedAtSubject = new BehaviorSubject<Date>(new Date());
|
|
91
|
-
private createdBySubject = new BehaviorSubject<UserResponse | undefined>(
|
|
92
|
-
undefined,
|
|
93
|
-
);
|
|
94
|
-
private customSubject = new BehaviorSubject<Record<string, any>>({});
|
|
95
|
-
private egressSubject = new BehaviorSubject<EgressResponse | undefined>(
|
|
96
|
-
undefined,
|
|
97
|
-
);
|
|
98
|
-
private ingressSubject = new BehaviorSubject<CallIngressResponse | undefined>(
|
|
99
|
-
undefined,
|
|
100
|
-
);
|
|
101
|
-
private recordingSubject = new BehaviorSubject<boolean>(false);
|
|
102
|
-
private individualRecordingSubject = new BehaviorSubject<boolean>(false);
|
|
103
|
-
private rawRecordingSubject = new BehaviorSubject<boolean>(false);
|
|
104
|
-
private sessionSubject = new BehaviorSubject<CallSessionResponse | undefined>(
|
|
105
|
-
undefined,
|
|
106
|
-
);
|
|
107
|
-
private settingsSubject = new BehaviorSubject<
|
|
108
|
-
CallSettingsResponse | undefined
|
|
109
|
-
>(undefined);
|
|
110
|
-
private transcribingSubject = new BehaviorSubject<boolean>(false);
|
|
111
|
-
private captioningSubject = new BehaviorSubject<boolean>(false);
|
|
112
|
-
private e2eeEnabledSubject = new BehaviorSubject<boolean>(false);
|
|
113
|
-
private endedBySubject = new BehaviorSubject<UserResponse | undefined>(
|
|
114
|
-
undefined,
|
|
115
|
-
);
|
|
116
|
-
private thumbnailsSubject = new BehaviorSubject<
|
|
117
|
-
ThumbnailResponse | undefined
|
|
118
|
-
>(undefined);
|
|
119
|
-
private membersSubject = new BehaviorSubject<MemberResponse[]>([]);
|
|
120
|
-
private ownCapabilitiesSubject = new BehaviorSubject<OwnCapability[]>([]);
|
|
121
|
-
private callingStateSubject = new BehaviorSubject<CallingState>(
|
|
122
|
-
CallingState.UNKNOWN,
|
|
123
|
-
);
|
|
124
|
-
private startedAtSubject = new BehaviorSubject<Date | undefined>(undefined);
|
|
125
|
-
private participantCountSubject = new BehaviorSubject<number>(0);
|
|
126
|
-
private anonymousParticipantCountSubject = new BehaviorSubject<number>(0);
|
|
127
|
-
private participantsSubject = new BehaviorSubject<StreamVideoParticipant[]>(
|
|
128
|
-
[],
|
|
129
|
-
);
|
|
130
|
-
private callStatsReportSubject = new BehaviorSubject<
|
|
131
|
-
CallStatsReport | undefined
|
|
132
|
-
>(undefined);
|
|
133
|
-
private closedCaptionsSubject = new BehaviorSubject<CallClosedCaption[]>([]);
|
|
134
|
-
|
|
135
|
-
// These are tracks that were delivered to the Subscriber's onTrack event
|
|
136
|
-
// that we couldn't associate with a participant yet.
|
|
137
|
-
// This happens when the participantJoined event hasn't been received yet.
|
|
138
|
-
// We keep these tracks around until we can associate them with a participant.
|
|
139
|
-
private orphanedTracks: OrphanedTrack[] = [];
|
|
88
|
+
readonly logger = videoLoggerSystem.getLogger('CallState');
|
|
89
|
+
/** A list of comparators that are used to sort the participants. */
|
|
90
|
+
private sortParticipantsBy = defaultSortPreset;
|
|
140
91
|
|
|
92
|
+
private backstageSubject = subject(true);
|
|
93
|
+
private blockedUserIdsSubject = subject<string[]>([]);
|
|
94
|
+
private createdAtSubject = subject(new Date());
|
|
95
|
+
private endedAtSubject = subject<Date>();
|
|
96
|
+
private startsAtSubject = subject<Date>();
|
|
97
|
+
private updatedAtSubject = subject(new Date());
|
|
98
|
+
private createdBySubject = subject<UserResponse>();
|
|
99
|
+
private customSubject = subject<Record<string, any>>({});
|
|
100
|
+
private egressSubject = subject<EgressResponse>();
|
|
101
|
+
private ingressSubject = subject<CallIngressResponse>();
|
|
102
|
+
private recordingSubject = subject(false);
|
|
103
|
+
private individualRecordingSubject = subject(false);
|
|
104
|
+
private rawRecordingSubject = subject(false);
|
|
105
|
+
private sessionSubject = subject<CallSessionResponse>();
|
|
106
|
+
private settingsSubject = subject<CallSettingsResponse>();
|
|
107
|
+
private transcribingSubject = subject(false);
|
|
108
|
+
private captioningSubject = subject(false);
|
|
109
|
+
private e2eeEnabledSubject = subject(false);
|
|
110
|
+
private endedBySubject = subject<UserResponse>();
|
|
111
|
+
private thumbnailsSubject = subject<ThumbnailResponse>();
|
|
112
|
+
private membersSubject = subject<MemberResponse[]>([]);
|
|
113
|
+
private ownCapabilitiesSubject = subject<OwnCapability[]>([]);
|
|
114
|
+
private callingStateSubject = subject<CallingState>(CallingState.UNKNOWN);
|
|
115
|
+
private startedAtSubject = subject<Date>();
|
|
116
|
+
private participantCountSubject = subject(0);
|
|
117
|
+
private anonymousParticipantCountSubject = subject(0);
|
|
118
|
+
private participantsSubject = subject<StreamVideoParticipant[]>([]);
|
|
119
|
+
private callStatsReportSubject = subject<CallStatsReport>();
|
|
120
|
+
private closedCaptionsSubject = subject<CallClosedCaption[]>([]);
|
|
141
121
|
private callGrantsSubject = new ReplaySubject<CallGrants>(1);
|
|
142
122
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* The time the call session actually started.
|
|
147
|
-
* Useful for displaying the call duration.
|
|
148
|
-
*/
|
|
149
|
-
startedAt$: Observable<Date | undefined>;
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* The server-side counted number of participants connected to the current call.
|
|
153
|
-
* This number includes the anonymous participants as well.
|
|
154
|
-
*/
|
|
155
|
-
participantCount$: Observable<number>;
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* The server-side counted number of anonymous participants connected to the current call.
|
|
159
|
-
* This number excludes the regular participants.
|
|
160
|
-
*/
|
|
161
|
-
anonymousParticipantCount$: Observable<number>;
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* All participants of the current call (this includes the current user and other participants as well),
|
|
165
|
-
* unsorted. This observable only updates when participants join or leave the call.
|
|
166
|
-
*/
|
|
167
|
-
rawParticipants$: Observable<StreamVideoParticipant[]>;
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* All participants of the current call (this includes the current user and other participants as well),
|
|
171
|
-
* sorted according to the current `sortByParticipantsBy` setting
|
|
172
|
-
*/
|
|
173
|
-
participants$: Observable<StreamVideoParticipant[]>;
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Remote participants of the current call (this includes every participant except the logged-in user).
|
|
177
|
-
*/
|
|
178
|
-
remoteParticipants$: Observable<StreamVideoParticipant[]>;
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* The local participant of the current call (the logged-in user).
|
|
182
|
-
*/
|
|
183
|
-
localParticipant$: Observable<StreamVideoParticipant | undefined>;
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Pinned participants of the current call.
|
|
187
|
-
*/
|
|
188
|
-
pinnedParticipants$: Observable<StreamVideoParticipant[]>;
|
|
123
|
+
/** The backstage state. */
|
|
124
|
+
backstage$ = duc(this.backstageSubject);
|
|
189
125
|
|
|
190
|
-
/**
|
|
191
|
-
|
|
192
|
-
*/
|
|
193
|
-
dominantSpeaker$: Observable<StreamVideoParticipant | undefined>;
|
|
126
|
+
/** Will provide the list of blocked user IDs. */
|
|
127
|
+
blockedUserIds$ = duc(this.blockedUserIdsSubject, isShallowArrayEqual);
|
|
194
128
|
|
|
195
|
-
/**
|
|
196
|
-
|
|
197
|
-
* the current call. Useful for displaying a "screen sharing" indicator and
|
|
198
|
-
* switching the layout to a screen sharing layout.
|
|
199
|
-
*
|
|
200
|
-
* The actual screen sharing track isn't exposed here, but can be retrieved
|
|
201
|
-
* from the list of call participants. We also don't want to be limiting
|
|
202
|
-
* to the number of share screen tracks are displayed in a call.
|
|
203
|
-
*/
|
|
204
|
-
hasOngoingScreenShare$: Observable<boolean>;
|
|
129
|
+
/** Will provide the time when this call has been created. */
|
|
130
|
+
createdAt$ = this.createdAtSubject.asObservable();
|
|
205
131
|
|
|
206
|
-
/**
|
|
207
|
-
|
|
208
|
-
* When stats gathering is enabled, this observable will emit a new value
|
|
209
|
-
* at a regular (configurable) interval.
|
|
210
|
-
*
|
|
211
|
-
* Consumers of this observable can implement their own batching logic
|
|
212
|
-
* in case they want to show historical stats data.
|
|
213
|
-
*/
|
|
214
|
-
callStatsReport$: Observable<CallStatsReport | undefined>;
|
|
132
|
+
/** Will provide the time when this call has been ended. */
|
|
133
|
+
endedAt$ = this.endedAtSubject.asObservable();
|
|
215
134
|
|
|
216
|
-
/**
|
|
217
|
-
|
|
218
|
-
*/
|
|
219
|
-
members$: Observable<MemberResponse[]>;
|
|
135
|
+
/** Will provide the time when this call has been scheduled to start. */
|
|
136
|
+
startsAt$ = this.startsAtSubject.asObservable();
|
|
220
137
|
|
|
221
|
-
/**
|
|
222
|
-
|
|
223
|
-
*/
|
|
224
|
-
ownCapabilities$: Observable<OwnCapability[]>;
|
|
138
|
+
/** Will provide the time when this call has been updated. */
|
|
139
|
+
updatedAt$ = this.updatedAtSubject.asObservable();
|
|
225
140
|
|
|
226
|
-
/**
|
|
227
|
-
|
|
228
|
-
*/
|
|
229
|
-
callingState$: Observable<CallingState>;
|
|
141
|
+
/** Will provide the user who created this call. */
|
|
142
|
+
createdBy$ = this.createdBySubject.asObservable();
|
|
230
143
|
|
|
231
|
-
/**
|
|
232
|
-
|
|
233
|
-
*/
|
|
234
|
-
backstage$: Observable<boolean>;
|
|
144
|
+
/** Will provide the custom data of this call. */
|
|
145
|
+
custom$ = this.customSubject.asObservable();
|
|
235
146
|
|
|
236
|
-
/**
|
|
237
|
-
|
|
238
|
-
*/
|
|
239
|
-
blockedUserIds$: Observable<string[]>;
|
|
147
|
+
/** Will provide the egress data of this call. */
|
|
148
|
+
egress$ = this.egressSubject.asObservable();
|
|
240
149
|
|
|
241
|
-
/**
|
|
242
|
-
|
|
243
|
-
*/
|
|
244
|
-
createdAt$: Observable<Date>;
|
|
150
|
+
/** Will provide the ingress data of this call. */
|
|
151
|
+
ingress$ = this.ingressSubject.asObservable();
|
|
245
152
|
|
|
246
|
-
/**
|
|
247
|
-
|
|
248
|
-
*/
|
|
249
|
-
endedAt$: Observable<Date | undefined>;
|
|
153
|
+
/** Will provide the recording state of this call. */
|
|
154
|
+
recording$ = duc(this.recordingSubject);
|
|
250
155
|
|
|
251
|
-
/**
|
|
252
|
-
|
|
253
|
-
*/
|
|
254
|
-
startsAt$: Observable<Date | undefined>;
|
|
156
|
+
/** Will provide the recording state of this call. */
|
|
157
|
+
individualRecording$ = duc(this.individualRecordingSubject);
|
|
255
158
|
|
|
256
|
-
/**
|
|
257
|
-
|
|
258
|
-
*/
|
|
259
|
-
updatedAt$: Observable<Date>;
|
|
159
|
+
/** Will provide the recording state of this call. */
|
|
160
|
+
rawRecording$ = duc(this.rawRecordingSubject);
|
|
260
161
|
|
|
261
|
-
/**
|
|
262
|
-
|
|
263
|
-
*/
|
|
264
|
-
createdBy$: Observable<UserResponse | undefined>;
|
|
162
|
+
/** Will provide the session data of this call. */
|
|
163
|
+
session$ = this.sessionSubject.asObservable();
|
|
265
164
|
|
|
266
|
-
/**
|
|
267
|
-
|
|
268
|
-
*/
|
|
269
|
-
custom$: Observable<Record<string, any>>;
|
|
165
|
+
/** Will provide the settings of this call. */
|
|
166
|
+
settings$ = this.settingsSubject.asObservable();
|
|
270
167
|
|
|
271
|
-
/**
|
|
272
|
-
|
|
273
|
-
*/
|
|
274
|
-
egress$: Observable<EgressResponse | undefined>;
|
|
168
|
+
/** Will provide the transcribing state of this call. */
|
|
169
|
+
transcribing$ = duc(this.transcribingSubject);
|
|
275
170
|
|
|
276
|
-
/**
|
|
277
|
-
|
|
278
|
-
*/
|
|
279
|
-
ingress$: Observable<CallIngressResponse | undefined>;
|
|
171
|
+
/** Will provide the closed captioning state of this call. */
|
|
172
|
+
captioning$ = duc(this.captioningSubject);
|
|
280
173
|
|
|
281
174
|
/**
|
|
282
|
-
*
|
|
175
|
+
* Whether end-to-end encryption is active for this call, as reported by the
|
|
176
|
+
* SFU in the join response.
|
|
283
177
|
*/
|
|
284
|
-
|
|
178
|
+
e2eeEnabled$ = duc(this.e2eeEnabledSubject);
|
|
285
179
|
|
|
286
|
-
/**
|
|
287
|
-
|
|
288
|
-
*/
|
|
289
|
-
individualRecording$: Observable<boolean>;
|
|
180
|
+
/** Will provide the user who ended this call. */
|
|
181
|
+
endedBy$ = this.endedBySubject.asObservable();
|
|
290
182
|
|
|
291
|
-
/**
|
|
292
|
-
|
|
293
|
-
*/
|
|
294
|
-
rawRecording$: Observable<boolean>;
|
|
183
|
+
/** Will provide the thumbnails of this call. */
|
|
184
|
+
thumbnails$ = this.thumbnailsSubject.asObservable();
|
|
295
185
|
|
|
296
|
-
/**
|
|
297
|
-
|
|
298
|
-
*/
|
|
299
|
-
session$: Observable<CallSessionResponse | undefined>;
|
|
186
|
+
/** The list of members in the current call. */
|
|
187
|
+
members$ = this.membersSubject.asObservable();
|
|
300
188
|
|
|
301
|
-
/**
|
|
302
|
-
|
|
303
|
-
*/
|
|
304
|
-
settings$: Observable<CallSettingsResponse | undefined>;
|
|
189
|
+
/** The calling state. */
|
|
190
|
+
callingState$ = duc(this.callingStateSubject);
|
|
305
191
|
|
|
306
192
|
/**
|
|
307
|
-
*
|
|
193
|
+
* The time the call session actually started.
|
|
194
|
+
* Useful for displaying the call duration.
|
|
308
195
|
*/
|
|
309
|
-
|
|
196
|
+
startedAt$ = this.startedAtSubject.asObservable();
|
|
310
197
|
|
|
311
198
|
/**
|
|
312
|
-
*
|
|
199
|
+
* The server-side counted number of participants connected to the current call.
|
|
200
|
+
* This number includes the anonymous participants as well.
|
|
313
201
|
*/
|
|
314
|
-
|
|
202
|
+
participantCount$ = duc(this.participantCountSubject);
|
|
315
203
|
|
|
316
204
|
/**
|
|
317
|
-
*
|
|
318
|
-
*
|
|
205
|
+
* The server-side counted number of anonymous participants connected to the current call.
|
|
206
|
+
* This number excludes the regular participants.
|
|
319
207
|
*/
|
|
320
|
-
|
|
208
|
+
anonymousParticipantCount$ = duc(this.anonymousParticipantCountSubject);
|
|
321
209
|
|
|
322
210
|
/**
|
|
323
|
-
*
|
|
211
|
+
* All participants of the current call (this includes the current user and other participants as well),
|
|
212
|
+
* unsorted. This observable only updates when participants join or leave the call.
|
|
324
213
|
*/
|
|
325
|
-
|
|
214
|
+
rawParticipants$ = shared(this.participantsSubject.asObservable());
|
|
326
215
|
|
|
327
|
-
/**
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
216
|
+
/** All participants of the current call sorted according to the current `sortByParticipantsBy` setting. */
|
|
217
|
+
participants$ = shared(
|
|
218
|
+
this.participantsSubject.asObservable().pipe(
|
|
219
|
+
// maintain stable-sort by mutating the participants stored in the original subject
|
|
220
|
+
map((ps) => ps.sort(this.sortParticipantsBy)),
|
|
221
|
+
),
|
|
222
|
+
);
|
|
331
223
|
|
|
332
|
-
/**
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
224
|
+
/** The local participant of the current call. */
|
|
225
|
+
localParticipant$ = shared(
|
|
226
|
+
this.participants$.pipe(map((ps) => ps.find((p) => p.isLocalParticipant))),
|
|
227
|
+
);
|
|
336
228
|
|
|
337
|
-
|
|
229
|
+
/** Remote participants of the current call (all except the local participant). */
|
|
230
|
+
remoteParticipants$ = shared(
|
|
231
|
+
this.participants$.pipe(
|
|
232
|
+
map((ps) => ps.filter((p) => !p.isLocalParticipant)),
|
|
233
|
+
),
|
|
234
|
+
);
|
|
338
235
|
|
|
339
|
-
/**
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
236
|
+
/** Pinned participants of the current call. */
|
|
237
|
+
pinnedParticipants$ = shared(
|
|
238
|
+
this.participants$.pipe(map((ps) => ps.filter((p) => !!p.pin))),
|
|
239
|
+
);
|
|
343
240
|
|
|
344
|
-
/**
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
private closedCaptionsTasks = new Map<string, NodeJS.Timeout>();
|
|
241
|
+
/** The currently elected dominant speaker in the current call. */
|
|
242
|
+
dominantSpeaker$ = shared(
|
|
243
|
+
this.participants$.pipe(map((ps) => ps.find((p) => p.isDominantSpeaker))),
|
|
244
|
+
);
|
|
349
245
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
246
|
+
/** Emits true whenever there is an active screen sharing session within the current call. */
|
|
247
|
+
hasOngoingScreenShare$ = shared(
|
|
248
|
+
this.participants$.pipe(
|
|
249
|
+
map((ps) => ps.some((p) => hasScreenShare(p))),
|
|
250
|
+
distinctUntilChanged(),
|
|
251
|
+
),
|
|
252
|
+
);
|
|
354
253
|
|
|
355
254
|
/**
|
|
356
|
-
*
|
|
255
|
+
* The latest stats report of the current call.
|
|
256
|
+
* When stats gathering is enabled, this observable will emit a new value
|
|
257
|
+
* at a regular (configurable) interval.
|
|
357
258
|
*
|
|
259
|
+
* Consumers of this observable can implement their own batching logic
|
|
260
|
+
* in case they want to show historical stats data.
|
|
358
261
|
*/
|
|
359
|
-
|
|
360
|
-
this.rawParticipants$ = this.participantsSubject
|
|
361
|
-
.asObservable()
|
|
362
|
-
.pipe(shareReplay({ bufferSize: 1, refCount: true }));
|
|
363
|
-
|
|
364
|
-
this.participants$ = this.participantsSubject.asObservable().pipe(
|
|
365
|
-
// maintain stable-sort by mutating the participants stored
|
|
366
|
-
// in the original subject
|
|
367
|
-
map((ps) => ps.sort(this.sortParticipantsBy)),
|
|
368
|
-
shareReplay({ bufferSize: 1, refCount: true }),
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
this.localParticipant$ = this.participants$.pipe(
|
|
372
|
-
map((participants) => participants.find((p) => p.isLocalParticipant)),
|
|
373
|
-
shareReplay({ bufferSize: 1, refCount: true }),
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
this.remoteParticipants$ = this.participants$.pipe(
|
|
377
|
-
map((participants) => participants.filter((p) => !p.isLocalParticipant)),
|
|
378
|
-
shareReplay({ bufferSize: 1, refCount: true }),
|
|
379
|
-
);
|
|
380
|
-
|
|
381
|
-
this.pinnedParticipants$ = this.participants$.pipe(
|
|
382
|
-
map((participants) => participants.filter((p) => !!p.pin)),
|
|
383
|
-
shareReplay({ bufferSize: 1, refCount: true }),
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
this.dominantSpeaker$ = this.participants$.pipe(
|
|
387
|
-
map((participants) => participants.find((p) => p.isDominantSpeaker)),
|
|
388
|
-
shareReplay({ bufferSize: 1, refCount: true }),
|
|
389
|
-
);
|
|
262
|
+
callStatsReport$ = this.callStatsReportSubject.asObservable();
|
|
390
263
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
distinctUntilChanged(),
|
|
394
|
-
shareReplay({ bufferSize: 1, refCount: true }),
|
|
395
|
-
);
|
|
264
|
+
/** The queue of closed captions. */
|
|
265
|
+
closedCaptions$ = this.closedCaptionsSubject.asObservable();
|
|
396
266
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
this.startsAt$ = this.startsAtSubject.asObservable();
|
|
401
|
-
this.startedAt$ = this.startedAtSubject.asObservable();
|
|
402
|
-
this.updatedAt$ = this.updatedAtSubject.asObservable();
|
|
403
|
-
|
|
404
|
-
this.callStatsReport$ = this.callStatsReportSubject.asObservable();
|
|
405
|
-
this.members$ = this.membersSubject.asObservable();
|
|
406
|
-
|
|
407
|
-
// complex objects should work as streams of data
|
|
408
|
-
this.createdBy$ = this.createdBySubject.asObservable();
|
|
409
|
-
this.custom$ = this.customSubject.asObservable();
|
|
410
|
-
this.egress$ = this.egressSubject.asObservable();
|
|
411
|
-
this.ingress$ = this.ingressSubject.asObservable();
|
|
412
|
-
this.session$ = this.sessionSubject.asObservable();
|
|
413
|
-
this.settings$ = this.settingsSubject.asObservable();
|
|
414
|
-
this.endedBy$ = this.endedBySubject.asObservable();
|
|
415
|
-
this.thumbnails$ = this.thumbnailsSubject.asObservable();
|
|
416
|
-
this.closedCaptions$ = this.closedCaptionsSubject.asObservable();
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Creates an Observable from the given subject by piping to the
|
|
420
|
-
* `distinctUntilChanged()` operator.
|
|
421
|
-
*/
|
|
422
|
-
const duc = <T>(
|
|
423
|
-
subject: BehaviorSubject<T>,
|
|
424
|
-
comparator?: (a: T, b: T) => boolean,
|
|
425
|
-
): Observable<T> => subject.pipe(distinctUntilChanged(comparator));
|
|
426
|
-
|
|
427
|
-
// primitive values should only emit once the value they hold changes
|
|
428
|
-
this.anonymousParticipantCount$ = duc(
|
|
429
|
-
this.anonymousParticipantCountSubject,
|
|
430
|
-
);
|
|
431
|
-
this.blockedUserIds$ = duc(
|
|
432
|
-
this.blockedUserIdsSubject,
|
|
433
|
-
RxUtils.isShallowArrayEqual,
|
|
434
|
-
);
|
|
435
|
-
this.backstage$ = duc(this.backstageSubject);
|
|
436
|
-
this.callingState$ = duc(this.callingStateSubject);
|
|
437
|
-
this.ownCapabilities$ = combineLatest([
|
|
267
|
+
/** The list of capabilities of the current user. */
|
|
268
|
+
ownCapabilities$ = shared(
|
|
269
|
+
combineLatest([
|
|
438
270
|
this.ownCapabilitiesSubject,
|
|
439
271
|
this.callGrantsSubject.pipe(startWith(undefined)),
|
|
440
272
|
]).pipe(
|
|
@@ -466,56 +298,42 @@ export class CallState {
|
|
|
466
298
|
|
|
467
299
|
return nextCapabilities;
|
|
468
300
|
}),
|
|
469
|
-
distinctUntilChanged(
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
301
|
+
distinctUntilChanged(isShallowArrayEqual),
|
|
302
|
+
),
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// These are tracks that were delivered to the Subscriber's onTrack event
|
|
306
|
+
// that we couldn't associate with a participant yet.
|
|
307
|
+
// This happens when the participantJoined event hasn't been received yet.
|
|
308
|
+
// We keep these tracks around until we can associate them with a participant.
|
|
309
|
+
private orphanedTracks: OrphanedTrack[] = [];
|
|
310
|
+
|
|
311
|
+
/** The closed captions configuration. */
|
|
312
|
+
private closedCaptionsSettings: ClosedCaptionsSettings | undefined;
|
|
313
|
+
private closedCaptionsTasks = new Map<string, NodeJS.Timeout>();
|
|
479
314
|
|
|
315
|
+
private readonly eventHandlers: Partial<AllEventHandlers>;
|
|
316
|
+
|
|
317
|
+
/** Creates a new instance of the CallState class. */
|
|
318
|
+
constructor() {
|
|
480
319
|
this.eventHandlers = {
|
|
481
|
-
// these events are not updating the call state:
|
|
482
|
-
'call.frame_recording_ready': undefined,
|
|
483
|
-
'call.kicked_user': undefined,
|
|
484
|
-
'call.moderation_blur': undefined,
|
|
485
|
-
'call.moderation_warning': undefined,
|
|
486
|
-
'call.permission_request': undefined,
|
|
487
|
-
'call.recording_ready': undefined,
|
|
488
|
-
'call.rtmp_broadcast_failed': undefined,
|
|
489
|
-
'call.rtmp_broadcast_started': undefined,
|
|
490
|
-
'call.rtmp_broadcast_stopped': undefined,
|
|
491
|
-
'call.stats_report_ready': undefined,
|
|
492
|
-
'call.transcription_ready': undefined,
|
|
493
|
-
'call.user_feedback_submitted': undefined,
|
|
494
|
-
'call.user_muted': undefined,
|
|
495
|
-
'connection.error': undefined,
|
|
496
|
-
'connection.ok': undefined,
|
|
497
|
-
'health.check': undefined,
|
|
498
|
-
'user.updated': undefined,
|
|
499
|
-
custom: undefined,
|
|
500
|
-
|
|
501
|
-
// events that update call state:
|
|
502
320
|
'call.accepted': (e) => this.updateFromCallResponse(e.call),
|
|
503
321
|
'call.blocked_user': this.blockUser,
|
|
504
322
|
'call.closed_caption': this.updateFromClosedCaptions,
|
|
505
323
|
'call.closed_captions_failed': () => {
|
|
506
|
-
|
|
324
|
+
setCurrentValue(this.captioningSubject, false);
|
|
507
325
|
},
|
|
508
326
|
'call.closed_captions_started': () => {
|
|
509
|
-
|
|
327
|
+
setCurrentValue(this.captioningSubject, true);
|
|
510
328
|
},
|
|
511
329
|
'call.closed_captions_stopped': () => {
|
|
512
|
-
|
|
330
|
+
setCurrentValue(this.captioningSubject, false);
|
|
513
331
|
},
|
|
514
332
|
'call.created': (e) => this.updateFromCallResponse(e.call),
|
|
515
333
|
'call.deleted': (e) => this.updateFromCallResponse(e.call),
|
|
516
334
|
'call.ended': (e) => {
|
|
517
335
|
this.updateFromCallResponse(e.call);
|
|
518
|
-
|
|
336
|
+
setCurrentValue(this.endedBySubject, e.user);
|
|
519
337
|
},
|
|
520
338
|
'call.frame_recording_failed': (e) => {
|
|
521
339
|
this.updateFromCallResponse(e.call);
|
|
@@ -562,22 +380,20 @@ export class CallState {
|
|
|
562
380
|
'call.session_participant_left': this.updateFromSessionParticipantLeft,
|
|
563
381
|
'call.session_started': (e) => this.updateFromCallResponse(e.call),
|
|
564
382
|
'call.transcription_started': () => {
|
|
565
|
-
|
|
383
|
+
setCurrentValue(this.transcribingSubject, true);
|
|
566
384
|
},
|
|
567
385
|
'call.transcription_stopped': () => {
|
|
568
|
-
|
|
386
|
+
setCurrentValue(this.transcribingSubject, false);
|
|
569
387
|
},
|
|
570
388
|
'call.transcription_failed': () => {
|
|
571
|
-
|
|
389
|
+
setCurrentValue(this.transcribingSubject, false);
|
|
572
390
|
},
|
|
573
391
|
'call.unblocked_user': this.unblockUser,
|
|
574
392
|
'call.updated': (e) => this.updateFromCallResponse(e.call),
|
|
575
|
-
};
|
|
393
|
+
} satisfies CallStateEventHandlers;
|
|
576
394
|
}
|
|
577
395
|
|
|
578
|
-
/**
|
|
579
|
-
* Runs the cleanup tasks.
|
|
580
|
-
*/
|
|
396
|
+
/** Runs the cleanup tasks. */
|
|
581
397
|
dispose = () => {
|
|
582
398
|
for (const [ccKey, taskId] of this.closedCaptionsTasks.entries()) {
|
|
583
399
|
clearTimeout(taskId);
|
|
@@ -595,43 +411,20 @@ export class CallState {
|
|
|
595
411
|
setSortParticipantsBy = (comparator: Comparator<StreamVideoParticipant>) => {
|
|
596
412
|
this.sortParticipantsBy = comparator;
|
|
597
413
|
// trigger re-sorting of participants
|
|
598
|
-
|
|
414
|
+
setCurrentValue(this.participantsSubject, (ps) => ps);
|
|
599
415
|
};
|
|
600
416
|
|
|
601
|
-
/**
|
|
602
|
-
* Returns the comparator currently used to sort the participants.
|
|
603
|
-
*/
|
|
417
|
+
/** Returns the comparator currently used to sort the participants. */
|
|
604
418
|
getSortParticipantsBy = (): Comparator<StreamVideoParticipant> => {
|
|
605
419
|
return this.sortParticipantsBy;
|
|
606
420
|
};
|
|
607
421
|
|
|
608
|
-
/**
|
|
609
|
-
* Gets the current value of an observable, or undefined if the observable has
|
|
610
|
-
* not emitted a value yet.
|
|
611
|
-
*
|
|
612
|
-
* @param observable$ the observable to get the value from.
|
|
613
|
-
*/
|
|
614
|
-
getCurrentValue = RxUtils.getCurrentValue;
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* Updates the value of the provided Subject.
|
|
618
|
-
* An `update` can either be a new value or a function which takes
|
|
619
|
-
* the current value and returns a new value.
|
|
620
|
-
*
|
|
621
|
-
* @internal
|
|
622
|
-
*
|
|
623
|
-
* @param subject the subject to update.
|
|
624
|
-
* @param update the update to apply to the subject.
|
|
625
|
-
* @return the updated value.
|
|
626
|
-
*/
|
|
627
|
-
setCurrentValue = RxUtils.setCurrentValue;
|
|
628
|
-
|
|
629
422
|
/**
|
|
630
423
|
* The server-side counted number of participants connected to the current call.
|
|
631
424
|
* This number includes the anonymous participants as well.
|
|
632
425
|
*/
|
|
633
426
|
get participantCount() {
|
|
634
|
-
return
|
|
427
|
+
return getCurrentValue(this.participantCountSubject);
|
|
635
428
|
}
|
|
636
429
|
|
|
637
430
|
/**
|
|
@@ -641,7 +434,7 @@ export class CallState {
|
|
|
641
434
|
* @param count the number of participants.
|
|
642
435
|
*/
|
|
643
436
|
setParticipantCount = (count: Patch<number>) => {
|
|
644
|
-
return
|
|
437
|
+
return setCurrentValue(this.participantCountSubject, count);
|
|
645
438
|
};
|
|
646
439
|
|
|
647
440
|
/**
|
|
@@ -649,7 +442,7 @@ export class CallState {
|
|
|
649
442
|
* Useful for displaying the call duration.
|
|
650
443
|
*/
|
|
651
444
|
get startedAt() {
|
|
652
|
-
return
|
|
445
|
+
return getCurrentValue(this.startedAtSubject);
|
|
653
446
|
}
|
|
654
447
|
|
|
655
448
|
/**
|
|
@@ -659,14 +452,12 @@ export class CallState {
|
|
|
659
452
|
* @param startedAt the time the call session actually started.
|
|
660
453
|
*/
|
|
661
454
|
setStartedAt = (startedAt: Patch<Date | undefined>) => {
|
|
662
|
-
return
|
|
455
|
+
return setCurrentValue(this.startedAtSubject, startedAt);
|
|
663
456
|
};
|
|
664
457
|
|
|
665
|
-
/**
|
|
666
|
-
* Returns whether closed captions are enabled in the current call.
|
|
667
|
-
*/
|
|
458
|
+
/** Returns whether closed captions are enabled in the current call. */
|
|
668
459
|
get captioning() {
|
|
669
|
-
return
|
|
460
|
+
return getCurrentValue(this.captioningSubject);
|
|
670
461
|
}
|
|
671
462
|
|
|
672
463
|
/**
|
|
@@ -676,7 +467,7 @@ export class CallState {
|
|
|
676
467
|
* @param captioning the closed captioning state.
|
|
677
468
|
*/
|
|
678
469
|
setCaptioning = (captioning: boolean) => {
|
|
679
|
-
return
|
|
470
|
+
return updateValue(this.captioningSubject, captioning);
|
|
680
471
|
};
|
|
681
472
|
|
|
682
473
|
/**
|
|
@@ -684,7 +475,7 @@ export class CallState {
|
|
|
684
475
|
* This number includes the anonymous participants as well.
|
|
685
476
|
*/
|
|
686
477
|
get anonymousParticipantCount() {
|
|
687
|
-
return
|
|
478
|
+
return getCurrentValue(this.anonymousParticipantCountSubject);
|
|
688
479
|
}
|
|
689
480
|
|
|
690
481
|
/**
|
|
@@ -694,21 +485,17 @@ export class CallState {
|
|
|
694
485
|
* @param count the number of anonymous participants.
|
|
695
486
|
*/
|
|
696
487
|
setAnonymousParticipantCount = (count: Patch<number>) => {
|
|
697
|
-
return
|
|
488
|
+
return setCurrentValue(this.anonymousParticipantCountSubject, count);
|
|
698
489
|
};
|
|
699
490
|
|
|
700
|
-
/**
|
|
701
|
-
* The list of participants in the current call.
|
|
702
|
-
*/
|
|
491
|
+
/** The list of participants in the current call. */
|
|
703
492
|
get participants() {
|
|
704
|
-
return
|
|
493
|
+
return getCurrentValue(this.participants$);
|
|
705
494
|
}
|
|
706
495
|
|
|
707
|
-
/**
|
|
708
|
-
* The stable list of participants in the current call, unsorted.
|
|
709
|
-
*/
|
|
496
|
+
/** The stable list of participants in the current call, unsorted. */
|
|
710
497
|
get rawParticipants() {
|
|
711
|
-
return
|
|
498
|
+
return getCurrentValue(this.rawParticipants$);
|
|
712
499
|
}
|
|
713
500
|
|
|
714
501
|
/**
|
|
@@ -731,49 +518,37 @@ export class CallState {
|
|
|
731
518
|
* @param participants the list of participants.
|
|
732
519
|
*/
|
|
733
520
|
setParticipants = (participants: Patch<StreamVideoParticipant[]>) => {
|
|
734
|
-
return
|
|
521
|
+
return setCurrentValue(this.participantsSubject, participants);
|
|
735
522
|
};
|
|
736
523
|
|
|
737
|
-
/**
|
|
738
|
-
* The local participant in the current call.
|
|
739
|
-
*/
|
|
524
|
+
/** The local participant in the current call. */
|
|
740
525
|
get localParticipant() {
|
|
741
|
-
return
|
|
526
|
+
return getCurrentValue(this.localParticipant$);
|
|
742
527
|
}
|
|
743
528
|
|
|
744
|
-
/**
|
|
745
|
-
* The list of remote participants in the current call.
|
|
746
|
-
*/
|
|
529
|
+
/** The list of remote participants in the current call. */
|
|
747
530
|
get remoteParticipants() {
|
|
748
|
-
return
|
|
531
|
+
return getCurrentValue(this.remoteParticipants$);
|
|
749
532
|
}
|
|
750
533
|
|
|
751
|
-
/**
|
|
752
|
-
* The dominant speaker in the current call.
|
|
753
|
-
*/
|
|
534
|
+
/** The dominant speaker in the current call. */
|
|
754
535
|
get dominantSpeaker() {
|
|
755
|
-
return
|
|
536
|
+
return getCurrentValue(this.dominantSpeaker$);
|
|
756
537
|
}
|
|
757
538
|
|
|
758
|
-
/**
|
|
759
|
-
* The list of pinned participants in the current call.
|
|
760
|
-
*/
|
|
539
|
+
/** The list of pinned participants in the current call. */
|
|
761
540
|
get pinnedParticipants() {
|
|
762
|
-
return
|
|
541
|
+
return getCurrentValue(this.pinnedParticipants$);
|
|
763
542
|
}
|
|
764
543
|
|
|
765
|
-
/**
|
|
766
|
-
* Tell if there is an ongoing screen share in this call.
|
|
767
|
-
*/
|
|
544
|
+
/** Tell if there is an ongoing screen share in this call. */
|
|
768
545
|
get hasOngoingScreenShare() {
|
|
769
|
-
return
|
|
546
|
+
return getCurrentValue(this.hasOngoingScreenShare$);
|
|
770
547
|
}
|
|
771
548
|
|
|
772
|
-
/**
|
|
773
|
-
* The calling state.
|
|
774
|
-
*/
|
|
549
|
+
/** The calling state. */
|
|
775
550
|
get callingState() {
|
|
776
|
-
return
|
|
551
|
+
return getCurrentValue(this.callingStateSubject);
|
|
777
552
|
}
|
|
778
553
|
|
|
779
554
|
/**
|
|
@@ -783,14 +558,12 @@ export class CallState {
|
|
|
783
558
|
* @param state the new calling state.
|
|
784
559
|
*/
|
|
785
560
|
setCallingState = (state: Patch<CallingState>) => {
|
|
786
|
-
return
|
|
561
|
+
return setCurrentValue(this.callingStateSubject, state);
|
|
787
562
|
};
|
|
788
563
|
|
|
789
|
-
/**
|
|
790
|
-
* The call stats report.
|
|
791
|
-
*/
|
|
564
|
+
/** The call stats report. */
|
|
792
565
|
get callStatsReport() {
|
|
793
|
-
return
|
|
566
|
+
return getCurrentValue(this.callStatsReportSubject);
|
|
794
567
|
}
|
|
795
568
|
|
|
796
569
|
/**
|
|
@@ -808,14 +581,12 @@ export class CallState {
|
|
|
808
581
|
* @param report the report to set.
|
|
809
582
|
*/
|
|
810
583
|
setCallStatsReport = (report: Patch<CallStatsReport | undefined>) => {
|
|
811
|
-
return
|
|
584
|
+
return setCurrentValue(this.callStatsReportSubject, report);
|
|
812
585
|
};
|
|
813
586
|
|
|
814
|
-
/**
|
|
815
|
-
* The members of the current call.
|
|
816
|
-
*/
|
|
587
|
+
/** The members of the current call. */
|
|
817
588
|
get members() {
|
|
818
|
-
return
|
|
589
|
+
return getCurrentValue(this.membersSubject);
|
|
819
590
|
}
|
|
820
591
|
|
|
821
592
|
/**
|
|
@@ -825,14 +596,12 @@ export class CallState {
|
|
|
825
596
|
* @param members the members to set.
|
|
826
597
|
*/
|
|
827
598
|
setMembers = (members: Patch<MemberResponse[]>) => {
|
|
828
|
-
|
|
599
|
+
setCurrentValue(this.membersSubject, members);
|
|
829
600
|
};
|
|
830
601
|
|
|
831
|
-
/**
|
|
832
|
-
* The capabilities of the current user for the current call.
|
|
833
|
-
*/
|
|
602
|
+
/** The capabilities of the current user for the current call. */
|
|
834
603
|
get ownCapabilities() {
|
|
835
|
-
return
|
|
604
|
+
return getCurrentValue(this.ownCapabilities$);
|
|
836
605
|
}
|
|
837
606
|
|
|
838
607
|
/**
|
|
@@ -842,7 +611,7 @@ export class CallState {
|
|
|
842
611
|
* @param capabilities the capabilities to set.
|
|
843
612
|
*/
|
|
844
613
|
setOwnCapabilities = (capabilities: Patch<OwnCapability[]>) => {
|
|
845
|
-
return
|
|
614
|
+
return setCurrentValue(this.ownCapabilitiesSubject, capabilities);
|
|
846
615
|
};
|
|
847
616
|
|
|
848
617
|
/**
|
|
@@ -852,14 +621,12 @@ export class CallState {
|
|
|
852
621
|
* @param grants the grants to set.
|
|
853
622
|
*/
|
|
854
623
|
setCallGrants = (grants: Patch<CallGrants>) => {
|
|
855
|
-
return
|
|
624
|
+
return setCurrentValue(this.callGrantsSubject, grants);
|
|
856
625
|
};
|
|
857
626
|
|
|
858
|
-
/**
|
|
859
|
-
* The backstage state.
|
|
860
|
-
*/
|
|
627
|
+
/** The backstage state. */
|
|
861
628
|
get backstage() {
|
|
862
|
-
return
|
|
629
|
+
return getCurrentValue(this.backstageSubject);
|
|
863
630
|
}
|
|
864
631
|
|
|
865
632
|
/**
|
|
@@ -867,28 +634,22 @@ export class CallState {
|
|
|
867
634
|
* @param backstage the backstage state.
|
|
868
635
|
*/
|
|
869
636
|
setBackstage = (backstage: Patch<boolean>) => {
|
|
870
|
-
return
|
|
637
|
+
return setCurrentValue(this.backstageSubject, backstage);
|
|
871
638
|
};
|
|
872
639
|
|
|
873
|
-
/**
|
|
874
|
-
* Will provide the list of blocked user IDs.
|
|
875
|
-
*/
|
|
640
|
+
/** Will provide the list of blocked user IDs. */
|
|
876
641
|
get blockedUserIds() {
|
|
877
|
-
return
|
|
642
|
+
return getCurrentValue(this.blockedUserIdsSubject);
|
|
878
643
|
}
|
|
879
644
|
|
|
880
|
-
/**
|
|
881
|
-
* Will provide the time when this call has been created.
|
|
882
|
-
*/
|
|
645
|
+
/** Will provide the time when this call has been created. */
|
|
883
646
|
get createdAt() {
|
|
884
|
-
return
|
|
647
|
+
return getCurrentValue(this.createdAtSubject);
|
|
885
648
|
}
|
|
886
649
|
|
|
887
|
-
/**
|
|
888
|
-
* Will provide the time when this call has been ended.
|
|
889
|
-
*/
|
|
650
|
+
/** Will provide the time when this call has been ended. */
|
|
890
651
|
get endedAt() {
|
|
891
|
-
return
|
|
652
|
+
return getCurrentValue(this.endedAtSubject);
|
|
892
653
|
}
|
|
893
654
|
|
|
894
655
|
/**
|
|
@@ -896,119 +657,91 @@ export class CallState {
|
|
|
896
657
|
* @param endedAt the time when this call has been ended.
|
|
897
658
|
*/
|
|
898
659
|
setEndedAt = (endedAt: Patch<Date | undefined>) => {
|
|
899
|
-
return
|
|
660
|
+
return setCurrentValue(this.endedAtSubject, endedAt);
|
|
900
661
|
};
|
|
901
662
|
|
|
902
|
-
/**
|
|
903
|
-
* Will provide the time when this call has been scheduled to start.
|
|
904
|
-
*/
|
|
663
|
+
/** Will provide the time when this call has been scheduled to start. */
|
|
905
664
|
get startsAt() {
|
|
906
|
-
return
|
|
665
|
+
return getCurrentValue(this.startsAtSubject);
|
|
907
666
|
}
|
|
908
667
|
|
|
909
|
-
/**
|
|
910
|
-
* Will provide the time when this call has been updated.
|
|
911
|
-
*/
|
|
668
|
+
/** Will provide the time when this call has been updated. */
|
|
912
669
|
get updatedAt() {
|
|
913
|
-
return
|
|
670
|
+
return getCurrentValue(this.updatedAtSubject);
|
|
914
671
|
}
|
|
915
672
|
|
|
916
|
-
/**
|
|
917
|
-
* Will provide the user who created this call.
|
|
918
|
-
*/
|
|
673
|
+
/** Will provide the user who created this call. */
|
|
919
674
|
get createdBy() {
|
|
920
|
-
return
|
|
675
|
+
return getCurrentValue(this.createdBySubject);
|
|
921
676
|
}
|
|
922
677
|
|
|
923
|
-
/**
|
|
924
|
-
* Will provide the custom data of this call.
|
|
925
|
-
*/
|
|
678
|
+
/** Will provide the custom data of this call. */
|
|
926
679
|
get custom() {
|
|
927
|
-
return
|
|
680
|
+
return getCurrentValue(this.customSubject);
|
|
928
681
|
}
|
|
929
682
|
|
|
930
683
|
/**
|
|
931
684
|
* Will provide the egress data of this call.
|
|
932
685
|
*/
|
|
933
686
|
get egress() {
|
|
934
|
-
return
|
|
687
|
+
return getCurrentValue(this.egressSubject);
|
|
935
688
|
}
|
|
936
689
|
|
|
937
|
-
/**
|
|
938
|
-
* Will provide the ingress data of this call.
|
|
939
|
-
*/
|
|
690
|
+
/** Will provide the ingress data of this call. */
|
|
940
691
|
get ingress() {
|
|
941
|
-
return
|
|
692
|
+
return getCurrentValue(this.ingressSubject);
|
|
942
693
|
}
|
|
943
694
|
|
|
944
|
-
/**
|
|
945
|
-
* Will provide the composite recording state of this call.
|
|
946
|
-
*/
|
|
695
|
+
/** Will provide the composite recording state of this call. */
|
|
947
696
|
get recording() {
|
|
948
|
-
return
|
|
697
|
+
return getCurrentValue(this.recordingSubject);
|
|
949
698
|
}
|
|
950
699
|
|
|
951
|
-
/**
|
|
952
|
-
* Will provide the individual recording state of this call.
|
|
953
|
-
*/
|
|
700
|
+
/** Will provide the individual recording state of this call. */
|
|
954
701
|
get individualRecording() {
|
|
955
|
-
return
|
|
702
|
+
return getCurrentValue(this.individualRecordingSubject);
|
|
956
703
|
}
|
|
957
704
|
|
|
958
|
-
/**
|
|
959
|
-
* Will provide the raw recording state of this call.
|
|
960
|
-
*/
|
|
705
|
+
/** Will provide the raw recording state of this call. */
|
|
961
706
|
get rawRecording() {
|
|
962
|
-
return
|
|
707
|
+
return getCurrentValue(this.rawRecordingSubject);
|
|
963
708
|
}
|
|
964
709
|
|
|
965
|
-
/**
|
|
966
|
-
* Will provide the session data of this call.
|
|
967
|
-
*/
|
|
710
|
+
/** Will provide the session data of this call. */
|
|
968
711
|
get session() {
|
|
969
|
-
return
|
|
712
|
+
return getCurrentValue(this.sessionSubject);
|
|
970
713
|
}
|
|
971
714
|
|
|
972
|
-
/**
|
|
973
|
-
* Will provide the settings of this call.
|
|
974
|
-
*/
|
|
715
|
+
/** Will provide the settings of this call. */
|
|
975
716
|
get settings() {
|
|
976
|
-
return
|
|
717
|
+
return getCurrentValue(this.settingsSubject);
|
|
977
718
|
}
|
|
978
719
|
|
|
979
|
-
/**
|
|
980
|
-
* Will provide the transcribing state of this call.
|
|
981
|
-
*/
|
|
720
|
+
/** Will provide the transcribing state of this call. */
|
|
982
721
|
get transcribing() {
|
|
983
|
-
return
|
|
722
|
+
return getCurrentValue(this.transcribingSubject);
|
|
984
723
|
}
|
|
985
724
|
|
|
986
|
-
/**
|
|
987
|
-
* Whether end-to-end encryption is active for this call.
|
|
988
|
-
*/
|
|
725
|
+
/** Whether end-to-end encryption is active for this call. */
|
|
989
726
|
get e2eeEnabled() {
|
|
990
|
-
return
|
|
727
|
+
return getCurrentValue(this.e2eeEnabledSubject);
|
|
991
728
|
}
|
|
992
729
|
|
|
993
|
-
/**
|
|
994
|
-
* Will provide the user who ended this call.
|
|
995
|
-
*/
|
|
730
|
+
/** Will provide the user who ended this call. */
|
|
996
731
|
get endedBy() {
|
|
997
|
-
return
|
|
732
|
+
return getCurrentValue(this.endedBySubject);
|
|
998
733
|
}
|
|
999
734
|
|
|
1000
735
|
/**
|
|
1001
736
|
* Will provide the thumbnails of this call, if enabled in the call settings.
|
|
1002
737
|
*/
|
|
1003
738
|
get thumbnails() {
|
|
1004
|
-
return
|
|
739
|
+
return getCurrentValue(this.thumbnailsSubject);
|
|
1005
740
|
}
|
|
1006
741
|
|
|
1007
|
-
/**
|
|
1008
|
-
* Returns the current queue of closed captions.
|
|
1009
|
-
*/
|
|
742
|
+
/** Returns the current queue of closed captions. */
|
|
1010
743
|
get closedCaptions() {
|
|
1011
|
-
return
|
|
744
|
+
return getCurrentValue(this.closedCaptionsSubject);
|
|
1012
745
|
}
|
|
1013
746
|
|
|
1014
747
|
/**
|
|
@@ -1023,9 +756,7 @@ export class CallState {
|
|
|
1023
756
|
return this.participants.find((p) => p.sessionId === sessionId);
|
|
1024
757
|
};
|
|
1025
758
|
|
|
1026
|
-
/**
|
|
1027
|
-
* Returns a new lookup table of participants indexed by their session ID.
|
|
1028
|
-
*/
|
|
759
|
+
/** Returns a new lookup table of participants indexed by their session ID. */
|
|
1029
760
|
getParticipantLookupBySessionId = () => {
|
|
1030
761
|
return this.participants.reduce<{
|
|
1031
762
|
[sessionId: string]: StreamVideoParticipant | undefined;
|
|
@@ -1317,39 +1048,63 @@ export class CallState {
|
|
|
1317
1048
|
*/
|
|
1318
1049
|
updateFromCallResponse = (call: CallResponse) => {
|
|
1319
1050
|
this.setBackstage(call.backstage);
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1051
|
+
setCurrentValue(this.blockedUserIdsSubject, call.blocked_user_ids);
|
|
1052
|
+
setCurrentValue(this.createdAtSubject, new Date(call.created_at));
|
|
1053
|
+
setCurrentValue(this.updatedAtSubject, new Date(call.updated_at));
|
|
1054
|
+
setCurrentValue(
|
|
1324
1055
|
this.startsAtSubject,
|
|
1325
1056
|
call.starts_at ? new Date(call.starts_at) : undefined,
|
|
1326
1057
|
);
|
|
1327
1058
|
this.setEndedAt(call.ended_at ? new Date(call.ended_at) : undefined);
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1059
|
+
setCurrentValue(this.createdBySubject, call.created_by);
|
|
1060
|
+
setCurrentValue(this.customSubject, call.custom);
|
|
1061
|
+
setCurrentValue(this.egressSubject, call.egress);
|
|
1062
|
+
setCurrentValue(this.ingressSubject, call.ingress);
|
|
1332
1063
|
const { individual_recording, composite_recording, raw_recording } =
|
|
1333
1064
|
call.egress;
|
|
1334
|
-
|
|
1065
|
+
setCurrentValue(
|
|
1335
1066
|
this.recordingSubject,
|
|
1336
1067
|
call.recording || composite_recording?.status === 'running',
|
|
1337
1068
|
);
|
|
1338
|
-
|
|
1069
|
+
setCurrentValue(
|
|
1339
1070
|
this.individualRecordingSubject,
|
|
1340
1071
|
individual_recording?.status === 'running',
|
|
1341
1072
|
);
|
|
1342
|
-
|
|
1073
|
+
setCurrentValue(
|
|
1343
1074
|
this.rawRecordingSubject,
|
|
1344
1075
|
raw_recording?.status === 'running',
|
|
1345
1076
|
);
|
|
1346
1077
|
|
|
1347
|
-
const s =
|
|
1078
|
+
const s = setCurrentValue(this.sessionSubject, call.session);
|
|
1348
1079
|
this.updateParticipantCountFromSession(s);
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1080
|
+
setCurrentValue(this.settingsSubject, call.settings);
|
|
1081
|
+
setCurrentValue(this.transcribingSubject, call.transcribing);
|
|
1082
|
+
setCurrentValue(this.captioningSubject, call.captioning);
|
|
1083
|
+
setCurrentValue(this.thumbnailsSubject, call.thumbnails);
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* Merges a polled ring state into the current session. Only the ring fields
|
|
1088
|
+
* are touched: the endpoint does not return the session roster.
|
|
1089
|
+
*
|
|
1090
|
+
* @internal
|
|
1091
|
+
*
|
|
1092
|
+
* @param ringState the ring state as returned by the coordinator.
|
|
1093
|
+
*/
|
|
1094
|
+
updateFromRingState = (ringState: GetCallRingStateResponse) => {
|
|
1095
|
+
setCurrentValue(this.sessionSubject, (session) => {
|
|
1096
|
+
if (!session || session.id !== ringState.session_id) return session;
|
|
1097
|
+
return {
|
|
1098
|
+
...session,
|
|
1099
|
+
accepted_by: ringState.accepted_by,
|
|
1100
|
+
rejected_by: ringState.rejected_by,
|
|
1101
|
+
missed_by: ringState.missed_by,
|
|
1102
|
+
ended_at: ringState.session_ended_at ?? session.ended_at,
|
|
1103
|
+
};
|
|
1104
|
+
});
|
|
1105
|
+
if (ringState.call_ended_at) {
|
|
1106
|
+
this.setEndedAt(new Date(ringState.call_ended_at));
|
|
1107
|
+
}
|
|
1353
1108
|
};
|
|
1354
1109
|
|
|
1355
1110
|
/**
|
|
@@ -1396,26 +1151,26 @@ export class CallState {
|
|
|
1396
1151
|
this.setAnonymousParticipantCount(participantCount?.anonymous || 0);
|
|
1397
1152
|
this.setStartedAt(startedAt ? Timestamp.toDate(startedAt) : new Date());
|
|
1398
1153
|
this.setServerSidePins(pins);
|
|
1399
|
-
|
|
1154
|
+
setCurrentValue(this.e2eeEnabledSubject, e2EeEnabled);
|
|
1400
1155
|
};
|
|
1401
1156
|
|
|
1402
1157
|
private updateFromMemberRemoved = (event: CallMemberRemovedEvent) => {
|
|
1403
1158
|
this.updateFromCallResponse(event.call);
|
|
1404
|
-
|
|
1159
|
+
setCurrentValue(this.membersSubject, (members) =>
|
|
1405
1160
|
members.filter((m) => event.members.indexOf(m.user_id) === -1),
|
|
1406
1161
|
);
|
|
1407
1162
|
};
|
|
1408
1163
|
|
|
1409
1164
|
private updateFromMemberAdded = (event: CallMemberAddedEvent) => {
|
|
1410
1165
|
this.updateFromCallResponse(event.call);
|
|
1411
|
-
|
|
1166
|
+
setCurrentValue(this.membersSubject, (members) => [
|
|
1412
1167
|
...members,
|
|
1413
1168
|
...event.members,
|
|
1414
1169
|
]);
|
|
1415
1170
|
};
|
|
1416
1171
|
|
|
1417
1172
|
private updateFromHLSBroadcastStopped = () => {
|
|
1418
|
-
|
|
1173
|
+
setCurrentValue(this.egressSubject, (egress = defaultEgress) => ({
|
|
1419
1174
|
...egress,
|
|
1420
1175
|
broadcasting: false,
|
|
1421
1176
|
hls: {
|
|
@@ -1426,7 +1181,7 @@ export class CallState {
|
|
|
1426
1181
|
};
|
|
1427
1182
|
|
|
1428
1183
|
private updateFromHLSBroadcastingFailed = () => {
|
|
1429
|
-
|
|
1184
|
+
setCurrentValue(this.egressSubject, (egress = defaultEgress) => ({
|
|
1430
1185
|
...egress,
|
|
1431
1186
|
broadcasting: false,
|
|
1432
1187
|
hls: {
|
|
@@ -1442,11 +1197,11 @@ export class CallState {
|
|
|
1442
1197
|
) => {
|
|
1443
1198
|
// handle the legacy format, where `type` is absent in the emitted events
|
|
1444
1199
|
if (type === undefined || type === 'composite') {
|
|
1445
|
-
|
|
1200
|
+
setCurrentValue(this.recordingSubject, running);
|
|
1446
1201
|
} else if (type === 'individual') {
|
|
1447
|
-
|
|
1202
|
+
setCurrentValue(this.individualRecordingSubject, running);
|
|
1448
1203
|
} else if (type === 'raw') {
|
|
1449
|
-
|
|
1204
|
+
setCurrentValue(this.rawRecordingSubject, running);
|
|
1450
1205
|
} else {
|
|
1451
1206
|
ensureExhausted(type, 'Unknown recording type');
|
|
1452
1207
|
}
|
|
@@ -1469,7 +1224,7 @@ export class CallState {
|
|
|
1469
1224
|
private updateFromSessionParticipantCountUpdate = (
|
|
1470
1225
|
event: CallSessionParticipantCountsUpdatedEvent,
|
|
1471
1226
|
) => {
|
|
1472
|
-
const s =
|
|
1227
|
+
const s = setCurrentValue(this.sessionSubject, (session) => {
|
|
1473
1228
|
if (!session) return session;
|
|
1474
1229
|
return {
|
|
1475
1230
|
...session,
|
|
@@ -1483,7 +1238,7 @@ export class CallState {
|
|
|
1483
1238
|
private updateFromSessionParticipantLeft = (
|
|
1484
1239
|
event: CallSessionParticipantLeftEvent,
|
|
1485
1240
|
) => {
|
|
1486
|
-
const s =
|
|
1241
|
+
const s = setCurrentValue(this.sessionSubject, (session) => {
|
|
1487
1242
|
if (!session) return session;
|
|
1488
1243
|
const { participants, participants_count_by_role } = session;
|
|
1489
1244
|
const { user, user_session_id } = event.participant;
|
|
@@ -1507,7 +1262,7 @@ export class CallState {
|
|
|
1507
1262
|
private updateFromSessionParticipantJoined = (
|
|
1508
1263
|
event: CallSessionParticipantJoinedEvent,
|
|
1509
1264
|
) => {
|
|
1510
|
-
const s =
|
|
1265
|
+
const s = setCurrentValue(this.sessionSubject, (session) => {
|
|
1511
1266
|
if (!session) return session;
|
|
1512
1267
|
const { participants, participants_count_by_role } = session;
|
|
1513
1268
|
const { user, user_session_id } = event.participant;
|
|
@@ -1547,7 +1302,7 @@ export class CallState {
|
|
|
1547
1302
|
event: CallMemberUpdatedEvent | CallMemberUpdatedPermissionEvent,
|
|
1548
1303
|
) => {
|
|
1549
1304
|
this.updateFromCallResponse(event.call);
|
|
1550
|
-
|
|
1305
|
+
setCurrentValue(this.membersSubject, (members) =>
|
|
1551
1306
|
members.map((member) => {
|
|
1552
1307
|
const memberUpdate = event.members.find(
|
|
1553
1308
|
(m) => m.user_id === member.user_id,
|
|
@@ -1577,14 +1332,14 @@ export class CallState {
|
|
|
1577
1332
|
};
|
|
1578
1333
|
|
|
1579
1334
|
private unblockUser = (event: UnblockedUserEvent) => {
|
|
1580
|
-
|
|
1335
|
+
setCurrentValue(this.blockedUserIdsSubject, (current) => {
|
|
1581
1336
|
if (!current) return current;
|
|
1582
1337
|
return current.filter((id) => id !== event.user.id);
|
|
1583
1338
|
});
|
|
1584
1339
|
};
|
|
1585
1340
|
|
|
1586
1341
|
private blockUser = (event: BlockedUserEvent) => {
|
|
1587
|
-
|
|
1342
|
+
setCurrentValue(this.blockedUserIdsSubject, (current) => [
|
|
1588
1343
|
...(current || []),
|
|
1589
1344
|
event.user.id,
|
|
1590
1345
|
]);
|
|
@@ -1597,7 +1352,7 @@ export class CallState {
|
|
|
1597
1352
|
};
|
|
1598
1353
|
|
|
1599
1354
|
private updateFromClosedCaptions = (event: ClosedCaptionEvent) => {
|
|
1600
|
-
|
|
1355
|
+
setCurrentValue(this.closedCaptionsSubject, (queue) => {
|
|
1601
1356
|
const { closed_caption } = event;
|
|
1602
1357
|
|
|
1603
1358
|
const keyOf = (c: CallClosedCaption) => `${c.speaker_id}/${c.start_time}`;
|
|
@@ -1613,7 +1368,7 @@ export class CallState {
|
|
|
1613
1368
|
// schedule the removal of the closed caption after the retention time
|
|
1614
1369
|
if (visibilityDurationMs > 0) {
|
|
1615
1370
|
const taskId = setTimeout(() => {
|
|
1616
|
-
|
|
1371
|
+
setCurrentValue(this.closedCaptionsSubject, (captions) =>
|
|
1617
1372
|
captions.filter((caption) => caption !== closed_caption),
|
|
1618
1373
|
);
|
|
1619
1374
|
this.closedCaptionsTasks.delete(currentKey);
|