Skip to content

Commit

Permalink
Simulcast
Browse files Browse the repository at this point in the history
  • Loading branch information
friendlymatthew committed Oct 24, 2024
1 parent bd13a30 commit 4aa3cb6
Show file tree
Hide file tree
Showing 3 changed files with 443 additions and 0 deletions.
236 changes: 236 additions & 0 deletions burrito/burrito.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
'use strict';

// non negotiable constants
const av1Profile = 'video/av01.0.04M.08';
const scalabilityMode = 'L1T3';

const spatial240pVideoElement = document.getElementById('spatial-240-video');
const spatial480pVideoElement = document.getElementById('spatial-480-video');
const spatial720pVideoElement = document.getElementById('spatial-720-video');

const spatial240pRemoteVideoElement = document.getElementById('spatial-240-remote-video');
const spatial480pRemoteVideoElement = document.getElementById('spatial-480-remote-video');
const spatial720pRemoteVideoElement = document.getElementById('spatial-720-remote-video');

const spatial240pWebCodecVideoElement = document.getElementById('spatial-240-webcodec-video');
const spatial480pWebCodecVideoElement = document.getElementById('spatial-480-webcodec-video');
const spatial720pWebCodecVideoElement = document.getElementById('spatial-720-webcodec-video');

const mediaStreamTrackGenerator240p = new MediaStreamTrackGenerator({kind: 'video'});
const mediaStreamWritable240p = mediaStreamTrackGenerator240p.writable;

const mediaStreamTrackGenerator480p = new MediaStreamTrackGenerator({kind: 'video'});
const mediaStreamWritable480p = mediaStreamTrackGenerator480p.writable;

const mediaStreamTrackGenerator720p = new MediaStreamTrackGenerator({kind: 'video'});
const mediaStreamWritable720p = mediaStreamTrackGenerator720p.writable;


let resolutions = [
{
title: "240p",
width: 320,
height: 240,
videoElement: spatial240pVideoElement,
stream: null,
videoPipe: null,
remoteStream: null,
remoteVideoElement: spatial240pRemoteVideoElement,
webCodecVideoElement: spatial240pWebCodecVideoElement,
webCodecTrackGenerator: mediaStreamTrackGenerator240p,
webCodecWritable: mediaStreamWritable240p,
},
{
title: "480p",
width: 640,
height: 480,
videoElement: spatial480pVideoElement,
stream: null,
videoPipe: null,
remoteStream: null,
remoteVideoElement: spatial480pRemoteVideoElement,
webCodecVideoElement: spatial480pWebCodecVideoElement,
webCodecTrackGenerator: mediaStreamTrackGenerator480p,
webCodecWritable: mediaStreamWritable480p,
},
{
title: "720p",
width: 1280,
height: 720,
videoElement: spatial720pVideoElement,
stream: null,
videoPipe: null,
remoteStream: null,
remoteVideoElement: spatial720pRemoteVideoElement,
webCodecVideoElement: spatial720pWebCodecVideoElement,
webCodecTrackGenerator: mediaStreamTrackGenerator720p,
webCodecWritable: mediaStreamWritable720p,
},
];

const worker = new Worker('./worker.js', {name: 'E2EE worker', type: 'module'});

for (let idx = 0; idx <= resolutions.length - 1; idx++) {
const {webCodecWritable: writable, title} = resolutions[idx];

worker.postMessage({
operation: `init-${title}`,
writable
}, [writable])
}

worker.onmessage = async ({data}) => {
const {operation} = data;

if (operation.startsWith('track-ready')) {
const resolutionMessage = operation.split('-')[2];

let idx;
switch (resolutionMessage) {
case '240p':
idx = 0;
break;

case '480p':
idx = 1;
break;

case '720p':
idx = 2;
break;

default:
throw new Error(`Unsupported resolution: ${resolutionMessage}`);
}

const {webCodecVideoElement, webCodecTrackGenerator} = resolutions[idx];
webCodecVideoElement.srcObject = new MediaStream([webCodecTrackGenerator]);
}

}

document.addEventListener('keydown', (e) => {
switch (e.key) {
case '1': {
e.preventDefault();
!startButton.disabled && start();
break;
}
case '2': {
e.preventDefault();
!callButton.disabled && call();
break;
}
case '3': {
e.preventDefault();
!hangupButton.disabled && hangup();
break;
}
default:
break;
}
});

