@stream-io/video-client 0.3.24 → 0.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/index.browser.es.js +82 -27
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +84 -28
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +82 -27
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +12 -3
- package/dist/src/gen/coordinator/index.d.ts +289 -142
- package/dist/src/sorting/__tests__/presets.test.d.ts +1 -0
- package/dist/src/sorting/presets.d.ts +5 -0
- package/dist/src/store/CallState.d.ts +12 -3
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/Call.ts +42 -4
- package/src/__tests__/server-side/call-types.test.ts +3 -7
- package/src/gen/coordinator/index.ts +295 -147
- package/src/sorting/__tests__/presets.test.ts +54 -0
- package/src/sorting/presets.ts +28 -0
- package/src/store/CallState.ts +28 -7
- package/src/store/__tests__/CallState.test.ts +6 -4
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import * as TestData from './participant-data';
|
|
3
|
+
import { VisibilityState } from '../../types';
|
|
4
|
+
import { paginatedLayoutSortPreset } from '../presets';
|
|
5
|
+
import { TrackType } from '../../gen/video/sfu/models/models';
|
|
6
|
+
|
|
7
|
+
describe('presets', () => {
|
|
8
|
+
it('paginatedLayoutSortPreset', () => {
|
|
9
|
+
const ps = TestData.participants().map((p) => ({
|
|
10
|
+
...p,
|
|
11
|
+
viewportVisibilityState: {
|
|
12
|
+
videoTrack: VisibilityState.UNKNOWN,
|
|
13
|
+
screenShareTrack: VisibilityState.UNKNOWN,
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
expect(ps.sort(paginatedLayoutSortPreset).map((p) => p.name)).toEqual([
|
|
18
|
+
'F',
|
|
19
|
+
'B',
|
|
20
|
+
'E',
|
|
21
|
+
'D',
|
|
22
|
+
'A',
|
|
23
|
+
'C',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
// server-pin C
|
|
27
|
+
ps.at(-1)!.pin = {
|
|
28
|
+
isLocalPin: false,
|
|
29
|
+
pinnedAt: Date.now(),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
expect(ps.sort(paginatedLayoutSortPreset).map((p) => p.name)).toEqual([
|
|
33
|
+
'C',
|
|
34
|
+
'F',
|
|
35
|
+
'B',
|
|
36
|
+
'E',
|
|
37
|
+
'D',
|
|
38
|
+
'A',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
ps.at(-3)!.publishedTracks = [TrackType.AUDIO]; // E
|
|
42
|
+
ps.at(-2)!.isDominantSpeaker = false; // D
|
|
43
|
+
ps.at(-1)!.isDominantSpeaker = true; // A
|
|
44
|
+
|
|
45
|
+
expect(ps.sort(paginatedLayoutSortPreset).map((p) => p.name)).toEqual([
|
|
46
|
+
'C',
|
|
47
|
+
'F',
|
|
48
|
+
'B',
|
|
49
|
+
'A',
|
|
50
|
+
'E',
|
|
51
|
+
'D',
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
});
|
package/src/sorting/presets.ts
CHANGED
|
@@ -20,6 +20,19 @@ const ifInvisibleBy = conditional(
|
|
|
20
20
|
b.viewportVisibilityState?.videoTrack === VisibilityState.INVISIBLE,
|
|
21
21
|
);
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* A comparator that applies the decorated comparator when a participant is
|
|
25
|
+
* either invisible or its visibility state isn't known.
|
|
26
|
+
* For visible participants, it ensures stable sorting.
|
|
27
|
+
*/
|
|
28
|
+
const ifInvisibleOrUnknownBy = conditional(
|
|
29
|
+
(a: StreamVideoParticipant, b: StreamVideoParticipant) =>
|
|
30
|
+
a.viewportVisibilityState?.videoTrack === VisibilityState.INVISIBLE ||
|
|
31
|
+
a.viewportVisibilityState?.videoTrack === VisibilityState.UNKNOWN ||
|
|
32
|
+
b.viewportVisibilityState?.videoTrack === VisibilityState.INVISIBLE ||
|
|
33
|
+
b.viewportVisibilityState?.videoTrack === VisibilityState.UNKNOWN,
|
|
34
|
+
);
|
|
35
|
+
|
|
23
36
|
/**
|
|
24
37
|
* The default sorting preset.
|
|
25
38
|
*/
|
|
@@ -48,6 +61,21 @@ export const speakerLayoutSortPreset = combineComparators(
|
|
|
48
61
|
// ifInvisibleBy(name),
|
|
49
62
|
);
|
|
50
63
|
|
|
64
|
+
/**
|
|
65
|
+
* The sorting preset for layouts that don't render all participants but
|
|
66
|
+
* instead, render them in pages.
|
|
67
|
+
*/
|
|
68
|
+
export const paginatedLayoutSortPreset = combineComparators(
|
|
69
|
+
pinned,
|
|
70
|
+
screenSharing,
|
|
71
|
+
dominantSpeaker,
|
|
72
|
+
ifInvisibleOrUnknownBy(speaking),
|
|
73
|
+
ifInvisibleOrUnknownBy(reactionType('raised-hand')),
|
|
74
|
+
ifInvisibleOrUnknownBy(publishingVideo),
|
|
75
|
+
ifInvisibleOrUnknownBy(publishingAudio),
|
|
76
|
+
// ifInvisibleOrUnknownBy(name),
|
|
77
|
+
);
|
|
78
|
+
|
|
51
79
|
/**
|
|
52
80
|
* The sorting preset for livestreams and audio rooms.
|
|
53
81
|
*/
|
package/src/store/CallState.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { CallStatsReport } from '../stats/types';
|
|
13
13
|
import {
|
|
14
14
|
BlockedUserEvent,
|
|
15
|
-
|
|
15
|
+
CallHLSBroadcastingStartedEvent,
|
|
16
16
|
CallIngressResponse,
|
|
17
17
|
CallMemberAddedEvent,
|
|
18
18
|
CallMemberRemovedEvent,
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
EgressResponse,
|
|
28
28
|
MemberResponse,
|
|
29
29
|
OwnCapability,
|
|
30
|
+
ThumbnailResponse,
|
|
30
31
|
UnblockedUserEvent,
|
|
31
32
|
UpdatedCallPermissionsEvent,
|
|
32
33
|
UserResponse,
|
|
@@ -125,6 +126,9 @@ export class CallState {
|
|
|
125
126
|
private endedBySubject = new BehaviorSubject<UserResponse | undefined>(
|
|
126
127
|
undefined,
|
|
127
128
|
);
|
|
129
|
+
private thumbnailsSubject = new BehaviorSubject<
|
|
130
|
+
ThumbnailResponse | undefined
|
|
131
|
+
>(undefined);
|
|
128
132
|
private membersSubject = new BehaviorSubject<MemberResponse[]>([]);
|
|
129
133
|
private ownCapabilitiesSubject = new BehaviorSubject<OwnCapability[]>([]);
|
|
130
134
|
private callingStateSubject = new BehaviorSubject<CallingState>(
|
|
@@ -298,6 +302,11 @@ export class CallState {
|
|
|
298
302
|
*/
|
|
299
303
|
endedBy$: Observable<UserResponse | undefined>;
|
|
300
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Will provide the thumbnails of this call.
|
|
307
|
+
*/
|
|
308
|
+
thumbnails$: Observable<ThumbnailResponse | undefined>;
|
|
309
|
+
|
|
301
310
|
readonly logger: Logger;
|
|
302
311
|
|
|
303
312
|
/**
|
|
@@ -381,6 +390,7 @@ export class CallState {
|
|
|
381
390
|
this.settings$ = this.settingsSubject.asObservable();
|
|
382
391
|
this.transcribing$ = this.transcribingSubject.asObservable();
|
|
383
392
|
this.endedBy$ = this.endedBySubject.asObservable();
|
|
393
|
+
this.thumbnails$ = this.thumbnailsSubject.asObservable();
|
|
384
394
|
|
|
385
395
|
this.eventHandlers = {
|
|
386
396
|
// these events are not updating the call state:
|
|
@@ -396,7 +406,10 @@ export class CallState {
|
|
|
396
406
|
// events that update call state:
|
|
397
407
|
'call.accepted': (e) => this.updateFromCallResponse(e.call),
|
|
398
408
|
'call.created': (e) => this.updateFromCallResponse(e.call),
|
|
399
|
-
'call.notification': (e) =>
|
|
409
|
+
'call.notification': (e) => {
|
|
410
|
+
this.updateFromCallResponse(e.call);
|
|
411
|
+
this.setMembers(e.members);
|
|
412
|
+
},
|
|
400
413
|
'call.rejected': (e) => this.updateFromCallResponse(e.call),
|
|
401
414
|
'call.ring': (e) => this.updateFromCallResponse(e.call),
|
|
402
415
|
'call.live_started': (e) => this.updateFromCallResponse(e.call),
|
|
@@ -411,8 +424,8 @@ export class CallState {
|
|
|
411
424
|
this.setCurrentValue(this.recordingSubject, true),
|
|
412
425
|
'call.recording_stopped': () =>
|
|
413
426
|
this.setCurrentValue(this.recordingSubject, false),
|
|
414
|
-
'call.
|
|
415
|
-
'call.
|
|
427
|
+
'call.hls_broadcasting_started': this.updateFromHLSBroadcastStarted,
|
|
428
|
+
'call.hls_broadcasting_stopped': this.updateFromHLSBroadcastStopped,
|
|
416
429
|
'call.session_participant_joined':
|
|
417
430
|
this.updateFromSessionParticipantJoined,
|
|
418
431
|
'call.session_participant_left': this.updateFromSessionParticipantLeft,
|
|
@@ -740,6 +753,13 @@ export class CallState {
|
|
|
740
753
|
return this.getCurrentValue(this.endedBy$);
|
|
741
754
|
}
|
|
742
755
|
|
|
756
|
+
/**
|
|
757
|
+
* Will provide the thumbnails of this call, if enabled in the call settings.
|
|
758
|
+
*/
|
|
759
|
+
get thumbnails() {
|
|
760
|
+
return this.getCurrentValue(this.thumbnails$);
|
|
761
|
+
}
|
|
762
|
+
|
|
743
763
|
/**
|
|
744
764
|
* Will try to find the participant with the given sessionId in the current call.
|
|
745
765
|
*
|
|
@@ -943,6 +963,7 @@ export class CallState {
|
|
|
943
963
|
this.setCurrentValue(this.sessionSubject, call.session);
|
|
944
964
|
this.setCurrentValue(this.settingsSubject, call.settings);
|
|
945
965
|
this.setCurrentValue(this.transcribingSubject, call.transcribing);
|
|
966
|
+
this.setCurrentValue(this.thumbnailsSubject, call.thumbnails);
|
|
946
967
|
};
|
|
947
968
|
|
|
948
969
|
private updateFromMemberRemoved = (event: CallMemberRemovedEvent) => {
|
|
@@ -958,15 +979,15 @@ export class CallState {
|
|
|
958
979
|
]);
|
|
959
980
|
};
|
|
960
981
|
|
|
961
|
-
private
|
|
982
|
+
private updateFromHLSBroadcastStopped = () => {
|
|
962
983
|
this.setCurrentValue(this.egressSubject, (egress) => ({
|
|
963
984
|
...egress!,
|
|
964
985
|
broadcasting: false,
|
|
965
986
|
}));
|
|
966
987
|
};
|
|
967
988
|
|
|
968
|
-
private
|
|
969
|
-
event:
|
|
989
|
+
private updateFromHLSBroadcastStarted = (
|
|
990
|
+
event: CallHLSBroadcastingStartedEvent,
|
|
970
991
|
) => {
|
|
971
992
|
this.setCurrentValue(this.egressSubject, (egress) => ({
|
|
972
993
|
...egress!,
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
import * as TestData from '../../sorting/__tests__/participant-data';
|
|
25
25
|
|
|
26
26
|
describe('CallState', () => {
|
|
27
|
+
// TODO OL add API verification test -> observable$ should have a getter!
|
|
28
|
+
|
|
27
29
|
describe('sorting', () => {
|
|
28
30
|
it('should emit sorted participants', () => {
|
|
29
31
|
const state = new CallState();
|
|
@@ -471,7 +473,7 @@ describe('CallState', () => {
|
|
|
471
473
|
expect(state.recording).toBe(false);
|
|
472
474
|
});
|
|
473
475
|
|
|
474
|
-
it('handles call.
|
|
476
|
+
it('handles call.hls_broadcasting_started events', () => {
|
|
475
477
|
const state = new CallState();
|
|
476
478
|
state.updateFromCallResponse({
|
|
477
479
|
// @ts-ignore
|
|
@@ -484,7 +486,7 @@ describe('CallState', () => {
|
|
|
484
486
|
});
|
|
485
487
|
// @ts-ignore
|
|
486
488
|
state.updateFromEvent({
|
|
487
|
-
type: 'call.
|
|
489
|
+
type: 'call.hls_broadcasting_started',
|
|
488
490
|
hls_playlist_url: 'https://example.com/playlist.m3u8',
|
|
489
491
|
});
|
|
490
492
|
expect(state.egress?.broadcasting).toBe(true);
|
|
@@ -493,13 +495,13 @@ describe('CallState', () => {
|
|
|
493
495
|
);
|
|
494
496
|
});
|
|
495
497
|
|
|
496
|
-
it('handles call.
|
|
498
|
+
it('handles call.hls_broadcasting_stopped events', () => {
|
|
497
499
|
const state = new CallState();
|
|
498
500
|
// @ts-ignore
|
|
499
501
|
state.updateFromCallResponse({});
|
|
500
502
|
// @ts-ignore
|
|
501
503
|
state.updateFromEvent({
|
|
502
|
-
type: 'call.
|
|
504
|
+
type: 'call.hls_broadcasting_stopped',
|
|
503
505
|
});
|
|
504
506
|
expect(state.egress?.broadcasting).toBe(false);
|
|
505
507
|
});
|