@stream-io/video-client 1.13.1 → 1.15.0
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 +1704 -1762
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +1706 -1780
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +1704 -1762
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +61 -30
- package/dist/src/StreamSfuClient.d.ts +4 -5
- package/dist/src/devices/CameraManager.d.ts +5 -8
- package/dist/src/devices/InputMediaDeviceManager.d.ts +5 -5
- package/dist/src/devices/MicrophoneManager.d.ts +7 -2
- package/dist/src/devices/ScreenShareManager.d.ts +1 -2
- package/dist/src/gen/coordinator/index.d.ts +904 -515
- package/dist/src/gen/video/sfu/event/events.d.ts +38 -19
- package/dist/src/gen/video/sfu/models/models.d.ts +76 -9
- package/dist/src/helpers/array.d.ts +7 -0
- package/dist/src/permissions/PermissionsContext.d.ts +6 -0
- package/dist/src/rtc/BasePeerConnection.d.ts +90 -0
- package/dist/src/rtc/Dispatcher.d.ts +0 -1
- package/dist/src/rtc/IceTrickleBuffer.d.ts +3 -2
- package/dist/src/rtc/Publisher.d.ts +32 -86
- package/dist/src/rtc/Subscriber.d.ts +4 -56
- package/dist/src/rtc/TransceiverCache.d.ts +55 -0
- package/dist/src/rtc/codecs.d.ts +1 -15
- package/dist/src/rtc/helpers/sdp.d.ts +8 -0
- package/dist/src/rtc/helpers/tracks.d.ts +1 -0
- package/dist/src/rtc/index.d.ts +3 -0
- package/dist/src/rtc/videoLayers.d.ts +11 -25
- package/dist/src/stats/{stateStoreStatsReporter.d.ts → CallStateStatsReporter.d.ts} +5 -1
- package/dist/src/stats/SfuStatsReporter.d.ts +4 -2
- package/dist/src/stats/index.d.ts +1 -1
- package/dist/src/stats/types.d.ts +8 -0
- package/dist/src/store/CallState.d.ts +47 -5
- package/dist/src/store/rxUtils.d.ts +15 -1
- package/dist/src/types.d.ts +26 -22
- package/package.json +1 -1
- package/src/Call.ts +310 -271
- package/src/StreamSfuClient.ts +9 -14
- package/src/StreamVideoClient.ts +1 -1
- package/src/__tests__/Call.publishing.test.ts +306 -0
- package/src/devices/CameraManager.ts +33 -16
- package/src/devices/InputMediaDeviceManager.ts +36 -27
- package/src/devices/MicrophoneManager.ts +29 -8
- package/src/devices/ScreenShareManager.ts +6 -8
- package/src/devices/__tests__/CameraManager.test.ts +111 -14
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +4 -4
- package/src/devices/__tests__/MicrophoneManager.test.ts +59 -21
- package/src/devices/__tests__/ScreenShareManager.test.ts +5 -5
- package/src/devices/__tests__/mocks.ts +1 -0
- package/src/events/__tests__/internal.test.ts +132 -0
- package/src/events/__tests__/mutes.test.ts +0 -3
- package/src/events/__tests__/speaker.test.ts +92 -0
- package/src/events/participant.ts +3 -4
- package/src/gen/coordinator/index.ts +902 -514
- package/src/gen/video/sfu/event/events.ts +91 -30
- package/src/gen/video/sfu/models/models.ts +105 -13
- package/src/helpers/array.ts +14 -0
- package/src/permissions/PermissionsContext.ts +22 -0
- package/src/permissions/__tests__/PermissionsContext.test.ts +40 -0
- package/src/rpc/__tests__/createClient.test.ts +38 -0
- package/src/rpc/createClient.ts +11 -5
- package/src/rtc/BasePeerConnection.ts +240 -0
- package/src/rtc/Dispatcher.ts +0 -9
- package/src/rtc/IceTrickleBuffer.ts +24 -4
- package/src/rtc/Publisher.ts +210 -528
- package/src/rtc/Subscriber.ts +26 -200
- package/src/rtc/TransceiverCache.ts +120 -0
- package/src/rtc/__tests__/Publisher.test.ts +407 -210
- package/src/rtc/__tests__/Subscriber.test.ts +88 -36
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +22 -2
- package/src/rtc/__tests__/videoLayers.test.ts +161 -54
- package/src/rtc/codecs.ts +1 -131
- package/src/rtc/helpers/__tests__/rtcConfiguration.test.ts +34 -0
- package/src/rtc/helpers/__tests__/sdp.test.ts +59 -0
- package/src/rtc/helpers/sdp.ts +30 -0
- package/src/rtc/helpers/tracks.ts +3 -0
- package/src/rtc/index.ts +4 -0
- package/src/rtc/videoLayers.ts +68 -76
- package/src/stats/{stateStoreStatsReporter.ts → CallStateStatsReporter.ts} +58 -27
- package/src/stats/SfuStatsReporter.ts +31 -3
- package/src/stats/index.ts +1 -1
- package/src/stats/types.ts +12 -0
- package/src/store/CallState.ts +115 -5
- package/src/store/__tests__/CallState.test.ts +101 -0
- package/src/store/rxUtils.ts +23 -1
- package/src/types.ts +27 -22
- package/dist/src/helpers/sdp-munging.d.ts +0 -24
- package/dist/src/rtc/bitrateLookup.d.ts +0 -2
- package/dist/src/rtc/helpers/iceCandidate.d.ts +0 -2
- package/src/helpers/__tests__/hq-audio-sdp.ts +0 -332
- package/src/helpers/__tests__/sdp-munging.test.ts +0 -283
- package/src/helpers/sdp-munging.ts +0 -265
- package/src/rtc/__tests__/bitrateLookup.test.ts +0 -12
- package/src/rtc/__tests__/codecs.test.ts +0 -145
- package/src/rtc/bitrateLookup.ts +0 -61
- package/src/rtc/helpers/iceCandidate.ts +0 -16
- /package/dist/src/{compatibility.d.ts → helpers/compatibility.d.ts} +0 -0
- /package/src/{compatibility.ts → helpers/compatibility.ts} +0 -0
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
import * as SDP from 'sdp-transform';
|
|
2
|
-
|
|
3
|
-
type Media = {
|
|
4
|
-
original: string;
|
|
5
|
-
mediaWithPorts: string;
|
|
6
|
-
codecOrder: string;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
type RtpMap = {
|
|
10
|
-
original: string;
|
|
11
|
-
payload: string;
|
|
12
|
-
codec: string;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
type Fmtp = {
|
|
16
|
-
original: string;
|
|
17
|
-
payload: string;
|
|
18
|
-
config: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const getRtpMap = (line: string): RtpMap | undefined => {
|
|
22
|
-
// Example: a=rtpmap:110 opus/48000/2
|
|
23
|
-
const rtpRegex = /^a=rtpmap:(\d*) ([\w\-.]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/;
|
|
24
|
-
// The first captured group is the payload type number, the second captured group is the encoding name, the third captured group is the clock rate, and the fourth captured group is any additional parameters.
|
|
25
|
-
const rtpMatch = rtpRegex.exec(line);
|
|
26
|
-
if (rtpMatch) {
|
|
27
|
-
return {
|
|
28
|
-
original: rtpMatch[0],
|
|
29
|
-
payload: rtpMatch[1],
|
|
30
|
-
codec: rtpMatch[2],
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const getFmtp = (line: string): Fmtp | undefined => {
|
|
36
|
-
// Example: a=fmtp:111 minptime=10; useinbandfec=1
|
|
37
|
-
const fmtpRegex = /^a=fmtp:(\d*) (.*)/;
|
|
38
|
-
const fmtpMatch = fmtpRegex.exec(line);
|
|
39
|
-
// The first captured group is the payload type number, the second captured group is any additional parameters.
|
|
40
|
-
if (fmtpMatch) {
|
|
41
|
-
return {
|
|
42
|
-
original: fmtpMatch[0],
|
|
43
|
-
payload: fmtpMatch[1],
|
|
44
|
-
config: fmtpMatch[2],
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* gets the media section for the specified media type.
|
|
51
|
-
* The media section contains the media type, port, codec, and payload type.
|
|
52
|
-
* Example: m=video 9 UDP/TLS/RTP/SAVPF 100 101 96 97 35 36 102 125 127
|
|
53
|
-
*/
|
|
54
|
-
const getMedia = (line: string, mediaType: string): Media | undefined => {
|
|
55
|
-
const regex = new RegExp(`(m=${mediaType} \\d+ [\\w/]+) ([\\d\\s]+)`);
|
|
56
|
-
const match = regex.exec(line);
|
|
57
|
-
if (match) {
|
|
58
|
-
return {
|
|
59
|
-
original: match[0],
|
|
60
|
-
mediaWithPorts: match[1],
|
|
61
|
-
codecOrder: match[2],
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const getMediaSection = (sdp: string, mediaType: 'video' | 'audio') => {
|
|
67
|
-
let media: Media | undefined;
|
|
68
|
-
const rtpMap: RtpMap[] = [];
|
|
69
|
-
const fmtp: Fmtp[] = [];
|
|
70
|
-
let isTheRequiredMediaSection = false;
|
|
71
|
-
sdp.split(/(\r\n|\r|\n)/).forEach((line) => {
|
|
72
|
-
const isValidLine = /^([a-z])=(.*)/.test(line);
|
|
73
|
-
if (!isValidLine) return;
|
|
74
|
-
/*
|
|
75
|
-
NOTE: according to https://www.rfc-editor.org/rfc/rfc8866.pdf
|
|
76
|
-
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
|
|
77
|
-
*/
|
|
78
|
-
const type = line[0];
|
|
79
|
-
if (type === 'm') {
|
|
80
|
-
const _media = getMedia(line, mediaType);
|
|
81
|
-
isTheRequiredMediaSection = !!_media;
|
|
82
|
-
if (_media) {
|
|
83
|
-
media = _media;
|
|
84
|
-
}
|
|
85
|
-
} else if (isTheRequiredMediaSection && type === 'a') {
|
|
86
|
-
const rtpMapLine = getRtpMap(line);
|
|
87
|
-
const fmtpLine = getFmtp(line);
|
|
88
|
-
if (rtpMapLine) {
|
|
89
|
-
rtpMap.push(rtpMapLine);
|
|
90
|
-
} else if (fmtpLine) {
|
|
91
|
-
fmtp.push(fmtpLine);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
if (media) {
|
|
96
|
-
return {
|
|
97
|
-
media,
|
|
98
|
-
rtpMap,
|
|
99
|
-
fmtp,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Gets the fmtp line corresponding to opus
|
|
106
|
-
*/
|
|
107
|
-
const getOpusFmtp = (sdp: string): Fmtp | undefined => {
|
|
108
|
-
const section = getMediaSection(sdp, 'audio');
|
|
109
|
-
const rtpMap = section?.rtpMap.find((r) => r.codec.toLowerCase() === 'opus');
|
|
110
|
-
const codecId = rtpMap?.payload;
|
|
111
|
-
if (codecId) {
|
|
112
|
-
return section?.fmtp.find((f) => f.payload === codecId);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Returns an SDP with DTX enabled or disabled.
|
|
118
|
-
*/
|
|
119
|
-
export const toggleDtx = (sdp: string, enable: boolean): string => {
|
|
120
|
-
const opusFmtp = getOpusFmtp(sdp);
|
|
121
|
-
if (!opusFmtp) return sdp;
|
|
122
|
-
|
|
123
|
-
const matchDtx = /usedtx=(\d)/.exec(opusFmtp.config);
|
|
124
|
-
const requiredDtxConfig = `usedtx=${enable ? '1' : '0'}`;
|
|
125
|
-
const newFmtp = matchDtx
|
|
126
|
-
? opusFmtp.original.replace(/usedtx=(\d)/, requiredDtxConfig)
|
|
127
|
-
: `${opusFmtp.original};${requiredDtxConfig}`;
|
|
128
|
-
|
|
129
|
-
return sdp.replace(opusFmtp.original, newFmtp);
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Returns and SDP with all the codecs except the given codec removed.
|
|
134
|
-
*/
|
|
135
|
-
export const preserveCodec = (
|
|
136
|
-
sdp: string,
|
|
137
|
-
mid: string,
|
|
138
|
-
codec: RTCRtpCodec,
|
|
139
|
-
): string => {
|
|
140
|
-
const [kind, codecName] = codec.mimeType.toLowerCase().split('/');
|
|
141
|
-
|
|
142
|
-
const toSet = (fmtpLine: string) =>
|
|
143
|
-
new Set(fmtpLine.split(';').map((f) => f.trim().toLowerCase()));
|
|
144
|
-
|
|
145
|
-
const equal = (a: Set<string>, b: Set<string>) => {
|
|
146
|
-
if (a.size !== b.size) return false;
|
|
147
|
-
for (const item of a) if (!b.has(item)) return false;
|
|
148
|
-
return true;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const codecFmtp = toSet(codec.sdpFmtpLine || '');
|
|
152
|
-
const parsedSdp = SDP.parse(sdp);
|
|
153
|
-
for (const media of parsedSdp.media) {
|
|
154
|
-
if (media.type !== kind || String(media.mid) !== mid) continue;
|
|
155
|
-
|
|
156
|
-
// find the payload id of the desired codec
|
|
157
|
-
const payloads = new Set<number>();
|
|
158
|
-
for (const rtp of media.rtp) {
|
|
159
|
-
if (rtp.codec.toLowerCase() !== codecName) continue;
|
|
160
|
-
const match =
|
|
161
|
-
// vp8 doesn't have any fmtp, we preserve it without any additional checks
|
|
162
|
-
codecName === 'vp8'
|
|
163
|
-
? true
|
|
164
|
-
: media.fmtp.some(
|
|
165
|
-
(f) =>
|
|
166
|
-
f.payload === rtp.payload && equal(toSet(f.config), codecFmtp),
|
|
167
|
-
);
|
|
168
|
-
if (match) {
|
|
169
|
-
payloads.add(rtp.payload);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// find the corresponding rtx codec by matching apt=<preserved-codec-payload>
|
|
174
|
-
for (const fmtp of media.fmtp) {
|
|
175
|
-
const match = fmtp.config.match(/(apt)=(\d+)/);
|
|
176
|
-
if (!match) continue;
|
|
177
|
-
const [, , preservedCodecPayload] = match;
|
|
178
|
-
if (payloads.has(Number(preservedCodecPayload))) {
|
|
179
|
-
payloads.add(fmtp.payload);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
media.rtp = media.rtp.filter((r) => payloads.has(r.payload));
|
|
184
|
-
media.fmtp = media.fmtp.filter((f) => payloads.has(f.payload));
|
|
185
|
-
media.rtcpFb = media.rtcpFb?.filter((f) => payloads.has(f.payload));
|
|
186
|
-
media.payloads = Array.from(payloads).join(' ');
|
|
187
|
-
}
|
|
188
|
-
return SDP.write(parsedSdp);
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Enables high-quality audio through SDP munging for the given trackMid.
|
|
193
|
-
*
|
|
194
|
-
* @param sdp the SDP to munge.
|
|
195
|
-
* @param trackMid the trackMid.
|
|
196
|
-
* @param maxBitrate the max bitrate to set.
|
|
197
|
-
*/
|
|
198
|
-
export const enableHighQualityAudio = (
|
|
199
|
-
sdp: string,
|
|
200
|
-
trackMid: string,
|
|
201
|
-
maxBitrate: number = 510000,
|
|
202
|
-
): string => {
|
|
203
|
-
maxBitrate = Math.max(Math.min(maxBitrate, 510000), 96000);
|
|
204
|
-
|
|
205
|
-
const parsedSdp = SDP.parse(sdp);
|
|
206
|
-
const audioMedia = parsedSdp.media.find(
|
|
207
|
-
(m) => m.type === 'audio' && String(m.mid) === trackMid,
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
if (!audioMedia) return sdp;
|
|
211
|
-
|
|
212
|
-
const opusRtp = audioMedia.rtp.find((r) => r.codec === 'opus');
|
|
213
|
-
if (!opusRtp) return sdp;
|
|
214
|
-
|
|
215
|
-
const opusFmtp = audioMedia.fmtp.find((f) => f.payload === opusRtp.payload);
|
|
216
|
-
if (!opusFmtp) return sdp;
|
|
217
|
-
|
|
218
|
-
// enable stereo, if not already enabled
|
|
219
|
-
if (opusFmtp.config.match(/stereo=(\d)/)) {
|
|
220
|
-
opusFmtp.config = opusFmtp.config.replace(/stereo=(\d)/, 'stereo=1');
|
|
221
|
-
} else {
|
|
222
|
-
opusFmtp.config = `${opusFmtp.config};stereo=1`;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// set maxaveragebitrate, to the given value
|
|
226
|
-
if (opusFmtp.config.match(/maxaveragebitrate=(\d*)/)) {
|
|
227
|
-
opusFmtp.config = opusFmtp.config.replace(
|
|
228
|
-
/maxaveragebitrate=(\d*)/,
|
|
229
|
-
`maxaveragebitrate=${maxBitrate}`,
|
|
230
|
-
);
|
|
231
|
-
} else {
|
|
232
|
-
opusFmtp.config = `${opusFmtp.config};maxaveragebitrate=${maxBitrate}`;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return SDP.write(parsedSdp);
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Extracts the mid from the transceiver or the SDP.
|
|
240
|
-
*
|
|
241
|
-
* @param transceiver the transceiver.
|
|
242
|
-
* @param transceiverInitIndex the index of the transceiver in the transceiver's init array.
|
|
243
|
-
* @param sdp the SDP.
|
|
244
|
-
*/
|
|
245
|
-
export const extractMid = (
|
|
246
|
-
transceiver: RTCRtpTransceiver,
|
|
247
|
-
transceiverInitIndex: number,
|
|
248
|
-
sdp: string | undefined,
|
|
249
|
-
): string => {
|
|
250
|
-
if (transceiver.mid) return transceiver.mid;
|
|
251
|
-
if (!sdp) return '';
|
|
252
|
-
|
|
253
|
-
const track = transceiver.sender.track!;
|
|
254
|
-
const parsedSdp = SDP.parse(sdp);
|
|
255
|
-
const media = parsedSdp.media.find((m) => {
|
|
256
|
-
return (
|
|
257
|
-
m.type === track.kind &&
|
|
258
|
-
// if `msid` is not present, we assume that the track is the first one
|
|
259
|
-
(m.msid?.includes(track.id) ?? true)
|
|
260
|
-
);
|
|
261
|
-
});
|
|
262
|
-
if (typeof media?.mid !== 'undefined') return String(media.mid);
|
|
263
|
-
if (transceiverInitIndex === -1) return '';
|
|
264
|
-
return String(transceiverInitIndex);
|
|
265
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { getOptimalBitrate } from '../bitrateLookup';
|
|
3
|
-
|
|
4
|
-
describe('bitrateLookup', () => {
|
|
5
|
-
it('should return optimal bitrate', () => {
|
|
6
|
-
expect(getOptimalBitrate('vp9', 720)).toBe(1_250_000);
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it('should return nearest bitrate for exotic dimensions', () => {
|
|
10
|
-
expect(getOptimalBitrate('vp9', 1000)).toBe(1_500_000);
|
|
11
|
-
});
|
|
12
|
-
});
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { getPreferredCodecs } from '../codecs';
|
|
3
|
-
import './mocks/webrtc.mocks';
|
|
4
|
-
|
|
5
|
-
describe('codecs', () => {
|
|
6
|
-
it('should return preferred audio codec', () => {
|
|
7
|
-
RTCRtpReceiver.getCapabilities = vi.fn().mockReturnValue(audioCodecs);
|
|
8
|
-
const codecs = getPreferredCodecs('audio', 'red', undefined, 'receiver');
|
|
9
|
-
expect(codecs).toBeDefined();
|
|
10
|
-
expect(codecs?.map((c) => c.mimeType)).toEqual([
|
|
11
|
-
'audio/red',
|
|
12
|
-
'audio/opus',
|
|
13
|
-
'audio/G722',
|
|
14
|
-
'audio/PCMU',
|
|
15
|
-
'audio/PCMA',
|
|
16
|
-
'audio/CN',
|
|
17
|
-
'audio/telephone-event',
|
|
18
|
-
]);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('should return preferred video codec', () => {
|
|
22
|
-
RTCRtpReceiver.getCapabilities = vi.fn().mockReturnValue(videoCodecs);
|
|
23
|
-
const codecs = getPreferredCodecs('video', 'vp8', undefined, 'receiver');
|
|
24
|
-
expect(codecs).toBeDefined();
|
|
25
|
-
// prettier-ignore
|
|
26
|
-
expect(codecs?.map((c) => [c.mimeType, c.sdpFmtpLine])).toEqual([
|
|
27
|
-
['video/VP8', undefined],
|
|
28
|
-
['video/H264', 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f'],
|
|
29
|
-
['video/rtx', undefined],
|
|
30
|
-
['video/H264', 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f'],
|
|
31
|
-
['video/H264', 'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=640c1f'],
|
|
32
|
-
['video/H264', 'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f'],
|
|
33
|
-
['video/VP9', 'profile-id=0'],
|
|
34
|
-
['video/VP9', 'profile-id=2'],
|
|
35
|
-
['video/red', undefined],
|
|
36
|
-
['video/ulpfec', undefined],
|
|
37
|
-
['video/flexfec-03', 'repair-window=10000000'],
|
|
38
|
-
]);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should pick the baseline H264 codec', () => {
|
|
42
|
-
RTCRtpReceiver.getCapabilities = vi.fn().mockReturnValue(videoCodecs);
|
|
43
|
-
const codecs = getPreferredCodecs('video', 'h264', undefined, 'receiver');
|
|
44
|
-
expect(codecs).toBeDefined();
|
|
45
|
-
// prettier-ignore
|
|
46
|
-
expect(codecs?.map((c) => [c.mimeType, c.sdpFmtpLine])).toEqual([
|
|
47
|
-
['video/H264', 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f'],
|
|
48
|
-
['video/H264', 'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f'],
|
|
49
|
-
['video/H264', 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f'],
|
|
50
|
-
['video/H264', 'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=640c1f'],
|
|
51
|
-
['video/rtx', undefined],
|
|
52
|
-
['video/VP8', undefined],
|
|
53
|
-
['video/VP9', 'profile-id=0'],
|
|
54
|
-
['video/VP9', 'profile-id=2'],
|
|
55
|
-
['video/red', undefined],
|
|
56
|
-
['video/ulpfec', undefined],
|
|
57
|
-
['video/flexfec-03', 'repair-window=10000000'],
|
|
58
|
-
]);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should pick the baseline H264 codec with optional packetization-mode', () => {
|
|
62
|
-
RTCRtpReceiver.getCapabilities = vi
|
|
63
|
-
.fn()
|
|
64
|
-
.mockReturnValue(videoCodecsFirefox);
|
|
65
|
-
const codecs = getPreferredCodecs('video', 'h264', undefined, 'receiver');
|
|
66
|
-
expect(codecs).toBeDefined();
|
|
67
|
-
// prettier-ignore
|
|
68
|
-
expect(codecs?.map((c) => [c.mimeType, c.sdpFmtpLine])).toEqual([
|
|
69
|
-
['video/H264', 'profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1'],
|
|
70
|
-
['video/H264', 'profile-level-id=42e01f;level-asymmetry-allowed=1'],
|
|
71
|
-
['video/VP8', 'max-fs=12288;max-fr=60'],
|
|
72
|
-
['video/rtx', undefined],
|
|
73
|
-
['video/VP9', 'max-fs=12288;max-fr=60'],
|
|
74
|
-
['video/ulpfec', undefined],
|
|
75
|
-
['video/red', undefined],
|
|
76
|
-
]);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// prettier-ignore
|
|
81
|
-
const videoCodecsFirefox: RTCRtpCapabilities = {
|
|
82
|
-
codecs: [
|
|
83
|
-
{ mimeType: 'video/VP8', sdpFmtpLine: 'max-fs=12288;max-fr=60', clockRate: 90000 },
|
|
84
|
-
{ mimeType: 'video/rtx', clockRate: 90000 },
|
|
85
|
-
{ mimeType: 'video/VP9', sdpFmtpLine: 'max-fs=12288;max-fr=60', clockRate: 90000 },
|
|
86
|
-
{ mimeType: 'video/H264', sdpFmtpLine: 'profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1', clockRate: 90000 },
|
|
87
|
-
{ mimeType: 'video/H264', sdpFmtpLine: 'profile-level-id=42e01f;level-asymmetry-allowed=1', clockRate: 90000 },
|
|
88
|
-
{ mimeType: 'video/ulpfec', clockRate: 90000 },
|
|
89
|
-
{ mimeType: 'video/red', clockRate: 90000 },
|
|
90
|
-
],
|
|
91
|
-
headerExtensions: [
|
|
92
|
-
{ uri: 'urn:ietf:params:rtp-hdrext:sdes:mid' },
|
|
93
|
-
{ uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' },
|
|
94
|
-
{ uri: 'urn:ietf:params:rtp-hdrext:toffset' },
|
|
95
|
-
{ uri: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay' },
|
|
96
|
-
],
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// prettier-ignore
|
|
100
|
-
const videoCodecs: RTCRtpCapabilities = {
|
|
101
|
-
codecs: [
|
|
102
|
-
{ mimeType: 'video/H264', sdpFmtpLine: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f', clockRate: 90000 },
|
|
103
|
-
{ mimeType: 'video/rtx', clockRate: 90000 },
|
|
104
|
-
{ mimeType: 'video/H264', sdpFmtpLine: 'level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f', clockRate: 90000 },
|
|
105
|
-
{ mimeType: 'video/H264', sdpFmtpLine: 'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=640c1f', clockRate: 90000 },
|
|
106
|
-
{ mimeType: 'video/H264', sdpFmtpLine: 'level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f', clockRate: 90000 },
|
|
107
|
-
{ mimeType: 'video/VP8', clockRate: 90000 },
|
|
108
|
-
{ mimeType: 'video/VP9', sdpFmtpLine: 'profile-id=0', clockRate: 90000 },
|
|
109
|
-
{ mimeType: 'video/VP9', sdpFmtpLine: 'profile-id=2', clockRate: 90000 },
|
|
110
|
-
{ mimeType: 'video/red', clockRate: 90000 },
|
|
111
|
-
{ mimeType: 'video/ulpfec', clockRate: 90000 },
|
|
112
|
-
{ mimeType: 'video/flexfec-03', sdpFmtpLine: 'repair-window=10000000', clockRate: 90000 },
|
|
113
|
-
],
|
|
114
|
-
headerExtensions: [
|
|
115
|
-
{ uri: 'urn:ietf:params:rtp-hdrext:toffset' },
|
|
116
|
-
{ uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' },
|
|
117
|
-
{ uri: 'urn:3gpp:video-orientation' },
|
|
118
|
-
{ uri: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01' },
|
|
119
|
-
{ uri: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay' },
|
|
120
|
-
{ uri: 'http://www.webrtc.org/experiments/rtp-hdrext/video-content-type' },
|
|
121
|
-
{ uri: 'http://www.webrtc.org/experiments/rtp-hdrext/video-timing' },
|
|
122
|
-
{ uri: 'http://www.webrtc.org/experiments/rtp-hdrext/color-space' },
|
|
123
|
-
{ uri: 'urn:ietf:params:rtp-hdrext:sdes:mid' },
|
|
124
|
-
{ uri: 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id' },
|
|
125
|
-
{ uri: 'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id' },
|
|
126
|
-
],
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
// prettier-ignore
|
|
130
|
-
const audioCodecs: RTCRtpCapabilities = {
|
|
131
|
-
codecs: [
|
|
132
|
-
{ mimeType: 'audio/opus', sdpFmtpLine: 'minptime=10;useinbandfec=1', clockRate: 48000 },
|
|
133
|
-
{ mimeType: 'audio/red', sdpFmtpLine: '=111/111', clockRate: 48000 },
|
|
134
|
-
{ mimeType: 'audio/G722', clockRate: 8000, channels: 1 },
|
|
135
|
-
{ mimeType: 'audio/PCMU', clockRate: 8000, channels: 1 },
|
|
136
|
-
{ mimeType: 'audio/PCMA', clockRate: 8000, channels: 1 },
|
|
137
|
-
{ mimeType: 'audio/CN', clockRate: 8000, channels: 1 },
|
|
138
|
-
{ mimeType: 'audio/telephone-event', clockRate: 8000, channels: 1 },
|
|
139
|
-
],
|
|
140
|
-
headerExtensions: [
|
|
141
|
-
{ uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level' },
|
|
142
|
-
{ uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' },
|
|
143
|
-
{ uri: 'urn:ietf:params:rtp-hdrext:sdes:mid' },
|
|
144
|
-
],
|
|
145
|
-
};
|
package/src/rtc/bitrateLookup.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { PreferredCodec } from '../types';
|
|
2
|
-
|
|
3
|
-
const bitrateLookupTable: Record<
|
|
4
|
-
PreferredCodec,
|
|
5
|
-
Record<number | 'default', number | undefined> | undefined
|
|
6
|
-
> = {
|
|
7
|
-
h264: {
|
|
8
|
-
2160: 5_000_000,
|
|
9
|
-
1440: 3_000_000,
|
|
10
|
-
1080: 2_000_000,
|
|
11
|
-
720: 1_250_000,
|
|
12
|
-
540: 750_000,
|
|
13
|
-
360: 400_000,
|
|
14
|
-
default: 1_250_000,
|
|
15
|
-
},
|
|
16
|
-
vp8: {
|
|
17
|
-
2160: 5_000_000,
|
|
18
|
-
1440: 2_750_000,
|
|
19
|
-
1080: 2_000_000,
|
|
20
|
-
720: 1_250_000,
|
|
21
|
-
540: 600_000,
|
|
22
|
-
360: 350_000,
|
|
23
|
-
default: 1_250_000,
|
|
24
|
-
},
|
|
25
|
-
vp9: {
|
|
26
|
-
2160: 3_000_000,
|
|
27
|
-
1440: 2_000_000,
|
|
28
|
-
1080: 1_500_000,
|
|
29
|
-
720: 1_250_000,
|
|
30
|
-
540: 500_000,
|
|
31
|
-
360: 275_000,
|
|
32
|
-
default: 1_250_000,
|
|
33
|
-
},
|
|
34
|
-
av1: {
|
|
35
|
-
2160: 2_000_000,
|
|
36
|
-
1440: 1_550_000,
|
|
37
|
-
1080: 1_000_000,
|
|
38
|
-
720: 600_000,
|
|
39
|
-
540: 350_000,
|
|
40
|
-
360: 200_000,
|
|
41
|
-
default: 600_000,
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export const getOptimalBitrate = (
|
|
46
|
-
codec: PreferredCodec,
|
|
47
|
-
frameHeight: number,
|
|
48
|
-
): number => {
|
|
49
|
-
const codecLookup = bitrateLookupTable[codec];
|
|
50
|
-
if (!codecLookup) throw new Error(`Unknown codec: ${codec}`);
|
|
51
|
-
|
|
52
|
-
let bitrate = codecLookup[frameHeight];
|
|
53
|
-
if (!bitrate) {
|
|
54
|
-
const keys = Object.keys(codecLookup).map(Number);
|
|
55
|
-
const nearest = keys.reduce((a, b) =>
|
|
56
|
-
Math.abs(b - frameHeight) < Math.abs(a - frameHeight) ? b : a,
|
|
57
|
-
);
|
|
58
|
-
bitrate = codecLookup[nearest];
|
|
59
|
-
}
|
|
60
|
-
return bitrate ?? codecLookup.default!;
|
|
61
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { ICETrickle } from '../../gen/video/sfu/models/models';
|
|
2
|
-
|
|
3
|
-
export function getIceCandidate(
|
|
4
|
-
candidate: RTCIceCandidate,
|
|
5
|
-
): ICETrickle['iceCandidate'] {
|
|
6
|
-
if (!candidate.usernameFragment) {
|
|
7
|
-
// react-native-webrtc doesn't include usernameFragment in the candidate
|
|
8
|
-
const splittedCandidate = candidate.candidate.split(' ');
|
|
9
|
-
const ufragIndex =
|
|
10
|
-
splittedCandidate.findIndex((s: string) => s === 'ufrag') + 1;
|
|
11
|
-
const usernameFragment = splittedCandidate[ufragIndex];
|
|
12
|
-
return JSON.stringify({ ...candidate, usernameFragment });
|
|
13
|
-
} else {
|
|
14
|
-
return JSON.stringify(candidate.toJSON());
|
|
15
|
-
}
|
|
16
|
-
}
|
|
File without changes
|
|
File without changes
|