feat: implement HLS streaming support in VideoPlayer and enhance stream URL handling
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
VideoPlayer,
|
||||
ChannelInfo,
|
||||
@@ -46,6 +47,13 @@ export default function TvPage() {
|
||||
const [showSchedule, setShowSchedule] = useState(false);
|
||||
const idleTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
// Video ref — used to resume playback if autoplay was blocked on load
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
// Stream error recovery
|
||||
const [streamError, setStreamError] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Tick for live progress calculation (every 30 s is fine for the progress bar)
|
||||
const [, setTick] = useState(0);
|
||||
useEffect(() => {
|
||||
@@ -57,11 +65,12 @@ export default function TvPage() {
|
||||
const { data: broadcast, isLoading: isLoadingBroadcast } =
|
||||
useCurrentBroadcast(channel?.id ?? "");
|
||||
const { data: epgSlots } = useEpg(channel?.id ?? "");
|
||||
const { data: streamUrl } = useStreamUrl(
|
||||
channel?.id,
|
||||
token,
|
||||
broadcast?.slot.id,
|
||||
);
|
||||
const { data: streamUrl } = useStreamUrl(channel?.id, token, broadcast?.slot.id);
|
||||
|
||||
// Clear stream error when the slot changes (next item started)
|
||||
useEffect(() => {
|
||||
setStreamError(false);
|
||||
}, [broadcast?.slot.id]);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Derived display values
|
||||
@@ -87,6 +96,8 @@ export default function TvPage() {
|
||||
() => setShowOverlays(false),
|
||||
IDLE_TIMEOUT_MS,
|
||||
);
|
||||
// Resume playback if autoplay was blocked (e.g. on page refresh with no prior interaction)
|
||||
videoRef.current?.play().catch(() => {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -151,6 +162,22 @@ export default function TvPage() {
|
||||
return () => window.removeEventListener("keydown", handleKey);
|
||||
}, [nextChannel, prevChannel, toggleSchedule]);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Stream error recovery
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
const handleStreamError = useCallback(() => {
|
||||
setStreamError(true);
|
||||
}, []);
|
||||
|
||||
const handleRetry = useCallback(() => {
|
||||
// Bust the cached stream URL so it refetches with the current offset
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["stream-url", channel?.id, broadcast?.slot.id],
|
||||
});
|
||||
setStreamError(false);
|
||||
}, [queryClient, channel?.id, broadcast?.slot.id]);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Render helpers
|
||||
// ------------------------------------------------------------------
|
||||
@@ -178,16 +205,30 @@ export default function TvPage() {
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (streamError) {
|
||||
return (
|
||||
<NoSignal variant="error" message="Stream failed to load.">
|
||||
<button
|
||||
onClick={handleRetry}
|
||||
className="mt-2 rounded-md border border-zinc-700 bg-zinc-800/80 px-4 py-2 text-xs text-zinc-300 transition-colors hover:bg-zinc-700 hover:text-zinc-100"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</NoSignal>
|
||||
);
|
||||
}
|
||||
if (streamUrl) {
|
||||
return (
|
||||
<VideoPlayer
|
||||
ref={videoRef}
|
||||
src={streamUrl}
|
||||
className="absolute inset-0 h-full w-full"
|
||||
initialOffset={broadcast?.offset_secs}
|
||||
initialOffset={broadcast?.offset_secs ?? 0}
|
||||
onStreamError={handleStreamError}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// Broadcast exists but stream URL resolving — show no-signal until ready
|
||||
// Broadcast exists but stream URL resolving — show loading until ready
|
||||
return <NoSignal variant="loading" message="Loading stream…" />;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user