const startButton = document.getElementById('startButton');
const callButton = document.getElementById('callButton');
const hangupButton = document.getElementById('hangupButton');

startButton.onclick = start;
callButton.onclick = call;
hangupButton.onclick = hangup;

async function requestLocalStream(resolution) {
let {width: videoWidth, height: videoHeight, videoElement, title} = resolution;

const options = {
audio: false,
video: {videoWidth, videoHeight}
};

let stream = await navigator.mediaDevices.getUserMedia(options);
console.log("Get user media: ", title, {videoWidth, videoHeight});
videoElement.srcObject = stream;
resolution.stream = stream;

try {
const {supported, smooth, powerEfficient} = await navigator.mediaCapabilities.encodingInfo({
type: 'webrtc',
video: {
contentType: av1Profile,
width: videoWidth,
height: videoHeight,
bitrate: 10000,
framerate: 29.97,
scalabilityMode,
}
});
} catch (e) {
throw new Error(`Failed to configure WebRTC: ${e}`);
}
}

async function start() {
startButton.disabled = true;

for (let idx = 0; idx <= resolutions.length - 1; idx++) {
const resolution = resolutions[idx];
await requestLocalStream(resolution);
}

callButton.disabled = false;
}

async function call() {
callButton.disabled = true;
hangupButton.disabled = false;

for (let idx = 0; idx <= resolutions.length - 1; idx++) {
let resolution = resolutions[idx];

console.log(`localStream`, resolution.stream);

resolution.videoPipe = new VideoPipe(
resolution.stream,
true,
true,
(e) => {
const receiverStreams = e.receiver.createEncodedStreams();
const {readable, writable} = receiverStreams;
worker.postMessage(
{
operation: `decode-${resolution.title}`,
readable,
writable,
}, [readable, writable]);

console.log(`e.streams[0]`, e.streams[0]);

resolution.remoteVideoElement.srcObject = e.streams[0];
},
scalabilityMode
);

resolution.videoPipe.pc1.getSenders().forEach((s) => {
const senderStreams = s.createEncodedStreams();
const {readable, writable} = senderStreams;

worker.postMessage({
operation: `encode-${resolution.title}`,
readable,
writable
}, [readable, writable])
});

await resolution.videoPipe.negotiate();
}
}

async function hangup() {
for (let idx = 0; idx <= resolutions.length - 1; idx++) {
let resolution = resolutions[idx];
resolution.videoPipe.close();
}

hangupButton.disabled = true;
callButton.disabled = false;
}
71 changes: 71 additions & 0 deletions burrito/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SVC-Burrito</title>
<style>
.video {
width: 320px;
height: 240px;
}
</style>
</head>
<body style="width: 100vw;">
<div style="display: flex;">
<button id="startButton">Start (Press 1)</button>
<button disabled id="callButton">Call (Press 2)</button>
<button disabled id="hangupButton">Hang Up (Press 3)</button>
</div>

<div style="display: flex; width: 100vw;">
<div>
<div>240p</div>
<video autoplay class="video" id="spatial-240-video" muted playsinline></video>
</div>
<div>
<div>480p</div>
<video autoplay class="video" id="spatial-480-video" muted playsinline></video>
</div>
<div>
<div>720p</div>
<video autoplay class="video" id="spatial-720-video" muted playsinline></video>
</div>
</div>

<div style="display: flex; width: 100vw;">
<div>
<div>240p</div>
<video autoplay class="video" id="spatial-240-remote-video" muted playsinline></video>
</div>
<div>
<div>480p</div>
<video autoplay class="video" id="spatial-480-remote-video" muted playsinline></video>
</div>
<div>
<div>720p</div>
<video autoplay class="video" id="spatial-720-remote-video" muted playsinline></video>
</div>
</div>


<div style="display: flex; width: 100vw;">
<div>
<div>240p</div>
<video autoplay class="video" id="spatial-240-webcodec-video" muted playsinline></video>
</div>
<div>
<div>480p</div>
<video autoplay class="video" id="spatial-480-webcodec-video" muted playsinline></video>
</div>
<div>
<div>720p</div>
<video autoplay class="video" id="spatial-720-webcodec-video" muted playsinline></video>
</div>
</div>


<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="../js/videopipe.js"></script>
<script async src="./burrito.js"></script>
</body>
</html>
Loading

0 comments on commit 4aa3cb6

Please sign in to comment.