Skip to content

Instantly share code, notes, and snippets.

@ggarber
Created July 13, 2020 12:11
Show Gist options
  • Save ggarber/c0620f1fba0facdae74a022615eda61e to your computer and use it in GitHub Desktop.
Save ggarber/c0620f1fba0facdae74a022615eda61e to your computer and use it in GitHub Desktop.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>octorus</title>
<meta name="description" content="octorus">
<style>
canvas, video {
margin: 20px;
}
</style>
</head>
<body>
<pre id="stats"></pre>
<div id="remotes">
<video id="local"></video>
</div>
<pre id="logs"></pre>
<script>
const WIDTH = 160;
const HEIGHT = 120;
let sentCount = 0;
let recvCount = 0;
let sentFramesCount = 0;
let recvFramesCount = 0;
const FRAMESIZE = WIDTH * HEIGHT * 4;
const HEADERSIZE = 16;
const views = {};
const showStats = () => {
document.querySelector('#stats').innerHTML = `QUIC Datagrams sent:${sentCount}\nQUIC Datagrams recv:${recvCount}\nFrames sent:${sentFramesCount}\nFrames recv:${recvFramesCount}\n`;
};
const transport = new QuicTransport('quic-transport://localhost:4433/room');
console.log("TRANSPORT", transport.maxDatagramSize);
transport.closed.then((info) => {
console.error('CLOSED. The connection is closed gracefully.');
}, (error) => {
console.error('CLOSED. The connection is closed abruptly.', error);
});
transport.ready.then(() => {
console.info('READY');
const sender = transport.sendDatagrams();
console.log("SENDER", sender.maxDatagramSize);
const writer = sender.getWriter();
const receiver = transport.receiveDatagrams();
const reader = receiver.getReader();
const read = () => {
reader.read().then(({value, done}) => {
// console.log("RECEIVED ", value.length);
const dataSize = value.length - HEADERSIZE;
let dv = new DataView(value.buffer);
const streamId = dv.getUint32(0);
const eof = dv.getUint32(12);
if (!views[streamId]) {
document.querySelector('#logs').innerHTML += `User ${streamId} connected\n`;
views[streamId] = createRemoteView(streamId);
document.getElementById('remotes').appendChild(views[streamId].canvas);
}
const { context, buffer, bufferOffset } = views[streamId];
if (bufferOffset + dataSize > FRAMESIZE) {
console.log("Crying, wait for next frame ");
} else {
buffer.set(value.slice(HEADERSIZE), bufferOffset);
views[streamId].bufferOffset += dataSize;
}
if (eof) {
render(views[streamId]);
views[streamId].bufferOffset = 0;
}
recvCount ++;
showStats();
read();
});
};
read();
startMedia(writer);
});
const createRemoteView = (id) => {
const canvas = document.createElement('canvas');
canvas.setAttribute("id", `remote_${id}`);
canvas.width = WIDTH;
canvas.height = HEIGHT;
const context = canvas.getContext('2d');
const buffer = new Uint8ClampedArray(FRAMESIZE);
const bufferOffset = 0;
return { canvas, context, buffer, bufferOffset };
};
const render = (view) => {
recvFramesCount ++;
console.log("RENDER frame");
view.context.putImageData(new ImageData(view.buffer, WIDTH, HEIGHT), 0, 0);
};
const startMedia = async (writer) => {
const stream = await navigator.mediaDevices.getUserMedia({ video: {width: {exact: WIDTH}, height: {exact: HEIGHT}}, audio: false });
const video = document.querySelector('#local');
video.srcObject = stream;
video.play();
let streamId = Math.floor(Math.random() * 4294967295);
let sequenceNumber = 0;
let ts = Date.now();
const canvasContext = document.createElement("canvas").getContext("2d");
setInterval(async () => {
canvasContext.drawImage(video, 0, 0);
const imgData = canvasContext.getImageData(0, 0, WIDTH, HEIGHT);
ts = Date.now();
sentFramesCount ++;
showStats();
let offset = 0;
while (true) {
let size = 1100;
if (offset + size > imgData.data.length) {
size = imgData.data.length - offset;
}
sentCount ++;
// console.log("SENDING", size);
await writer.ready;
const chunk = imgData.data.slice(offset, offset + size);
let writeBuffer = new Uint8ClampedArray(HEADERSIZE + chunk.length);
const dv = new DataView(writeBuffer.buffer, 0);
writeBuffer.set(chunk, HEADERSIZE);
dv.setUint32(0, streamId);
dv.setUint32(4, sequenceNumber++);
dv.setUint32(8, ts);
dv.setUint32(12, offset + size >= imgData.data.length ? 1 : 0);
await writer.write(writeBuffer);
offset += size;
if (offset >= imgData.data.length) {
break;
}
}
}, 100);
};
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment