Article WebRTC dynamic downscale w/safari workaround
by jib1
HTML
Height <input id="heightNumber" type="number" min="2" max="1024" value="480" size="5"><input id="heightRange" type="range" min="2" max="1024" value="480">Max bitrate <input id="rateNumber" type="number" min="2" max="9000000" value="2000000" size="9">
<input id="rateRange" type="range" min="2" max="9000000" value="2000000"><br><br>
<table>
<tr>
<td>Receiver</td>
<td>Sender</td>
</tr>
<tr>
<td id="v2info">0x0</td>
<td id="v1info">0x0</td>
</tr>
<tr>
<td id="v2brinfo">0 bps</td>
<td id="v1brinfo">0 bps</td>
</tr>
<tr>
<td><video id="v2" autoplay></video></td>
<td><video id="v1" autoplay muted></video></td>
</tr>
</table>
<div id="div"></div>
CSS
body { white-space: nowrap; font-family: monospace; }
JavaScript
const console = { log: msg => div.innerHTML += msg + "<br>" };
const update = (div, msg) => div.innerHTML = msg;
const pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();
heightRange.oninput = () => apply(heightNumber.value = heightRange.value, rateNumber.value);
heightNumber.oninput = () => apply(heightRange.value = heightNumber.value, rateNumber.value);
rateRange.oninput = () => apply(heightNumber.value, rateNumber.value = rateRange.value);
rateNumber.oninput = () => apply(heightNumber.value, rateRange.value = rateNumber.value);
let track, applying = false, height, range;
async function apply(h, r) {
height = h;
rate = r;
if (applying) return;
try {
applying = true;
do {
h = height;
r = rate;
const [sender] = pc1.getSenders();
const ratio = sender.track.getSettings().height / height;
const params = sender.getParameters();
if (!params.encodings) params.encodings = [{scaleResolutionDownBy: 1}]; // Firefox workaround!
params.encodings[0].scaleResolutionDownBy = Math.max(ratio, 1);
params.encodings[0].maxBitrate = rate;
await sender.setParameters(params);
if (sender.getParameters().encodings[0].scaleResolutionDownBy == 1) {
await sender.track.applyConstraints({height}); // fallback approach
}
} while (h != height);
} catch (e) {
console.log(e);
} finally {
applying = false;
}
}
(async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({video: {width: 1920}});
await setVideo(v1, v1info, stream);
const sender = pc1.addTrack(stream.getVideoTracks()[0].clone(), stream);
await new Promise(r => pc1.oniceconnectionstatechange =
() => pc1.iceConnectionState == "connected" && r());
heightRange.max = heightNumber.max = sender.track.getSettings().height;
heightRange.value = heightNumber.value = heightNumber.max * 3 / 4;
apply(heightNumber.value, rateNumber.value);
} catch (e) {
...