import jsQR from "./jsQR/"; var SCREEN_WIDTH = 256; var SCREEN_HEIGHT = 240; const nesCanvas = document.getElementById("nes-canvas"); canvas_ctx = nesCanvas.getContext("2d"); let image = canvas_ctx.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); canvas_ctx.fillStyle = "black"; canvas_ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); const nesWorker = new Worker(new URL("nes.js", import.meta.url), { type: "module", }); nesWorker.onmessage = (buffer) => { image.data.set(buffer["data"]); }; function onAnimationFrame() { window.requestAnimationFrame(onAnimationFrame); canvas_ctx.putImageData(image, 0, 0); } onAnimationFrame(); var video = document.createElement("video"); var canvasElement = document.getElementById("canvas"); var canvas = canvasElement.getContext("2d", { willReadFrequently: true }); var loadingMessage = document.getElementById("loadingMessage"); var outputContainer = document.getElementById("output"); var outputData = document.getElementById("outputData"); const latencyDiv = document.getElementById("latency"); const latencyScan = document.getElementById("scan"); // buttons const up = document.getElementById("up"); const down = document.getElementById("down"); const left = document.getElementById("left"); const right = document.getElementById("right"); const a = document.getElementById("a"); const b = document.getElementById("b"); const select = document.getElementById("select"); const start = document.getElementById("start"); let qr = {}; let pressedButtons = {}; // SO BAD AAAAAAA up.onpointerdown = () => { pressedButtons.up = true; checkButtonStatus(); }; up.onpointerup = () => { pressedButtons.up = false; checkButtonStatus(); }; down.onpointerdown = () => { pressedButtons.down = true; checkButtonStatus(); }; down.onpointerup = () => { pressedButtons.down = false; checkButtonStatus(); }; left.onpointerdown = () => { pressedButtons.left = true; checkButtonStatus(); }; left.onpointerup = () => { pressedButtons.left = false; checkButtonStatus(); }; right.onpointerdown = () => { pressedButtons.right = true; checkButtonStatus(); }; right.onpointerup = () => { pressedButtons.right = false; checkButtonStatus(); }; a.onpointerdown = () => { pressedButtons.a = true; checkButtonStatus(); }; a.onpointerup = () => { pressedButtons.a = false; checkButtonStatus(); }; b.onpointerdown = () => { pressedButtons.b = true; checkButtonStatus(); }; b.onpointerup = () => { pressedButtons.b = false; checkButtonStatus(); }; start.onpointerdown = () => { pressedButtons.start = true; checkButtonStatus(); }; start.onpointerup = () => { pressedButtons.start = false; checkButtonStatus(); }; select.onpointerdown = () => { pressedButtons.select = true; checkButtonStatus(); }; select.onpointerup = () => { pressedButtons.select = false; checkButtonStatus(); }; function checkButtonStatus() { const pressed = { left: pressedButtons.left || qr.left, right: pressedButtons.right || qr.right, up: pressedButtons.up || qr.up, down: pressedButtons.down || qr.down, a: pressedButtons.a || qr.a, b: pressedButtons.b || qr.b, start: pressedButtons.start || qr.start, select: pressedButtons.select || qr.select, }; up.style = pressed.up ? "background-color: red;" : ""; down.style = pressed.down ? "background-color: red;" : ""; left.style = pressed.left ? "background-color: red;" : ""; right.style = pressed.right ? "background-color: red;" : ""; a.style = pressed.a ? "background-color: red;" : ""; b.style = pressed.b ? "background-color: red;" : ""; start.style = pressed.start ? "background-color: red;" : ""; select.style = pressed.select ? "background-color: red;" : ""; nesWorker.postMessage({ keys: pressed }); } let datas = {}; function renderData() { let html = "<table><tr><th>Data</th><th>Count</th><th>Buttons</th></tr>"; const keys = Object.keys(datas); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const val = datas[key]; html += `<tr><td>${key}</td><td>${val.count}</td><td>${val.buttons}</td></tr>`; } html += "</table>"; outputData.innerHTML = html; } function drawLine(begin, end, color) { canvas.beginPath(); canvas.moveTo(begin.x, begin.y); canvas.lineTo(end.x, end.y); canvas.lineWidth = 4; canvas.strokeStyle = color; canvas.stroke(); } // Use facingMode: environment to attemt to get the front camera on phones navigator.mediaDevices .getUserMedia({ video: { facingMode: "environment" } }) .then(function (stream) { video.srcObject = stream; video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen video.play(); requestAnimationFrame(tick); }); function tick() { loadingMessage.innerText = "⌛ Loading video..."; if (video.readyState === video.HAVE_ENOUGH_DATA) { loadingMessage.hidden = true; canvasElement.hidden = false; outputContainer.hidden = false; canvasElement.height = video.videoHeight; canvasElement.width = video.videoWidth; canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height); var imageData = canvas.getImageData( 0, 0, canvasElement.width, canvasElement.height, ); const start = window.performance.now(); var code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: "dontInvert", }); const end = window.performance.now(); const delta = end - start; latencyDiv.innerText = `Frame latency: ${delta}ms`; if (code?.data.length > 0) { // reset qr = { //a: qr.a, // don't reset a! }; latencyScan.innerText = `Scan latency: ${delta}ms`; // Left if ( code.data.includes("OP3%S") || code.data.includes("OP9OS") || code.data.includes("OOP8S") || code.data.includes("OOI3S") || code.data.includes("OP9+S") || code.data.includes("OP8$S") || code.data.includes("OP8$S") || code.data.includes("OP98S") ) { qr.left = true; } // Right if ( code.data.includes("OO$:S") || code.data.includes("OPJMS") || code.data.includes("OOZ6S") || code.data.includes("OPZ6S") || code.data.includes("OPCHS") || code.data.includes("OPD S") || code.data.includes("OOD S") || code.data.includes("OPGTS") || code.data.includes("OO PS") || code.data.includes("OOYJS") || code.data.includes("OOFAS") || code.data.includes("OPH0S") || code.data.includes("OPDGS") || code.data.includes("OPDKS") || code.data.includes("OPXWS") || code.data.includes("OPU*S") || code.data.includes("OOGTS") || code.data.includes("OODKS") ) { qr.right = true; } // A button if (code.data.startsWith("ARC")) { qr.a = false; } else if ( code.data.startsWith("A7H") || code.data.startsWith("ANH") || code.data.startsWith("A8+") || code.data.startsWith("A8H") || code.data.startsWith("A9H") || code.data.startsWith("AO+") ) { qr.a = true; } drawLine( code.location.topLeftCorner, code.location.topRightCorner, "#FF3B58", ); drawLine( code.location.topRightCorner, code.location.bottomRightCorner, "#FF3B58", ); drawLine( code.location.bottomRightCorner, code.location.bottomLeftCorner, "#FF3B58", ); drawLine( code.location.bottomLeftCorner, code.location.topLeftCorner, "#FF3B58", ); if (!datas[code.data]) { let buttons = []; for (let i = 0; i < Object.keys(qr).length; i++) { const k = Object.keys(qr)[i]; if (qr[k]) { buttons.push(k); } } datas[code.data] = { count: 1, buttons }; } else { datas[code.data].count += 1; } renderData(); } checkButtonStatus(); } requestAnimationFrame(tick); }