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