vycxexra8: Play feed videos from hlsUrl with a thumbnail fallback

This commit is contained in:
2026-10-19 14:03:12 -06:00
parent 18e0386a99
commit 69e3c1ba2f
8 changed files with 178 additions and 2 deletions
+69
View File
@@ -0,0 +1,69 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { choosePlayback, supportsNativeHls } from "@/lib/video-playback";
export interface VideoPlayerProps {
hlsUrl: string;
poster: string | null;
label: string;
}
/**
* Real <video controls>. Native HLS where the browser has it, otherwise hls.js,
* loaded only when a video card mounts. Never autoplays.
*/
export function VideoPlayer({ hlsUrl, poster, label }: VideoPlayerProps) {
const ref = useRef<HTMLVideoElement>(null);
const [failed, setFailed] = useState(false);
useEffect(() => {
const video = ref.current;
if (!video) return;
let destroy: (() => void) | undefined;
let cancelled = false;
const native = supportsNativeHls(video);
if (native) {
if (choosePlayback(hlsUrl, { nativeHls: true, hlsJsSupported: false }) === "native") {
video.src = hlsUrl;
}
return;
}
import("hls.js")
.then(({ default: Hls }) => {
if (cancelled) return;
const mode = choosePlayback(hlsUrl, { nativeHls: false, hlsJsSupported: Hls.isSupported() });
if (mode !== "hlsjs") {
setFailed(true);
return;
}
const hls = new Hls();
hls.on(Hls.Events.ERROR, (_e, data) => {
if (data.fatal) setFailed(true);
});
hls.loadSource(hlsUrl);
hls.attachMedia(video);
destroy = () => hls.destroy();
})
.catch(() => setFailed(true));
return () => {
cancelled = true;
destroy?.();
};
}, [hlsUrl]);
if (failed && poster) {
// eslint-disable-next-line @next/next/no-img-element
return <img src={poster} alt="Video thumbnail" loading="lazy" />;
}
return (
<video
ref={ref}
controls
playsInline
preload="none"
poster={poster ?? undefined}
aria-label={label}
/>
);
}