Add GameCult livestream viewer
This commit is contained in:
parent
cc6e871517
commit
6cbd700de0
3 changed files with 156 additions and 0 deletions
31
GameCult/livestream.md
Normal file
31
GameCult/livestream.md
Normal 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>
|
||||
82
GameCult/static/livestream/player.css
Normal file
82
GameCult/static/livestream/player.css
Normal 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;
|
||||
}
|
||||
}
|
||||
43
GameCult/static/livestream/player.js
Normal file
43
GameCult/static/livestream/player.js
Normal 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.")
|
||||
})()
|
||||
Loading…
Reference in a new issue