Skip to content

Instantly share code, notes, and snippets.

@kwindla
Created January 17, 2021 23:55
Show Gist options
  • Save kwindla/05efd2e39689322698f6dde6f51a7568 to your computer and use it in GitHub Desktop.
Save kwindla/05efd2e39689322698f6dde6f51a7568 to your computer and use it in GitHub Desktop.
Daily API switching between 720p and 360p during a call
<html>
<head>
<title>test quality and cpu difference for 720p vs 360p video</title>
<script src="https://unpkg.com/@daily-co/daily-js"></script>
</head>
<body onload="main()">
<div id="local-controls">
<div>
<input
type="radio"
id="720p"
name="video-properties"
value="720p"
checked
onchange="buttonChangeVideoSendProperties(this)"
/>
<label for="720p">720p (Daily defaults)</label>
</div>
<div>
<input
type="radio"
id="360p"
name="video-properties"
value="360p"
onchange="buttonChangeVideoSendProperties(this)"
/>
<label for="360p">360p</label>
</div>
</div>
<hr />
<div>Video send bitrate: <span id="video-send-kbps">0</span></div>
<div>Video recv bitrate: <span id="video-recv-kbps">0</span></div>
<div>Active video resolution: <span id="active-res">-</span></div>
<div>
Video receive resolutions:
<div id="receive-res"></div>
</div>
<hr />
<div id="local-video"></div>
<div id="videos"></div>
<script>
async function main() {
const ROOM_URL = "ROOM ... URL ... HERE";
window.callObject = DailyIframe.createCallObject();
callObject.on("track-started", displayVideo);
callObject.on("track-stopped", destroyVideo);
callObject.on("app-message", handleMessage);
await callObject.join({ url: ROOM_URL });
await callObject.setNetworkTopology({ topology: "sfu" });
console.log("setting interval");
setInterval(updateStatsDisplay, 3000);
}
async function handleMessage(msg) {
console.log("app-message", msg);
if (msg.data.newMode) {
document.getElementById(msg.data.newMode).checked = true;
changeVideoSendProperties(msg.data.newMode);
return;
}
}
async function buttonChangeVideoSendProperties(el) {
const newMode = el.value;
// tell everyone else about the new mode
callObject.sendAppMessage({ newMode });
// now change locally
changeVideoSendProperties(newMode);
}
async function changeVideoSendProperties(newMode) {
console.log("switching to", newMode);
// get rtp parameters so we can modify the simulcast layers
const producer = window.rtcpeers.sfu.getProducerByTag("cam-video");
if (!(producer && producer.track && producer.rtpSender)) {
console.log("UNEXPECTED: no rtpSender");
return;
}
const params = producer.rtpSender.getParameters();
if (newMode === "720p") {
// current Daily defaults
await callObject.setBandwidth({
trackConstraints: { width: 1280, height: 720 },
});
params.encodings[0].maxBitrate = 80 * 1000;
params.encodings[0].maxFramerate = 10;
params.encodings[0].scaleResolutionDownBy = 4;
params.encodings[1].maxBitrate = 200 * 1000;
params.encodings[1].maxFramerate = 15;
params.encodings[1].scaleResolutionDownBy = 2;
params.encodings[2].maxBitrate = 680 * 1000;
params.encodings[2].maxFramerate = 30;
params.encodings[2].scaleResolutionDownBy = 1;
params.encodings[2].active = true;
await producer.rtpSender.setParameters(params);
return;
}
if (newMode === "360p") {
await callObject.setBandwidth({
trackConstraints: { width: 640, height: 360 },
});
// change to two layers, by setting the top simulcast layer to inactive.
// Chrome will do this anyway, but let's make it explicit, as well
// set our new top layer's bitrate and framerate higher than the
// Daily default for the middle layer
params.encodings[0].maxBitrate = 80 * 1000;
params.encodings[0].maxFramerate = 10;
params.encodings[0].scaleResolutionDownBy = 2;
params.encodings[1].maxBitrate = 500 * 1000;
params.encodings[1].maxFramerate = 30;
params.encodings[1].scaleResolutionDownBy = 1;
params.encodings[2].active = false;
await producer.rtpSender.setParameters(params);
return;
}
}
async function subscribeAll() {
let updateList = {};
for (let id in callObject.participants()) {
if (id === "local") {
continue;
}
updateList[id] = { setSubscribedTracks: true };
}
callObject.updateParticipants(updateList);
}
function displayVideo(evt) {
console.log(evt);
if (!(evt.track.kind === "video")) {
return;
}
let videosDiv = document.getElementById(
evt.participant.local ? "local-video" : "videos"
);
let videoEl = document.createElement("video");
videoEl._participant = evt.participant;
videosDiv.appendChild(videoEl);
videoEl.style.width = "100%";
videoEl.srcObject = new MediaStream([evt.track]);
videoEl.play();
doLayout();
}
function destroyVideo(evt) {
let vids = document.getElementsByTagName("video");
for (let vid of vids) {
if (
vid.srcObject &&
vid.srcObject.getVideoTracks()[0] === evt.track
) {
vid.remove();
}
}
doLayout();
}
const SMALL_VIDEO_WIDTH = 220;
function doLayout() {
console.log("laying out");
const vids = Array.from(document.getElementsByTagName("video"));
// local
const localVid = vids.find((v) => v._participant.local);
if (localVid) {
localVid.style.width = SMALL_VIDEO_WIDTH;
localVid.style.padding = "10px";
localVid.style.display = "inline";
}
// lon-local
vids
.filter((v) => !v._participant.local)
.forEach((v) => {
v.style.width = "50%";
v.style.display = "inline";
});
}
async function updateStatsDisplay() {
console.log("stats");
const simpleStats = (await callObject.getNetworkStats()).stats.latest;
const producerStats = Array.from(
await rtcpeers.sfu.producers[1].getStats()
);
const trackStats = producerStats.find(
(s) =>
s[0].match(/RTCMediaStreamTrack_sender/) && s[1].kind === "video"
);
document.getElementById("video-send-kbps").innerText = Math.round(
simpleStats.videoSendBitsPerSecond / 1000
);
document.getElementById("video-recv-kbps").innerText = Math.round(
simpleStats.videoRecvBitsPerSecond / 1000
);
if (trackStats) {
document.getElementById("active-res").innerText =
trackStats[1].frameWidth + " x " + trackStats[1].frameHeight;
}
const inboundVids = document
.getElementById("videos")
.getElementsByTagName("video");
document.getElementById("receive-res").innerHTML = Array.from(
inboundVids
).reduce(
(a, c) => a + c.videoWidth + " x " + c.videoHeight + "<br />",
""
);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment