mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-11 20:00:23 +01:00
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:
1
packages/web/src/env.d.ts
vendored
1
packages/web/src/env.d.ts
vendored
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user