mirror of
https://github.com/servo/servo.git
synced 2025-10-16 16:29:18 +01:00
121 lines
4.9 KiB
JavaScript
121 lines
4.9 KiB
JavaScript
'use strict';
|
|
/* Helper functions to munge SDP and split the sending track into
|
|
* separate tracks on the receiving end. This can be done in a number
|
|
* of ways, the one used here uses the fact that the MID and RID header
|
|
* extensions which are used for packet routing share the same wire
|
|
* format. The receiver interprets the rids from the sender as mids
|
|
* which allows receiving the different spatial resolutions on separate
|
|
* m-lines and tracks.
|
|
*/
|
|
const extensionsToFilter = [
|
|
'urn:ietf:params:rtp-hdrext:sdes:mid',
|
|
'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id',
|
|
'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id',
|
|
];
|
|
|
|
function swapRidAndMidExtensionsInSimulcastOffer(offer, rids) {
|
|
const sections = SDPUtils.splitSections(offer.sdp);
|
|
const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
|
|
const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
|
|
const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);
|
|
|
|
// The gist of this hack is that rid and mid have the same wire format.
|
|
const rid = rtpParameters.headerExtensions.find(ext => ext.uri === 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id');
|
|
rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(ext => {
|
|
return !extensionsToFilter.includes(ext.uri);
|
|
});
|
|
// This tells the other side that the RID packets are actually mids.
|
|
rtpParameters.headerExtensions.push({id: rid.id, uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', direction: 'sendrecv'});
|
|
|
|
// Filter rtx as we have no way to (re)interpret rrid.
|
|
// Not doing this makes probing use RTX, it's not understood and ramp-up is slower.
|
|
rtpParameters.codecs = rtpParameters.codecs.filter(c => c.name.toUpperCase() !== 'RTX');
|
|
|
|
let sdp = SDPUtils.writeSessionBoilerplate() +
|
|
SDPUtils.writeDtlsParameters(dtls, 'actpass') +
|
|
SDPUtils.writeIceParameters(ice) +
|
|
'a=group:BUNDLE ' + rids.join(' ') + '\r\n';
|
|
const baseRtpDescription = SDPUtils.writeRtpDescription('video', rtpParameters);
|
|
rids.forEach(rid => {
|
|
sdp += baseRtpDescription +
|
|
'a=mid:' + rid + '\r\n' +
|
|
'a=msid:rid-' + rid + ' rid-' + rid + '\r\n';
|
|
});
|
|
return sdp;
|
|
}
|
|
|
|
function swapRidAndMidExtensionsInSimulcastAnswer(answer, localDescription, rids) {
|
|
const sections = SDPUtils.splitSections(answer.sdp);
|
|
const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
|
|
const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
|
|
const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);
|
|
|
|
rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(ext => {
|
|
return !extensionsToFilter.includes(ext.uri);
|
|
});
|
|
const localMid = SDPUtils.getMid(SDPUtils.splitSections(localDescription.sdp)[1]);
|
|
let sdp = SDPUtils.writeSessionBoilerplate() +
|
|
SDPUtils.writeDtlsParameters(dtls, 'active') +
|
|
SDPUtils.writeIceParameters(ice) +
|
|
'a=group:BUNDLE ' + localMid + '\r\n';
|
|
sdp += SDPUtils.writeRtpDescription('video', rtpParameters);
|
|
sdp += 'a=mid:' + localMid + '\r\n';
|
|
|
|
rids.forEach(rid => {
|
|
sdp += 'a=rid:' + rid + ' recv\r\n';
|
|
});
|
|
sdp += 'a=simulcast:recv ' + rids.join(';') + '\r\n';
|
|
|
|
// Re-add headerextensions we filtered.
|
|
const headerExtensions = SDPUtils.parseRtpParameters(SDPUtils.splitSections(localDescription.sdp)[1]).headerExtensions;
|
|
headerExtensions.forEach(ext => {
|
|
if (extensionsToFilter.includes(ext.uri)) {
|
|
sdp += 'a=extmap:' + ext.id + ' ' + ext.uri + '\r\n';
|
|
}
|
|
});
|
|
return sdp;
|
|
}
|
|
|
|
async function negotiateSimulcastAndWaitForVideo(t, rids, pc1, pc2, codec) {
|
|
exchangeIceCandidates(pc1, pc2);
|
|
|
|
const metadataToBeLoaded = [];
|
|
pc2.ontrack = (e) => {
|
|
const stream = e.streams[0];
|
|
const v = document.createElement('video');
|
|
v.autoplay = true;
|
|
v.srcObject = stream;
|
|
v.id = stream.id
|
|
metadataToBeLoaded.push(new Promise((resolve) => {
|
|
v.addEventListener('loadedmetadata', () => {
|
|
resolve();
|
|
});
|
|
}));
|
|
};
|
|
|
|
// Use getUserMedia as getNoiseStream does not have enough entropy to ramp-up.
|
|
const stream = await navigator.mediaDevices.getUserMedia({video: {width: 1280, height: 720}});
|
|
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
|
|
const transceiver = pc1.addTransceiver(stream.getVideoTracks()[0], {
|
|
streams: [stream],
|
|
sendEncodings: rids.map(rid => {rid}),
|
|
});
|
|
if (codec) {
|
|
preferCodec(transceiver, codec.mimeType, codec.sdpFmtpLine);
|
|
}
|
|
|
|
const offer = await pc1.createOffer();
|
|
await pc1.setLocalDescription(offer),
|
|
await pc2.setRemoteDescription({
|
|
type: 'offer',
|
|
sdp: swapRidAndMidExtensionsInSimulcastOffer(offer, rids),
|
|
});
|
|
const answer = await pc2.createAnswer();
|
|
await pc2.setLocalDescription(answer);
|
|
await pc1.setRemoteDescription({
|
|
type: 'answer',
|
|
sdp: swapRidAndMidExtensionsInSimulcastAnswer(answer, pc1.localDescription, rids),
|
|
});
|
|
assert_equals(metadataToBeLoaded.length, rids.length);
|
|
return Promise.all(metadataToBeLoaded);
|
|
}
|