Add GameCult livestream viewer

This commit is contained in:
Meta Vens 2026-05-30 22:18:36 +01:00
commit 6cbd700de0
3 changed files with 156 additions and 0 deletions

31
GameCult/livestream.md Normal file
View file

@ -0,0 +1,31 @@
# GameCult Livestream
<link rel="stylesheet" href="/static/livestream/player.css">
<main class="gamecult-live-shell">
<section class="gamecult-live-stage" aria-label="GameCult livestream">
<video id="gamecult-live-video" controls playsinline preload="metadata"></video>
<div class="gamecult-live-status" id="gamecult-live-status">Waiting for the live edge.</div>
</section>
<section class="gamecult-live-panel">
<p class="gamecult-live-kicker">Self-hosted broadcast</p>
<h2>GameCult Livestream</h2>
<p>
Starfire encodes the final Mimir program once. Yggdrasil serves the HLS
edge through StreamPixels. This page is only the viewer.
</p>
<dl>
<div>
<dt>Playlist</dt>
<dd><a id="gamecult-live-playlist-link" href="https://streampixels.gamecult.org/mimir/live/hls/mimir.m3u8">streampixels.gamecult.org/mimir/live/hls/mimir.m3u8</a></dd>
</div>
<div>
<dt>Authority</dt>
<dd>Mimir encodes. StreamPixels serves. GameCult watches.</dd>
</div>
</dl>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.17/dist/hls.min.js" crossorigin="anonymous"></script>
<script src="/static/livestream/player.js"></script>

View file

@ -0,0 +1,82 @@
.gamecult-live-shell {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(280px, 380px);
gap: 24px;
align-items: start;
}
.gamecult-live-shell * {
box-sizing: border-box;
}
.gamecult-live-stage {
display: grid;
gap: 10px;
}
.gamecult-live-shell video {
width: 100%;
aspect-ratio: 16 / 9;
background: #020507;
border: 1px solid rgba(100, 183, 255, 0.28);
border-radius: 6px;
}
.gamecult-live-status {
min-height: 1.4rem;
color: var(--gray);
font-size: 0.92rem;
}
.gamecult-live-panel {
display: grid;
gap: 14px;
padding-top: 4px;
}
.gamecult-live-kicker {
margin: 0;
color: var(--secondary);
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.gamecult-live-panel h2 {
margin: 0;
font-family: Montserrat, system-ui, sans-serif;
font-size: clamp(2rem, 5vw, 4.2rem);
font-weight: 200;
letter-spacing: 0;
}
.gamecult-live-panel p {
margin: 0;
color: var(--darkgray);
line-height: 1.55;
}
.gamecult-live-panel dl {
display: grid;
gap: 14px;
margin: 6px 0 0;
}
.gamecult-live-panel dt {
color: var(--gray);
font-size: 0.78rem;
font-weight: 700;
text-transform: uppercase;
}
.gamecult-live-panel dd {
margin: 4px 0 0;
overflow-wrap: anywhere;
}
@media (max-width: 840px) {
.gamecult-live-shell {
grid-template-columns: 1fr;
}
}

View file

@ -0,0 +1,43 @@
(function () {
const playlistUrl = "https://streampixels.gamecult.org/mimir/live/hls/mimir.m3u8"
const video = document.getElementById("gamecult-live-video")
const status = document.getElementById("gamecult-live-status")
const link = document.getElementById("gamecult-live-playlist-link")
if (!video || !status || !link) {
return
}
link.href = playlistUrl
link.textContent = playlistUrl.replace(/^https?:\/\//, "")
function setStatus(message) {
status.textContent = message
}
if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = playlistUrl
setStatus("Using native HLS playback.")
return
}
if (window.Hls && window.Hls.isSupported()) {
const hls = new window.Hls({
lowLatencyMode: false,
backBufferLength: 30,
maxBufferLength: 18,
})
hls.loadSource(playlistUrl)
hls.attachMedia(video)
hls.on(window.Hls.Events.MANIFEST_PARSED, () => {
setStatus("Live edge found. Playback is ready.")
})
hls.on(window.Hls.Events.ERROR, (_event, data) => {
const detail = data && data.details ? ` ${data.details}` : ""
setStatus(`Waiting for stream.${detail}`)
})
return
}
setStatus("This browser cannot play HLS here. Try Safari, Edge, or Chrome with hls.js available.")
})()