@stream-io/video-client 0.0.2-alpha.9 → 0.0.4
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 +7 -43
- package/dist/index.browser.es.js +99 -65
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +99 -65
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +99 -65
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +2 -9
- package/dist/src/StreamSfuClient.d.ts +25 -1
- package/dist/src/gen/coordinator/index.d.ts +48 -64
- 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/types.d.ts +7 -1
- package/package.json +1 -1
- package/src/Call.ts +41 -40
- package/src/StreamSfuClient.ts +37 -7
- package/src/StreamVideoClient.ts +1 -0
- package/src/events/__tests__/call-permissions.test.ts +40 -8
- package/src/events/__tests__/sessions.test.ts +0 -2
- package/src/events/call-permissions.ts +7 -12
- package/src/events/sessions.ts +2 -12
- package/src/gen/coordinator/index.ts +48 -62
- package/src/helpers/sdp-munging.ts +7 -11
- package/src/helpers/sound-detector.ts +13 -9
- package/src/rtc/__tests__/publisher.test.ts +5 -5
- package/src/rtc/flows/join.ts +7 -1
- package/src/rtc/publisher.ts +8 -7
- package/src/rtc/videoLayers.ts +4 -1
- package/src/store/CallState.ts +31 -0
- package/src/types.ts +8 -0
|
@@ -10,7 +10,10 @@ import { OwnCapability } from '../gen/coordinator';
|
|
|
10
10
|
export const watchCallPermissionRequest = (state: CallState) => {
|
|
11
11
|
return function onCallPermissionRequest(event: StreamVideoEvent) {
|
|
12
12
|
if (event.type !== 'call.permission_request') return;
|
|
13
|
-
state
|
|
13
|
+
const { localParticipant } = state;
|
|
14
|
+
if (event.user.id !== localParticipant?.userId) {
|
|
15
|
+
state.setCallPermissionRequest(event);
|
|
16
|
+
}
|
|
14
17
|
};
|
|
15
18
|
};
|
|
16
19
|
|
|
@@ -22,10 +25,7 @@ export const watchCallPermissionsUpdated = (state: CallState) => {
|
|
|
22
25
|
if (event.type !== 'call.permissions_updated') return;
|
|
23
26
|
const { localParticipant } = state;
|
|
24
27
|
if (event.user.id === localParticipant?.userId) {
|
|
25
|
-
state.
|
|
26
|
-
...metadata!,
|
|
27
|
-
own_capabilities: event.own_capabilities,
|
|
28
|
-
}));
|
|
28
|
+
state.setOwnCapabilities(event.own_capabilities);
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
31
|
};
|
|
@@ -49,7 +49,7 @@ export const watchCallGrantsUpdated = (state: CallState) => {
|
|
|
49
49
|
[OwnCapability.SCREENSHARE]: canScreenshare,
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
const nextCapabilities =
|
|
52
|
+
const nextCapabilities = state.ownCapabilities.filter(
|
|
53
53
|
(capability) => update[capability] !== false,
|
|
54
54
|
);
|
|
55
55
|
Object.entries(update).forEach(([capability, value]) => {
|
|
@@ -58,12 +58,7 @@ export const watchCallGrantsUpdated = (state: CallState) => {
|
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
state.
|
|
62
|
-
return {
|
|
63
|
-
...metadata!,
|
|
64
|
-
own_capabilities: nextCapabilities,
|
|
65
|
-
};
|
|
66
|
-
});
|
|
61
|
+
state.setOwnCapabilities(nextCapabilities);
|
|
67
62
|
}
|
|
68
63
|
};
|
|
69
64
|
};
|
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
|
|
|
@@ -1028,12 +1028,6 @@ export interface CallResponse {
|
|
|
1028
1028
|
* @memberof CallResponse
|
|
1029
1029
|
*/
|
|
1030
1030
|
ingress: CallIngressResponse;
|
|
1031
|
-
/**
|
|
1032
|
-
* The capabilities of the current user
|
|
1033
|
-
* @type {Array<OwnCapability>}
|
|
1034
|
-
* @memberof CallResponse
|
|
1035
|
-
*/
|
|
1036
|
-
own_capabilities: Array<OwnCapability>;
|
|
1037
1031
|
/**
|
|
1038
1032
|
*
|
|
1039
1033
|
* @type {boolean}
|
|
@@ -1475,6 +1469,12 @@ export interface CallStateResponseFields {
|
|
|
1475
1469
|
* @memberof CallStateResponseFields
|
|
1476
1470
|
*/
|
|
1477
1471
|
membership?: MemberResponse;
|
|
1472
|
+
/**
|
|
1473
|
+
*
|
|
1474
|
+
* @type {Array<OwnCapability>}
|
|
1475
|
+
* @memberof CallStateResponseFields
|
|
1476
|
+
*/
|
|
1477
|
+
own_capabilities: Array<OwnCapability>;
|
|
1478
1478
|
}
|
|
1479
1479
|
/**
|
|
1480
1480
|
*
|
|
@@ -2048,62 +2048,6 @@ export interface GeofenceSettingsRequest {
|
|
|
2048
2048
|
*/
|
|
2049
2049
|
names?: Array<string>;
|
|
2050
2050
|
}
|
|
2051
|
-
/**
|
|
2052
|
-
*
|
|
2053
|
-
* @export
|
|
2054
|
-
* @interface GetCallEdgeServerRequest
|
|
2055
|
-
*/
|
|
2056
|
-
export interface GetCallEdgeServerRequest {
|
|
2057
|
-
/**
|
|
2058
|
-
*
|
|
2059
|
-
* @type {{ [key: string]: Array<number>; }}
|
|
2060
|
-
* @memberof GetCallEdgeServerRequest
|
|
2061
|
-
*/
|
|
2062
|
-
latency_measurements: { [key: string]: Array<number> };
|
|
2063
|
-
}
|
|
2064
|
-
/**
|
|
2065
|
-
*
|
|
2066
|
-
* @export
|
|
2067
|
-
* @interface GetCallEdgeServerResponse
|
|
2068
|
-
*/
|
|
2069
|
-
export interface GetCallEdgeServerResponse {
|
|
2070
|
-
/**
|
|
2071
|
-
*
|
|
2072
|
-
* @type {Array<UserResponse>}
|
|
2073
|
-
* @memberof GetCallEdgeServerResponse
|
|
2074
|
-
*/
|
|
2075
|
-
blocked_users: Array<UserResponse>;
|
|
2076
|
-
/**
|
|
2077
|
-
*
|
|
2078
|
-
* @type {CallResponse}
|
|
2079
|
-
* @memberof GetCallEdgeServerResponse
|
|
2080
|
-
*/
|
|
2081
|
-
call: CallResponse;
|
|
2082
|
-
/**
|
|
2083
|
-
*
|
|
2084
|
-
* @type {Credentials}
|
|
2085
|
-
* @memberof GetCallEdgeServerResponse
|
|
2086
|
-
*/
|
|
2087
|
-
credentials: Credentials;
|
|
2088
|
-
/**
|
|
2089
|
-
* Duration of the request in human-readable format
|
|
2090
|
-
* @type {string}
|
|
2091
|
-
* @memberof GetCallEdgeServerResponse
|
|
2092
|
-
*/
|
|
2093
|
-
duration: string;
|
|
2094
|
-
/**
|
|
2095
|
-
*
|
|
2096
|
-
* @type {Array<MemberResponse>}
|
|
2097
|
-
* @memberof GetCallEdgeServerResponse
|
|
2098
|
-
*/
|
|
2099
|
-
members: Array<MemberResponse>;
|
|
2100
|
-
/**
|
|
2101
|
-
*
|
|
2102
|
-
* @type {MemberResponse}
|
|
2103
|
-
* @memberof GetCallEdgeServerResponse
|
|
2104
|
-
*/
|
|
2105
|
-
membership?: MemberResponse;
|
|
2106
|
-
}
|
|
2107
2051
|
/**
|
|
2108
2052
|
*
|
|
2109
2053
|
* @export
|
|
@@ -2140,6 +2084,12 @@ export interface GetCallResponse {
|
|
|
2140
2084
|
* @memberof GetCallResponse
|
|
2141
2085
|
*/
|
|
2142
2086
|
membership?: MemberResponse;
|
|
2087
|
+
/**
|
|
2088
|
+
*
|
|
2089
|
+
* @type {Array<OwnCapability>}
|
|
2090
|
+
* @memberof GetCallResponse
|
|
2091
|
+
*/
|
|
2092
|
+
own_capabilities: Array<OwnCapability>;
|
|
2143
2093
|
}
|
|
2144
2094
|
/**
|
|
2145
2095
|
*
|
|
@@ -2282,6 +2232,12 @@ export interface GetOrCreateCallResponse {
|
|
|
2282
2232
|
* @memberof GetOrCreateCallResponse
|
|
2283
2233
|
*/
|
|
2284
2234
|
membership?: MemberResponse;
|
|
2235
|
+
/**
|
|
2236
|
+
*
|
|
2237
|
+
* @type {Array<OwnCapability>}
|
|
2238
|
+
* @memberof GetOrCreateCallResponse
|
|
2239
|
+
*/
|
|
2240
|
+
own_capabilities: Array<OwnCapability>;
|
|
2285
2241
|
}
|
|
2286
2242
|
/**
|
|
2287
2243
|
*
|
|
@@ -2468,6 +2424,12 @@ export interface JoinCallResponse {
|
|
|
2468
2424
|
* @memberof JoinCallResponse
|
|
2469
2425
|
*/
|
|
2470
2426
|
membership?: MemberResponse;
|
|
2427
|
+
/**
|
|
2428
|
+
*
|
|
2429
|
+
* @type {Array<OwnCapability>}
|
|
2430
|
+
* @memberof JoinCallResponse
|
|
2431
|
+
*/
|
|
2432
|
+
own_capabilities: Array<OwnCapability>;
|
|
2471
2433
|
}
|
|
2472
2434
|
/**
|
|
2473
2435
|
*
|
|
@@ -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
|
*
|
|
@@ -69,7 +69,7 @@ const getMediaSection = (sdp: string, mediaType: 'video' | 'audio') => {
|
|
|
69
69
|
sdp.split(/(\r\n|\r|\n)/).forEach((line) => {
|
|
70
70
|
const isValidLine = /^([a-z])=(.*)/.test(line);
|
|
71
71
|
if (!isValidLine) return;
|
|
72
|
-
/*
|
|
72
|
+
/*
|
|
73
73
|
NOTE: according to https://www.rfc-editor.org/rfc/rfc8866.pdf
|
|
74
74
|
Each media description starts with an "m=" line and continues to the next media description or the end of the whole session description, whichever comes first
|
|
75
75
|
*/
|
|
@@ -170,7 +170,7 @@ export const removeCodec = (
|
|
|
170
170
|
sdp: string,
|
|
171
171
|
mediaType: 'video' | 'audio',
|
|
172
172
|
codecToRemove: string,
|
|
173
|
-
) => {
|
|
173
|
+
): string => {
|
|
174
174
|
const section = getMediaSection(sdp, mediaType);
|
|
175
175
|
const mediaSection = section?.media;
|
|
176
176
|
if (!mediaSection) {
|
|
@@ -190,30 +190,26 @@ export const removeCodec = (
|
|
|
190
190
|
mediaSection.original,
|
|
191
191
|
`${mediaSection.mediaWithPorts} ${newCodecOrder}`,
|
|
192
192
|
)
|
|
193
|
-
.replace(new RegExp(`${rtpMap.original}[\r\n
|
|
194
|
-
.replace(
|
|
195
|
-
fmtp?.original ? new RegExp(`${fmtp?.original}[\r\n|\r|\n]`) : '',
|
|
196
|
-
'',
|
|
197
|
-
); // remove the corresponding fmtp line
|
|
193
|
+
.replace(new RegExp(`${rtpMap.original}[\r\n]+`), '') // remove the corresponding rtpmap line
|
|
194
|
+
.replace(fmtp?.original ? new RegExp(`${fmtp?.original}[\r\n]+`) : '', ''); // remove the corresponding fmtp line
|
|
198
195
|
};
|
|
199
196
|
|
|
200
197
|
/**
|
|
201
198
|
* Gets the fmtp line corresponding to opus
|
|
202
199
|
*/
|
|
203
|
-
const getOpusFmtp = (sdp: string) => {
|
|
200
|
+
const getOpusFmtp = (sdp: string): Fmtp | undefined => {
|
|
204
201
|
const section = getMediaSection(sdp, 'audio');
|
|
205
202
|
const rtpMap = section?.rtpMap.find((r) => r.codec.toLowerCase() === 'opus');
|
|
206
203
|
const codecId = rtpMap?.payload;
|
|
207
204
|
if (codecId) {
|
|
208
|
-
|
|
209
|
-
return fmtp;
|
|
205
|
+
return section?.fmtp.find((f) => f.payload === codecId);
|
|
210
206
|
}
|
|
211
207
|
};
|
|
212
208
|
|
|
213
209
|
/**
|
|
214
210
|
* Returns an SDP with DTX enabled or disabled.
|
|
215
211
|
*/
|
|
216
|
-
export const toggleDtx = (sdp: string, enable: boolean) => {
|
|
212
|
+
export const toggleDtx = (sdp: string, enable: boolean): string => {
|
|
217
213
|
const opusFmtp = getOpusFmtp(sdp);
|
|
218
214
|
if (opusFmtp) {
|
|
219
215
|
const matchDtx = /usedtx=(\d)/.exec(opusFmtp.config);
|
|
@@ -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() {
|
|
@@ -35,12 +35,12 @@ describe('Publisher', () => {
|
|
|
35
35
|
|
|
36
36
|
beforeEach(() => {
|
|
37
37
|
const dispatcher = new Dispatcher();
|
|
38
|
-
sfuClient = new StreamSfuClient(
|
|
38
|
+
sfuClient = new StreamSfuClient({
|
|
39
39
|
dispatcher,
|
|
40
|
-
'https://getstream.io/',
|
|
41
|
-
'https://getstream.io/ws',
|
|
42
|
-
'token',
|
|
43
|
-
);
|
|
40
|
+
url: 'https://getstream.io/',
|
|
41
|
+
wsEndpoint: 'https://getstream.io/ws',
|
|
42
|
+
token: 'token',
|
|
43
|
+
});
|
|
44
44
|
|
|
45
45
|
// @ts-ignore
|
|
46
46
|
sfuClient['sessionId'] = sessionId;
|
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
|
|
|
@@ -341,7 +342,7 @@ export class Publisher {
|
|
|
341
342
|
const offer = await this.publisher.createOffer();
|
|
342
343
|
let sdp = offer.sdp;
|
|
343
344
|
if (sdp) {
|
|
344
|
-
toggleDtx(sdp, this.isDtxEnabled);
|
|
345
|
+
sdp = toggleDtx(sdp, this.isDtxEnabled);
|
|
345
346
|
if (isReactNative()) {
|
|
346
347
|
if (this.preferredVideoCodec) {
|
|
347
348
|
sdp = setPreferredCodec(sdp, 'video', this.preferredVideoCodec);
|
package/src/rtc/videoLayers.ts
CHANGED
|
@@ -25,7 +25,10 @@ export const findOptimalVideoLayers = (
|
|
|
25
25
|
) => {
|
|
26
26
|
const optimalVideoLayers: OptimalVideoLayer[] = [];
|
|
27
27
|
const settings = videoTrack.getSettings();
|
|
28
|
-
const {
|
|
28
|
+
const {
|
|
29
|
+
width: w = targetResolution.width,
|
|
30
|
+
height: h = targetResolution.height,
|
|
31
|
+
} = settings;
|
|
29
32
|
|
|
30
33
|
const maxBitrate = getComputedMaxBitrate(targetResolution, w, h);
|
|
31
34
|
let downscaleFactor = 1;
|
package/src/store/CallState.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
CallRecording,
|
|
15
15
|
CallResponse,
|
|
16
16
|
MemberResponse,
|
|
17
|
+
OwnCapability,
|
|
17
18
|
PermissionRequestEvent,
|
|
18
19
|
} from '../gen/coordinator';
|
|
19
20
|
import { TrackType } from '../gen/video/sfu/models/models';
|
|
@@ -91,6 +92,13 @@ export class CallState {
|
|
|
91
92
|
*/
|
|
92
93
|
private membersSubject = new BehaviorSubject<MemberResponse[]>([]);
|
|
93
94
|
|
|
95
|
+
/**
|
|
96
|
+
* The list of capabilities of the current user.
|
|
97
|
+
*
|
|
98
|
+
* @private
|
|
99
|
+
*/
|
|
100
|
+
private ownCapabilitiesSubject = new BehaviorSubject<OwnCapability[]>([]);
|
|
101
|
+
|
|
94
102
|
/**
|
|
95
103
|
* The calling state.
|
|
96
104
|
*
|
|
@@ -249,6 +257,11 @@ export class CallState {
|
|
|
249
257
|
*/
|
|
250
258
|
members$: Observable<MemberResponse[]>;
|
|
251
259
|
|
|
260
|
+
/**
|
|
261
|
+
* The list of capabilities of the current user.
|
|
262
|
+
*/
|
|
263
|
+
ownCapabilities$: Observable<OwnCapability[]>;
|
|
264
|
+
|
|
252
265
|
/**
|
|
253
266
|
* The calling state.
|
|
254
267
|
*/
|
|
@@ -307,6 +320,7 @@ export class CallState {
|
|
|
307
320
|
this.callRecordingList$ = this.callRecordingListSubject.asObservable();
|
|
308
321
|
this.metadata$ = this.metadataSubject.asObservable();
|
|
309
322
|
this.members$ = this.membersSubject.asObservable();
|
|
323
|
+
this.ownCapabilities$ = this.ownCapabilitiesSubject.asObservable();
|
|
310
324
|
this.callingState$ = this.callingStateSubject.asObservable();
|
|
311
325
|
}
|
|
312
326
|
|
|
@@ -555,6 +569,23 @@ export class CallState {
|
|
|
555
569
|
this.setCurrentValue(this.membersSubject, members);
|
|
556
570
|
};
|
|
557
571
|
|
|
572
|
+
/**
|
|
573
|
+
* The capabilities of the current user for the current call.
|
|
574
|
+
*/
|
|
575
|
+
get ownCapabilities() {
|
|
576
|
+
return this.getCurrentValue(this.ownCapabilities$);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Sets the own capabilities.
|
|
581
|
+
*
|
|
582
|
+
* @internal
|
|
583
|
+
* @param capabilities the capabilities to set.
|
|
584
|
+
*/
|
|
585
|
+
setOwnCapabilities = (capabilities: Patch<OwnCapability[]>) => {
|
|
586
|
+
return this.setCurrentValue(this.ownCapabilitiesSubject, capabilities);
|
|
587
|
+
};
|
|
588
|
+
|
|
558
589
|
/**
|
|
559
590
|
* Will try to find the participant with the given sessionId in the current call.
|
|
560
591
|
*
|
package/src/types.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
CallResponse,
|
|
7
7
|
JoinCallRequest,
|
|
8
8
|
MemberResponse,
|
|
9
|
+
OwnCapability,
|
|
9
10
|
ReactionResponse,
|
|
10
11
|
} from './gen/coordinator';
|
|
11
12
|
import type { StreamClient } from './coordinator/connection/client';
|
|
@@ -184,6 +185,13 @@ export type CallConstructor = {
|
|
|
184
185
|
*/
|
|
185
186
|
members?: MemberResponse[];
|
|
186
187
|
|
|
188
|
+
/**
|
|
189
|
+
* An optional list of {@link OwnCapability} coming from the backed.
|
|
190
|
+
* If provided, the call will be initialized with the data from this object.
|
|
191
|
+
* This is useful when initializing a new "pending call" from an event.
|
|
192
|
+
*/
|
|
193
|
+
ownCapabilities?: OwnCapability[];
|
|
194
|
+
|
|
187
195
|
/**
|
|
188
196
|
* Flags the call as a ringing call.
|
|
189
197
|
* @default false
|