UNPKG

@saran-ign/video-annotation-tool

Version:

[![npm version](https://img.shields.io/npm/v/@saran-ign/video-annotation-tool.svg)](https://www.npmjs.com/package/@saran-ign/video-annotation-tool) [![npm downloads](https://img.shields.io/npm/dm/@saran-ign/video-annotation-tool.svg)](https://www.npmjs.co

154 lines (153 loc) 7.13 kB
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;