Initial commit
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
// app/components/hls.tsx
|
||||
"use client";
|
||||
import React from "react";
|
||||
import Hls from "hls.js";
|
||||
|
||||
type Props = {
|
||||
src: string;
|
||||
fallbackSrc?: string; // MP4 fallback URL
|
||||
// optional props if you want
|
||||
controls?: boolean;
|
||||
autoPlay?: boolean;
|
||||
videoId?: string; // Auto-load subtitles from /subtitles/{videoId}.vtt
|
||||
subtitles?: Array<{
|
||||
src: string;
|
||||
kind?: "subtitles" | "captions" | "descriptions" | "chapters" | "metadata";
|
||||
srclang?: string;
|
||||
label?: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export const HlsPlayer = React.forwardRef<HTMLVideoElement, Props>(function HlsPlayer(
|
||||
{ src, fallbackSrc, controls = true, autoPlay = false, videoId, subtitles = [] },
|
||||
ref
|
||||
) {
|
||||
const internalRef = React.useRef<HTMLVideoElement | null>(null);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
// allow parent ref to point to the underlying video
|
||||
React.useImperativeHandle(ref, () => internalRef.current || ({} as HTMLVideoElement), [internalRef.current]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const video = internalRef.current;
|
||||
if (!video) return;
|
||||
|
||||
// avoid attaching multiple Hls instances if src didn't change
|
||||
let hls: Hls | null = null;
|
||||
let hasError = false;
|
||||
|
||||
const loadHls = (sourceUrl: string) => {
|
||||
// Check if it's an HLS URL
|
||||
if (sourceUrl.endsWith('.m3u8')) {
|
||||
if (video.canPlayType("application/vnd.apple.mpegurl")) {
|
||||
// native HLS (Safari)
|
||||
video.src = sourceUrl;
|
||||
} else if (Hls.isSupported()) {
|
||||
hls = new Hls();
|
||||
|
||||
// Handle HLS errors with fallback
|
||||
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||
console.error('HLS Error:', event, data);
|
||||
if (data.fatal) {
|
||||
hasError = true;
|
||||
// Try fallback if available
|
||||
if (fallbackSrc) {
|
||||
console.log('Falling back to MP4:', fallbackSrc);
|
||||
setError(null);
|
||||
loadMp4(fallbackSrc);
|
||||
} else {
|
||||
setError('Failed to load HLS stream');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
hls.loadSource(sourceUrl);
|
||||
hls.attachMedia(video);
|
||||
} else {
|
||||
// HLS not supported, try fallback
|
||||
if (fallbackSrc) {
|
||||
console.log('HLS not supported, using MP4 fallback');
|
||||
loadMp4(fallbackSrc);
|
||||
} else {
|
||||
setError('HLS streaming not supported on this device');
|
||||
// Try to load as MP4 anyway
|
||||
video.src = sourceUrl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Non-HLS URL, load as MP4
|
||||
loadMp4(sourceUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const loadMp4 = (sourceUrl: string) => {
|
||||
if (hls) {
|
||||
hls.destroy();
|
||||
hls = null;
|
||||
}
|
||||
video.src = sourceUrl;
|
||||
};
|
||||
|
||||
loadHls(src);
|
||||
|
||||
// cleanup
|
||||
return () => {
|
||||
if (hls) {
|
||||
hls.destroy();
|
||||
hls = null;
|
||||
}
|
||||
// optionally pause and clear src
|
||||
if (video) {
|
||||
try { video.pause(); } catch {}
|
||||
// video.src = "";
|
||||
}
|
||||
};
|
||||
}, [src, fallbackSrc]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<video
|
||||
ref={internalRef}
|
||||
controls={controls}
|
||||
autoPlay={autoPlay}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
controlsList="nodownload"
|
||||
className="w-full"
|
||||
>
|
||||
{videoId && (
|
||||
<track
|
||||
src={`/subtitles/${videoId}.vtt`}
|
||||
kind="subtitles"
|
||||
srcLang="en"
|
||||
label="Subtitles"
|
||||
default
|
||||
/>
|
||||
)}
|
||||
{subtitles.map((subtitle, index) => (
|
||||
<track
|
||||
key={index}
|
||||
src={subtitle.src}
|
||||
kind={subtitle.kind || "subtitles"}
|
||||
srcLang={subtitle.srclang || "en"}
|
||||
label={subtitle.label || `Subtitle ${index + 1}`}
|
||||
default={!videoId && index === 0}
|
||||
/>
|
||||
))}
|
||||
</video>
|
||||
{error && (
|
||||
<div className="text-red-500 text-sm mt-2">{error}</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default HlsPlayer;
|
||||
Reference in New Issue
Block a user