mirror of
https://github.com/nestriness/nestri.git
synced 2026-03-17 03:43:07 +02:00
feat: WIP s6-overlay and friends
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
153
packages/play-standalone/src/components/NerdStats.astro
Normal file
153
packages/play-standalone/src/components/NerdStats.astro
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user