Initial commit

This commit is contained in:
twotalesanimation
2026-06-11 10:46:09 +02:00
commit 81ad7e4ea9
223 changed files with 39530 additions and 0 deletions
+144
View File
@@ -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;