| @@ -1,5 +1,5 @@ | |||
| let http = require("http"); | |||
| let net = require("net"); | |||
| let fs = require("fs"); | |||
| let tcpport = process.env["TCPPORT"] ? parseInt(process.env["TCPPORT"]) : 8099; | |||
| let httpport = process.env["HTTPPORT"] ? parseInt(process.env["HTTPPORT"]) : 8098; | |||
| @@ -11,84 +11,199 @@ let html = ` | |||
| <meta charset="utf-8"> | |||
| <title>XRemoteView</title> | |||
| </head> | |||
| <body> | |||
| <img id="image" src="/image" style="object-fit: contain; height: 100vh; width: 100%;"> | |||
| <body style="margin: 0px"> | |||
| <img | |||
| src="/image" | |||
| style="display: block; object-fit: contain; height: 100vh; width: 100%" | |||
| ondblclick="fullscreen(event.target)"> | |||
| <script> | |||
| let img = document.getElementById("image"); | |||
| let url = img.src; | |||
| function refresh() { | |||
| img.onload = wait; | |||
| img.onerror = () => { | |||
| console.error("Load error"); | |||
| setTimeout(refresh, 4000); | |||
| }; | |||
| let rand = Math.floor(Math.random() * 1000000); | |||
| img.src = url + "?" + rand; | |||
| } | |||
| function wait() { | |||
| fetch("/wait").then(refresh).catch(err => { | |||
| console.error("Wait error:", err); | |||
| wait(); | |||
| }); | |||
| function fullscreen(el) { | |||
| if (document.fullscreenElement) { | |||
| document.exitFullscreen(); | |||
| } else if (el.requestFullscreen) { | |||
| el.requestFullscreen(); | |||
| } | |||
| } | |||
| wait(); | |||
| </script> | |||
| </body> | |||
| </html> | |||
| `; | |||
| let imgdata = Buffer.alloc(0); | |||
| let boundary = "mjpeg-boundary"; | |||
| let imgdata = fs.readFileSync("placeholder.jpg"); | |||
| let waiters = []; | |||
| net.createServer(conn => { | |||
| console.log("Connection", conn.remoteAddress); | |||
| class Reader { | |||
| constructor(stream) { | |||
| this.stream = stream; | |||
| this.buf = Buffer.alloc(0); | |||
| this.resolve = null; | |||
| this.resolveMax = null; | |||
| this.good = true; | |||
| received = Buffer.alloc(0); | |||
| let hasLength = false; | |||
| let expectedLen = 0; | |||
| conn.on("data", data => { | |||
| received = Buffer.concat([received, data]); | |||
| while (true) { | |||
| if (!hasLength && received.length >= 4) { | |||
| expectedLen = received.readUInt32BE(0); | |||
| received = received.slice(4); | |||
| hasLength = true; | |||
| this.stream.on("close", () => { | |||
| this.good = false; | |||
| if (this.resolve) { | |||
| let resolve = this.resolve; | |||
| this.resolve = null; | |||
| resolve(Buffer.alloc(0)); | |||
| } | |||
| }); | |||
| if (hasLength && received.length >= expectedLen) { | |||
| imgdata = received.slice(0, expectedLen); | |||
| received = received.slice(expectedLen); | |||
| hasLength = false; | |||
| for (let waiter of waiters) { | |||
| waiter.end(); | |||
| } | |||
| this.stream.on("error", err => { | |||
| console.log(this.stream.remoteAddress + ": " + err.code); | |||
| }); | |||
| waiters = []; | |||
| this.stream.on("data", data => { | |||
| let resolve = this.resolve; | |||
| let max = this.resolveMax; | |||
| if (resolve && data.length <= this.resolveMax) { | |||
| this.resolve = null; | |||
| resolve(data); | |||
| } else if (this.resolve) { | |||
| this.resolve = null; | |||
| this.buf = data.slice(max); | |||
| resolve(data.slice(0, max)); | |||
| } else { | |||
| break; | |||
| this.buf = Buffer.concat([this.buf, data]); | |||
| } | |||
| }); | |||
| } | |||
| read(max) { | |||
| if (this.buf.length > max) { | |||
| let buf = this.buf.slice(0, max); | |||
| this.buf = this.buf.slice(max); | |||
| return Promise.resolve(buf); | |||
| } else if (this.buf.length != 0) { | |||
| let buf = this.buf; | |||
| this.buf = Buffer.alloc(0); | |||
| return buf; | |||
| } else if (this.good) { | |||
| this.resolveMax = max; | |||
| return new Promise((resolve, reject) => { | |||
| this.resolve = resolve; | |||
| }); | |||
| } else { | |||
| return Buffer.alloc(0); | |||
| } | |||
| }); | |||
| }).on("error", err => console.error(err)).listen(tcpport, "0.0.0.0"); | |||
| console.log(`TCP listening on 0.0.0.0:${tcpport}`); | |||
| } | |||
| async readAll(length) { | |||
| if (!this.good) { | |||
| return Buffer.alloc(0); | |||
| } | |||
| let buf = Buffer.alloc(0); | |||
| while (buf.length < length && this.good) { | |||
| let b = await this.read(length - buf.length); | |||
| buf = Buffer.concat([buf, b]); | |||
| } | |||
| return buf; | |||
| } | |||
| }; | |||
| async function respondToHttp(reader, conn) { | |||
| let path = ""; | |||
| while (true) { | |||
| let ch = (await reader.readAll(1)).toString(); | |||
| if (ch == " ") { | |||
| break; | |||
| } | |||
| path += ch; | |||
| } | |||
| // We frankly don't care about anything else the client has to say | |||
| http.createServer((req, res) => { | |||
| if (req.url.split("?")[0] == "/image") { | |||
| res.writeHead(200, { | |||
| "Content-Type": "image/jpeg", | |||
| if (path == "/image") { | |||
| conn.write( | |||
| "HTTP/1.1 200 OK\r\n" + | |||
| "Content-Type: multipart/x-mixed-replace;boundary=" + boundary + "\r\n" + | |||
| "\r\n"); | |||
| conn.write( | |||
| "--" + boundary + "\r\n" + | |||
| "Content-Type: image/jpeg\r\n" + | |||
| "\r\n"); | |||
| conn.write(imgdata); | |||
| conn.write( | |||
| "\r\n" + | |||
| "--" + boundary + "\r\n"); | |||
| let idx = waiters.length; | |||
| waiters.push({ w: conn, fresh: true }); | |||
| conn.on("close", () => { | |||
| delete waiters[idx]; | |||
| }); | |||
| res.end(imgdata); | |||
| } else if (req.url.split("?")[0] == "/wait") { | |||
| waiters.push(res); | |||
| } else { | |||
| res.writeHead(200); | |||
| res.end(html); | |||
| conn.write( | |||
| "HTTP/1.1 200 OK\r\n" + | |||
| "Connection: close\r\n" + | |||
| "Content-Type: text/html\r\n" + | |||
| "\r\n"); | |||
| conn.write(html); | |||
| conn.end(); | |||
| } | |||
| } | |||
| net.createServer(async conn => { | |||
| console.log("Connection", conn.remoteAddress); | |||
| let reader = new Reader(conn); | |||
| let magic = await reader.readAll(4); | |||
| if (magic.equals(Buffer.from("GET "))) { | |||
| return respondToHttp(reader, conn); | |||
| } | |||
| let length = magic.readUInt32BE(0); | |||
| let received = Buffer.alloc(0); | |||
| while (reader.good) { | |||
| for (let waiter of waiters) { | |||
| if (waiter == null) continue; | |||
| waiter.fresh = false; | |||
| waiter.w.write( | |||
| "Content-Type: image/jpeg\r\n" + | |||
| "\r\n"); | |||
| } | |||
| while (length > 0) { | |||
| let buf = await reader.read(length); | |||
| if (buf.length == 0) { | |||
| console.log(conn.remoteAddress, "disconnected."); | |||
| return; | |||
| } | |||
| for (let waiter of waiters) { | |||
| if (waiter && !waiter.fresh) { | |||
| waiter.w.write(buf); | |||
| } | |||
| } | |||
| received = Buffer.concat([received, buf]); | |||
| length -= buf.length; | |||
| } | |||
| for (let waiter of waiters) { | |||
| if (waiter && !waiter.fresh) { | |||
| waiter.w.write( | |||
| "\r\n" + | |||
| "--" + boundary + "\r\n"); | |||
| } | |||
| } | |||
| imgdata = received; | |||
| received = await reader.readAll(4); | |||
| if (received.length < 4) { | |||
| console.log(conn.remoteAddress, "disconnected."); | |||
| return; | |||
| } | |||
| length = received.readUInt32BE(0); | |||
| received = Buffer.alloc(0); | |||
| } | |||
| }).listen(httpport, "0.0.0.0"); | |||
| console.log(`HTTP listening on 0.0.0.0:${httpport}`); | |||
| }).on("error", err => console.error(err)).listen(tcpport, "0.0.0.0"); | |||
| console.log(`TCP listening on 0.0.0.0:${tcpport}`); | |||