feat: WIP s6-overlay and friends

This commit is contained in:
DatCaptainHorse
2026-02-19 18:02:10 +02:00
parent b743dab332
commit 34afd371ad
96 changed files with 2340 additions and 1063 deletions

View File

@@ -9,8 +9,8 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/node": "9.5.0",
"@astrojs/node": "9.5.1",
"@nestri/input": "*",
"astro": "5.15.1"
"astro": "5.16.5"
}
}

View File

@@ -0,0 +1,153 @@
---
interface Props {
audioCodec?: string;
audioBitrate?: number;
fps?: number;
resolution?: { width: number; height: number };
videoCodec?: string;
videoBitrate?: number;
}
const {
audioCodec,
audioBitrate,
fps,
resolution,
videoCodec,
videoBitrate
} = Astro.props;
---
<button id="nerdStatsBtnOpen">
{"|>"}
</button>
<div id="nerdStatsBox" class="statsBox">
<button id="nerdStatsBtnClose">
{"<|"}
</button>
<span>Nerd Stats</span>
<div class="h-line"></div>
<div id="audioStatsList" class="statsList">
<span>Audio Stream</span>
<div class="h-line"></div>
<ul>
<li>Codec: {audioCodec}</li>
<li>Bitrate: {audioBitrate}</li>
</ul>
</div>
<div id="videoStatsList" class="statsList">
<span>Video Stream</span>
<div class="h-line"></div>
<ul>
<li>FPS: {fps}</li>
<li>WxH: {resolution ? resolution?.width + "x" + resolution?.height : ""}</li>
<li>Codec: {videoCodec}</li>
<li>Bitrate: {videoBitrate}</li>
</ul>
</div>
</div>
<script>
const openBtn = document.getElementById("nerdStatsBtnOpen")! as HTMLButtonElement;
const closeBtn = document.getElementById("nerdStatsBtnClose")! as HTMLButtonElement;
const box = document.getElementById("nerdStatsBox")! as HTMLDivElement;
openBtn.style.display = "flex";
box.style.display = "none";
openBtn.addEventListener("click", () => {
openBtn.style.display = "none";
box.style.display = "flex";
});
closeBtn.addEventListener("click", () => {
openBtn.style.display = "flex";
box.style.display = "none";
});
</script>
<style>
#nerdStatsBtnOpen {
font-family: "Geist Mono", sans-serif;
font-size: 1em;
position: absolute;
top: 50%;
left: 0;
display: flex;
color: #d1d1d1;
background-color: #fe500f;
padding: 0.25rem;
transform: translateY(-50%);
border: #fe500f 2px solid;
&:hover {
background-color: #cc3f0c;
}
&:active {
background-color: #993009;
}
}
.statsBox {
font-family: "Geist Mono", sans-serif;
font-size: 1em;
position: absolute;
top: 50%;
left: 0;
display: none;
flex-direction: column;
color: #d1d1d1;
background-color: #993009;
min-width: 10rem;
padding: 0.5rem;
text-align: center;
transform: translateY(-50%);
border: #fe500f 2px solid;
button {
font-family: "Geist Mono", sans-serif;
font-size: 1em;
position: absolute;
color: #d1d1d1;
background-color: #fe500f;
padding: 0.25rem;
top: 50%;
right: 0;
transform: translateY(-50%) translateX(100%);
border: #fe500f 2px solid;
&:hover {
background-color: #cc3f0c;
}
&:active {
background-color: #993009;
}
}
}
.h-line {
margin: 0.25rem 0;
padding: 0;
width: 100%;
height: 2px;
background-color: #cc3f0c;
}
.statsList {
margin: 0.25rem 0 0 0;
padding: 0.5rem;
border: #cc3f0c 2px solid;
background-color: #802808;
text-align: center;
ul {
margin: 0;
padding: 0;
list-style: none;
text-align: left;
}
}
</style>

View File

