Update environment typings and stream handling for dynamic HLS URL

- Add `CLOUDFLARE_STREAM_NIKIV_VIDEO_ID` to environment variables
- Refactor `$username.tsx` to initialize stream data with default HLS URL
- Implement dynamic HLS URL update based on API response for Nikiv stream
- Modify `/api/check-hls` route to use environment variable for video ID
- Ensure HLS URL updates trigger stream data refresh in client
- Update `wrangler.jsonc` to include `CLOUDFLARE_STREAM_NIKIV_VIDEO_ID` in env vars
This commit is contained in:
Nikita
2025-12-24 23:17:59 -08:00
parent 78a4be3104
commit ca8af44c72
4 changed files with 53 additions and 33 deletions

View File

@@ -13,6 +13,7 @@ declare namespace Cloudflare {
OPENROUTER_MODEL?: string OPENROUTER_MODEL?: string
GEMINI_API_KEY?: string GEMINI_API_KEY?: string
FLOWGLAD_SECRET_KEY?: string FLOWGLAD_SECRET_KEY?: string
CLOUDFLARE_STREAM_NIKIV_VIDEO_ID?: string
} }
} }

View File

@@ -22,31 +22,32 @@ export const Route = createFileRoute("/$username")({
component: StreamPage, component: StreamPage,
}) })
// Cloudflare Stream HLS URL // Default Cloudflare Stream HLS URL (will be overridden by API)
const HLS_URL = "https://customer-xctsztqzu046isdc.cloudflarestream.com/1b0363e3f8d54ddc639dc85737f8c28a/manifest/video.m3u8" const DEFAULT_HLS_URL = "https://customer-xctsztqzu046isdc.cloudflarestream.com/cd56ef73791c628c252cd290ee710275/manifest/video.m3u8"
const NIKIV_PLAYBACK = resolveStreamPlayback({ hlsUrl: HLS_URL, webrtcUrl: null })
const READY_PULSE_MS = 1200 const READY_PULSE_MS = 1200
// Hardcoded user for nikiv // Hardcoded user for nikiv (hls_url will be updated from API)
const NIKIV_DATA: StreamPageData = { function makeNikivData(hlsUrl: string): StreamPageData {
user: { return {
id: "nikiv", user: {
name: "Nikita", id: "nikiv",
username: "nikiv", name: "Nikita",
image: null, username: "nikiv",
}, image: null,
stream: { },
id: "nikiv-stream", stream: {
title: "Live Coding", id: "nikiv-stream",
description: "Building in public", title: "Live Coding",
is_live: false, // Set to true when actually streaming description: "Building in public",
viewer_count: 0, is_live: false,
hls_url: HLS_URL, viewer_count: 0,
webrtc_url: null, hls_url: hlsUrl,
playback: NIKIV_PLAYBACK, webrtc_url: null,
thumbnail_url: null, playback: resolveStreamPlayback({ hlsUrl, webrtcUrl: null }),
started_at: null, thumbnail_url: null,
}, started_at: null,
},
}
} }
function StreamPage() { function StreamPage() {
@@ -57,6 +58,7 @@ function StreamPage() {
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [playerReady, setPlayerReady] = useState(false) const [playerReady, setPlayerReady] = useState(false)
const [hlsLive, setHlsLive] = useState<boolean | null>(null) const [hlsLive, setHlsLive] = useState<boolean | null>(null)
const [hlsUrl, setHlsUrl] = useState<string>(DEFAULT_HLS_URL)
const [isConnecting, setIsConnecting] = useState(false) const [isConnecting, setIsConnecting] = useState(false)
const [nowPlaying, setNowPlaying] = useState<SpotifyNowPlayingResponse | null>( const [nowPlaying, setNowPlaying] = useState<SpotifyNowPlayingResponse | null>(
null, null,
@@ -76,9 +78,9 @@ function StreamPage() {
useEffect(() => { useEffect(() => {
let isActive = true let isActive = true
// Special handling for nikiv - hardcoded stream // Special handling for nikiv - hardcoded stream with dynamic HLS URL
if (username === "nikiv") { if (username === "nikiv") {
setData(NIKIV_DATA) setData(makeNikivData(hlsUrl))
setLoading(false) setLoading(false)
return () => { return () => {
isActive = false isActive = false
@@ -193,9 +195,15 @@ function StreamPage() {
const res = await fetch("/api/check-hls", { cache: "no-store" }) const res = await fetch("/api/check-hls", { cache: "no-store" })
if (!isActive) return if (!isActive) return
const data = await res.json() const apiData = await res.json()
if (data.isLive) { // Update HLS URL if returned from API
if (apiData.hlsUrl && apiData.hlsUrl !== hlsUrl) {
setHlsUrl(apiData.hlsUrl)
setData(makeNikivData(apiData.hlsUrl))
}
if (apiData.isLive) {
// Stream is live - set connecting state if first time // Stream is live - set connecting state if first time
if (!hasConnectedOnce.current) { if (!hasConnectedOnce.current) {
setIsConnecting(true) setIsConnecting(true)
@@ -224,7 +232,7 @@ function StreamPage() {
isActive = false isActive = false
clearInterval(interval) clearInterval(interval)
} }
}, [username]) }, [username, hlsUrl])
// For non-nikiv users, use direct HLS check // For non-nikiv users, use direct HLS check
useEffect(() => { useEffect(() => {

View File

@@ -1,4 +1,4 @@
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute, getServerContext } from "@tanstack/react-router"
const json = (data: unknown, status = 200) => const json = (data: unknown, status = 200) =>
new Response(JSON.stringify(data), { new Response(JSON.stringify(data), {
@@ -6,8 +6,14 @@ const json = (data: unknown, status = 200) =>
headers: { "content-type": "application/json" }, headers: { "content-type": "application/json" },
}) })
// Cloudflare Stream HLS URL // Default video ID (fallback)
const HLS_URL = "https://customer-xctsztqzu046isdc.cloudflarestream.com/1b0363e3f8d54ddc639dc85737f8c28a/manifest/video.m3u8" const DEFAULT_VIDEO_ID = "cd56ef73791c628c252cd290ee710275"
function getHlsUrl(): string {
const ctx = (getServerContext as () => { cloudflare?: { env?: Record<string, string> } } | null)()
const videoId = ctx?.cloudflare?.env?.CLOUDFLARE_STREAM_NIKIV_VIDEO_ID || DEFAULT_VIDEO_ID
return `https://customer-xctsztqzu046isdc.cloudflarestream.com/${videoId}/manifest/video.m3u8`
}
function isHlsPlaylistLive(manifest: string): boolean { function isHlsPlaylistLive(manifest: string): boolean {
const upper = manifest.toUpperCase() const upper = manifest.toUpperCase()
@@ -29,7 +35,8 @@ export const Route = createFileRoute("/api/check-hls")({
handlers: { handlers: {
GET: async () => { GET: async () => {
try { try {
const res = await fetch(HLS_URL, { const hlsUrl = getHlsUrl()
const res = await fetch(hlsUrl, {
cache: "no-store", cache: "no-store",
}) })
@@ -38,6 +45,7 @@ export const Route = createFileRoute("/api/check-hls")({
if (!res.ok) { if (!res.ok) {
return json({ return json({
isLive: false, isLive: false,
hlsUrl,
status: res.status, status: res.status,
error: "HLS not available", error: "HLS not available",
}) })
@@ -54,6 +62,7 @@ export const Route = createFileRoute("/api/check-hls")({
return json({ return json({
isLive, isLive,
hlsUrl,
status: res.status, status: res.status,
manifestLength: manifest.length, manifestLength: manifest.length,
}) })
@@ -62,6 +71,7 @@ export const Route = createFileRoute("/api/check-hls")({
console.error("[check-hls] Error:", error.message) console.error("[check-hls] Error:", error.message)
return json({ return json({
isLive: false, isLive: false,
hlsUrl: getHlsUrl(),
error: error.message, error: error.message,
}) })
} }

View File

@@ -29,7 +29,8 @@
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables * https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
*/ */
"vars": { "vars": {
"APP_BASE_URL": "https://linsa.io" "APP_BASE_URL": "https://linsa.io",
"CLOUDFLARE_STREAM_NIKIV_VIDEO_ID": "cd56ef73791c628c252cd290ee710275"
}, },
/** /**
* Note: Use secrets to store sensitive data. * Note: Use secrets to store sensitive data.