@stream-io/video-client 0.0.2-alpha.2 → 0.0.2-alpha.20
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 +97 -0
- package/dist/index.browser.es.js +226 -88
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +226 -88
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +226 -88
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +47 -23
- package/dist/src/StreamVideoClient.d.ts +1 -0
- package/dist/src/events/callEventHandlers.d.ts +12 -0
- package/dist/src/gen/coordinator/index.d.ts +60 -76
- package/dist/src/helpers/sound-detector.d.ts +10 -1
- package/dist/src/rtc/flows/join.d.ts +1 -0
- package/dist/src/store/CallState.d.ts +22 -1
- package/dist/src/store/stateStore.d.ts +7 -0
- package/dist/src/types.d.ts +7 -1
- package/package.json +1 -1
- package/src/Call.ts +147 -80
- package/src/StreamVideoClient.ts +76 -41
- package/src/events/__tests__/call-permissions.test.ts +4 -8
- package/src/events/__tests__/call.test.ts +127 -15
- package/src/events/__tests__/sessions.test.ts +0 -2
- package/src/events/call-permissions.ts +3 -11
- package/src/events/call.ts +31 -8
- package/src/events/callEventHandlers.ts +17 -7
- package/src/events/sessions.ts +2 -12
- package/src/gen/coordinator/index.ts +60 -74
- package/src/helpers/sound-detector.ts +13 -9
- package/src/rtc/flows/join.ts +7 -1
- package/src/rtc/publisher.ts +7 -6
- package/src/store/CallState.ts +31 -0
- package/src/store/stateStore.ts +10 -0
- package/src/types.ts +8 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
CallAcceptedEvent,
|
|
15
15
|
CallEndedEvent,
|
|
16
|
+
CallResponse,
|
|
16
17
|
CallUpdatedEvent,
|
|
17
18
|
} from '../../gen/coordinator';
|
|
18
19
|
import { Call } from '../../Call';
|
|
@@ -93,28 +94,58 @@ describe('Call ringing events', () => {
|
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
describe(`call.rejected`, () => {
|
|
96
|
-
it(`will leave the call if all callees have rejected`, async () => {
|
|
97
|
-
const call = fakeCall();
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
it(`caller will leave the call if all callees have rejected`, async () => {
|
|
98
|
+
const call = fakeCall({ currentUserId: 'm1' });
|
|
99
|
+
call.state.setMetadata({
|
|
100
|
+
...fakeMetadata(),
|
|
101
|
+
// @ts-ignore
|
|
102
|
+
created_by: { id: 'm1' },
|
|
103
|
+
});
|
|
104
|
+
call.state.setMembers([
|
|
105
|
+
// @ts-expect-error
|
|
106
|
+
{ user_id: 'm1' },
|
|
107
|
+
// @ts-expect-error
|
|
108
|
+
{ user_id: 'm2' },
|
|
109
|
+
// @ts-expect-error
|
|
110
|
+
{ user_id: 'm3' },
|
|
111
|
+
]);
|
|
112
|
+
call.state.setCallingState(CallingState.RINGING);
|
|
100
113
|
vi.spyOn(call, 'leave').mockImplementation(async () => {
|
|
101
114
|
console.log(`TEST: leave() called`);
|
|
102
115
|
});
|
|
103
116
|
|
|
104
117
|
const handler = watchCallRejected(call);
|
|
105
118
|
// all members reject the call
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const event: CallAcceptedEvent = { type: 'call.rejected' };
|
|
119
|
+
await handler({
|
|
120
|
+
type: 'call.rejected',
|
|
109
121
|
// @ts-ignore
|
|
110
|
-
|
|
122
|
+
user: {
|
|
123
|
+
id: 'm2',
|
|
124
|
+
},
|
|
125
|
+
call: {
|
|
126
|
+
// @ts-ignore
|
|
127
|
+
created_by: {
|
|
128
|
+
id: 'm1',
|
|
129
|
+
},
|
|
130
|
+
// @ts-ignore
|
|
131
|
+
session: {
|
|
132
|
+
rejected_by: {
|
|
133
|
+
m2: new Date().toISOString(),
|
|
134
|
+
m3: new Date().toISOString(),
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
111
138
|
});
|
|
112
|
-
|
|
113
139
|
expect(call.leave).toHaveBeenCalled();
|
|
114
140
|
});
|
|
115
141
|
|
|
116
|
-
it(`will not leave the call if only one callee rejects`, async () => {
|
|
142
|
+
it(`caller will not leave the call if only one callee rejects`, async () => {
|
|
117
143
|
const call = fakeCall();
|
|
144
|
+
call.state.setMetadata({
|
|
145
|
+
...fakeMetadata(),
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
created_by: { id: 'm0' },
|
|
148
|
+
});
|
|
118
149
|
// @ts-expect-error
|
|
119
150
|
call.state.setMembers([{ user_id: 'm1' }, { user_id: 'm2' }]);
|
|
120
151
|
vi.spyOn(call, 'leave').mockImplementation(async () => {
|
|
@@ -124,12 +155,71 @@ describe('Call ringing events', () => {
|
|
|
124
155
|
|
|
125
156
|
// only one member rejects the call
|
|
126
157
|
// @ts-ignore
|
|
127
|
-
const event: CallAcceptedEvent = {
|
|
158
|
+
const event: CallAcceptedEvent = {
|
|
159
|
+
type: 'call.rejected',
|
|
160
|
+
// @ts-ignore
|
|
161
|
+
user: {
|
|
162
|
+
id: 'm2',
|
|
163
|
+
},
|
|
164
|
+
call: {
|
|
165
|
+
// @ts-ignore
|
|
166
|
+
created_by: {
|
|
167
|
+
id: 'm0',
|
|
168
|
+
},
|
|
169
|
+
// @ts-ignore
|
|
170
|
+
session: {
|
|
171
|
+
rejected_by: {
|
|
172
|
+
m2: new Date().toISOString(),
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
128
177
|
// @ts-ignore
|
|
129
178
|
await handler(event);
|
|
130
179
|
|
|
131
180
|
expect(call.leave).not.toHaveBeenCalled();
|
|
132
181
|
});
|
|
182
|
+
|
|
183
|
+
it('callee will leave the call if caller rejects', async () => {
|
|
184
|
+
const call = fakeCall({ currentUserId: 'm1' });
|
|
185
|
+
call.state.setMetadata({
|
|
186
|
+
...fakeMetadata(),
|
|
187
|
+
// @ts-ignore
|
|
188
|
+
created_by: { id: 'm0' },
|
|
189
|
+
});
|
|
190
|
+
// @ts-expect-error
|
|
191
|
+
call.state.setMembers([{ user_id: 'm1' }, { user_id: 'm2' }]);
|
|
192
|
+
vi.spyOn(call, 'leave').mockImplementation(async () => {
|
|
193
|
+
console.log(`TEST: leave() called`);
|
|
194
|
+
});
|
|
195
|
+
const handler = watchCallRejected(call);
|
|
196
|
+
|
|
197
|
+
// only one member rejects the call
|
|
198
|
+
// @ts-ignore
|
|
199
|
+
const event: CallAcceptedEvent = {
|
|
200
|
+
type: 'call.rejected',
|
|
201
|
+
// @ts-ignore
|
|
202
|
+
user: {
|
|
203
|
+
id: 'm0',
|
|
204
|
+
},
|
|
205
|
+
call: {
|
|
206
|
+
// @ts-ignore
|
|
207
|
+
created_by: {
|
|
208
|
+
id: 'm0',
|
|
209
|
+
},
|
|
210
|
+
// @ts-ignore
|
|
211
|
+
session: {
|
|
212
|
+
rejected_by: {
|
|
213
|
+
m0: new Date().toISOString(),
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
// @ts-ignore
|
|
219
|
+
await handler(event);
|
|
220
|
+
|
|
221
|
+
expect(call.leave).toHaveBeenCalled();
|
|
222
|
+
});
|
|
133
223
|
});
|
|
134
224
|
|
|
135
225
|
describe(`call.ended`, () => {
|
|
@@ -171,8 +261,7 @@ describe('Call ringing events', () => {
|
|
|
171
261
|
});
|
|
172
262
|
|
|
173
263
|
it(`will not leave the call if idle`, async () => {
|
|
174
|
-
const
|
|
175
|
-
const call = fakeCall(ringing);
|
|
264
|
+
const call = fakeCall({ ring: false });
|
|
176
265
|
vi.spyOn(call, 'leave').mockImplementation(async () => {
|
|
177
266
|
console.log(`TEST: leave() called`);
|
|
178
267
|
});
|
|
@@ -189,10 +278,10 @@ describe('Call ringing events', () => {
|
|
|
189
278
|
});
|
|
190
279
|
});
|
|
191
280
|
|
|
192
|
-
const fakeCall = (ring = true) => {
|
|
281
|
+
const fakeCall = ({ ring = true, currentUserId = 'test-user-id' } = {}) => {
|
|
193
282
|
const store = new StreamVideoWriteableStateStore();
|
|
194
283
|
store.setConnectedUser({
|
|
195
|
-
id:
|
|
284
|
+
id: currentUserId,
|
|
196
285
|
});
|
|
197
286
|
const client = new StreamClient('api-key');
|
|
198
287
|
return new Call({
|
|
@@ -203,3 +292,26 @@ const fakeCall = (ring = true) => {
|
|
|
203
292
|
ringing: ring,
|
|
204
293
|
});
|
|
205
294
|
};
|
|
295
|
+
|
|
296
|
+
const fakeMetadata = (): CallResponse => {
|
|
297
|
+
return {
|
|
298
|
+
id: '12345',
|
|
299
|
+
type: 'development',
|
|
300
|
+
cid: 'development:12345',
|
|
301
|
+
|
|
302
|
+
// @ts-ignore
|
|
303
|
+
created_by: {
|
|
304
|
+
id: 'test-user-id',
|
|
305
|
+
},
|
|
306
|
+
own_capabilities: [],
|
|
307
|
+
blocked_user_ids: [],
|
|
308
|
+
|
|
309
|
+
// @ts-ignore
|
|
310
|
+
settings: {
|
|
311
|
+
ring: {
|
|
312
|
+
auto_cancel_timeout_ms: 30000,
|
|
313
|
+
incoming_call_timeout_ms: 30000,
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
};
|
|
@@ -24,7 +24,6 @@ describe('call.session events', () => {
|
|
|
24
24
|
|
|
25
25
|
expect(state.metadata).toEqual({
|
|
26
26
|
cid: 'cid',
|
|
27
|
-
own_capabilities: [],
|
|
28
27
|
session: {
|
|
29
28
|
id: 'session-id',
|
|
30
29
|
},
|
|
@@ -46,7 +45,6 @@ describe('call.session events', () => {
|
|
|
46
45
|
});
|
|
47
46
|
expect(state.metadata).toEqual({
|
|
48
47
|
cid: 'cid',
|
|
49
|
-
own_capabilities: [],
|
|
50
48
|
session: {
|
|
51
49
|
id: 'session-id',
|
|
52
50
|
},
|
|
@@ -22,10 +22,7 @@ export const watchCallPermissionsUpdated = (state: CallState) => {
|
|
|
22
22
|
if (event.type !== 'call.permissions_updated') return;
|
|
23
23
|
const { localParticipant } = state;
|
|
24
24
|
if (event.user.id === localParticipant?.userId) {
|
|
25
|
-
state.
|
|
26
|
-
...metadata!,
|
|
27
|
-
own_capabilities: event.own_capabilities,
|
|
28
|
-
}));
|
|
25
|
+
state.setOwnCapabilities(event.own_capabilities);
|
|
29
26
|
}
|
|
30
27
|
};
|
|
31
28
|
};
|
|
@@ -49,7 +46,7 @@ export const watchCallGrantsUpdated = (state: CallState) => {
|
|
|
49
46
|
[OwnCapability.SCREENSHARE]: canScreenshare,
|
|
50
47
|
};
|
|
51
48
|
|
|
52
|
-
const nextCapabilities =
|
|
49
|
+
const nextCapabilities = state.ownCapabilities.filter(
|
|
53
50
|
(capability) => update[capability] !== false,
|
|
54
51
|
);
|
|
55
52
|
Object.entries(update).forEach(([capability, value]) => {
|
|
@@ -58,12 +55,7 @@ export const watchCallGrantsUpdated = (state: CallState) => {
|
|
|
58
55
|
}
|
|
59
56
|
});
|
|
60
57
|
|
|
61
|
-
state.
|
|
62
|
-
return {
|
|
63
|
-
...metadata!,
|
|
64
|
-
own_capabilities: nextCapabilities,
|
|
65
|
-
};
|
|
66
|
-
});
|
|
58
|
+
state.setOwnCapabilities(nextCapabilities);
|
|
67
59
|
}
|
|
68
60
|
};
|
|
69
61
|
};
|
package/src/events/call.ts
CHANGED
|
@@ -27,16 +27,39 @@ export const watchCallAccepted = (call: Call) => {
|
|
|
27
27
|
* Once the event is received, the call is left.
|
|
28
28
|
*/
|
|
29
29
|
export const watchCallRejected = (call: Call) => {
|
|
30
|
-
let totalRejections = 0;
|
|
31
30
|
return async function onCallRejected(event: StreamVideoEvent) {
|
|
32
31
|
if (event.type !== 'call.rejected') return;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
) {
|
|
39
|
-
|
|
32
|
+
// We want to discard the event if it's from the current user
|
|
33
|
+
if (event.user.id === call.currentUserId) return;
|
|
34
|
+
const { call: eventCall } = event;
|
|
35
|
+
const { session: callSession } = eventCall;
|
|
36
|
+
|
|
37
|
+
if (!callSession) {
|
|
38
|
+
console.log('No call session provided. Ignoring call.rejected event.');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const rejectedBy = callSession.rejected_by;
|
|
43
|
+
const { members, callingState } = call.state;
|
|
44
|
+
if (callingState !== CallingState.RINGING) {
|
|
45
|
+
console.log(
|
|
46
|
+
'Call is not in ringing mode (it is either accepted or rejected already). Ignoring call.rejected event.',
|
|
47
|
+
);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (call.isCreatedByMe) {
|
|
51
|
+
const everyoneElseRejected = members
|
|
52
|
+
.filter((m) => m.user_id !== call.currentUserId)
|
|
53
|
+
.every((m) => rejectedBy[m.user_id]);
|
|
54
|
+
if (everyoneElseRejected) {
|
|
55
|
+
console.log('everyone rejected, leaving the call');
|
|
56
|
+
await call.leave();
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
if (rejectedBy[eventCall.created_by.id]) {
|
|
60
|
+
console.log('call creator rejected, leaving call');
|
|
61
|
+
await call.leave();
|
|
62
|
+
}
|
|
40
63
|
}
|
|
41
64
|
};
|
|
42
65
|
};
|
|
@@ -45,13 +45,22 @@ type RingCallEvents = Extract<
|
|
|
45
45
|
'call.accepted' | 'call.rejected'
|
|
46
46
|
>;
|
|
47
47
|
|
|
48
|
-
// call.created is handled by the StreamVideoClient
|
|
49
|
-
// custom events should be handled by integrators
|
|
50
48
|
type AllCallEvents = Exclude<
|
|
51
49
|
CallEventTypes,
|
|
52
|
-
'call.created'
|
|
50
|
+
| 'call.created' // handled by StreamVideoClient
|
|
51
|
+
| 'call.ring' // handled by StreamVideoClient
|
|
52
|
+
| 'call.notification' // not used currently
|
|
53
|
+
| 'custom' // integrators should handle custom events
|
|
54
|
+
| RingCallEvents // handled by registerRingingCallEventHandlers
|
|
53
55
|
>;
|
|
54
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Registers the default event handlers for a call during its lifecycle.
|
|
59
|
+
*
|
|
60
|
+
* @param call the call to register event handlers for.
|
|
61
|
+
* @param state the call state.
|
|
62
|
+
* @param dispatcher the dispatcher.
|
|
63
|
+
*/
|
|
55
64
|
export const registerEventHandlers = (
|
|
56
65
|
call: Call,
|
|
57
66
|
state: CallState,
|
|
@@ -80,10 +89,6 @@ export const registerEventHandlers = (
|
|
|
80
89
|
'call.session_participant_left': watchCallSessionParticipantLeft(state),
|
|
81
90
|
'call.unblocked_user': watchUnblockedUser(state),
|
|
82
91
|
'call.updated': watchCallUpdated(state),
|
|
83
|
-
'call.notification': (event: StreamCallEvent) =>
|
|
84
|
-
console.log(`Received ${event.type} event`, event),
|
|
85
|
-
'call.ring': (event: StreamCallEvent) =>
|
|
86
|
-
console.log(`Received ${event.type} event`, event),
|
|
87
92
|
};
|
|
88
93
|
const eventHandlers = [
|
|
89
94
|
watchChangePublishQuality(dispatcher, call),
|
|
@@ -117,6 +122,11 @@ export const registerEventHandlers = (
|
|
|
117
122
|
};
|
|
118
123
|
};
|
|
119
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Registers event handlers for a call that is of ringing type.
|
|
127
|
+
*
|
|
128
|
+
* @param call the call to register event handlers for.
|
|
129
|
+
*/
|
|
120
130
|
export const registerRingingCallEventHandlers = (call: Call) => {
|
|
121
131
|
const coordinatorRingEvents: {
|
|
122
132
|
[key in RingCallEvents]: (e: StreamCallEvent) => any;
|
package/src/events/sessions.ts
CHANGED
|
@@ -9,12 +9,7 @@ import { StreamVideoEvent } from '../coordinator/connection/types';
|
|
|
9
9
|
export const watchCallSessionStarted = (state: CallState) => {
|
|
10
10
|
return function onCallSessionStarted(event: StreamVideoEvent) {
|
|
11
11
|
if (event.type !== 'call.session_started') return;
|
|
12
|
-
|
|
13
|
-
state.setMetadata((metadata) => ({
|
|
14
|
-
...call,
|
|
15
|
-
// FIXME OL: temporary, until the backend sends the own_capabilities
|
|
16
|
-
own_capabilities: metadata?.own_capabilities || [],
|
|
17
|
-
}));
|
|
12
|
+
state.setMetadata(event.call);
|
|
18
13
|
};
|
|
19
14
|
};
|
|
20
15
|
|
|
@@ -26,12 +21,7 @@ export const watchCallSessionStarted = (state: CallState) => {
|
|
|
26
21
|
export const watchCallSessionEnded = (state: CallState) => {
|
|
27
22
|
return function onCallSessionEnded(event: StreamVideoEvent) {
|
|
28
23
|
if (event.type !== 'call.session_ended') return;
|
|
29
|
-
|
|
30
|
-
state.setMetadata((metadata) => ({
|
|
31
|
-
...call,
|
|
32
|
-
// FIXME OL: temporary, until the backend sends the own_capabilities
|
|
33
|
-
own_capabilities: metadata?.own_capabilities || [],
|
|
34
|
-
}));
|
|
24
|
+
state.setMetadata(event.call);
|
|
35
25
|
};
|
|
36
26
|
};
|
|
37
27
|
|
|
@@ -708,6 +708,12 @@ export interface CallNotificationEvent {
|
|
|
708
708
|
* @memberof CallNotificationEvent
|
|
709
709
|
*/
|
|
710
710
|
created_at: string;
|
|
711
|
+
/**
|
|
712
|
+
* Call members
|
|
713
|
+
* @type {Array<MemberResponse>}
|
|
714
|
+
* @memberof CallNotificationEvent
|
|
715
|
+
*/
|
|
716
|
+
members: Array<MemberResponse>;
|
|
711
717
|
/**
|
|
712
718
|
* Call session ID
|
|
713
719
|
* @type {string}
|
|
@@ -1022,12 +1028,6 @@ export interface CallResponse {
|
|
|
1022
1028
|
* @memberof CallResponse
|
|
1023
1029
|
*/
|
|
1024
1030
|
ingress: CallIngressResponse;
|
|
1025
|
-
/**
|
|
1026
|
-
* The capabilities of the current user
|
|
1027
|
-
* @type {Array<OwnCapability>}
|
|
1028
|
-
* @memberof CallResponse
|
|
1029
|
-
*/
|
|
1030
|
-
own_capabilities: Array<OwnCapability>;
|
|
1031
1031
|
/**
|
|
1032
1032
|
*
|
|
1033
1033
|
* @type {boolean}
|
|
@@ -1101,6 +1101,12 @@ export interface CallRingEvent {
|
|
|
1101
1101
|
* @memberof CallRingEvent
|
|
1102
1102
|
*/
|
|
1103
1103
|
created_at: string;
|
|
1104
|
+
/**
|
|
1105
|
+
* Call members
|
|
1106
|
+
* @type {Array<MemberResponse>}
|
|
1107
|
+
* @memberof CallRingEvent
|
|
1108
|
+
*/
|
|
1109
|
+
members: Array<MemberResponse>;
|
|
1104
1110
|
/**
|
|
1105
1111
|
* Call session ID
|
|
1106
1112
|
* @type {string}
|
|
@@ -1463,6 +1469,12 @@ export interface CallStateResponseFields {
|
|
|
1463
1469
|
* @memberof CallStateResponseFields
|
|
1464
1470
|
*/
|
|
1465
1471
|
membership?: MemberResponse;
|
|
1472
|
+
/**
|
|
1473
|
+
*
|
|
1474
|
+
* @type {Array<OwnCapability>}
|
|
1475
|
+
* @memberof CallStateResponseFields
|
|
1476
|
+
*/
|
|
1477
|
+
own_capabilities: Array<OwnCapability>;
|
|
1466
1478
|
}
|
|
1467
1479
|
/**
|
|
1468
1480
|
*
|
|
@@ -2036,62 +2048,6 @@ export interface GeofenceSettingsRequest {
|
|
|
2036
2048
|
*/
|
|
2037
2049
|
names?: Array<string>;
|
|
2038
2050
|
}
|
|
2039
|
-
/**
|
|
2040
|
-
*
|
|
2041
|
-
* @export
|
|
2042
|
-
* @interface GetCallEdgeServerRequest
|
|
2043
|
-
*/
|
|
2044
|
-
export interface GetCallEdgeServerRequest {
|
|
2045
|
-
/**
|
|
2046
|
-
*
|
|
2047
|
-
* @type {{ [key: string]: Array<number>; }}
|
|
2048
|
-
* @memberof GetCallEdgeServerRequest
|
|
2049
|
-
*/
|
|
2050
|
-
latency_measurements: { [key: string]: Array<number> };
|
|
2051
|
-
}
|
|
2052
|
-
/**
|
|
2053
|
-
*
|
|
2054
|
-
* @export
|
|
2055
|
-
* @interface GetCallEdgeServerResponse
|
|
2056
|
-
*/
|
|
2057
|
-
export interface GetCallEdgeServerResponse {
|
|
2058
|
-
/**
|
|
2059
|
-
*
|
|
2060
|
-
* @type {Array<UserResponse>}
|
|
2061
|
-
* @memberof GetCallEdgeServerResponse
|
|
2062
|
-
*/
|
|
2063
|
-
blocked_users: Array<UserResponse>;
|
|
2064
|
-
/**
|
|
2065
|
-
*
|
|
2066
|
-
* @type {CallResponse}
|
|
2067
|
-
* @memberof GetCallEdgeServerResponse
|
|
2068
|
-
*/
|
|
2069
|
-
call: CallResponse;
|
|
2070
|
-
/**
|
|
2071
|
-
*
|
|
2072
|
-
* @type {Credentials}
|
|
2073
|
-
* @memberof GetCallEdgeServerResponse
|
|
2074
|
-
*/
|
|
2075
|
-
credentials: Credentials;
|
|
2076
|
-
/**
|
|
2077
|
-
* Duration of the request in human-readable format
|
|
2078
|
-
* @type {string}
|
|
2079
|
-
* @memberof GetCallEdgeServerResponse
|
|
2080
|
-
*/
|
|
2081
|
-
duration: string;
|
|
2082
|
-
/**
|
|
2083
|
-
*
|
|
2084
|
-
* @type {Array<MemberResponse>}
|
|
2085
|
-
* @memberof GetCallEdgeServerResponse
|
|
2086
|
-
*/
|
|
2087
|
-
members: Array<MemberResponse>;
|
|
2088
|
-
/**
|
|
2089
|
-
*
|
|
2090
|
-
* @type {MemberResponse}
|
|
2091
|
-
* @memberof GetCallEdgeServerResponse
|
|
2092
|
-
*/
|
|
2093
|
-
membership?: MemberResponse;
|
|
2094
|
-
}
|
|
2095
2051
|
/**
|
|
2096
2052
|
*
|
|
2097
2053
|
* @export
|
|
@@ -2128,6 +2084,12 @@ export interface GetCallResponse {
|
|
|
2128
2084
|
* @memberof GetCallResponse
|
|
2129
2085
|
*/
|
|
2130
2086
|
membership?: MemberResponse;
|
|
2087
|
+
/**
|
|
2088
|
+
*
|
|
2089
|
+
* @type {Array<OwnCapability>}
|
|
2090
|
+
* @memberof GetCallResponse
|
|
2091
|
+
*/
|
|
2092
|
+
own_capabilities: Array<OwnCapability>;
|
|
2131
2093
|
}
|
|
2132
2094
|
/**
|
|
2133
2095
|
*
|
|
@@ -2270,6 +2232,12 @@ export interface GetOrCreateCallResponse {
|
|
|
2270
2232
|
* @memberof GetOrCreateCallResponse
|
|
2271
2233
|
*/
|
|
2272
2234
|
membership?: MemberResponse;
|
|
2235
|
+
/**
|
|
2236
|
+
*
|
|
2237
|
+
* @type {Array<OwnCapability>}
|
|
2238
|
+
* @memberof GetOrCreateCallResponse
|
|
2239
|
+
*/
|
|
2240
|
+
own_capabilities: Array<OwnCapability>;
|
|
2273
2241
|
}
|
|
2274
2242
|
/**
|
|
2275
2243
|
*
|
|
@@ -2456,6 +2424,12 @@ export interface JoinCallResponse {
|
|
|
2456
2424
|
* @memberof JoinCallResponse
|
|
2457
2425
|
*/
|
|
2458
2426
|
membership?: MemberResponse;
|
|
2427
|
+
/**
|
|
2428
|
+
*
|
|
2429
|
+
* @type {Array<OwnCapability>}
|
|
2430
|
+
* @memberof JoinCallResponse
|
|
2431
|
+
*/
|
|
2432
|
+
own_capabilities: Array<OwnCapability>;
|
|
2459
2433
|
}
|
|
2460
2434
|
/**
|
|
2461
2435
|
*
|
|
@@ -3213,12 +3187,6 @@ export interface RingSettings {
|
|
|
3213
3187
|
* @memberof RingSettings
|
|
3214
3188
|
*/
|
|
3215
3189
|
auto_cancel_timeout_ms: number;
|
|
3216
|
-
/**
|
|
3217
|
-
*
|
|
3218
|
-
* @type {number}
|
|
3219
|
-
* @memberof RingSettings
|
|
3220
|
-
*/
|
|
3221
|
-
auto_reject_timeout_ms: number;
|
|
3222
3190
|
/**
|
|
3223
3191
|
*
|
|
3224
3192
|
* @type {number}
|
|
@@ -3238,12 +3206,6 @@ export interface RingSettingsRequest {
|
|
|
3238
3206
|
* @memberof RingSettingsRequest
|
|
3239
3207
|
*/
|
|
3240
3208
|
auto_cancel_timeout_ms?: number;
|
|
3241
|
-
/**
|
|
3242
|
-
*
|
|
3243
|
-
* @type {number}
|
|
3244
|
-
* @memberof RingSettingsRequest
|
|
3245
|
-
*/
|
|
3246
|
-
auto_reject_timeout_ms?: number;
|
|
3247
3209
|
/**
|
|
3248
3210
|
*
|
|
3249
3211
|
* @type {number}
|
|
@@ -3745,6 +3707,12 @@ export interface UpdateCallRequest {
|
|
|
3745
3707
|
* @interface UpdateCallResponse
|
|
3746
3708
|
*/
|
|
3747
3709
|
export interface UpdateCallResponse {
|
|
3710
|
+
/**
|
|
3711
|
+
*
|
|
3712
|
+
* @type {Array<UserResponse>}
|
|
3713
|
+
* @memberof UpdateCallResponse
|
|
3714
|
+
*/
|
|
3715
|
+
blocked_users: Array<UserResponse>;
|
|
3748
3716
|
/**
|
|
3749
3717
|
*
|
|
3750
3718
|
* @type {CallResponse}
|
|
@@ -3757,6 +3725,24 @@ export interface UpdateCallResponse {
|
|
|
3757
3725
|
* @memberof UpdateCallResponse
|
|
3758
3726
|
*/
|
|
3759
3727
|
duration: string;
|
|
3728
|
+
/**
|
|
3729
|
+
*
|
|
3730
|
+
* @type {Array<MemberResponse>}
|
|
3731
|
+
* @memberof UpdateCallResponse
|
|
3732
|
+
*/
|
|
3733
|
+
members: Array<MemberResponse>;
|
|
3734
|
+
/**
|
|
3735
|
+
*
|
|
3736
|
+
* @type {MemberResponse}
|
|
3737
|
+
* @memberof UpdateCallResponse
|
|
3738
|
+
*/
|
|
3739
|
+
membership?: MemberResponse;
|
|
3740
|
+
/**
|
|
3741
|
+
*
|
|
3742
|
+
* @type {Array<OwnCapability>}
|
|
3743
|
+
* @memberof UpdateCallResponse
|
|
3744
|
+
*/
|
|
3745
|
+
own_capabilities: Array<OwnCapability>;
|
|
3760
3746
|
}
|
|
3761
3747
|
/**
|
|
3762
3748
|
*
|
|
@@ -27,6 +27,17 @@ export type SoundDetectorOptions = {
|
|
|
27
27
|
destroyStreamOnStop?: boolean;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
export type SoundDetectorState = {
|
|
31
|
+
isSoundDetected: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Represented as percentage (0-100) where 100% is defined by `audioLevelThreshold` property.
|
|
34
|
+
* Decrease time between samples (to 50-100ms) with `detectionFrequencyInMs` property.
|
|
35
|
+
*/
|
|
36
|
+
audioLevel: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type SoundStateChangeHandler = (state: SoundDetectorState) => void;
|
|
40
|
+
|
|
30
41
|
const DETECTION_FREQUENCY_IN_MS = 500;
|
|
31
42
|
const AUDIO_LEVEL_THRESHOLD = 150;
|
|
32
43
|
const FFT_SIZE = 128;
|
|
@@ -41,14 +52,7 @@ const FFT_SIZE = 128;
|
|
|
41
52
|
*/
|
|
42
53
|
export const createSoundDetector = (
|
|
43
54
|
audioStream: MediaStream,
|
|
44
|
-
onSoundDetectedStateChanged:
|
|
45
|
-
isSoundDetected: boolean,
|
|
46
|
-
/**
|
|
47
|
-
* Represented as percentage (0-100) where 100% is defined by `audioLevelThreshold` property.
|
|
48
|
-
* Decrease time between samples (to 50-100ms) with `detectionFrequencyInMs` property.
|
|
49
|
-
*/
|
|
50
|
-
audioLevel: number,
|
|
51
|
-
) => void,
|
|
55
|
+
onSoundDetectedStateChanged: SoundStateChangeHandler,
|
|
52
56
|
options: SoundDetectorOptions = {},
|
|
53
57
|
) => {
|
|
54
58
|
const {
|
|
@@ -78,7 +82,7 @@ export const createSoundDetector = (
|
|
|
78
82
|
? 100
|
|
79
83
|
: Math.round((averagedDataValue / audioLevelThreshold) * 100);
|
|
80
84
|
|
|
81
|
-
onSoundDetectedStateChanged(isSoundDetected, percentage);
|
|
85
|
+
onSoundDetectedStateChanged({ isSoundDetected, audioLevel: percentage });
|
|
82
86
|
}, detectionFrequencyInMs);
|
|
83
87
|
|
|
84
88
|
return async function stop() {
|
package/src/rtc/flows/join.ts
CHANGED
|
@@ -24,13 +24,14 @@ export const join = async (
|
|
|
24
24
|
await httpClient.connectionIdPromise;
|
|
25
25
|
|
|
26
26
|
const joinCallResponse = await doJoin(httpClient, type, id, data);
|
|
27
|
-
const { call, credentials, members } = joinCallResponse;
|
|
27
|
+
const { call, credentials, members, own_capabilities } = joinCallResponse;
|
|
28
28
|
return {
|
|
29
29
|
connectionConfig: toRtcConfiguration(credentials.ice_servers),
|
|
30
30
|
sfuServer: credentials.server,
|
|
31
31
|
token: credentials.token,
|
|
32
32
|
metadata: call,
|
|
33
33
|
members,
|
|
34
|
+
ownCapabilities: own_capabilities,
|
|
34
35
|
};
|
|
35
36
|
};
|
|
36
37
|
|
|
@@ -68,15 +69,20 @@ const doJoin = async (
|
|
|
68
69
|
|
|
69
70
|
const getLocationHint = async () => {
|
|
70
71
|
const hintURL = `https://hint.stream-io-video.com/`;
|
|
72
|
+
const abortController = new AbortController();
|
|
73
|
+
const timeoutId = setTimeout(() => abortController.abort(), 1000);
|
|
71
74
|
try {
|
|
72
75
|
const response = await fetch(hintURL, {
|
|
73
76
|
method: 'HEAD',
|
|
77
|
+
signal: abortController.signal,
|
|
74
78
|
});
|
|
75
79
|
const awsPop = response.headers.get('x-amz-cf-pop') || 'ERR';
|
|
76
80
|
return awsPop.substring(0, 3); // AMS1-P2 -> AMS
|
|
77
81
|
} catch (e) {
|
|
78
82
|
console.error(`Failed to get location hint from ${hintURL}`, e);
|
|
79
83
|
return 'ERR';
|
|
84
|
+
} finally {
|
|
85
|
+
clearTimeout(timeoutId);
|
|
80
86
|
}
|
|
81
87
|
};
|
|
82
88
|
|
package/src/rtc/publisher.ts
CHANGED
|
@@ -269,19 +269,14 @@ export class Publisher {
|
|
|
269
269
|
};
|
|
270
270
|
|
|
271
271
|
updateVideoPublishQuality = async (enabledRids: string[]) => {
|
|
272
|
-
console.log(
|
|
273
|
-
'Updating publish quality, qualities requested by SFU:',
|
|
274
|
-
enabledRids,
|
|
275
|
-
);
|
|
272
|
+
console.log('Update publish quality, requested rids by SFU:', enabledRids);
|
|
276
273
|
|
|
277
274
|
const videoSender = this.transceiverRegistry[TrackType.VIDEO]?.sender;
|
|
278
|
-
|
|
279
275
|
if (!videoSender) return;
|
|
280
276
|
|
|
281
277
|
const params = videoSender.getParameters();
|
|
282
278
|
let changed = false;
|
|
283
279
|
params.encodings.forEach((enc) => {
|
|
284
|
-
console.log(enc.rid, enc.active);
|
|
285
280
|
// flip 'active' flag only when necessary
|
|
286
281
|
const shouldEnable = enabledRids.includes(enc.rid!);
|
|
287
282
|
if (shouldEnable !== enc.active) {
|
|
@@ -294,6 +289,12 @@ export class Publisher {
|
|
|
294
289
|
console.warn('No suitable video encoding quality found');
|
|
295
290
|
}
|
|
296
291
|
await videoSender.setParameters(params);
|
|
292
|
+
console.log(
|
|
293
|
+
`Update publish quality, enabled rids: ${params.encodings
|
|
294
|
+
.filter((e) => e.active)
|
|
295
|
+
.map((e) => e.rid)
|
|
296
|
+
.join(', ')}`,
|
|
297
|
+
);
|
|
297
298
|
}
|
|
298
299
|
};
|
|
299
300
|
|