mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-11 00:05:36 +02:00
196 lines
5.3 KiB
Plaintext
196 lines
5.3 KiB
Plaintext
---
|
|
import DefaultLayout from "../layouts/DefaultLayout.astro";
|
|
|
|
// Passing of environment variables to the client side
|
|
// gotta love node and it's ecosystem..
|
|
const envs_map: Map<string, string | undefined> = new Map();
|
|
import { PEER_URL, getSecret } from "astro:env/server";
|
|
if (PEER_URL) {
|
|
envs_map.set("PEER_URL", getSecret("PEER_URL"));
|
|
}
|
|
|
|
let envs: string = "";
|
|
if (envs_map.size > 0) {
|
|
envs = JSON.stringify(Array.from(envs_map.entries()));
|
|
}
|
|
---
|
|
|
|
<DefaultLayout>
|
|
<h1 id="offlineText" class="offline">Offline</h1>
|
|
<h1 id="loadingText" class="loading">Warming up the GPU...</h1>
|
|
<canvas id="playCanvas" class="playCanvas"></canvas>
|
|
<div id="ENVS" data-envs={envs}></div>
|
|
</DefaultLayout>
|
|
|
|
<script>
|
|
import { Mouse, Keyboard, Controller, WebRTCStream } from "@nestri/input";
|
|
const ENVS = document.getElementById("ENVS")!.dataset.envs as string;
|
|
let ENVS_MAP: Map<string, string | undefined> | null = null;
|
|
if (ENVS && ENVS.length > 0) {
|
|
ENVS_MAP = new Map(JSON.parse(ENVS));
|
|
console.debug("ENVS_MAP:", ENVS_MAP);
|
|
}
|
|
|
|
// Method which returns true if mobile device
|
|
const isMobile = () => {
|
|
return /Mobi|Android/i.test(navigator.userAgent);
|
|
};
|
|
|
|
// Elements
|
|
const canvas = document.getElementById("playCanvas")! as HTMLCanvasElement;
|
|
const offlineText = document.getElementById("offlineText")! as HTMLHeadingElement;
|
|
const loadingText = document.getElementById("loadingText")! as HTMLHeadingElement;
|
|
|
|
const room = window.location.hash.substring(1);
|
|
if (!room || room.length <= 0) {
|
|
throw new Error("Room parameter is required in URL hash");
|
|
}
|
|
|
|
offlineText.style.display = "flex";
|
|
loadingText.style.display = "none";
|
|
|
|
// Get query parameter "peerURL" from the URL
|
|
let peerURL = new URLSearchParams(window.location.search).get("peerURL");
|
|
if (!peerURL || peerURL.length <= 0) {
|
|
peerURL = (ENVS_MAP && ENVS_MAP.get("PEER_URL")) ?? "/dnsaddr/relay.dathorse.com/p2p/12D3KooWPK4v5wKYNYx9oXWjqLM8Xix6nm13o91j1Feqq98fLBsw";
|
|
}
|
|
|
|
console.debug("Using Peer URL:", peerURL);
|
|
|
|
// Stream
|
|
const stream = new WebRTCStream(peerURL, room, async (mediaStream) => {
|
|
if (mediaStream && video.srcObject === null) {
|
|
video.srcObject = mediaStream;
|
|
offlineText.style.display = "none";
|
|
loadingText.style.display = "flex";
|
|
|
|
await video.play().catch((e) => {
|
|
console.error("Failed to play video:", e);
|
|
});
|
|
|
|
canvas.width = video.videoWidth;
|
|
canvas.height = video.videoHeight;
|
|
const ctx = canvas.getContext("2d");
|
|
const renderer = () => {
|
|
if (ctx && video.srcObject) {
|
|
ctx.drawImage(video, 0, 0);
|
|
video.requestVideoFrameCallback(renderer);
|
|
}
|
|
};
|
|
video.requestVideoFrameCallback(renderer);
|
|
loadingText.style.display = "none";
|
|
}
|
|
});
|
|
const video = document.createElement("video") as HTMLVideoElement;
|
|
|
|
// Input
|
|
let nestriMouse: Mouse | null = null;
|
|
let nestriKeyboard: Keyboard | null = null;
|
|
let nestriControllers: Controller[] = [];
|
|
|
|
window.addEventListener("gamepadconnected", (e) => {
|
|
console.log("Gamepad connected:", e.gamepad);
|
|
const controller = new Controller({
|
|
webrtc: stream,
|
|
e: e,
|
|
});
|
|
nestriControllers.push(controller);
|
|
});
|
|
window.addEventListener("gamepaddisconnected", (e) => {
|
|
console.log("Gamepad disconnected:", e.gamepad);
|
|
if (e.gamepad.id.toLowerCase().includes("nestri"))
|
|
return;
|
|
|
|
let disconnected = nestriControllers.find((c) => c.getSlot() === e.gamepad.index);
|
|
if (disconnected) {
|
|
disconnected.dispose();
|
|
nestriControllers = nestriControllers.filter((c) => c !== disconnected);
|
|
}
|
|
});
|
|
|
|
document.addEventListener("pointerlockchange", () => {
|
|
if (document.pointerLockElement === canvas) {
|
|
if (nestriMouse)
|
|
return;
|
|
|
|
nestriMouse = new Mouse({
|
|
canvas: canvas,
|
|
webrtc: stream,
|
|
});
|
|
} else {
|
|
if (nestriMouse) {
|
|
nestriMouse.dispose();
|
|
nestriMouse = null;
|
|
}
|
|
}
|
|
});
|
|
|
|
document.addEventListener("fullscreenchange", () => {
|
|
if (document.fullscreenElement) {
|
|
if (nestriKeyboard)
|
|
return;
|
|
|
|
nestriKeyboard = new Keyboard({
|
|
webrtc: stream,
|
|
});
|
|
|
|
nestriControllers.forEach((c) => c.run());
|
|
|
|
if ("keyboard" in navigator && "lock" in (navigator.keyboard as any)) {
|
|
const keys = [
|
|
"AltLeft", "AltRight", "Tab", "Escape",
|
|
"ContextMenu", "MetaLeft", "MetaRight"
|
|
];
|
|
|
|
(navigator.keyboard as any).lock(keys).then(() => {
|
|
console.log("Keyboard lock acquired");
|
|
}).catch((err: any) => {
|
|
console.error("Failed to acquire keyboard lock:", err);
|
|
});
|
|
}
|
|
} else {
|
|
if (nestriKeyboard) {
|
|
nestriKeyboard.dispose();
|
|
nestriKeyboard = null;
|
|
}
|
|
nestriControllers.forEach((c) => c.stop());
|
|
}
|
|
})
|
|
|
|
const lockPlay = async function () {
|
|
if (document.fullscreenElement)
|
|
return;
|
|
|
|
await canvas.requestFullscreen();
|
|
if (!isMobile())
|
|
await canvas.requestPointerLock();
|
|
};
|
|
|
|
canvas.addEventListener("click", lockPlay);
|
|
</script>
|
|
|
|
<style>
|
|
.playCanvas {
|
|
width: 100%;
|
|
height: 100%;
|
|
max-height: 100vh;
|
|
object-fit: contain;
|
|
aspect-ratio: 16 / 9;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.offline, .loading {
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
align-items: center;
|
|
display: flex;
|
|
justify-content: center;
|
|
color: lightgray;
|
|
font-size: 1.5rem;
|
|
line-height: 2rem;
|
|
font-weight: 600;
|
|
}
|
|
</style>
|