@zezosoft/react-player
Version:
A lightweight and customizable video player by Zezosoft, built for seamless streaming with advanced controls, adaptive playback, and modern UI. Perfect for web and React applications.
592 lines (562 loc) • 52.6 kB
JavaScript
import * as React from 'react';
import React__default, { memo, useCallback, useEffect, useRef, useState } from 'react';
import { create } from 'zustand';
import { Settings, Check, VolumeOff, Volume2, Shrink, Maximize, X } from 'lucide-react';
import Hls from 'hls.js';
function styleInject(css, ref) {
if ( ref === void 0 ) ref = {};
var insertAt = ref.insertAt;
if (!css || typeof document === 'undefined') { return; }
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.createElement('style');
style.type = 'text/css';
if (insertAt === 'top') {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
var css_248z$1 = "/*! tailwindcss v4.0.14 | MIT License | https://tailwindcss.com */\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --font-sans: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\",\n \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\",\n \"Courier New\", monospace;\n --color-gray-200: oklch(0.928 0.006 264.531);\n --color-gray-900: oklch(0.21 0.034 264.665);\n --color-black: #000;\n --color-white: #fff;\n --spacing: 0.25rem;\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --text-xl: 1.25rem;\n --text-xl--line-height: calc(1.75 / 1.25);\n --text-2xl: 1.5rem;\n --text-2xl--line-height: calc(2 / 1.5);\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --radius-md: 0.375rem;\n --radius-lg: 0.5rem;\n --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);\n --default-transition-duration: 150ms;\n --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --default-font-family: var(--font-sans);\n --default-font-feature-settings: var(--font-sans--font-feature-settings);\n --default-font-variation-settings: var(\n --font-sans--font-variation-settings\n );\n --default-mono-font-family: var(--font-mono);\n --default-mono-font-feature-settings: var(\n --font-mono--font-feature-settings\n );\n --default-mono-font-variation-settings: var(\n --font-mono--font-variation-settings\n );\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var( --default-font-family, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\" );\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var( --default-font-variation-settings, normal );\n -webkit-tap-highlight-color: transparent;\n }\n body {\n line-height: inherit;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var( --default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace );\n font-feature-settings: var( --default-mono-font-feature-settings, normal );\n font-variation-settings: var( --default-mono-font-variation-settings, normal );\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n color: color-mix(in oklab, currentColor 50%, transparent);\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type=\"button\"], [type=\"reset\"], [type=\"submit\"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden=\"until-found\"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .absolute {\n position: absolute;\n }\n .relative {\n position: relative;\n }\n .inset-0 {\n inset: calc(var(--spacing) * 0);\n }\n .top-0 {\n top: calc(var(--spacing) * 0);\n }\n .top-1\\/2 {\n top: calc(1/2 * 100%);\n }\n .top-full {\n top: 100%;\n }\n .right-full {\n right: 100%;\n }\n .bottom-full {\n bottom: 100%;\n }\n .left-0 {\n left: calc(var(--spacing) * 0);\n }\n .left-1\\/2 {\n left: calc(1/2 * 100%);\n }\n .left-full {\n left: 100%;\n }\n .z-50 {\n z-index: 50;\n }\n .mx-1 {\n margin-inline: calc(var(--spacing) * 1);\n }\n .mx-auto {\n margin-inline: auto;\n }\n .mt-2 {\n margin-top: calc(var(--spacing) * 2);\n }\n .mr-2 {\n margin-right: calc(var(--spacing) * 2);\n }\n .mb-2 {\n margin-bottom: calc(var(--spacing) * 2);\n }\n .ml-2 {\n margin-left: calc(var(--spacing) * 2);\n }\n .block {\n display: block;\n }\n .flex {\n display: flex;\n }\n .inline-block {\n display: inline-block;\n }\n .size-10 {\n width: calc(var(--spacing) * 10);\n height: calc(var(--spacing) * 10);\n }\n .h-5 {\n height: calc(var(--spacing) * 5);\n }\n .h-10 {\n height: calc(var(--spacing) * 10);\n }\n .h-full {\n height: 100%;\n }\n .w-5 {\n width: calc(var(--spacing) * 5);\n }\n .w-\\[2px\\] {\n width: 2px;\n }\n .w-\\[10vw\\] {\n width: 10vw;\n }\n .w-\\[15vw\\] {\n width: 15vw;\n }\n .w-\\[720px\\] {\n width: 720px;\n }\n .w-fit {\n width: fit-content;\n }\n .w-full {\n width: 100%;\n }\n .-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .transform {\n transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z) var(--tw-skew-x) var(--tw-skew-y);\n }\n .cursor-pointer {\n cursor: pointer;\n }\n .flex-col {\n flex-direction: column;\n }\n .items-center {\n align-items: center;\n }\n .justify-between {\n justify-content: space-between;\n }\n .justify-center {\n justify-content: center;\n }\n .gap-1\\.5 {\n gap: calc(var(--spacing) * 1.5);\n }\n .gap-2 {\n gap: calc(var(--spacing) * 2);\n }\n .gap-7 {\n gap: calc(var(--spacing) * 7);\n }\n .rounded-lg {\n border-radius: var(--radius-lg);\n }\n .rounded-md {\n border-radius: var(--radius-md);\n }\n .border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n }\n .border-gray-200 {\n border-color: var(--color-gray-200);\n }\n .bg-\\[rgba\\(0\\,0\\,0\\,0\\.5\\)\\] {\n background-color: rgba(0,0,0,0.5);\n }\n .bg-gray-900 {\n background-color: var(--color-gray-900);\n }\n .bg-white {\n background-color: var(--color-white);\n }\n .bg-gradient-to-b {\n --tw-gradient-position: to bottom in oklab;\n background-image: linear-gradient(var(--tw-gradient-stops));\n }\n .from-black {\n --tw-gradient-from: var(--color-black);\n --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));\n }\n .p-2 {\n padding: calc(var(--spacing) * 2);\n }\n .p-4 {\n padding: calc(var(--spacing) * 4);\n }\n .p-10 {\n padding: calc(var(--spacing) * 10);\n }\n .px-3 {\n padding-inline: calc(var(--spacing) * 3);\n }\n .px-10 {\n padding-inline: calc(var(--spacing) * 10);\n }\n .py-1 {\n padding-block: calc(var(--spacing) * 1);\n }\n .pt-6 {\n padding-top: calc(var(--spacing) * 6);\n }\n .pb-10 {\n padding-bottom: calc(var(--spacing) * 10);\n }\n .pb-16 {\n padding-bottom: calc(var(--spacing) * 16);\n }\n .text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n }\n .font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n }\n .font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n }\n .text-black {\n color: var(--color-black);\n }\n .text-white {\n color: var(--color-white);\n }\n .shadow-lg {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .shadow-md {\n --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .transition-opacity {\n transition-property: opacity;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .duration-200 {\n --tw-duration: 200ms;\n transition-duration: 200ms;\n }\n .ease-in-out {\n --tw-ease: var(--ease-in-out);\n transition-timing-function: var(--ease-in-out);\n }\n .lg\\:size-12 {\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 12);\n height: calc(var(--spacing) * 12);\n }\n }\n .lg\\:size-15 {\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 15);\n height: calc(var(--spacing) * 15);\n }\n }\n .lg\\:h-8 {\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 8);\n }\n }\n .lg\\:w-8 {\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 8);\n }\n }\n .lg\\:pb-10 {\n @media (width >= 64rem) {\n padding-bottom: calc(var(--spacing) * 10);\n }\n }\n .lg\\:text-2xl {\n @media (width >= 64rem) {\n font-size: var(--text-2xl);\n line-height: var(--tw-leading, var(--text-2xl--line-height));\n }\n }\n .lg\\:text-lg {\n @media (width >= 64rem) {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n }\n }\n .lg\\:text-xl {\n @media (width >= 64rem) {\n font-size: var(--text-xl);\n line-height: var(--tw-leading, var(--text-xl--line-height));\n }\n }\n}\n.noCursor {\n cursor: none !important;\n}\n@property --tw-translate-x {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-rotate-x {\n syntax: \"*\";\n inherits: false;\n initial-value: rotateX(0);\n}\n@property --tw-rotate-y {\n syntax: \"*\";\n inherits: false;\n initial-value: rotateY(0);\n}\n@property --tw-rotate-z {\n syntax: \"*\";\n inherits: false;\n initial-value: rotateZ(0);\n}\n@property --tw-skew-x {\n syntax: \"*\";\n inherits: false;\n initial-value: skewX(0);\n}\n@property --tw-skew-y {\n syntax: \"*\";\n inherits: false;\n initial-value: skewY(0);\n}\n@property --tw-border-style {\n syntax: \"*\";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-gradient-position {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-from {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-via {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-to {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-stops {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-via-stops {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-from-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 0%;\n}\n@property --tw-gradient-via-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 50%;\n}\n@property --tw-gradient-to-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-font-weight {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: \"<length>\";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: \"*\";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-duration {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ease {\n syntax: \"*\";\n inherits: false;\n}";
styleInject(css_248z$1,{"insertAt":"top"});
const useVideoStore = create((set) => ({
videoRef: null,
setVideoRef: (ref) => set({ videoRef: ref }),
videoWrapperRef: null,
setVideoWrapperRef: (ref) => set({ videoWrapperRef: ref }),
isPlaying: false,
setIsPlaying: (isPlaying) => set({ isPlaying }),
controls: false,
setControls: (controls) => set({ controls }),
currentTime: 0,
setCurrentTime: (currentTime) => set({ currentTime }),
hlsInstance: undefined,
setHlsInstance: (hlsInstance) => set({ hlsInstance }),
qualityLevels: undefined,
setQualityLevels: (qualityLevels) => set({ qualityLevels }),
activeQuality: "auto",
setActiveQuality: (activeQuality) => set({ activeQuality }),
isFullscreen: false,
setIsFullscreen: (isFullscreen) => set({ isFullscreen }),
duration: 0,
setDuration: (duration) => set({ duration }),
}));
/**
* @description Converts seconds to hh:mm:ss
* @param seconds
* @returns
*/
const timeFormat = (seconds) => {
if (isNaN(seconds)) {
return `00:00`;
}
const date = new Date(seconds * 1000);
const hh = date.getUTCHours();
const mm = date.getUTCMinutes();
const ss = date.getUTCSeconds().toString().padStart(2, "0");
if (hh) {
return `${hh}:${mm.toString().padStart(2, "0")}:${ss}`;
}
return `${mm}:${ss}`;
};
/**
* @description Converts seconds to milliseconds
* @param seconds
* @returns
*/
const secondsToMilliseconds = (seconds) => {
return seconds * 1000;
};
/**
* @description get extension from url
* @param url
* @returns string | undefined
*/
const getExtensionFromUrl = (url) => {
const extension = url?.split(".")?.pop();
if (extension === "m3u8") {
return "hls";
}
return extension;
};
function getPositionPercent(max, current) {
const divider = max || -1; // prevent division by zero
return (current * 100) / divider;
}
const isInRange = (time, start, end) => time >= start && time <= end;
function getTimeScale(currentTime, startTime, endTime, isTimePassed) {
const isActiveTime = isInRange(currentTime, startTime, endTime);
const timeDiff = endTime - startTime;
const timeDiffWithCurrent = currentTime - startTime;
const currentScalePercent = isActiveTime ? timeDiffWithCurrent / timeDiff : 0;
return isTimePassed ? 1 : currentScalePercent;
}
const TimeCodeItem = memo(({ label = "", startTime, maxTime, endTime, currentTime, seekHoverTime, bufferTime, isTimePassed = false, isBufferPassed = false, isHoverPassed = false, onHover = () => undefined, withGap, trackColor, }) => {
const positionPercent = getPositionPercent(maxTime, startTime);
const timeDiff = endTime - startTime;
const widthPercent = (timeDiff / maxTime) * 100;
const mainClassName = `main${withGap ? " with-gap" : ""}`;
const currentTimeScale = getTimeScale(currentTime, startTime, endTime, isTimePassed);
const seekHoverTimeScale = getTimeScale(seekHoverTime, startTime, endTime, isHoverPassed);
const bufferTimeScale = getTimeScale(bufferTime, startTime, endTime, isBufferPassed);
const handleMouseMove = () => onHover(label);
return (React__default.createElement("div", { className: mainClassName, onMouseMove: handleMouseMove, style: {
width: `${widthPercent}%`,
left: `${positionPercent}%`,
} },
React__default.createElement("div", { className: "inner-seek-block buffered", "data-test-id": "test-buffered", style: { transform: `scaleX(${bufferTimeScale})` } }),
React__default.createElement("div", { className: "inner-seek-block seek-hover", "data-test-id": "test-seek-hover", style: { transform: `scaleX(${seekHoverTimeScale})` } }),
React__default.createElement("div", { className: "inner-seek-block connect", style: {
transform: `scaleX(${currentTimeScale})`,
backgroundColor: trackColor || "#ff0000",
} })));
});
function positionToMs(max, position, trackWidth) {
const percent = (position * 100) / trackWidth;
return Math.floor(+(percent * (max / 100)));
}
const getEndTimeByIndex = (timeCodes, index, max) => (index + 1 < timeCodes.length ? timeCodes[index + 1].fromMs : max);
const TimeCodes = ({ max = 1000, currentTime = 0, bufferTime = 0, seekHoverPosition = 0, timeCodes, trackWidth, mobileSeeking, label, setLabel, trackColor, }) => {
const hoverTimeValue = positionToMs(max, seekHoverPosition, trackWidth);
const handleLabelChange = useCallback((currentLabel) => {
if (label !== currentLabel) {
setLabel(currentLabel);
}
}, [label]);
useEffect(() => {
if (!mobileSeeking) {
return;
}
const currentCode = timeCodes?.find(({ fromMs }, index) => {
const endTime = getEndTimeByIndex(timeCodes, index, max);
return isInRange(currentTime, fromMs, endTime);
});
if (currentCode?.description !== label) {
setLabel(currentCode?.description || "");
}
}, [currentTime, label, max, timeCodes]);
return (React__default.createElement(React__default.Fragment, null, timeCodes?.map(({ fromMs, description }, index) => {
const endTime = getEndTimeByIndex(timeCodes, index, max);
const isTimePassed = endTime <= currentTime;
const isBufferPassed = endTime <= bufferTime;
const isHoverPassed = endTime <= hoverTimeValue;
let inRange = isInRange(currentTime, fromMs, endTime);
const newCurrentTime = isTimePassed || !inRange ? 0 : currentTime;
inRange = isInRange(bufferTime, fromMs, endTime);
const newBufferTime = isBufferPassed || !inRange ? 0 : bufferTime;
inRange = isInRange(hoverTimeValue, fromMs, endTime);
const newHoverTime = isHoverPassed || !inRange ? 0 : hoverTimeValue;
return (React__default.createElement(TimeCodeItem, { key: fromMs, label: description, maxTime: max, startTime: fromMs, endTime: endTime, isTimePassed: isTimePassed, isBufferPassed: isBufferPassed, isHoverPassed: isHoverPassed, currentTime: newCurrentTime, bufferTime: newBufferTime, seekHoverTime: newHoverTime, onHover: handleLabelChange, withGap: true, trackColor: trackColor }));
})));
};
function getHoverTimePosition(seekHoverPosition, hoverTimeElement, trackWidth, limitTimeTooltipBySides) {
let position = 0;
if (hoverTimeElement) {
position = seekHoverPosition - hoverTimeElement.offsetWidth / 2;
if (limitTimeTooltipBySides) {
if (position < 0) {
position = 0;
}
else if (position + hoverTimeElement.offsetWidth > trackWidth) {
position = trackWidth - hoverTimeElement.offsetWidth;
}
}
}
return { transform: `translateX(${position}px)` };
}
function millisecondsToTime(ms, offset = 0) {
const roundedSeconds = Math.round(ms / 1000 + offset);
const hours = Math.floor(roundedSeconds / 3600);
const divirsForMinutes = roundedSeconds % 3600;
const minutes = Math.floor(divirsForMinutes / 60);
const sec = Math.ceil(divirsForMinutes % 60);
return {
hh: hours.toString(),
mm: minutes < 10 ? `0${minutes}` : minutes.toString(),
ss: sec < 10 ? `0${sec}` : sec.toString(),
};
}
function timeToTimeString(max, seekHoverTime, offset = 0, minutesPrefix = "", secondsPrefix = "") {
const times = millisecondsToTime(seekHoverTime, offset);
if (max + offset < 60 * 1000) {
return secondsPrefix + times.ss;
}
if (max + offset < 3600 * 1000) {
return `${minutesPrefix + times.mm}:${times.ss}`;
}
return `${times.hh}:${times.mm}:${times.ss}`;
}
const HoverTimeWithPreview = ({ max, hoverTimeValue, offset, trackWidth, seekHoverPosition, isThumbActive, limitTimeTooltipBySides, label, minutesPrefix, secondsPrefix, getPreviewScreenUrl, }) => {
const hoverTimeElement = useRef(null);
const hoverTimeClassName = isThumbActive ? "hover-time active" : "hover-time";
const hoverTimePosition = getHoverTimePosition(seekHoverPosition, hoverTimeElement?.current, trackWidth, limitTimeTooltipBySides);
const hoverTimeString = timeToTimeString(max, hoverTimeValue, offset, minutesPrefix, secondsPrefix);
return (React__default.createElement("div", { className: hoverTimeClassName, style: hoverTimePosition, ref: hoverTimeElement, "data-testid": "hover-time" },
isThumbActive && getPreviewScreenUrl && (React__default.createElement("div", { className: "preview-screen", style: {
backgroundImage: `url(${getPreviewScreenUrl(hoverTimeValue)})`,
} })),
label && React__default.createElement("div", null, label),
hoverTimeString));
};
const Thumb = ({ max, currentTime, isThumbActive, trackColor, }) => {
const getThumbHandlerPosition = () => {
const thumbConstantOffset = -6;
const leftPosition = (currentTime / max) * 100;
return {
left: `calc(${leftPosition}% + ${thumbConstantOffset}px)`,
};
};
return (React__default.createElement("div", { className: isThumbActive ? "thumb active" : "thumb active", "data-testid": "testThumb", style: getThumbHandlerPosition() },
React__default.createElement("div", { className: "handler", style: {
backgroundColor: trackColor || "#ff0000",
} })));
};
const VideoSeekSlider = ({ max = 1000, currentTime = 0, bufferTime = 0, hideThumbTooltip = false, offset = 0, secondsPrefix = "", minutesPrefix = "", limitTimeTooltipBySides = true, timeCodes, onChange = () => undefined, getPreviewScreenUrl, trackColor, }) => {
const [seekHoverPosition, setSeekHoverPosition] = useState(0);
const [label, setLabel] = useState("");
const seeking = useRef(false);
const mobileSeeking = useRef(false);
const trackElement = useRef(null);
const trackWidth = trackElement.current?.offsetWidth || 0;
const isThumbActive = seekHoverPosition > 0 || seeking.current;
const hoverTimeValue = positionToMs(max, seekHoverPosition, trackWidth);
const changeCurrentTimePosition = (pageX) => {
const clientRect = trackElement.current?.getBoundingClientRect();
const left = clientRect?.left || 0;
const width = clientRect?.width || 0;
let position = pageX - left;
position = position < 0 ? 0 : position;
position = position > width ? width : position;
const percent = (position * 100) / width;
const time = +(percent * (max / 100)).toFixed(0);
setSeekHoverPosition(position);
onChange(time, time + offset);
};
const handleTouchSeeking = (event) => {
event.preventDefault();
event.stopPropagation();
if (!mobileSeeking.current) {
return;
}
const { changedTouches } = event;
let pageX = changedTouches?.[changedTouches.length - 1]?.pageX || 0;
pageX = pageX < 0 ? 0 : pageX;
changeCurrentTimePosition(pageX);
};
const handleSeeking = (event) => {
if (seeking.current) {
changeCurrentTimePosition(event.pageX);
}
};
const handleTrackHover = (clear, event) => {
const left = trackElement.current?.getBoundingClientRect().left || 0;
const position = clear ? 0 : event.pageX - left;
setSeekHoverPosition(position);
};
const setMobileSeeking = (state = true) => {
mobileSeeking.current = state;
setSeekHoverPosition(state ? seekHoverPosition : 0);
};
const setSeeking = (state, event) => {
event.preventDefault();
handleSeeking(event);
seeking.current = state;
setSeekHoverPosition(state ? seekHoverPosition : 0);
};
const mouseSeekingHandler = (event) => {
setSeeking(false, event);
};
const mobileTouchSeekingHandler = () => {
setMobileSeeking(false);
};
useEffect(() => {
if (!mobileSeeking.current) {
return;
}
const currentCode = timeCodes?.find(({ fromMs }, index) => {
const endTime = getEndTimeByIndex(timeCodes, index, max);
return isInRange(currentTime, fromMs, endTime);
});
if (currentCode?.description !== label) {
setLabel(currentCode?.description || "");
}
}, [currentTime, label, max, timeCodes]);
useEffect(() => {
window.addEventListener("mousemove", handleSeeking);
window.addEventListener("mouseup", mouseSeekingHandler);
window.addEventListener("touchmove", handleTouchSeeking);
window.addEventListener("touchend", mobileTouchSeekingHandler);
return () => {
window.removeEventListener("mousemove", handleSeeking);
window.removeEventListener("mouseup", mouseSeekingHandler);
window.removeEventListener("touchmove", handleTouchSeeking);
window.removeEventListener("touchend", mobileTouchSeekingHandler);
};
}, [max, offset, trackWidth]);
return (React__default.createElement("div", { className: "ui-video-seek-slider" },
React__default.createElement("div", { className: isThumbActive ? "track active" : "track", ref: trackElement, onMouseMove: (event) => handleTrackHover(false, event), onMouseLeave: (event) => handleTrackHover(true, event), onMouseDown: (event) => setSeeking(true, event), onTouchStart: () => setMobileSeeking(true), "data-testid": "main-track" },
Boolean(timeCodes?.length) && (React__default.createElement(TimeCodes, { currentTime: currentTime, max: max, bufferTime: bufferTime, seekHoverPosition: seekHoverPosition, timeCodes: timeCodes, mobileSeeking: mobileSeeking.current, trackWidth: trackWidth, label: label, setLabel: setLabel, trackColor: trackColor })),
!timeCodes && (React__default.createElement(TimeCodeItem, { maxTime: max, startTime: 0, endTime: max, currentTime: currentTime, bufferTime: bufferTime, seekHoverTime: hoverTimeValue, trackColor: trackColor }))),
!hideThumbTooltip && (React__default.createElement(HoverTimeWithPreview, { max: max, hoverTimeValue: hoverTimeValue, isThumbActive: isThumbActive, label: label, limitTimeTooltipBySides: limitTimeTooltipBySides, offset: offset, seekHoverPosition: seekHoverPosition, trackWidth: trackWidth, getPreviewScreenUrl: getPreviewScreenUrl, minutesPrefix: minutesPrefix, secondsPrefix: secondsPrefix })),
React__default.createElement(Thumb, { max: max, currentTime: currentTime, isThumbActive: isThumbActive, trackColor: trackColor })));
};
var css_248z = ".ui-video-seek-slider {\n position: relative;\n touch-action: none;\n }\n .ui-video-seek-slider:focus {\n outline: none;\n }\n .ui-video-seek-slider .track {\n padding: 0;\n cursor: pointer;\n outline: none;\n }\n .ui-video-seek-slider .track:focus {\n border: 0;\n outline: none;\n }\n .ui-video-seek-slider .track .main {\n width: 100%;\n outline: none;\n height: 18px;\n top: 0;\n position: absolute;\n display: flex;\n align-items: center;\n box-sizing: border-box;\n }\n .ui-video-seek-slider .track .main:before {\n content: \"\";\n position: absolute;\n width: 100%;\n height: 3px;\n background-color: rgba(255, 255, 255, 0.2);\n overflow: hidden;\n transition: height 0.1s;\n outline: none;\n }\n .ui-video-seek-slider .track .main .inner-seek-block {\n position: absolute;\n width: 100%;\n height: 3px;\n transition: height 0.1s, opacity 0.4s;\n transform-origin: 0 0;\n }\n .ui-video-seek-slider .track .main:focus {\n border: 0;\n outline: none;\n }\n .ui-video-seek-slider .track .main .buffered {\n background-color: rgba(255, 255, 255, 0.3);\n z-index: 2;\n }\n .ui-video-seek-slider .track .main .seek-hover {\n background-color: rgba(255, 255, 255, 0.5);\n z-index: 1;\n }\n .ui-video-seek-slider .track .main .connect {\n background-color: #ff0000;\n z-index: 3;\n transform-origin: 0 0;\n }\n .ui-video-seek-slider .track .main.with-gap .inner-seek-block, .ui-video-seek-slider .track .main.with-gap:before {\n width: calc(100% - 2px);\n margin: 0 auto;\n }\n @media (hover) {\n .ui-video-seek-slider .track .main:hover:before {\n height: 8px;\n }\n .ui-video-seek-slider .track .main:hover .inner-seek-block {\n height: 8px;\n }\n }\n .ui-video-seek-slider .thumb {\n pointer-events: none;\n position: absolute;\n width: 12px;\n height: 12px;\n left: -6px;\n z-index: 4;\n top: 3px;\n }\n .ui-video-seek-slider .thumb .handler {\n border-radius: 100%;\n width: 100%;\n height: 100%;\n background-color: #ff0000;\n opacity: 0;\n transform: scale(0.4);\n transition: transform 0.2s, opacity 0.2s;\n }\n .ui-video-seek-slider .thumb.active .handler {\n opacity: 1;\n transform: scale(1);\n }\n .ui-video-seek-slider .hover-time {\n text-shadow: 1px 1px 1px #000;\n position: absolute;\n line-height: 18px;\n font-size: 16px;\n color: #ddd;\n bottom: 5px;\n left: 0;\n padding: 5px 10px;\n opacity: 0;\n pointer-events: none;\n text-align: center;\n }\n .ui-video-seek-slider .hover-time.active {\n opacity: 1;\n }\n .ui-video-seek-slider .hover-time .preview-screen {\n background-repeat: no-repeat;\n background-size: cover;\n background-position: center;\n width: 200px;\n height: 110px;\n border-radius: 5px;\n background-color: #000;\n margin: 0 auto 10px;\n }\n .ui-video-seek-slider:hover .track .main .seek-hover {\n opacity: 1;\n }";
styleInject(css_248z,{"insertAt":"top"});
const BottomControls = ({ config }) => {
const { videoRef, currentTime, isFullscreen } = useVideoStore();
const duration = videoRef?.duration;
return (React__default.createElement("div", { className: "px-10 text-white" },
React__default.createElement(VideoSeekSlider, { max: secondsToMilliseconds(duration || 0), currentTime: secondsToMilliseconds(currentTime || 0), bufferTime: secondsToMilliseconds(0), onChange: (currentTime) => {
if (videoRef) {
videoRef.currentTime = currentTime / 1000;
}
}, secondsPrefix: "00:00:", minutesPrefix: "00:", getPreviewScreenUrl: config?.seekBarConfig?.getPreviewScreenUrl, timeCodes: config?.seekBarConfig?.timeCodes, trackColor: config?.seekBarConfig?.trackColor }),
React__default.createElement("div", { className: `pt-6 ${isFullscreen ? "pb-10" : "pb-16"} lg:pb-10 flex gap-2 items-center` },
React__default.createElement("p", { className: "lg:text-xl font-semibold" }, timeFormat(currentTime || 0)),
" ",
React__default.createElement("p", { className: "lg:text-2xl" }, "/"),
" ",
React__default.createElement("p", { className: "lg:text-xl font-semibold" }, timeFormat(duration || 0)))));
};
const Tooltip = ({ children, title, position = "top", }) => {
const [visible, setVisible] = useState(false);
const positionStyles = {
top: "bottom-full left-1/2 transform -translate-x-1/2 mb-2",
bottom: "top-full left-1/2 transform -translate-x-1/2 mt-2",
left: "right-full top-1/2 transform -translate-y-1/2 mr-2",
right: "left-full top-1/2 transform -translate-y-1/2 ml-2",
};
return (React__default.createElement("div", { className: "relative inline-block cursor-pointer", onMouseEnter: () => setVisible(true), onMouseLeave: () => setVisible(false) },
children,
visible && (React__default.createElement("div", { className: `absolute z-50 px-3 py-1 text-sm text-white bg-gray-900 rounded-md shadow-md transition-opacity duration-200 ease-in-out ${positionStyles[position]}` }, title))));
};
const Popover = ({ button, children, }) => {
const [isOpen, setIsOpen] = useState(false);
const popoverRef = useRef(null);
// Close popover when clicking outside
useEffect(() => {
const handleClickOutside = (event) => {
if (popoverRef.current &&
!popoverRef.current.contains(event.target)) {
setIsOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
return (React__default.createElement("div", { className: "relative inline-block", ref: popoverRef },
React__default.createElement("div", { onClick: () => setIsOpen(!isOpen) }, button),
isOpen && (React__default.createElement("div", { className: "absolute left-0 mt-2 w-fit bg-white shadow-lg rounded-lg border border-gray-200 z-50 p-4" }, children))));
};
const ControlsHeader = ({ config }) => {
const className = "w-5 h-5 lg:w-8 lg:h-8";
const { videoWrapperRef, videoRef, qualityLevels, hlsInstance, setActiveQuality, activeQuality, } = useVideoStore();
const handleFullscreen = () => {
if (document.fullscreenElement) {
document.exitFullscreen();
}
else {
videoWrapperRef?.requestFullscreen();
}
};
const isFullscreen = document.fullscreenElement !== null;
const handleMute = () => {
if (videoRef?.muted) {
if (videoRef) {
videoRef.muted = false;
}
}
else {
if (videoRef) {
videoRef.muted = true;
}
}
};
return (React.createElement("div", { className: "flex items-center justify-between p-10 bg-gradient-to-b from-black" },
React.createElement("div", null,
config?.title && (React.createElement("h1", { className: "text-white lg:text-2xl font-bold" }, config.title)),
config?.isTrailer && (React.createElement("h1", { className: "text-white lg:text-lg" }, "Trailer"))),
React.createElement("div", { className: "flex items-center gap-7 text-white" },
React.createElement("div", null,
React.createElement(Tooltip, { title: "Settings" },
React.createElement(Popover, { button: React.createElement(Settings, { className: className }) },
React.createElement("div", { className: "text-black" },
React.createElement("div", null,
React.createElement("p", { className: "p-2 font-bold" }, "Quality"),
React.createElement("p", { onClick: () => {
if (hlsInstance) {
hlsInstance.currentLevel = -1;
setActiveQuality("auto");
}
}, className: "p-2 cursor-pointer flex items-center gap-1.5" },
activeQuality === "auto" && React.createElement(Check, null),
" Auto"),
qualityLevels
?.map((level, index) => (React.createElement("p", { key: index, onClick: () => {
if (hlsInstance) {
hlsInstance.currentLevel = index;
setActiveQuality(String(level.height));
}
}, className: "p-2 cursor-pointer flex items-center gap-1.5" },
activeQuality === String(level.height) && React.createElement(Check, null),
" ",
level.height,
"p")))
.reverse()))))),
React.createElement("div", { onClick: handleMute }, videoRef?.muted ? (React.createElement(Tooltip, { title: "Unmute" },
React.createElement(VolumeOff, { className: className }))) : (React.createElement(Tooltip, { title: "Mute" },
React.createElement(Volume2, { className: className })))),
React.createElement("div", { onClick: handleFullscreen }, isFullscreen ? (React.createElement(Tooltip, { title: "Exit" },
React.createElement(Shrink, { className: className }))) : (React.createElement(Tooltip, { title: "Fullscreen" },
React.createElement(Maximize, { className: className })))),
config?.onClose && (React.createElement(React.Fragment, null,
React.createElement("div", { className: "w-[2px] h-10 bg-white mx-1" }),
React.createElement("div", { onClick: config.onClose },
React.createElement(Tooltip, { title: "Close" },
React.createElement(X, { className: className }))))))));
};
const MiddleControls = () => {
const { videoRef, isPlaying, setIsPlaying } = useVideoStore();
const handlePlayPause = () => {
if (!videoRef)
return;
if (videoRef.paused) {
videoRef
.play()
.catch((error) => console.error("Error playing video:", error));
setIsPlaying(true);
}
else {
videoRef.pause();
setIsPlaying(false);
}
};
const handleBackword = () => {
if (!videoRef)
return;
videoRef.currentTime -= 10;
};
const handleForword = () => {
if (!videoRef)
return;
videoRef.currentTime += 10;
};
const nextAndPrevIconSize = "size-10 lg:size-12";
const playPauseIconSize = "size-10 lg:size-15";
return (React__default.createElement("div", { className: "flex justify-center items-center" },
React__default.createElement("div", { onClick: handleBackword, className: "w-[15vw] flex justify-center items-center h-full cursor-pointer" },
React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: nextAndPrevIconSize, fill: "none", viewBox: "0 0 67 67" },
React__default.createElement("path", { fill: "#fff", fillRule: "evenodd", clipRule: "evenodd", d: "M33.5 0C52 0 67 15 67 33.5S52 67 33.5 67 0 52 0 33.5c.03-1.4 1.17-2.53 2.58-2.53 1.4 0 2.55 1.13 2.57 2.53 0 15.65 12.7 28.35 28.35 28.35 15.66 0 28.35-12.7 28.35-28.35 0-15.66-12.69-28.35-28.35-28.35h-.04c-7 0-13.76 2.61-18.94 7.3-.46.42-.91.85-1.34 1.29h6.58c1.42 0 2.57 1.16 2.57 2.58 0 1.42-1.15 2.58-2.57 2.58H6.01c-1.42 0-2.57-1.16-2.57-2.58V2.58C3.44 1.15 4.59 0 6.01 0c1.43 0 2.58 1.15 2.58 2.58v8.52c.78-.86 1.61-1.7 2.47-2.47A33.407 33.407 0 0 1 33.46 0h.04zm.48 41.34c-1.6-2.21-2-5.2-2-7.85 0-2.65.4-5.63 2-7.83 1.44-1.97 3.47-2.84 5.88-2.84 2.41 0 4.42.87 5.86 2.84 1.61 2.21 2.03 5.16 2.03 7.83 0 2.66-.4 5.64-2 7.85-1.43 1.97-3.47 2.84-5.89 2.84-2.41 0-4.45-.86-5.88-2.84zm-9.73-12.77l-5 1.58v-4.21l5.87-2.65h4.28v20.47h-5.15V28.57zm17.61 9.96c.61-1.33.68-3.6.68-5.04s-.07-3.7-.68-5.02c-.4-.86-1.04-1.29-2-1.29-.95 0-1.59.42-1.99 1.29-.61 1.32-.68 3.58-.68 5.02 0 1.44.07 3.71.68 5.04.4.87 1.04 1.29 1.99 1.29.96 0 1.6-.42 2-1.29z" }))),
React__default.createElement("div", { className: "w-[10vw] flex justify-center items-center h-full cursor-pointer", onClick: handlePlayPause }, isPlaying ? (React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: playPauseIconSize, fill: "none", viewBox: "0 0 67 67" },
React__default.createElement("path", { fill: "#fff", fillRule: "evenodd", d: "M46.332 5.773a4.125 4.125 0 0 0-4.125 4.125v46.75a4.127 4.127 0 0 0 4.125 4.125 4.127 4.127 0 0 0 4.125-4.125V9.898a4.125 4.125 0 0 0-4.125-4.125zM25.707 9.898v46.75a4.125 4.125 0 1 1-8.25 0V9.898a4.123 4.123 0 0 1 4.125-4.125 4.123 4.123 0 0 1 4.125 4.125z", clipRule: "evenodd" }))) : (React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: playPauseIconSize, width: "67", height: "67", fill: "none", viewBox: "0 0 67 67" },
React__default.createElement("path", { fill: "#fff", d: "M20.28 9.65c-2.205-1.268-4.026-.228-4.026 2.307v43.805c0 2.535 1.82 3.574 4.027 2.307l38.471-21.903a2.556 2.556 0 0 0 1.094-.935 2.514 2.514 0 0 0 0-2.743 2.556 2.556 0 0 0-1.093-.936L20.28 9.65z" })))),
React__default.createElement("div", { className: "w-[15vw] flex justify-center items-center h-full cursor-pointer", onClick: handleForword },
React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: nextAndPrevIconSize, fill: "none", viewBox: "0 0 67 67" },
React__default.createElement("path", { fill: "#fff", fillRule: "evenodd", d: "M33.5 0C15 0 0 15 0 33.5S15 67 33.5 67 67 52 67 33.5a2.583 2.583 0 0 0-2.58-2.53c-1.4 0-2.55 1.13-2.57 2.53 0 15.66-12.69 28.35-28.35 28.35-15.65 0-28.35-12.7-28.35-28.35 0-15.66 12.7-28.35 28.35-28.35 7.3 0 13.96 2.76 18.99 7.3.46.42.9.85 1.34 1.29h-6.59a2.58 2.58 0 0 0 0 5.16h13.75c1.42 0 2.57-1.16 2.57-2.58V2.58c0-1.43-1.15-2.58-2.57-2.58-1.43 0-2.58 1.15-2.58 2.58v8.52c-.78-.87-1.61-1.7-2.47-2.48A33.446 33.446 0 0 0 33.54 0h-.04zm.48 41.34c-1.6-2.21-2-5.2-2-7.85 0-2.65.4-5.63 2-7.83 1.44-1.97 3.47-2.84 5.88-2.84 2.41 0 4.42.87 5.86 2.84 1.61 2.21 2.03 5.16 2.03 7.83 0 2.66-.4 5.64-2 7.85-1.43 1.97-3.47 2.84-5.89 2.84-2.41 0-4.45-.87-5.88-2.84zm-9.73-12.77l-5 1.58v-4.21l5.87-2.65h4.28v20.47h-5.15V28.57zm17.61 9.96c.61-1.33.68-3.6.68-5.04s-.07-3.7-.68-5.02c-.4-.87-1.04-1.29-2-1.29-.95 0-1.59.42-1.99 1.29-.61 1.32-.68 3.58-.68 5.02 0 1.44.07 3.71.68 5.04.4.86 1.04 1.28 1.99 1.28.96 0 1.6-.42 2-1.28z" })))));
};
const VideoPlayerControls = ({ config, height, width, }) => {
return (React.createElement("div", { className: `absolute top-0 left-0 ${height || "h-full"} ${width || "w-full"} bg-[rgba(0,0,0,0.5)] flex flex-col justify-between` },
React.createElement(ControlsHeader, { config: config?.headerConfig?.config }),
React.createElement(MiddleControls, null),
React.createElement(BottomControls, { config: config?.bottomConfig?.config })));
};
const Overlay = ({ config }) => {
const controlsTimerRef = useRef(null);
const { setControls, controls } = useVideoStore();
const handleMouseEnter = useCallback(() => {
const videoPlayerControls = document?.getElementById("videoPlayerControls");
if (videoPlayerControls) {
videoPlayerControls.classList.remove("noCursor");
}
setControls(true);
if (controlsTimerRef.current) {
clearTimeout(controlsTimerRef.current);
}
controlsTimerRef.current = setTimeout(() => {
setControls(false);
if (videoPlayerControls) {
videoPlayerControls.classList.add("noCursor");
}
}, 3000); // 3 seconds
}, [setControls]);
return (React.createElement("div", { id: "videoPlayerControls", className: "absolute inset-0", onMouseMove: handleMouseEnter }, controls && React.createElement(VideoPlayerControls, { config: config })));
};
const VideoPlayer = ({ trackSrc, trackTitle, trackPoster, isTrailer, className, type, height, width, timeCodes, getPreviewScreenUrl, tracking, }) => {
const { setVideoRef, setCurrentTime, setVideoWrapperRef, videoRef, setQualityLevels, setHlsInstance, setDuration, setIsPlaying, } = useVideoStore();
const onRightClick = (e) => {
e.preventDefault();
};
useEffect(() => {
if (!videoRef) {
return;
}
const getVideoExtension = getExtensionFromUrl(trackSrc);
const contentType = type || getVideoExtension;
if (contentType === "mp4") {
videoRef.src = trackSrc;
setQualityLevels([]);
}
else if (contentType === "hls") {
if (videoRef?.canPlayType("application/vnd.apple.mpegurl")) {
// Native HLS support (Safari)
videoRef.src = trackSrc;
}
else if (Hls.isSupported()) {
// Use hls.js for other browsers
const hls = new Hls();
hls.loadSource(trackSrc);
hls.attachMedia(videoRef);
setHlsInstance(hls);
// Get quality levels when HLS loads
hls.on(Hls.Events.MANIFEST_PARSED, () => {
setQualityLevels(hls.levels);
});
return () => {
hls.destroy(); // Cleanup on unmount
};
}
}
else {
videoRef.src = trackSrc;
setQualityLevels([]);
}
}, [trackSrc, videoRef]);
// Analytics Start
const startTime = useRef(null);
const isViewCounted = useRef(false);
useEffect(() => {
if (videoRef) {
videoRef.addEventListener("play", () => {
if (!isViewCounted.current) {
isViewCounted.current = true;
if (tracking?.onViewed) {
tracking.onViewed();
}
}
startTime.current = Date.now();
setIsPlaying(true);
});
videoRef.addEventListener("pause", () => {
if (startTime.current) {
const elapsedTime = (Date.now() - startTime.current) / 1000;
const getCurrentTime = localStorage.getItem("current_time");
localStorage.setItem("current_time", (Number(getCurrentTime || 0) + elapsedTime).toString());
startTime.current = null;
}
setIsPlaying(false);
});
return () => {
videoRef.removeEventListener("play", () => { });
videoRef.removeEventListener("pause", () => { });
};
}
}, [videoRef]);
const handleUnload = (e)