Created
July 13, 2020 12:11
-
-
Save ggarber/c0620f1fba0facdae74a022615eda61e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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