@@ -20,11 +20,25 @@
font-display: swap;
}
@font-face {
font-family: "Geist Mono";
src: url("/fonts/GeistMono-VariableFont_wght.ttf");
font-weight: normal;
font-style: normal;
font-display: swap;
}
* {
font-family: "Basement Grotesque", sans-serif;
}
html, body {
width: 100%;
height: 100%;
}
body {
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;

View File

@@ -1,5 +1,6 @@
---
import DefaultLayout from "../layouts/DefaultLayout.astro";
import NerdStats from "../components/NerdStats.astro";
const { room } = Astro.params;
// Passing of environment variables to the client side
@@ -16,13 +17,22 @@ if (envs_map.size > 0) {
}
---
<DefaultLayout>
<h1 id="offlineText" class="offline">Offline</h1>
<h1 id="loadingText" class="loading">Warming up the GPU...</h1>
<div id="playOverlay">
<button id="playBtn">Start</button>
<div id="darkener"></div>
</div>
<h1 id="offlineText" class="statusText">Offline</h1>
<h1 id="loadingText" class="statusText">Loading..</h1>
<div id="nerdStats">
<NerdStats></NerdStats>
</div>
<canvas id="playCanvas" class="playCanvas" data-room={room}></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;
@@ -38,18 +48,18 @@ if (envs_map.size > 0) {
};
// Elements
const playOverlay = document.getElementById("playOverlay")! as HTMLDivElement;
const playBtn = document.getElementById("playBtn")! as HTMLButtonElement;
const nerdStats = document.getElementById("nerdStats")! as HTMLDivElement;
const canvas = document.getElementById("playCanvas")! as HTMLCanvasElement;
const offlineText = document.getElementById("offlineText")! as HTMLHeadingElement;
const loadingText = document.getElementById("loadingText")! as HTMLHeadingElement;
const loadingText = document.getElementById("loadingText")! as HTMLHeadingElement;
const room = canvas.dataset.room;
if (!room || room.length <= 0) {
throw new Error("Room parameter is required");
}
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) {
@@ -58,32 +68,60 @@ if (envs_map.size > 0) {
console.debug("Using Peer URL:", peerURL);
loadingText.style.display = "flex";
// Stream
const stream = new WebRTCStream(peerURL, room, async (mediaStream) => {
const stream = new WebRTCStream(peerURL, room, (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";
}
loadingText.style.display = "none";
offlineText.style.display = "none";
playOverlay.style.display = "flex";
} else if (!mediaStream && video.srcObject === null) {
offlineText.style.display = "flex";
loadingText.style.display = "none";
}
});
const video = document.createElement("video") as HTMLVideoElement;
const lockPlay = async function () {
if (document.fullscreenElement)
return;
await canvas.requestFullscreen();
if (!isMobile())
await canvas.requestPointerLock();
};
const streamStart = async function () {
playBtn.style.display = "none";
loadingText.style.display = "flex";
await video.play().catch((e) => {
console.error("Failed to play video:", e);
});
canvas.style.display = "flex";
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);
nerdStats.style.display = "flex";
loadingText.style.display = "none";
playBtn.style.display = "flex"
playBtn.innerText = "Play";
playBtn.removeEventListener("click", streamStart);
playBtn.addEventListener("click", lockPlay);
};
playBtn.addEventListener("click", streamStart);
// Input
let nestriMouse: Mouse | null = null;
let nestriKeyboard: Keyboard | null = null;
@@ -132,6 +170,12 @@ if (envs_map.size > 0) {
webrtc: stream,
});
nestriKeyboard.setOnEscape(async () => {
await document.exitFullscreen();
if (!isMobile())
await document.exitPointerLock();
});
nestriControllers.forEach((c) => c.run());
if ("keyboard" in navigator && "lock" in (navigator.keyboard as any)) {
@@ -152,38 +196,77 @@ if (envs_map.size > 0) {
nestriKeyboard = null;
}
nestriControllers.forEach((c) => c.stop());
playOverlay.style.display = "flex";
playBtn.style.display = "flex";
}
})
const lockPlay = async function () {
if (document.fullscreenElement)
return;
await canvas.requestFullscreen();
if (!isMobile())
await canvas.requestPointerLock();
};
canvas.addEventListener("click", lockPlay);
</script>
<style>
#nerdStats {
display: none;
}
#playOverlay {
position: absolute;
display: none;
top: 0;
left: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
button {
font-size: 1.5em;
line-height: 2rem;
font-weight: 600;
position: absolute;
top: 50%;
left: 50%;
display: flex;
color: lightgray;
background-color: #fe500f;
padding: 0.5rem 1rem;
transform: translateY(-50%) translateX(-50%);
border: none;
border-radius: 0.25rem;
&:hover {
background-color: #cc3f0c;
}
&:active {
background-color: #993009;
}
}
#darkener {
width: 100%;
height: auto;
margin: 0.5rem;
padding: 0;
border-radius: 1rem;
background-color: rgba(0, 0, 0, 0.5);
}
}
.playCanvas {
width: 100%;
height: 100%;
max-height: 100vh;
display: none;
object-fit: contain;
aspect-ratio: 16 / 9;
margin: 0;
width: 100%;
height: auto;
margin: 0.5rem;
padding: 0;
}
.offline, .loading {
.statusText {
position: absolute;
width: 100%;
height: 100%;
align-items: center;
display: flex;
display: none;
justify-content: center;
color: lightgray;
font-size: 1.5rem;