@saran-ign/video-annotation-tool
Version:
[](https://www.npmjs.com/package/@saran-ign/video-annotation-tool) [](https://www.npmjs.co
154 lines (153 loc) • 7.13 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { forwardRef, useEffect, useRef, useState } from "react";
import Hls from "hls.js";
const Player = forwardRef((props, ref) => {
var _a;
const videoControls = props.videoControls;
const videoId = (_a = props === null || props === void 0 ? void 0 : props.videoId) !== null && _a !== void 0 ? _a : "video";
const playerRef = useRef(null);
const [hlsInstance, setHlsInstance] = useState(null);
const [retryCount, setRetryCount] = useState(0);
useEffect(() => {
const video = playerRef.current;
if (!video)
return;
const handleLoadedMetadata = () => {
console.log("Video resolution:", video.videoWidth, "x", video.videoHeight);
};
video.addEventListener("loadedmetadata", handleLoadedMetadata);
return () => {
video.removeEventListener("loadedmetadata", handleLoadedMetadata);
};
}, []);
useEffect(() => {
const videoElement = playerRef.current;
if (!videoElement)
return;
// Forward ref properly
if (typeof ref === "function") {
ref(videoElement);
}
else if (ref) {
ref.current =
videoElement;
}
let hls;
const initializePlayer = () => {
if (props.url && props.url.endsWith(".m3u8")) {
if (videoElement.canPlayType("application/vnd.apple.mpegurl")) {
// Native HLS support (Safari)
videoElement.src = props.url;
videoElement.play().catch(console.error);
}
else if (Hls.isSupported()) {
hls = new Hls({
startLevel: 0,
maxBufferLength: 30, // Reduce buffer size
maxMaxBufferLength: 60,
maxBufferSize: 60 * 1000 * 1000, // 60MB
maxBufferHole: 0.5, // Allow small gaps
lowLatencyMode: false,
enableWorker: true,
abrEwmaDefaultEstimate: 500000, // 0.5 Mbps
abrBandWidthFactor: 0.95,
abrBandWidthUpFactor: 0.7,
abrMaxWithRealBitrate: true,
liveDurationInfinity: true,
liveSyncDurationCount: 3,
});
let lastErrorTime = Date.now();
hls.on(Hls.Events.ERROR, (event, data) => {
const now = Date.now();
if (data.fatal) {
console.error("Fatal error:", data);
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.warn("Network error. Retrying...");
if (retryCount < 3) {
const delay = 1000 * Math.pow(2, retryCount); // Exponential backoff
setRetryCount(prev => prev + 1);
setTimeout(() => {
hls === null || hls === void 0 ? void 0 : hls.destroy();
initializePlayer();
}, delay);
}
else {
console.error("Max retry limit reached. Giving up.");
}
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.warn("Recovering from media error...");
hls === null || hls === void 0 ? void 0 : hls.recoverMediaError();
break;
default:
// Prevent infinite crash loop
if (now - lastErrorTime > 5000) {
console.warn("Unexpected fatal error. Restarting player.");
hls === null || hls === void 0 ? void 0 : hls.destroy();
initializePlayer();
}
else {
console.error("Too many errors in short time. Not restarting.");
}
break;
}
lastErrorTime = now;
}
else if (data.type === Hls.ErrorTypes.MEDIA_ERROR &&
data.details === "bufferStalledError") {
console.warn("Buffer stalled. Trying recovery...");
videoElement.play().catch(() => {
// If play fails, try seeking forward slightly
try {
videoElement.currentTime += 0.5;
videoElement.play().catch(console.error);
}
catch (err) {
console.error("Seeking recovery failed:", err);
}
});
}
else {
console.warn("Non-fatal HLS error:", data);
}
});
hls.on(Hls.Events.MANIFEST_PARSED, () => {
videoElement.play().catch(console.error);
});
hls.loadSource(props.url);
hls.attachMedia(videoElement);
setHlsInstance(hls);
}
else {
console.error("HLS is not supported in this browser.");
}
}
else {
videoElement.src = props.url;
videoElement.play().catch(console.error);
}
};
initializePlayer();
// Common video settings
videoElement.crossOrigin = "anonymous";
videoElement.loop = true;
return () => {
if (hlsInstance) {
hlsInstance.destroy();
setHlsInstance(null);
}
};
}, [props.url, ref, retryCount]);
return (_jsx("video", Object.assign({ id: videoId, style: {
minHeight: 300,
objectFit: "cover",
minWidth: 500,
position: "absolute",
top: 0,
left: 0,
width: props.dimensions.width,
height: props.dimensions.height,
}, ref: playerRef }, videoControls, { preload: "auto", controls: false, muted: true })));
});
export default Player;