vycxexra8: Play feed videos from hlsUrl with a thumbnail fallback
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { VideoPlayer } from "@/components/VideoPlayer";
|
||||
import { initials, relativeTime, type CardPost } from "@/lib/feed-model";
|
||||
|
||||
export function PostCard({ post, now }: { post: CardPost; now?: number }) {
|
||||
@@ -19,7 +20,11 @@ export function PostCard({ post, now }: { post: CardPost; now?: number }) {
|
||||
</header>
|
||||
{post.text ? <p className="post-text">{post.text}</p> : null}
|
||||
{post.media.map((m, i) =>
|
||||
m.src ? (
|
||||
m.kind === "video" && m.hlsUrl ? (
|
||||
<div className="post-media post-video" key={i}>
|
||||
<VideoPlayer hlsUrl={m.hlsUrl} poster={m.src} label={`Video from ${post.authorName}`} />
|
||||
</div>
|
||||
) : m.src ? (
|
||||
<div className={m.kind === "video" ? "post-media post-video" : "post-media"} key={i}>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={m.src} alt={m.kind === "video" ? "Video thumbnail" : ""} loading="lazy" />
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user