@stream-io/video-client 0.1.11 → 0.2.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 +254 -28
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +254 -28
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +254 -28
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +27 -6
- package/dist/src/events/internal.d.ts +6 -0
- package/dist/src/gen/coordinator/index.d.ts +133 -0
- package/dist/src/gen/video/sfu/event/events.d.ts +31 -1
- package/dist/src/gen/video/sfu/models/models.d.ts +34 -0
- package/dist/src/store/CallState.d.ts +7 -0
- package/dist/src/types.d.ts +16 -2
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Call.ts +60 -10
- package/src/events/call.ts +1 -0
- package/src/events/callEventHandlers.ts +2 -0
- package/src/events/internal.ts +13 -0
- package/src/gen/coordinator/index.ts +133 -0
- package/src/gen/video/sfu/event/events.ts +131 -0
- package/src/gen/video/sfu/models/models.ts +122 -1
- package/src/rtc/Dispatcher.ts +1 -0
- package/src/rtc/flows/join.ts +2 -8
- package/src/sorting/__tests__/participant-data.ts +4 -1
- package/src/sorting/participants.ts +7 -5
- package/src/store/CallState.ts +43 -2
- package/src/store/__tests__/CallState.test.ts +133 -78
- package/src/types.ts +18 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { anyNumber } from 'vitest-mock-extended';
|
|
2
3
|
import { StreamVideoParticipant, VisibilityState } from '../../types';
|
|
3
4
|
import { CallState } from '../CallState';
|
|
4
5
|
import {
|
|
@@ -16,90 +17,144 @@ import {
|
|
|
16
17
|
import * as TestData from '../../sorting/__tests__/participant-data';
|
|
17
18
|
|
|
18
19
|
describe('CallState', () => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
20
|
+
describe('sorting', () => {
|
|
21
|
+
it('should emit sorted participants', () => {
|
|
22
|
+
const state = new CallState();
|
|
23
|
+
state.setSortParticipantsBy(noopComparator());
|
|
24
|
+
state.setParticipants(TestData.participants());
|
|
25
|
+
|
|
26
|
+
// initial sort criteria
|
|
27
|
+
const ps = state.participants;
|
|
28
|
+
expect(ps.map((p) => p.name)).toEqual(['A', 'B', 'C', 'D', 'E', 'F']);
|
|
29
|
+
|
|
30
|
+
// update sort criteria
|
|
31
|
+
state.setSortParticipantsBy(
|
|
32
|
+
combineComparators(
|
|
33
|
+
dominantSpeaker,
|
|
34
|
+
publishingAudio,
|
|
35
|
+
publishingVideo,
|
|
36
|
+
screenSharing,
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const ps2 = state.participants;
|
|
41
|
+
expect(ps2.map((p) => p.name)).toEqual(['D', 'B', 'A', 'F', 'E', 'C']);
|
|
42
|
+
});
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
it('should be able to disable sorting', () => {
|
|
45
|
+
const participants = TestData.participants();
|
|
46
|
+
const state = new CallState();
|
|
47
|
+
state.setParticipants(TestData.participants());
|
|
48
|
+
// initial sort criteria
|
|
49
|
+
const ps = state.participants;
|
|
50
|
+
expect(ps.map((p) => p.name)).toEqual(['F', 'B', 'E', 'A', 'C', 'D']);
|
|
51
|
+
|
|
52
|
+
// disable sorting
|
|
53
|
+
state.setSortParticipantsBy(noopComparator());
|
|
54
|
+
|
|
55
|
+
// update the dominant speaker -> in this case, no sorting should be applied
|
|
56
|
+
const [A] = participants;
|
|
57
|
+
state.updateParticipant(A.sessionId, {
|
|
58
|
+
isDominantSpeaker: true,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const ps2 = state.participants;
|
|
62
|
+
expect(ps2.map((p) => p.name)).toEqual(['F', 'B', 'E', 'A', 'C', 'D']);
|
|
57
63
|
});
|
|
58
64
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
it('should support custom sorting', () => {
|
|
66
|
+
const state = new CallState();
|
|
67
|
+
state.setSortParticipantsBy(descending(name));
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
69
|
+
state.setParticipants(TestData.participants());
|
|
70
|
+
const ps = state.participants;
|
|
71
|
+
expect(ps.map((p) => p.name)).toEqual(['F', 'E', 'D', 'C', 'B', 'A']);
|
|
72
|
+
});
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
it('should consider participant visibility', () => {
|
|
75
|
+
const [A, B, C, D] = TestData.participants();
|
|
76
|
+
|
|
77
|
+
const state = new CallState();
|
|
78
|
+
state.setSortParticipantsBy(name);
|
|
79
|
+
state.setParticipants([A, B, C, D]);
|
|
80
|
+
expect(state.participants).toEqual([A, B, C, D]);
|
|
81
|
+
|
|
82
|
+
const Z = {
|
|
83
|
+
...A,
|
|
84
|
+
name: 'Z',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// normal mode: Z is pushed to the end
|
|
88
|
+
state.setParticipants([Z, B, C, D]);
|
|
89
|
+
expect(state.participants).toEqual([B, C, D, Z]);
|
|
90
|
+
|
|
91
|
+
const ifInvisibleBy = conditional(
|
|
92
|
+
(a: StreamVideoParticipant, b: StreamVideoParticipant) =>
|
|
93
|
+
a.viewportVisibilityState === VisibilityState.INVISIBLE ||
|
|
94
|
+
b.viewportVisibilityState === VisibilityState.INVISIBLE,
|
|
95
|
+
);
|
|
96
|
+
state.setSortParticipantsBy(ifInvisibleBy(name));
|
|
97
|
+
|
|
98
|
+
// Z is visible, so it is kept in the same position
|
|
99
|
+
state.setParticipants([Z, B, C, D]);
|
|
100
|
+
expect(state.participants).toEqual([Z, B, C, D]);
|
|
101
|
+
|
|
102
|
+
// Z is invisible, so, the normal sorting is applied and Z is pushed to the end
|
|
103
|
+
Z.viewportVisibilityState = VisibilityState.INVISIBLE;
|
|
104
|
+
state.setParticipants([Z, B, C, D]);
|
|
105
|
+
expect(state.participants).toEqual([B, C, D, Z]);
|
|
106
|
+
});
|
|
70
107
|
});
|
|
71
108
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
describe('pinning', () => {
|
|
110
|
+
it('should update the pinned state of participants in the call', () => {
|
|
111
|
+
const state = new CallState();
|
|
112
|
+
state.setSortParticipantsBy(noopComparator());
|
|
113
|
+
// @ts-ignore
|
|
114
|
+
state.setParticipants([{ sessionId: '123' }, { sessionId: '456' }]);
|
|
115
|
+
|
|
116
|
+
state.setServerSidePins([{ sessionId: '123', userId: 'user-id' }]);
|
|
117
|
+
|
|
118
|
+
expect(state.participants).toEqual([
|
|
119
|
+
{ sessionId: '123', pin: { isLocalPin: false, pinnedAt: anyNumber() } },
|
|
120
|
+
{ sessionId: '456' },
|
|
121
|
+
]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should unpin participants that are no longer pinned', () => {
|
|
125
|
+
const state = new CallState();
|
|
126
|
+
state.setSortParticipantsBy(noopComparator());
|
|
127
|
+
state.setParticipants([
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
{ sessionId: '123', pin: { isLocalPin: false, pinnedAt: 1000 } },
|
|
130
|
+
// @ts-ignore
|
|
131
|
+
{ sessionId: '456' },
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
state.setServerSidePins([]);
|
|
135
|
+
|
|
136
|
+
expect(state.participants).toEqual([
|
|
137
|
+
{ sessionId: '123', pin: undefined },
|
|
138
|
+
{ sessionId: '456' },
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should not unpin participants that are pinned locally', () => {
|
|
143
|
+
const state = new CallState();
|
|
144
|
+
state.setSortParticipantsBy(noopComparator());
|
|
145
|
+
state.setParticipants([
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
{ sessionId: '123', pin: { isLocalPin: true, pinnedAt: 1000 } },
|
|
148
|
+
// @ts-ignore
|
|
149
|
+
{ sessionId: '456' },
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
state.setServerSidePins([]);
|
|
153
|
+
|
|
154
|
+
expect(state.participants).toEqual([
|
|
155
|
+
{ sessionId: '123', pin: { isLocalPin: true, pinnedAt: 1000 } },
|
|
156
|
+
{ sessionId: '456' },
|
|
157
|
+
]);
|
|
158
|
+
});
|
|
104
159
|
});
|
|
105
160
|
});
|
package/src/types.ts
CHANGED
|
@@ -69,9 +69,9 @@ export interface StreamVideoParticipant extends Participant {
|
|
|
69
69
|
isLocalParticipant?: boolean;
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
72
|
+
* The pin state of the participant.
|
|
73
73
|
*/
|
|
74
|
-
|
|
74
|
+
pin?: ParticipantPin;
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
77
|
* The last reaction this user has sent to this call.
|
|
@@ -106,6 +106,22 @@ export interface StreamVideoLocalParticipant extends StreamVideoParticipant {
|
|
|
106
106
|
audioOutputDeviceId?: string;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Represents a participant's pin state.
|
|
111
|
+
*/
|
|
112
|
+
export type ParticipantPin = {
|
|
113
|
+
/**
|
|
114
|
+
* Set to true if the participant is pinned by the local user.
|
|
115
|
+
* False if the participant is pinned server-side, by the call moderator.
|
|
116
|
+
*/
|
|
117
|
+
isLocalPin: boolean;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Timestamp when the participant is pinned.
|
|
121
|
+
*/
|
|
122
|
+
pinnedAt: number;
|
|
123
|
+
};
|
|
124
|
+
|
|
109
125
|
export const isStreamVideoLocalParticipant = (
|
|
110
126
|
p: StreamVideoParticipant | StreamVideoLocalParticipant,
|
|
111
127
|
): p is StreamVideoLocalParticipant => {
|