@zezosoft/react-player 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/VideoPlayer/MediaControls/BottomControls.d.ts +5 -0
  2. package/dist/VideoPlayer/MediaControls/ControlsHeader.d.ts +5 -0
  3. package/dist/VideoPlayer/MediaControls/MiddleControls.d.ts +3 -0
  4. package/dist/VideoPlayer/MediaControls/VideoPlayerControls.d.ts +4 -0
  5. package/dist/VideoPlayer/VideoPlayer.d.ts +1 -0
  6. package/dist/VideoPlayer/components/AdOverlay.d.ts +10 -0
  7. package/dist/VideoPlayer/components/Overlay.d.ts +4 -0
  8. package/dist/VideoPlayer/components/SubtitleOverlay.d.ts +7 -0
  9. package/dist/VideoPlayer/components/controls/BottomControls.d.ts +5 -0
  10. package/dist/VideoPlayer/components/controls/ControlsHeader.d.ts +5 -0
  11. package/dist/VideoPlayer/components/controls/MiddleControls.d.ts +3 -0
  12. package/dist/VideoPlayer/components/controls/VideoPlayerControls.d.ts +4 -0
  13. package/dist/VideoPlayer/components/controls/index.d.ts +4 -0
  14. package/dist/VideoPlayer/components/time-line/TimeLine.d.ts +21 -0
  15. package/dist/VideoPlayer/components/time-line/components/HoverTimeWithPreview.d.ts +16 -0
  16. package/dist/VideoPlayer/components/time-line/components/Thumb.d.ts +9 -0
  17. package/dist/VideoPlayer/components/time-line/components/TimeCodeItem.d.ts +21 -0
  18. package/dist/VideoPlayer/components/time-line/components/TimeCodes.d.ts +15 -0
  19. package/dist/VideoPlayer/components/time-line/utils/getEndTimeByIndex.d.ts +2 -0
  20. package/dist/VideoPlayer/components/time-line/utils/getHoverTimePosition.d.ts +3 -0
  21. package/dist/VideoPlayer/components/time-line/utils/getPositionPercent.d.ts +1 -0
  22. package/dist/VideoPlayer/components/time-line/utils/getTimeScale.d.ts +1 -0
  23. package/dist/VideoPlayer/components/time-line/utils/isInRange.d.ts +1 -0
  24. package/dist/VideoPlayer/components/time-line/utils/positionToMs.d.ts +1 -0
  25. package/dist/VideoPlayer/components/time-line/utils/secondsToTime.d.ts +6 -0
  26. package/dist/VideoPlayer/components/time-line/utils/timeToTimeString.d.ts +1 -0
  27. package/dist/VideoPlayer/constants.d.ts +3 -0
  28. package/dist/VideoPlayer/hooks/index.d.ts +2 -0
  29. package/dist/VideoPlayer/hooks/useAdManager.d.ts +8 -0
  30. package/dist/VideoPlayer/hooks/usePrimaryVideoLifecycle.d.ts +17 -0
  31. package/dist/VideoPlayer/hooks/useVideoEvents.d.ts +1 -1
  32. package/dist/VideoPlayer/hooks/useVideoSource.d.ts +1 -1
  33. package/dist/VideoPlayer/types/AdTypes.d.ts +36 -0
  34. package/dist/VideoPlayer/types/VideoPlayerTypes.d.ts +8 -2
  35. package/dist/VideoPlayer/utils/index.d.ts +1 -0
  36. package/dist/VideoPlayer/utils/qualityManager.d.ts +52 -0
  37. package/dist/components/ui/FullScreenToggle.d.ts +1 -1
  38. package/dist/components/ui/PiPictureInPictureToggle.d.ts +1 -1
  39. package/dist/index.js +1836 -228
  40. package/dist/store/slices/adsSlice.d.ts +24 -0
  41. package/dist/store/slices/index.d.ts +1 -0
  42. package/dist/store/types/StoreTypes.d.ts +44 -5
  43. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import * as React from 'react';
2
- import React__default, { memo, useCallback, useEffect, useRef, useState } from 'react';
2
+ import React__default, { memo, useCallback, useEffect, useRef, useState, useMemo } from 'react';
3
+ import { useShallow } from 'zustand/react/shallow';
3
4
  import { create } from 'zustand';
4
5
  import { IoVolumeMuteOutline, IoVolumeHighOutline } from 'react-icons/io5';
5
6
  import { IoMdClose } from 'react-icons/io';
6
- import { Settings as Settings$1, ChevronRight, Check, Loader, ArrowRight } from 'lucide-react';
7
+ import { Settings as Settings$1, ChevronRight, Check, Loader, ArrowRight, SkipForward } from 'lucide-react';
7
8
  import screenfull from 'screenfull';
8
9
  import Hls from 'hls.js';
10
+ import * as dashjs from 'dashjs';
9
11
 
10
12
  function styleInject(css, ref) {
11
13
  if ( ref === void 0 ) ref = {};
@@ -34,8 +36,8 @@ function styleInject(css, ref) {
34
36
  }
35
37
  }
36
38
 
37
- var css_248z$3 = "/*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */\n@layer properties;\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-green-500: oklch(72.3% 0.219 149.579);\n --color-blue-500: oklch(62.3% 0.214 259.815);\n --color-purple-500: oklch(62.7% 0.265 303.9);\n --color-gray-200: oklch(92.8% 0.006 264.531);\n --color-gray-300: oklch(87.2% 0.01 258.338);\n --color-gray-400: oklch(70.7% 0.022 261.325);\n --color-gray-500: oklch(55.1% 0.027 264.364);\n --color-gray-600: oklch(44.6% 0.03 256.802);\n --color-gray-900: oklch(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-base: 1rem;\n --text-base--line-height: calc(1.5 / 1);\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 --text-3xl: 1.875rem;\n --text-3xl--line-height: calc(2.25 / 1.875);\n --font-weight-normal: 400;\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 --animate-spin: spin 1s linear infinite;\n --blur-sm: 8px;\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-mono-font-family: var(--font-mono);\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 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 }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\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 ::-webkit-calendar-picker-indicator {\n line-height: 1;\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 .pointer-events-none {\n pointer-events: none;\n }\n .absolute {\n position: absolute;\n }\n .relative {\n position: relative;\n }\n .inset-0 {\n inset: calc(var(--spacing) * 0);\n }\n .-top-2 {\n top: calc(var(--spacing) * -2);\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-0 {\n right: calc(var(--spacing) * 0);\n }\n .right-32 {\n right: calc(var(--spacing) * 32);\n }\n .right-full {\n right: 100%;\n }\n .bottom-36 {\n bottom: calc(var(--spacing) * 36);\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-32 {\n left: calc(var(--spacing) * 32);\n }\n .left-full {\n left: 100%;\n }\n .z-50 {\n z-index: 50;\n }\n .z-\\[-1\\] {\n z-index: -1;\n }\n .mx-2 {\n margin-inline: calc(var(--spacing) * 2);\n }\n .mx-auto {\n margin-inline: auto;\n }\n .mt-1 {\n margin-top: calc(var(--spacing) * 1);\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-1 {\n margin-bottom: calc(var(--spacing) * 1);\n }\n .mb-2 {\n margin-bottom: calc(var(--spacing) * 2);\n }\n .mb-4 {\n margin-bottom: calc(var(--spacing) * 4);\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 .hidden {\n display: none;\n }\n .inline {\n display: inline;\n }\n .inline-block {\n display: inline-block;\n }\n .h-3 {\n height: calc(var(--spacing) * 3);\n }\n .h-5 {\n height: calc(var(--spacing) * 5);\n }\n .h-6 {\n height: calc(var(--spacing) * 6);\n }\n .h-10 {\n height: calc(var(--spacing) * 10);\n }\n .h-24 {\n height: calc(var(--spacing) * 24);\n }\n .h-full {\n height: 100%;\n }\n .max-h-80 {\n max-height: calc(var(--spacing) * 80);\n }\n .w-3 {\n width: calc(var(--spacing) * 3);\n }\n .w-5 {\n width: calc(var(--spacing) * 5);\n }\n .w-6 {\n width: calc(var(--spacing) * 6);\n }\n .w-24 {\n width: calc(var(--spacing) * 24);\n }\n .w-80 {\n width: calc(var(--spacing) * 80);\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 .rotate-45 {\n rotate: 45deg;\n }\n .rotate-180 {\n rotate: 180deg;\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 .animate-spin {\n animation: var(--animate-spin);\n }\n .cursor-not-allowed {\n cursor: not-allowed;\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 .items-start {\n align-items: flex-start;\n }\n .justify-between {\n justify-content: space-between;\n }\n .justify-center {\n justify-content: center;\n }\n .gap-3 {\n gap: calc(var(--spacing) * 3);\n }\n .gap-4 {\n gap: calc(var(--spacing) * 4);\n }\n .gap-7 {\n gap: calc(var(--spacing) * 7);\n }\n .space-y-0 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-3 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .overflow-hidden {\n overflow: hidden;\n }\n .overflow-y-auto {\n overflow-y: auto;\n }\n .rounded {\n border-radius: 0.25rem;\n }\n .rounded-\\[5px\\] {\n border-radius: 5px;\n }\n .rounded-\\[7px\\] {\n border-radius: 7px;\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-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n }\n .border-b {\n border-bottom-style: var(--tw-border-style);\n border-bottom-width: 1px;\n }\n .border-l {\n border-left-style: var(--tw-border-style);\n border-left-width: 1px;\n }\n .border-gray-600 {\n border-color: var(--color-gray-600);\n }\n .border-white\\/10 {\n border-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n .bg-\\[\\#3a4049\\] {\n background-color: #3a4049;\n }\n .bg-\\[\\#454545\\] {\n background-color: #454545;\n }\n .bg-\\[rgba\\(0\\,0\\,0\\,0\\.5\\)\\] {\n background-color: rgba(0,0,0,0.5);\n }\n .bg-blue-500 {\n background-color: var(--color-blue-500);\n }\n .bg-gray-500 {\n background-color: var(--color-gray-500);\n }\n .bg-gray-900 {\n background-color: var(--color-gray-900);\n }\n .bg-green-500 {\n background-color: var(--color-green-500);\n }\n .bg-purple-500 {\n background-color: var(--color-purple-500);\n }\n .bg-white\\/10 {\n background-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n .bg-white\\/80 {\n background-color: color-mix(in srgb, #fff 80%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 80%, transparent);\n }\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 .bg-cover {\n background-size: cover;\n }\n .bg-center {\n background-position: center;\n }\n .p-0 {\n padding: calc(var(--spacing) * 0);\n }\n .p-1 {\n padding: calc(var(--spacing) * 1);\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-4 {\n padding-inline: calc(var(--spacing) * 4);\n }\n .px-6 {\n padding-inline: calc(var(--spacing) * 6);\n }\n .px-10 {\n padding-inline: calc(var(--spacing) * 10);\n }\n .px-20 {\n padding-inline: calc(var(--spacing) * 20);\n }\n .py-1 {\n padding-block: calc(var(--spacing) * 1);\n }\n .py-2 {\n padding-block: calc(var(--spacing) * 2);\n }\n .py-3 {\n padding-block: calc(var(--spacing) * 3);\n }\n .py-4 {\n padding-block: calc(var(--spacing) * 4);\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-left {\n text-align: left;\n }\n .text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n }\n .text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n }\n .text-xl {\n font-size: var(--text-xl);\n line-height: var(--tw-leading, var(--text-xl--line-height));\n }\n .font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n }\n .font-normal {\n --tw-font-weight: var(--font-weight-normal);\n font-weight: var(--font-weight-normal);\n }\n .font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n }\n .text-gray-200 {\n color: var(--color-gray-200);\n }\n .text-gray-300 {\n color: var(--color-gray-300);\n }\n .text-gray-400 {\n color: var(--color-gray-400);\n }\n .text-gray-500 {\n color: var(--color-gray-500);\n }\n .text-gray-900 {\n color: var(--color-gray-900);\n }\n .text-white {\n color: var(--color-white);\n }\n .opacity-50 {\n opacity: 50%;\n }\n .shadow-2xl {\n --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\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 .backdrop-blur-sm {\n --tw-backdrop-blur: blur(var(--blur-sm));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .transition {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, visibility, content-visibility, overlay, pointer-events;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-all {\n transition-property: all;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-colors {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\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 .hover\\:bg-gray-300 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-gray-300);\n }\n }\n }\n .hover\\:bg-white\\/5 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 5%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 5%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/10 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/90 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 90%, transparent);\n }\n }\n }\n }\n .hover\\:text-gray-200 {\n &:hover {\n @media (hover: hover) {\n color: var(--color-gray-200);\n }\n }\n }\n .focus\\:ring-2 {\n &:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\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 }\n .focus\\:ring-gray-400 {\n &:focus {\n --tw-ring-color: var(--color-gray-400);\n }\n }\n .focus\\:ring-offset-1 {\n &:focus {\n --tw-ring-offset-width: 1px;\n --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n }\n }\n .focus\\:outline-none {\n &:focus {\n --tw-outline-style: none;\n outline-style: none;\n }\n }\n .disabled\\:cursor-not-allowed {\n &:disabled {\n cursor: not-allowed;\n }\n }\n .disabled\\:bg-white\\/50 {\n &:disabled {\n background-color: color-mix(in srgb, #fff 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 50%, transparent);\n }\n }\n }\n .disabled\\:opacity-50 {\n &:disabled {\n opacity: 50%;\n }\n }\n .lg\\:h-32 {\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 32);\n }\n }\n .lg\\:w-32 {\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 32);\n }\n }\n .lg\\:pb-12 {\n @media (width >= 64rem) {\n padding-bottom: calc(var(--spacing) * 12);\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-3xl {\n @media (width >= 64rem) {\n font-size: var(--text-3xl);\n line-height: var(--tw-leading, var(--text-3xl--line-height));\n }\n }\n .lg\\:text-base {\n @media (width >= 64rem) {\n font-size: var(--text-base);\n line-height: var(--tw-leading, var(--text-base--line-height));\n }\n }\n}\n.noCursor {\n cursor: none !important;\n}\n.icon-class {\n height: calc(var(--spacing) * 14);\n width: calc(var(--spacing) * 14);\n cursor: pointer;\n color: var(--color-gray-400);\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n --tw-duration: 200ms;\n transition-duration: 200ms;\n &:hover {\n @media (hover: hover) {\n color: var(--color-gray-200);\n }\n }\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 18);\n }\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 18);\n }\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}\n@property --tw-rotate-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-z {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-space-y-reverse {\n syntax: \"*\";\n inherits: false;\n initial-value: 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-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\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-inset-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\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-backdrop-blur {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-brightness {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-contrast {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-grayscale {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-hue-rotate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-invert {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-opacity {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-saturate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-sepia {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-duration {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ease {\n syntax: \"*\";\n inherits: false;\n}\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-rotate-x: initial;\n --tw-rotate-y: initial;\n --tw-rotate-z: initial;\n --tw-skew-x: initial;\n --tw-skew-y: initial;\n --tw-space-y-reverse: 0;\n --tw-border-style: solid;\n --tw-gradient-position: initial;\n --tw-gradient-from: #0000;\n --tw-gradient-via: #0000;\n --tw-gradient-to: #0000;\n --tw-gradient-stops: initial;\n --tw-gradient-via-stops: initial;\n --tw-gradient-from-position: 0%;\n --tw-gradient-via-position: 50%;\n --tw-gradient-to-position: 100%;\n --tw-font-weight: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-backdrop-blur: initial;\n --tw-backdrop-brightness: initial;\n --tw-backdrop-contrast: initial;\n --tw-backdrop-grayscale: initial;\n --tw-backdrop-hue-rotate: initial;\n --tw-backdrop-invert: initial;\n --tw-backdrop-opacity: initial;\n --tw-backdrop-saturate: initial;\n --tw-backdrop-sepia: initial;\n --tw-duration: initial;\n --tw-ease: initial;\n }\n }\n}\n";
38
- styleInject(css_248z$3,{"insertAt":"top"});
39
+ var css_248z$4 = "/*! tailwindcss v4.1.17 | MIT License | https://tailwindcss.com */\n@layer properties;\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-yellow-500: oklch(79.5% 0.184 86.047);\n --color-green-500: oklch(72.3% 0.219 149.579);\n --color-sky-300: oklch(82.8% 0.111 230.318);\n --color-blue-500: oklch(62.3% 0.214 259.815);\n --color-purple-500: oklch(62.7% 0.265 303.9);\n --color-gray-200: oklch(92.8% 0.006 264.531);\n --color-gray-300: oklch(87.2% 0.01 258.338);\n --color-gray-400: oklch(70.7% 0.022 261.325);\n --color-gray-500: oklch(55.1% 0.027 264.364);\n --color-gray-600: oklch(44.6% 0.03 256.802);\n --color-gray-700: oklch(37.3% 0.034 259.733);\n --color-gray-900: oklch(21% 0.034 264.665);\n --color-black: #000;\n --color-white: #fff;\n --spacing: 0.25rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-base: 1rem;\n --text-base--line-height: calc(1.5 / 1);\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 --text-3xl: 1.875rem;\n --text-3xl--line-height: calc(2.25 / 1.875);\n --font-weight-normal: 400;\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --tracking-wide: 0.025em;\n --radius-md: 0.375rem;\n --radius-lg: 0.5rem;\n --ease-out: cubic-bezier(0, 0, 0.2, 1);\n --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);\n --animate-spin: spin 1s linear infinite;\n --blur-sm: 8px;\n --blur-md: 12px;\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-mono-font-family: var(--font-mono);\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 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 }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\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 ::-webkit-calendar-picker-indicator {\n line-height: 1;\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 .pointer-events-none {\n pointer-events: none;\n }\n .absolute {\n position: absolute;\n }\n .relative {\n position: relative;\n }\n .static {\n position: static;\n }\n .inset-0 {\n inset: calc(var(--spacing) * 0);\n }\n .-top-2 {\n top: calc(var(--spacing) * -2);\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-0 {\n right: calc(var(--spacing) * 0);\n }\n .right-32 {\n right: calc(var(--spacing) * 32);\n }\n .right-full {\n right: 100%;\n }\n .bottom-36 {\n bottom: calc(var(--spacing) * 36);\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-32 {\n left: calc(var(--spacing) * 32);\n }\n .left-full {\n left: 100%;\n }\n .z-40 {\n z-index: 40;\n }\n .z-50 {\n z-index: 50;\n }\n .z-\\[-1\\] {\n z-index: -1;\n }\n .mx-2 {\n margin-inline: calc(var(--spacing) * 2);\n }\n .mx-auto {\n margin-inline: auto;\n }\n .mt-1 {\n margin-top: calc(var(--spacing) * 1);\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-1 {\n margin-bottom: calc(var(--spacing) * 1);\n }\n .mb-2 {\n margin-bottom: calc(var(--spacing) * 2);\n }\n .mb-4 {\n margin-bottom: calc(var(--spacing) * 4);\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 .hidden {\n display: none;\n }\n .inline {\n display: inline;\n }\n .inline-block {\n display: inline-block;\n }\n .inline-flex {\n display: inline-flex;\n }\n .h-1 {\n height: calc(var(--spacing) * 1);\n }\n .h-3 {\n height: calc(var(--spacing) * 3);\n }\n .h-4 {\n height: calc(var(--spacing) * 4);\n }\n .h-5 {\n height: calc(var(--spacing) * 5);\n }\n .h-6 {\n height: calc(var(--spacing) * 6);\n }\n .h-10 {\n height: calc(var(--spacing) * 10);\n }\n .h-24 {\n height: calc(var(--spacing) * 24);\n }\n .h-full {\n height: 100%;\n }\n .max-h-80 {\n max-height: calc(var(--spacing) * 80);\n }\n .w-3 {\n width: calc(var(--spacing) * 3);\n }\n .w-4 {\n width: calc(var(--spacing) * 4);\n }\n .w-5 {\n width: calc(var(--spacing) * 5);\n }\n .w-6 {\n width: calc(var(--spacing) * 6);\n }\n .w-24 {\n width: calc(var(--spacing) * 24);\n }\n .w-80 {\n width: calc(var(--spacing) * 80);\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 .flex-1 {\n flex: 1;\n }\n .shrink-0 {\n flex-shrink: 0;\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 .rotate-45 {\n rotate: 45deg;\n }\n .rotate-180 {\n rotate: 180deg;\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 .animate-spin {\n animation: var(--animate-spin);\n }\n .cursor-not-allowed {\n cursor: not-allowed;\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 .items-start {\n align-items: flex-start;\n }\n .justify-between {\n justify-content: space-between;\n }\n .justify-center {\n justify-content: center;\n }\n .justify-end {\n justify-content: flex-end;\n }\n .gap-2 {\n gap: calc(var(--spacing) * 2);\n }\n .gap-3 {\n gap: calc(var(--spacing) * 3);\n }\n .gap-4 {\n gap: calc(var(--spacing) * 4);\n }\n .gap-7 {\n gap: calc(var(--spacing) * 7);\n }\n .space-y-0 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-3 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .overflow-hidden {\n overflow: hidden;\n }\n .overflow-y-auto {\n overflow-y: auto;\n }\n .rounded {\n border-radius: 0.25rem;\n }\n .rounded-\\[5px\\] {\n border-radius: 5px;\n }\n .rounded-\\[7px\\] {\n border-radius: 7px;\n }\n .rounded-full {\n border-radius: calc(infinity * 1px);\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-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n }\n .border-b {\n border-bottom-style: var(--tw-border-style);\n border-bottom-width: 1px;\n }\n .border-l {\n border-left-style: var(--tw-border-style);\n border-left-width: 1px;\n }\n .border-gray-600 {\n border-color: var(--color-gray-600);\n }\n .border-gray-700\\/60 {\n border-color: color-mix(in srgb, oklch(37.3% 0.034 259.733) 60%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-gray-700) 60%, transparent);\n }\n }\n .border-white\\/10 {\n border-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n .border-white\\/30 {\n border-color: color-mix(in srgb, #fff 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 30%, transparent);\n }\n }\n .border-white\\/40 {\n border-color: color-mix(in srgb, #fff 40%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 40%, transparent);\n }\n }\n .bg-\\[\\#2D2F31\\] {\n background-color: #2D2F31;\n }\n .bg-\\[\\#3a4049\\] {\n background-color: #3a4049;\n }\n .bg-\\[\\#454545\\] {\n background-color: #454545;\n }\n .bg-\\[rgba\\(0\\,0\\,0\\,0\\.5\\)\\] {\n background-color: rgba(0,0,0,0.5);\n }\n .bg-black {\n background-color: var(--color-black);\n }\n .bg-black\\/60 {\n background-color: color-mix(in srgb, #000 60%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-black) 60%, transparent);\n }\n }\n .bg-blue-500 {\n background-color: var(--color-blue-500);\n }\n .bg-gray-500 {\n background-color: var(--color-gray-500);\n }\n .bg-gray-900 {\n background-color: var(--color-gray-900);\n }\n .bg-green-500 {\n background-color: var(--color-green-500);\n }\n .bg-purple-500 {\n background-color: var(--color-purple-500);\n }\n .bg-white {\n background-color: var(--color-white);\n }\n .bg-white\\/5 {\n background-color: color-mix(in srgb, #fff 5%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 5%, transparent);\n }\n }\n .bg-white\\/10 {\n background-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n .bg-white\\/20 {\n background-color: color-mix(in srgb, #fff 20%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 20%, transparent);\n }\n }\n .bg-white\\/80 {\n background-color: color-mix(in srgb, #fff 80%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 80%, transparent);\n }\n }\n .bg-linear-to-b {\n --tw-gradient-position: to bottom;\n @supports (background-image: linear-gradient(in lab, red, red)) {\n --tw-gradient-position: to bottom in oklab;\n }\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 .from-black\\/80 {\n --tw-gradient-from: color-mix(in srgb, #000 80%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n --tw-gradient-from: color-mix(in oklab, var(--color-black) 80%, transparent);\n }\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 .via-transparent {\n --tw-gradient-via: transparent;\n --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);\n --tw-gradient-stops: var(--tw-gradient-via-stops);\n }\n .to-black\\/90 {\n --tw-gradient-to: color-mix(in srgb, #000 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n --tw-gradient-to: color-mix(in oklab, var(--color-black) 90%, transparent);\n }\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 .bg-cover {\n background-size: cover;\n }\n .bg-center {\n background-position: center;\n }\n .object-contain {\n object-fit: contain;\n }\n .p-0 {\n padding: calc(var(--spacing) * 0);\n }\n .p-1 {\n padding: calc(var(--spacing) * 1);\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-4 {\n padding-inline: calc(var(--spacing) * 4);\n }\n .px-5 {\n padding-inline: calc(var(--spacing) * 5);\n }\n .px-6 {\n padding-inline: calc(var(--spacing) * 6);\n }\n .px-10 {\n padding-inline: calc(var(--spacing) * 10);\n }\n .px-20 {\n padding-inline: calc(var(--spacing) * 20);\n }\n .py-1 {\n padding-block: calc(var(--spacing) * 1);\n }\n .py-2 {\n padding-block: calc(var(--spacing) * 2);\n }\n .py-3 {\n padding-block: calc(var(--spacing) * 3);\n }\n .py-4 {\n padding-block: calc(var(--spacing) * 4);\n }\n .pt-6 {\n padding-top: calc(var(--spacing) * 6);\n }\n .pb-3 {\n padding-bottom: calc(var(--spacing) * 3);\n }\n .pb-4 {\n padding-bottom: calc(var(--spacing) * 4);\n }\n .pb-6 {\n padding-bottom: 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-left {\n text-align: left;\n }\n .text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n }\n .text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n }\n .text-xl {\n font-size: var(--text-xl);\n line-height: var(--tw-leading, var(--text-xl--line-height));\n }\n .text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n }\n .font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n }\n .font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n }\n .font-normal {\n --tw-font-weight: var(--font-weight-normal);\n font-weight: var(--font-weight-normal);\n }\n .font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n }\n .tracking-wide {\n --tw-tracking: var(--tracking-wide);\n letter-spacing: var(--tracking-wide);\n }\n .whitespace-nowrap {\n white-space: nowrap;\n }\n .text-gray-200 {\n color: var(--color-gray-200);\n }\n .text-gray-300 {\n color: var(--color-gray-300);\n }\n .text-gray-400 {\n color: var(--color-gray-400);\n }\n .text-gray-500 {\n color: var(--color-gray-500);\n }\n .text-gray-900 {\n color: var(--color-gray-900);\n }\n .text-sky-300 {\n color: var(--color-sky-300);\n }\n .text-white {\n color: var(--color-white);\n }\n .text-yellow-500 {\n color: var(--color-yellow-500);\n }\n .uppercase {\n text-transform: uppercase;\n }\n .opacity-0 {\n opacity: 0%;\n }\n .opacity-50 {\n opacity: 50%;\n }\n .opacity-100 {\n opacity: 100%;\n }\n .shadow-2xl {\n --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\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-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 .backdrop-blur-md {\n --tw-backdrop-blur: blur(var(--blur-md));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .backdrop-blur-sm {\n --tw-backdrop-blur: blur(var(--blur-sm));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .transition {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-all {\n transition-property: all;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-colors {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\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 .duration-300 {\n --tw-duration: 300ms;\n transition-duration: 300ms;\n }\n .ease-in-out {\n --tw-ease: var(--ease-in-out);\n transition-timing-function: var(--ease-in-out);\n }\n .ease-out {\n --tw-ease: var(--ease-out);\n transition-timing-function: var(--ease-out);\n }\n .select-none {\n -webkit-user-select: none;\n user-select: none;\n }\n .hover\\:scale-105 {\n &:hover {\n @media (hover: hover) {\n --tw-scale-x: 105%;\n --tw-scale-y: 105%;\n --tw-scale-z: 105%;\n scale: var(--tw-scale-x) var(--tw-scale-y);\n }\n }\n }\n .hover\\:border-white\\/50 {\n &:hover {\n @media (hover: hover) {\n border-color: color-mix(in srgb, #fff 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 50%, transparent);\n }\n }\n }\n }\n .hover\\:bg-gray-300 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-gray-300);\n }\n }\n }\n .hover\\:bg-white\\/5 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 5%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 5%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/10 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/30 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 30%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/90 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 90%, transparent);\n }\n }\n }\n }\n .hover\\:text-gray-200 {\n &:hover {\n @media (hover: hover) {\n color: var(--color-gray-200);\n }\n }\n }\n .hover\\:text-white {\n &:hover {\n @media (hover: hover) {\n color: var(--color-white);\n }\n }\n }\n .hover\\:shadow-lg {\n &:hover {\n @media (hover: hover) {\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 }\n }\n .focus\\:ring-2 {\n &:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\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 }\n .focus\\:ring-gray-400 {\n &:focus {\n --tw-ring-color: var(--color-gray-400);\n }\n }\n .focus\\:ring-offset-1 {\n &:focus {\n --tw-ring-offset-width: 1px;\n --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n }\n }\n .focus\\:outline-none {\n &:focus {\n --tw-outline-style: none;\n outline-style: none;\n }\n }\n .active\\:scale-95 {\n &:active {\n --tw-scale-x: 95%;\n --tw-scale-y: 95%;\n --tw-scale-z: 95%;\n scale: var(--tw-scale-x) var(--tw-scale-y);\n }\n }\n .disabled\\:cursor-not-allowed {\n &:disabled {\n cursor: not-allowed;\n }\n }\n .disabled\\:bg-white\\/50 {\n &:disabled {\n background-color: color-mix(in srgb, #fff 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 50%, transparent);\n }\n }\n }\n .disabled\\:opacity-50 {\n &:disabled {\n opacity: 50%;\n }\n }\n .lg\\:h-32 {\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 32);\n }\n }\n .lg\\:w-32 {\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 32);\n }\n }\n .lg\\:pb-12 {\n @media (width >= 64rem) {\n padding-bottom: calc(var(--spacing) * 12);\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-3xl {\n @media (width >= 64rem) {\n font-size: var(--text-3xl);\n line-height: var(--tw-leading, var(--text-3xl--line-height));\n }\n }\n .lg\\:text-base {\n @media (width >= 64rem) {\n font-size: var(--text-base);\n line-height: var(--tw-leading, var(--text-base--line-height));\n }\n }\n}\n.noCursor {\n cursor: none !important;\n}\n.icon-class {\n height: calc(var(--spacing) * 14);\n width: calc(var(--spacing) * 14);\n cursor: pointer;\n color: var(--color-gray-400);\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n --tw-duration: 200ms;\n transition-duration: 200ms;\n &:hover {\n @media (hover: hover) {\n color: var(--color-gray-200);\n }\n }\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 18);\n }\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 18);\n }\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}\n@property --tw-rotate-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-z {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-space-y-reverse {\n syntax: \"*\";\n inherits: false;\n initial-value: 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-tracking {\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-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\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-inset-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\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-backdrop-blur {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-brightness {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-contrast {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-grayscale {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-hue-rotate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-invert {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-opacity {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-saturate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-sepia {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-duration {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ease {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-scale-x {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@property --tw-scale-y {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@property --tw-scale-z {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-rotate-x: initial;\n --tw-rotate-y: initial;\n --tw-rotate-z: initial;\n --tw-skew-x: initial;\n --tw-skew-y: initial;\n --tw-space-y-reverse: 0;\n --tw-border-style: solid;\n --tw-gradient-position: initial;\n --tw-gradient-from: #0000;\n --tw-gradient-via: #0000;\n --tw-gradient-to: #0000;\n --tw-gradient-stops: initial;\n --tw-gradient-via-stops: initial;\n --tw-gradient-from-position: 0%;\n --tw-gradient-via-position: 50%;\n --tw-gradient-to-position: 100%;\n --tw-font-weight: initial;\n --tw-tracking: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-backdrop-blur: initial;\n --tw-backdrop-brightness: initial;\n --tw-backdrop-contrast: initial;\n --tw-backdrop-grayscale: initial;\n --tw-backdrop-hue-rotate: initial;\n --tw-backdrop-invert: initial;\n --tw-backdrop-opacity: initial;\n --tw-backdrop-saturate: initial;\n --tw-backdrop-sepia: initial;\n --tw-duration: initial;\n --tw-ease: initial;\n --tw-scale-x: 1;\n --tw-scale-y: 1;\n --tw-scale-z: 1;\n }\n }\n}\n";
40
+ styleInject(css_248z$4,{"insertAt":"top"});
39
41
 
40
42
  const createVideoRefsSlice = (set) => ({
41
43
  videoRef: null,
@@ -74,12 +76,18 @@ const createVideoControlsSlice = (set) => ({
74
76
  });
75
77
 
76
78
  const createVideoQualitySlice = (set) => ({
77
- hlsInstance: undefined,
79
+ hlsInstance: null,
78
80
  setHlsInstance: (hlsInstance) => set({ hlsInstance }),
79
- qualityLevels: undefined,
81
+ dashInstance: null,
82
+ setDashInstance: (dashInstance) => set({ dashInstance }),
83
+ qualityLevels: [],
80
84
  setQualityLevels: (qualityLevels) => set({ qualityLevels }),
81
85
  activeQuality: "auto",
82
86
  setActiveQuality: (activeQuality) => set({ activeQuality }),
87
+ currentQuality: "auto",
88
+ setCurrentQuality: (currentQuality) => set({ currentQuality }),
89
+ streamType: "mp4",
90
+ setStreamType: (streamType) => set({ streamType }),
83
91
  });
84
92
 
85
93
  const createSubtitlesSlice = (set) => ({
@@ -107,8 +115,54 @@ const createIntroSlice = (set) => ({
107
115
  setShowIntroSkip: (show) => set({ showIntroSkip: show }),
108
116
  });
109
117
 
118
+ const createAdsSlice = (set, get) => ({
119
+ isAdPlaying: false,
120
+ setIsAdPlaying: (isAdPlaying) => set({ isAdPlaying }),
121
+ currentAd: null,
122
+ setCurrentAd: (currentAd) => set({ currentAd }),
123
+ adType: null,
124
+ setAdType: (adType) => set({ adType }),
125
+ adCurrentTime: 0,
126
+ setAdCurrentTime: (adCurrentTime) => set({ adCurrentTime }),
127
+ canSkipAd: false,
128
+ setCanSkipAd: (canSkipAd) => set({ canSkipAd }),
129
+ skipCountdown: 0,
130
+ setSkipCountdown: (skipCountdown) => set({ skipCountdown }),
131
+ playedAdBreaks: [],
132
+ addPlayedAdBreak: (id) => set((state) => ({
133
+ playedAdBreaks: [...state.playedAdBreaks, id],
134
+ })),
135
+ midRollQueue: [],
136
+ setMidRollQueue: (midRollQueue) => set({ midRollQueue }),
137
+ adVideoRef: null,
138
+ setAdVideoRef: (adVideoRef) => set({ adVideoRef }),
139
+ });
140
+
110
141
  const createResetSlice = (set, get) => ({
111
142
  resetStore: () => {
143
+ const safeStopMediaElement = (media) => {
144
+ if (!media)
145
+ return;
146
+ try {
147
+ media.pause();
148
+ }
149
+ catch (_error) { }
150
+ try {
151
+ media.currentTime = 0;
152
+ }
153
+ catch (_error) { }
154
+ media.removeAttribute("src");
155
+ media.load();
156
+ };
157
+ const { videoRef, adVideoRef, hlsInstance, dashInstance } = get();
158
+ safeStopMediaElement(videoRef);
159
+ safeStopMediaElement(adVideoRef);
160
+ if (hlsInstance && typeof hlsInstance.destroy === "function") {
161
+ hlsInstance.destroy();
162
+ }
163
+ if (dashInstance && typeof dashInstance.reset === "function") {
164
+ dashInstance.reset();
165
+ }
112
166
  set({
113
167
  videoRef: null,
114
168
  videoWrapperRef: null,
@@ -123,6 +177,7 @@ const createResetSlice = (set, get) => ({
123
177
  controls: false,
124
178
  isFullscreen: false,
125
179
  hlsInstance: undefined,
180
+ dashInstance: undefined,
126
181
  qualityLevels: undefined,
127
182
  activeQuality: "auto",
128
183
  activeSubtitle: null,
@@ -133,6 +188,15 @@ const createResetSlice = (set, get) => ({
133
188
  countdownTime: 10,
134
189
  autoPlayNext: false,
135
190
  showIntroSkip: false,
191
+ isAdPlaying: false,
192
+ currentAd: null,
193
+ adType: null,
194
+ adCurrentTime: 0,
195
+ canSkipAd: false,
196
+ skipCountdown: 0,
197
+ playedAdBreaks: [],
198
+ midRollQueue: [],
199
+ adVideoRef: null,
136
200
  });
137
201
  },
138
202
  });
@@ -146,9 +210,145 @@ const useVideoStore = create()((set, get, store) => ({
146
210
  ...createSubtitlesSlice(set),
147
211
  ...createEpisodesSlice(set),
148
212
  ...createIntroSlice(set),
149
- ...createResetSlice(set),
213
+ ...createAdsSlice(set),
214
+ ...createResetSlice(set, get),
150
215
  }));
151
216
 
217
+ class QualityManager {
218
+ /**
219
+ * Set video quality for HLS streams with OTT-grade smoothness
220
+ *
221
+ * Best practices implemented:
222
+ * 1. Use currentLevel for immediate quality change
223
+ * 2. Use autoLevelCapping to prevent ABR from switching back
224
+ * 3. Use nextLevel to ensure next segment uses selected quality
225
+ * 4. Handle edge cases and errors gracefully
226
+ *
227
+ * @param hlsInstance HLS.js instance (null for native HLS, undefined when not available)
228
+ * @param levelIndex Quality level index (-1 for auto)
229
+ */
230
+ static setHlsQuality(hlsInstance, levelIndex) {
231
+ if (!hlsInstance)
232
+ return;
233
+ const levels = hlsInstance?.levels ?? [];
234
+ const levelExists = levelIndex >= 0 && levels[levelIndex];
235
+ if (levelIndex < -1 || (levelIndex >= 0 && !levelExists)) {
236
+ return;
237
+ }
238
+ if (levelIndex === -1) {
239
+ hlsInstance.currentLevel = -1;
240
+ hlsInstance.nextLevel = -1;
241
+ hlsInstance.loadLevel = -1;
242
+ return;
243
+ }
244
+ hlsInstance.loadLevel = levelIndex;
245
+ hlsInstance.nextLevel = levelIndex;
246
+ hlsInstance.currentLevel = levelIndex;
247
+ }
248
+ /**
249
+
250
+ * @param dashInstance DASH.js instance
251
+ * @param qualityId Quality level ID (undefined/null for auto)
252
+ */
253
+ static setDashQuality(dashInstance, qualityIndex) {
254
+ if (!dashInstance)
255
+ return;
256
+ const player = dashInstance;
257
+ if (qualityIndex === null ||
258
+ qualityIndex === undefined ||
259
+ qualityIndex < 0) {
260
+ player.setAutoSwitchQualityFor("video", true);
261
+ return;
262
+ }
263
+ player.setAutoSwitchQualityFor("video", false);
264
+ if (player.getQualityFor("video") !== qualityIndex) {
265
+ player.setQualityFor("video", qualityIndex);
266
+ }
267
+ }
268
+ /**
269
+ * @param hlsInstance HLS.js instance
270
+ * @returns Array of quality level objects
271
+ */
272
+ static getHlsQualityLevels(hlsInstance) {
273
+ if (!hlsInstance || !hlsInstance.levels) {
274
+ return [];
275
+ }
276
+ try {
277
+ return hlsInstance.levels.map((level, index) => ({
278
+ height: level.height || 0,
279
+ bitrate: level.bitrate || 0,
280
+ originalIndex: index,
281
+ }));
282
+ }
283
+ catch (_error) {
284
+ return [];
285
+ }
286
+ }
287
+ /**
288
+ * @param dashInstance DASH.js instance
289
+ * @returns Array of quality level objects
290
+ */
291
+ static getDashQualityLevels(dashInstance) {
292
+ if (!dashInstance) {
293
+ return [];
294
+ }
295
+ try {
296
+ const representations = dashInstance.getRepresentationsByType("video");
297
+ if (!representations || !representations.length) {
298
+ return [];
299
+ }
300
+ return representations.map((rep) => ({
301
+ height: rep.height || Math.round(rep.bandwidth / 1000) || 0,
302
+ bitrate: rep.bandwidth,
303
+ id: rep.id,
304
+ }));
305
+ }
306
+ catch (_error) {
307
+ return [];
308
+ }
309
+ }
310
+ /**
311
+ * @param streamType Type of stream (hls, dash, etc.)
312
+ * @param qualityIdentifier Quality level identifier (index for HLS, ID for DASH)
313
+ */
314
+ static setQuality(streamType, qualityIdentifier) {
315
+ const { hlsInstance, dashInstance, setActiveQuality, setCurrentQuality } = useVideoStore.getState();
316
+ if (qualityIdentifier === "auto") {
317
+ if (streamType === "hls")
318
+ this.setHlsQuality(hlsInstance, -1);
319
+ if (streamType === "dash")
320
+ this.setDashQuality(dashInstance ?? null, null);
321
+ setActiveQuality("auto");
322
+ setCurrentQuality("auto");
323
+ return;
324
+ }
325
+ const parseIndex = (prefix) => parseInt(qualityIdentifier.replace(prefix, ""), 10);
326
+ if (streamType === "hls" && qualityIdentifier.startsWith("hls-")) {
327
+ const levelIndex = parseIndex("hls-");
328
+ if (!Number.isNaN(levelIndex)) {
329
+ this.setHlsQuality(hlsInstance, levelIndex);
330
+ setActiveQuality(qualityIdentifier);
331
+ setCurrentQuality(qualityIdentifier);
332
+ }
333
+ return;
334
+ }
335
+ if (streamType === "dash" && qualityIdentifier.startsWith("dash-")) {
336
+ const levelIndex = parseIndex("dash-");
337
+ if (!Number.isNaN(levelIndex)) {
338
+ this.setDashQuality(dashInstance ?? null, levelIndex);
339
+ setActiveQuality(qualityIdentifier);
340
+ setCurrentQuality(qualityIdentifier);
341
+ }
342
+ }
343
+ }
344
+ /**
345
+ * @param streamType Type of stream (hls, dash, etc.)
346
+ */
347
+ static setAutoQuality(streamType) {
348
+ this.setQuality(streamType, "auto");
349
+ }
350
+ }
351
+
152
352
  /**
153
353
  * @description
154
354
  * @param seconds
@@ -181,15 +381,25 @@ const secondsToMilliseconds = (seconds) => {
181
381
  * @returns
182
382
  */
183
383
  const getExtensionFromUrl = (url) => {
184
- const extension = url?.split(".")?.pop();
384
+ if (!url) {
385
+ return undefined;
386
+ }
387
+ const sanitized = url.split("#")[0]?.split("?")[0] ?? url;
388
+ const extension = sanitized?.split(".")?.pop()?.toLowerCase();
185
389
  if (extension === "m3u8") {
186
390
  return "hls";
187
391
  }
392
+ if (extension === "mpd") {
393
+ return "dash";
394
+ }
395
+ if (extension === "mp4") {
396
+ return "mp4";
397
+ }
188
398
  return extension;
189
399
  };
190
400
 
191
401
  function getPositionPercent(max, current) {
192
- const divider = max || -1; // prevent division by zero
402
+ const divider = max || -1;
193
403
  return (current * 100) / divider;
194
404
  }
195
405
 
@@ -237,7 +447,7 @@ const TimeCodes = ({ max = 1000, currentTime = 0, bufferTime = 0, seekHoverPosit
237
447
  if (label !== currentLabel) {
238
448
  setLabel(currentLabel);
239
449
  }
240
- }, [label]);
450
+ }, [label, setLabel]);
241
451
  useEffect(() => {
242
452
  if (!mobileSeeking) {
243
453
  return;
@@ -249,7 +459,7 @@ const TimeCodes = ({ max = 1000, currentTime = 0, bufferTime = 0, seekHoverPosit
249
459
  if (currentCode?.description !== label) {
250
460
  setLabel(currentCode?.description || "");
251
461
  }
252
- }, [currentTime, label, max, timeCodes]);
462
+ }, [currentTime, label, max, timeCodes, mobileSeeking, setLabel]);
253
463
  return (React__default.createElement(React__default.Fragment, null, timeCodes?.map(({ fromMs, description }, index) => {
254
464
  const endTime = getEndTimeByIndex(timeCodes, index, max);
255
465
  const isTimePassed = endTime <= currentTime;
@@ -326,12 +536,15 @@ const Thumb = ({ max, currentTime, isThumbActive, trackColor, }) => {
326
536
  left: `calc(${leftPosition}% + ${thumbConstantOffset}px)`,
327
537
  };
328
538
  };
329
- return (React__default.createElement("div", { className: isThumbActive ? "thumb active" : "thumb active", "data-testid": "testThumb", style: getThumbHandlerPosition() },
539
+ return (React__default.createElement("div", { className: "thumb active", "data-testid": "testThumb", style: getThumbHandlerPosition() },
330
540
  React__default.createElement("div", { className: "handler", style: {
331
541
  backgroundColor: trackColor || "#ff0000",
332
542
  } })));
333
543
  };
334
544
 
545
+ var css_248z$3 = ".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, transform 0.2s ease-out;\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 transition: transform 0.2s ease-out;\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,\n.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}\n\n";
546
+ styleInject(css_248z$3,{"insertAt":"top"});
547
+
335
548
  const VideoSeekSlider = ({ max = 1000, currentTime = 0, bufferTime = 0, hideThumbTooltip = false, offset = 0, secondsPrefix = "", minutesPrefix = "", limitTimeTooltipBySides = true, timeCodes, onChange = () => undefined, getPreviewScreenUrl, trackColor, }) => {
336
549
  const [seekHoverPosition, setSeekHoverPosition] = useState(0);
337
550
  const [label, setLabel] = useState("");
@@ -422,39 +635,44 @@ const VideoSeekSlider = ({ max = 1000, currentTime = 0, bufferTime = 0, hideThum
422
635
  React__default.createElement(Thumb, { max: max, currentTime: currentTime, isThumbActive: isThumbActive, trackColor: trackColor })));
423
636
  };
424
637
 
425
- var css_248z$2 = ".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, transform 0.2s ease-out;\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 transition: transform 0.2s ease-out;\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,\n.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}\n";
426
- styleInject(css_248z$2,{"insertAt":"top"});
427
-
428
638
  const BottomControls = ({ config }) => {
429
- const { videoRef, currentTime, isFullscreen, bufferedProgress } = useVideoStore();
430
- const duration = videoRef?.duration;
639
+ const { videoRef, currentTime, isFullscreen, bufferedProgress, isAdPlaying } = useVideoStore(useShallow((state) => ({
640
+ videoRef: state.videoRef,
641
+ currentTime: state.currentTime,
642
+ isFullscreen: state.isFullscreen,
643
+ bufferedProgress: state.bufferedProgress,
644
+ isAdPlaying: state.isAdPlaying,
645
+ })));
646
+ const duration = videoRef?.duration ?? 0;
647
+ const currentTimeValue = currentTime || 0;
648
+ const bufferedValue = bufferedProgress || 0;
649
+ const handleSeek = useCallback((currentTimeInMs) => {
650
+ if (!videoRef) {
651
+ return;
652
+ }
653
+ videoRef.currentTime = currentTimeInMs / 1000;
654
+ }, [videoRef]);
655
+ const bufferTime = useMemo(() => {
656
+ if (!duration) {
657
+ return 0;
658
+ }
659
+ return secondsToMilliseconds(duration * (bufferedValue / 100));
660
+ }, [bufferedValue, duration]);
661
+ const durationFormatted = useMemo(() => timeFormat(duration), [duration]);
662
+ const currentTimeFormatted = useMemo(() => timeFormat(currentTimeValue), [currentTimeValue]);
663
+ if (isAdPlaying) {
664
+ return null;
665
+ }
431
666
  return (React__default.createElement("div", { className: "px-10" },
432
- React__default.createElement(VideoSeekSlider, { max: secondsToMilliseconds(duration || 0), currentTime: secondsToMilliseconds(currentTime || 0), bufferTime: secondsToMilliseconds((duration || 0) * (bufferedProgress / 100)), onChange: (currentTime) => {
433
- if (videoRef) {
434
- videoRef.currentTime = currentTime / 1000;
435
- }
436
- }, secondsPrefix: "00:00:", minutesPrefix: "00:", getPreviewScreenUrl: config?.seekBarConfig?.getPreviewScreenUrl, timeCodes: config?.seekBarConfig?.timeCodes, trackColor: config?.seekBarConfig?.trackColor }),
667
+ React__default.createElement(VideoSeekSlider, { max: secondsToMilliseconds(duration), currentTime: secondsToMilliseconds(currentTimeValue), bufferTime: bufferTime, onChange: handleSeek, secondsPrefix: "00:00:", minutesPrefix: "00:", getPreviewScreenUrl: config?.seekBarConfig?.getPreviewScreenUrl, timeCodes: config?.seekBarConfig?.timeCodes, trackColor: config?.seekBarConfig?.trackColor }),
437
668
  React__default.createElement("div", { className: `pt-6 ${isFullscreen ? "pb-10" : "pb-16"} lg:pb-12 flex items-center gap-4 text-white` },
438
- React__default.createElement("span", { className: "text-lg lg:text-2xl font-semibold text-white cursor-pointer hover:text-gray-200 transition-colors duration-200" }, timeFormat(currentTime || 0)),
669
+ React__default.createElement("span", { className: "text-lg lg:text-2xl font-semibold text-white cursor-pointer hover:text-gray-200 transition-colors duration-200" }, currentTimeFormatted),
439
670
  React__default.createElement("span", { className: "text-lg lg:text-3xl font-semibold text-gray-500 cursor-pointer hover:text-gray-200 transition-colors duration-200" }, "/"),
440
- React__default.createElement("span", { className: "text-lg lg:text-2xl font-semibold text-gray-400 cursor-pointer hover:text-gray-200 transition-colors duration-200" }, timeFormat(duration || 0)))));
441
- };
442
-
443
- const Tooltip = ({ children, title, position = "top", }) => {
444
- const [visible, setVisible] = useState(false);
445
- const positionStyles = {
446
- top: "bottom-full left-1/2 transform -translate-x-1/2 mb-2",
447
- bottom: "top-full left-1/2 transform -translate-x-1/2 mt-2",
448
- left: "right-full top-1/2 transform -translate-y-1/2 mr-2",
449
- right: "left-full top-1/2 transform -translate-y-1/2 ml-2",
450
- };
451
- return (React__default.createElement("div", { className: "relative inline-block cursor-pointer", onMouseEnter: () => setVisible(true), onMouseLeave: () => setVisible(false) },
452
- children,
453
- 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))));
671
+ React__default.createElement("span", { className: "text-lg lg:text-2xl font-semibold text-gray-400 cursor-pointer hover:text-gray-200 transition-colors duration-200" }, durationFormatted))));
454
672
  };
455
673
 
456
- var css_248z$1 = ".icon-button {\n width: 20px;\n height: 20px;\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.icon-button:hover {\n color: #e5e7eb;\n}\n\n@media (min-width: 1024px) {\n .icon-button {\n width: 32px;\n height: 32px;\n }\n}\n\n/* styles/fullscreen-toggle.css */\n.fullscreen-icon {\n width: 20px;\n height: 20px;\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.fullscreen-icon:hover {\n color: #e5e7eb;\n}\n\n@media (min-width: 1024px) {\n .fullscreen-icon {\n width: 32px;\n height: 32px;\n }\n}\n\n/* styles/pi-picture-in-picture-toggle.css */\n.pip-toggle {\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.pip-toggle:hover {\n color: #e5e7eb;\n}\n\n.pip-icon {\n width: 15px;\n height: 15px;\n}\n\n@media (min-width: 1024px) {\n .pip-icon {\n width: 28px;\n height: 28px;\n }\n}\n";
457
- styleInject(css_248z$1,{"insertAt":"top"});
674
+ var css_248z$2 = ".icon-button {\n width: 20px;\n height: 20px;\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.icon-button:hover {\n color: #e5e7eb;\n}\n\n@media (min-width: 1024px) {\n .icon-button {\n width: 32px;\n height: 32px;\n }\n}\n\n.fullscreen-icon {\n width: 20px;\n height: 20px;\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.fullscreen-icon:hover {\n color: #e5e7eb;\n}\n\n@media (min-width: 1024px) {\n .fullscreen-icon {\n width: 32px;\n height: 32px;\n }\n}\n\n.pip-toggle {\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.pip-toggle:hover {\n color: #e5e7eb;\n}\n\n.pip-icon {\n width: 15px;\n height: 15px;\n}\n\n@media (min-width: 1024px) {\n .pip-icon {\n width: 28px;\n height: 28px;\n }\n}\n";
675
+ styleInject(css_248z$2,{"insertAt":"top"});
458
676
 
459
677
  const FullScreenToggle = ({ isFullScreen, onClick, className = "fullscreen-icon", }) => {
460
678
  return (React__default.createElement("div", { onClick: onClick }, isFullScreen ? (React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 25 25", className: className },
@@ -521,8 +739,21 @@ const Popover = ({ button, children, closeOnButtonClick = false, className = "",
521
739
  children))));
522
740
  };
523
741
 
742
+ const Tooltip = ({ children, title, position = "top", className, }) => {
743
+ const [visible, setVisible] = useState(false);
744
+ const positionStyles = {
745
+ top: "bottom-full left-1/2 transform -translate-x-1/2 mb-2",
746
+ bottom: "top-full left-1/2 transform -translate-x-1/2 mt-2",
747
+ left: "right-full top-1/2 transform -translate-y-1/2 mr-2",
748
+ right: "left-full top-1/2 transform -translate-y-1/2 ml-2",
749
+ };
750
+ return (React__default.createElement("div", { className: `relative inline-block cursor-pointer ${className || ""}`, onMouseEnter: () => setVisible(true), onMouseLeave: () => setVisible(false) },
751
+ children,
752
+ 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 whitespace-nowrap ${positionStyles[position]}` }, title))));
753
+ };
754
+
524
755
  const Settings = ({ iconClassName }) => {
525
- const { qualityLevels, hlsInstance, setActiveQuality, activeQuality, subtitles, activeSubtitle, setActiveSubtitle, videoRef, } = useVideoStore();
756
+ const { qualityLevels, activeQuality, currentQuality, subtitles, activeSubtitle, setActiveSubtitle, videoRef, streamType, } = useVideoStore();
526
757
  const [speed, setSpeed] = React.useState(1);
527
758
  const [activeMenu, setActiveMenu] = React.useState("main");
528
759
  const handleSpeedChange = (newSpeed) => {
@@ -531,37 +762,57 @@ const Settings = ({ iconClassName }) => {
531
762
  videoRef.playbackRate = newSpeed;
532
763
  }
533
764
  };
534
- const uniqueQualityLevels = React.useMemo(() => {
535
- if (!qualityLevels)
765
+ const isAdaptiveStream = streamType === "hls" || streamType === "dash";
766
+ const qualityOptions = React.useMemo(() => {
767
+ if (!qualityLevels || !isAdaptiveStream) {
536
768
  return [];
537
- const seenHeights = new Set();
538
- const unique = [];
539
- qualityLevels.forEach((level, originalIndex) => {
540
- if (seenHeights.has(level.height))
541
- return;
542
- seenHeights.add(level.height);
543
- unique.push({
544
- height: level.height,
545
- bitrate: level.bitrate,
546
- originalIndex,
547
- });
769
+ }
770
+ const prefix = streamType === "dash" ? "dash" : "hls";
771
+ return [...qualityLevels]
772
+ .map((level) => ({
773
+ value: `${prefix}-${level.originalIndex}`,
774
+ height: level.height,
775
+ bitrate: level.bitrate,
776
+ originalIndex: level.originalIndex,
777
+ }))
778
+ .sort((a, b) => {
779
+ const heightDiff = (b.height || 0) - (a.height || 0);
780
+ if (heightDiff !== 0)
781
+ return heightDiff;
782
+ const bitrateDiff = (b.bitrate || 0) - (a.bitrate || 0);
783
+ if (bitrateDiff !== 0)
784
+ return bitrateDiff;
785
+ return b.originalIndex - a.originalIndex;
548
786
  });
549
- return unique;
550
- }, [qualityLevels]);
787
+ }, [qualityLevels, isAdaptiveStream, streamType]);
551
788
  const speedOptions = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
552
789
  const handleBack = () => setActiveMenu("main");
790
+ const formatBitrate = (bitrate) => {
791
+ if (!bitrate || bitrate <= 0)
792
+ return "";
793
+ if (bitrate >= 1000000) {
794
+ return `${(bitrate / 1000000).toFixed(1)} Mbps`;
795
+ }
796
+ return `${Math.round(bitrate / 1000)} Kbps`;
797
+ };
798
+ // Get quality label: show explicit resolution to avoid duplicates
799
+ const getQualityName = (height, bitrate) => {
800
+ if (height && height > 0)
801
+ return `${height}p`;
802
+ const bitrateLabel = formatBitrate(bitrate);
803
+ return bitrateLabel || "Quality";
804
+ };
553
805
  // Get quality label for display
554
806
  const getQualityLabel = () => {
555
- if (activeQuality === "auto")
807
+ if (!isAdaptiveStream)
556
808
  return "Auto";
557
- const level = uniqueQualityLevels.find((l) => String(l.height) === activeQuality);
558
- return level ? `${level.height}p` : "Auto";
559
- };
560
- // Get quality label: show explicit resolution to avoid duplicates
561
- const getQualityName = (height) => {
562
- if (!height || height <= 0)
809
+ if (currentQuality === "auto")
563
810
  return "Auto";
564
- return `${height}p`;
811
+ const option = qualityOptions.find((q) => q.value === currentQuality);
812
+ if (!option)
813
+ return "Auto";
814
+ const label = getQualityName(option.height, option.bitrate);
815
+ return label === "Quality" ? "Custom" : label;
565
816
  };
566
817
  // Get estimated data usage using bitrate when available
567
818
  const getDataUsage = (height, bitrate) => {
@@ -629,33 +880,28 @@ const Settings = ({ iconClassName }) => {
629
880
  React.createElement("h3", { className: "text-white font-bold text-xl" }, "Video Quality")),
630
881
  React.createElement("div", { className: "space-y-3" },
631
882
  React.createElement("button", { onClick: () => {
632
- if (hlsInstance) {
633
- hlsInstance.currentLevel = -1;
634
- setActiveQuality("auto");
883
+ if (isAdaptiveStream) {
884
+ QualityManager.setQuality(streamType, "auto");
635
885
  }
636
- }, className: `w-full text-left px-4 py-3 rounded-md transition-all ${activeQuality === "auto"
886
+ }, disabled: !isAdaptiveStream, className: `w-full text-left px-4 py-3 rounded-md transition-all ${activeQuality === "auto"
637
887
  ? "bg-white/10"
638
- : "hover:bg-white/5"}` },
888
+ : isAdaptiveStream
889
+ ? "hover:bg-white/5"
890
+ : "opacity-50 cursor-not-allowed"}` },
639
891
  React.createElement("div", { className: "flex items-start justify-between" },
640
892
  React.createElement("div", null,
641
893
  React.createElement("div", { className: "text-white font-semibold text-lg mb-1" }, "Auto"),
642
894
  React.createElement("div", { className: "text-gray-400 text-sm" }, "Adjust to your connection")),
643
895
  activeQuality === "auto" && (React.createElement(Check, { className: "w-6 h-6 text-white mt-1" })))),
644
- uniqueQualityLevels
645
- .map((level) => (React.createElement("button", { key: level.originalIndex, onClick: () => {
646
- if (hlsInstance) {
647
- hlsInstance.currentLevel = level.originalIndex;
648
- setActiveQuality(String(level.height));
649
- }
650
- }, className: `w-full text-left px-4 py-3 rounded-md transition-all ${activeQuality === String(level.height)
896
+ isAdaptiveStream && qualityOptions.length > 0 ? (qualityOptions.map((level) => (React.createElement("button", { key: level.value, onClick: () => QualityManager.setQuality(streamType, level.value), className: `w-full text-left px-4 py-3 rounded-md transition-all ${activeQuality === level.value
651
897
  ? "bg-white/10"
652
898
  : "hover:bg-white/5"}` },
653
899
  React.createElement("div", { className: "flex items-start justify-between" },
654
900
  React.createElement("div", null,
655
- React.createElement("div", { className: "text-white font-semibold text-lg mb-1" }, getQualityName(level.height)),
901
+ React.createElement("div", { className: "text-white font-semibold text-lg mb-1" }, getQualityName(level.height, level.bitrate)),
656
902
  React.createElement("div", { className: "text-gray-400 text-sm" }, getDataUsage(level.height, level.bitrate))),
657
- activeQuality === String(level.height) && (React.createElement(Check, { className: "w-6 h-6 text-white mt-1" }))))))
658
- .reverse()))),
903
+ (activeQuality === level.value ||
904
+ currentQuality === level.value) && (React.createElement(Check, { className: "w-6 h-6 text-white mt-1" }))))))) : (React.createElement("div", { className: "px-4 py-3 text-gray-400 text-sm bg-white/5 rounded-md" }, "Quality selection is unavailable for this stream."))))),
659
905
  activeMenu === "subtitles" && (React.createElement("div", { className: "p-4" },
660
906
  React.createElement("div", { className: "flex items-center gap-3 mb-4" },
661
907
  React.createElement("button", { onClick: handleBack, className: "p-1 hover:bg-white/10 rounded-md transition-colors" },
@@ -682,7 +928,17 @@ const Settings = ({ iconClassName }) => {
682
928
 
683
929
  const ControlsHeader = ({ config }) => {
684
930
  const iconClassName = "icon-button";
685
- const { videoWrapperRef, videoRef, episodeList, currentEpisodeIndex, resetStore, } = useVideoStore();
931
+ const { videoWrapperRef, videoRef, adVideoRef, episodeList, currentEpisodeIndex, resetStore, isAdPlaying, muted, setMuted, } = useVideoStore(useShallow((state) => ({
932
+ videoWrapperRef: state.videoWrapperRef,
933
+ videoRef: state.videoRef,
934
+ adVideoRef: state.adVideoRef,
935
+ episodeList: state.episodeList,
936
+ currentEpisodeIndex: state.currentEpisodeIndex,
937
+ resetStore: state.resetStore,
938
+ isAdPlaying: state.isAdPlaying,
939
+ muted: state.muted,
940
+ setMuted: state.setMuted,
941
+ })));
686
942
  const [isPipActive, setIsPipActive] = React.useState(false);
687
943
  const [isFullscreen, setIsFullscreen] = React.useState(false);
688
944
  const handleFullscreen = () => {
@@ -705,9 +961,17 @@ const ControlsHeader = ({ config }) => {
705
961
  };
706
962
  }, []);
707
963
  const handleMute = () => {
708
- if (videoRef) {
709
- videoRef.muted = !videoRef.muted;
964
+ const targetElement = isAdPlaying ? adVideoRef ?? videoRef : videoRef;
965
+ if (!targetElement)
966
+ return;
967
+ const nextMuted = !targetElement.muted;
968
+ if (videoRef && videoRef.muted !== nextMuted) {
969
+ videoRef.muted = nextMuted;
710
970
  }
971
+ if (adVideoRef && adVideoRef.muted !== nextMuted) {
972
+ adVideoRef.muted = nextMuted;
973
+ }
974
+ setMuted(nextMuted);
711
975
  };
712
976
  const handlePipToggle = async () => {
713
977
  if (!videoRef)
@@ -722,9 +986,7 @@ const ControlsHeader = ({ config }) => {
722
986
  setIsPipActive(false);
723
987
  }
724
988
  }
725
- catch (error) {
726
- console.error("PiP toggle failed:", error);
727
- }
989
+ catch (_error) { }
728
990
  };
729
991
  React.useEffect(() => {
730
992
  const handlePipChange = () => setIsPipActive(!!document.pictureInPictureElement);
@@ -741,16 +1003,19 @@ const ControlsHeader = ({ config }) => {
741
1003
  config.onClose();
742
1004
  }
743
1005
  };
744
- return (React.createElement("div", { className: "flex items-center justify-between p-10 bg-gradient-to-b from-black" },
745
- React.createElement("div", { className: "flex" },
746
- React.createElement("div", null,
747
- React.createElement("h1", { className: "text-gray-200 text-lg lg:text-2xl font-semibold" }, episodeList.length > 0
748
- ? episodeList[currentEpisodeIndex]?.title
749
- : config?.title),
750
- config?.isTrailer && (React.createElement("p", { className: "text-gray-300 text-sm lg:text-base font-normal" }, "Trailer")))),
1006
+ const renderAdHeader = () => (React.createElement("div", { className: "flex items-center gap-4" },
1007
+ React.createElement("span", { className: "inline-flex items-center rounded bg-[#2D2F31] px-3 py-1 text-xs font-semibold uppercase tracking-wide text-yellow-500" }, "Ad")));
1008
+ const renderVideoHeader = () => (React.createElement("div", { className: "flex" },
1009
+ React.createElement("div", null,
1010
+ React.createElement("h1", { className: "text-gray-200 text-lg lg:text-2xl font-semibold" }, episodeList.length > 0
1011
+ ? episodeList[currentEpisodeIndex]?.title
1012
+ : config?.title),
1013
+ config?.isTrailer && (React.createElement("p", { className: "text-gray-300 text-sm lg:text-base font-normal" }, "Trailer")))));
1014
+ return (React.createElement("div", { className: "flex items-center justify-between p-10 bg-linear-to-b from-black" },
1015
+ isAdPlaying ? renderAdHeader() : renderVideoHeader(),
751
1016
  React.createElement("div", { className: "flex items-center gap-7 text-white" },
752
- React.createElement(Settings, { iconClassName: iconClassName }),
753
- React.createElement("div", { onClick: handleMute }, videoRef?.muted ? (React.createElement(Tooltip, { title: "Unmute" },
1017
+ !isAdPlaying && React.createElement(Settings, { iconClassName: iconClassName }),
1018
+ React.createElement("div", { onClick: handleMute }, muted ? (React.createElement(Tooltip, { title: "Unmute" },
754
1019
  React.createElement(IoVolumeMuteOutline, { className: iconClassName }))) : (React.createElement(Tooltip, { title: "Mute" },
755
1020
  React.createElement(IoVolumeHighOutline, { className: iconClassName })))),
756
1021
  React.createElement(Tooltip, { title: isPipActive
@@ -760,9 +1025,9 @@ const ControlsHeader = ({ config }) => {
760
1025
  : "Fullscreen", className: `${iconClassName} ${isPipActive ? "opacity-50 cursor-not-allowed" : ""}` },
761
1026
  React.createElement("div", { onClick: handleFullscreen, className: isPipActive ? "pointer-events-none" : "" },
762
1027
  React.createElement(FullScreenToggle, { isFullScreen: isFullscreen, className: iconClassName }))),
763
- React.createElement(Tooltip, { title: isPipActive ? "Exit PiP" : "Enter PiP" },
1028
+ !isAdPlaying && (React.createElement(Tooltip, { className: "whitespace-nowrap", title: isPipActive ? "Exit PiP" : "Enter PiP" },
764
1029
  React.createElement("div", { onClick: handlePipToggle },
765
- React.createElement(PiPictureInPictureToggle, { className: iconClassName }))),
1030
+ React.createElement(PiPictureInPictureToggle, { className: iconClassName })))),
766
1031
  config?.onClose && (React.createElement(React.Fragment, null,
767
1032
  React.createElement("div", { className: "w-[2px] h-10 bg-gray-500 hover:bg-gray-300 mx-2" }),
768
1033
  React.createElement("div", { onClick: handleClose },
@@ -770,93 +1035,123 @@ const ControlsHeader = ({ config }) => {
770
1035
  React.createElement(IoMdClose, { className: iconClassName }))))))));
771
1036
  };
772
1037
 
1038
+ const CONTROL_INTERACTION_EVENT = "video-controls:interaction";
1039
+ const CONTROLS_HIDE_DELAY_MS = 3000;
1040
+ const SKIP_INTERVAL_SECONDS = 10;
1041
+
1042
+ const BackwardIcon = memo(() => (React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
1043
+ React__default.createElement("path", { 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.04zM33.98 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" }))));
1044
+ BackwardIcon.displayName = "BackwardIcon";
1045
+ const ForwardIcon = memo(() => (React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
1046
+ React__default.createElement("path", { 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-.04zM33.98 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" }))));
1047
+ ForwardIcon.displayName = "ForwardIcon";
1048
+ const PauseIcon = memo(() => (React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
1049
+ React__default.createElement("path", { fillRule: "evenodd", clipRule: "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" }))));
1050
+ PauseIcon.displayName = "PauseIcon";
1051
+ const PlayIcon = memo(() => (React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
1052
+ React__default.createElement("path", { 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" }))));
1053
+ PlayIcon.displayName = "PlayIcon";
1054
+ const ControlButtonComponent = ({ onClick, icon, className, }) => (React__default.createElement("button", { onClick: onClick, className: `flex justify-center items-center h-full cursor-pointer ${className}` }, icon));
1055
+ const ControlButton = memo(ControlButtonComponent);
1056
+ ControlButton.displayName = "ControlButton";
773
1057
  const MiddleControls = () => {
774
- const { videoRef, isPlaying, setIsPlaying, isBuffering, setIsBuffering } = useVideoStore();
775
- const handlePlayPause = () => {
776
- if (!videoRef)
1058
+ const { videoRef, adVideoRef, isPlaying, setIsPlaying, isAdPlaying } = useVideoStore(useShallow((state) => ({
1059
+ videoRef: state.videoRef,
1060
+ adVideoRef: state.adVideoRef,
1061
+ isPlaying: state.isPlaying,
1062
+ setIsPlaying: state.setIsPlaying,
1063
+ isAdPlaying: state.isAdPlaying,
1064
+ })));
1065
+ const [isBuffering, setIsBuffering] = useState(false);
1066
+ const videoElement = isAdPlaying ? adVideoRef : videoRef;
1067
+ const resetControlsVisibility = useCallback(() => {
1068
+ if (typeof window === "undefined") {
1069
+ return;
1070
+ }
1071
+ window.dispatchEvent(new Event(CONTROL_INTERACTION_EVENT));
1072
+ }, []);
1073
+ const handlePlayPause = useCallback(() => {
1074
+ if (!videoElement)
777
1075
  return;
778
- if (videoRef.paused) {
779
- videoRef
1076
+ if (videoElement.paused) {
1077
+ videoElement
780
1078
  .play()
781
- .catch((error) => console.error("Error playing video:", error));
782
- setIsPlaying(true);
1079
+ .then(() => {
1080
+ setIsPlaying(true);
1081
+ resetControlsVisibility();
1082
+ })
1083
+ .catch(() => undefined);
783
1084
  }
784
1085
  else {
785
- videoRef.pause();
1086
+ videoElement.pause();
786
1087
  setIsPlaying(false);
1088
+ resetControlsVisibility();
787
1089
  }
788
- };
789
- const handleBackword = () => {
790
- if (!videoRef)
1090
+ }, [videoElement, setIsPlaying, resetControlsVisibility]);
1091
+ const handleBackward = useCallback(() => {
1092
+ if (!videoElement)
791
1093
  return;
792
- videoRef.currentTime -= 10;
793
- };
794
- const handleForword = () => {
795
- if (!videoRef)
1094
+ videoElement.currentTime = Math.max(0, videoElement.currentTime - SKIP_INTERVAL_SECONDS);
1095
+ resetControlsVisibility();
1096
+ }, [videoElement, resetControlsVisibility]);
1097
+ const handleForward = useCallback(() => {
1098
+ if (!videoElement)
796
1099
  return;
797
- videoRef.currentTime += 10;
798
- };
1100
+ videoElement.currentTime = Math.min(videoElement.duration, videoElement.currentTime + SKIP_INTERVAL_SECONDS);
1101
+ resetControlsVisibility();
1102
+ }, [videoElement, resetControlsVisibility]);
799
1103
  useEffect(() => {
800
- if (!videoRef)
1104
+ if (!videoElement)
801
1105
  return;
802
- const handleWaiting = () => {
803
- if (!videoRef.paused) {
804
- setIsBuffering(true);
805
- }
806
- };
807
- const handlePlaying = () => {
808
- setIsBuffering(false);
809
- };
810
- const handleCanPlay = () => {
811
- setIsBuffering(false);
812
- };
813
- const handlePause = () => {
814
- setIsBuffering(false);
815
- };
816
- videoRef.addEventListener("waiting", handleWaiting);
817
- videoRef.addEventListener("playing", handlePlaying);
818
- videoRef.addEventListener("canplay", handleCanPlay);
819
- videoRef.addEventListener("pause", handlePause);
1106
+ const handleWaiting = () => setIsBuffering(true);
1107
+ const handlePlaying = () => setIsBuffering(false);
1108
+ videoElement.addEventListener("waiting", handleWaiting);
1109
+ videoElement.addEventListener("playing", handlePlaying);
820
1110
  return () => {
821
- videoRef.removeEventListener("waiting", handleWaiting);
822
- videoRef.removeEventListener("playing", handlePlaying);
823
- videoRef.removeEventListener("canplay", handleCanPlay);
824
- videoRef.removeEventListener("pause", handlePause);
1111
+ videoElement.removeEventListener("waiting", handleWaiting);
1112
+ videoElement.removeEventListener("playing", handlePlaying);
825
1113
  };
826
- }, [videoRef, setIsBuffering]);
1114
+ }, [videoElement, isAdPlaying]);
827
1115
  useEffect(() => {
828
1116
  const handleKeyDown = (e) => {
829
- if (!videoRef)
1117
+ if (!videoElement || isAdPlaying)
830
1118
  return;
831
1119
  switch (e.code) {
832
1120
  case "Space":
833
1121
  e.preventDefault();
834
1122
  handlePlayPause();
1123
+ resetControlsVisibility();
835
1124
  break;
836
1125
  case "ArrowLeft":
837
- handleBackword();
1126
+ e.preventDefault();
1127
+ handleBackward();
838
1128
  break;
839
1129
  case "ArrowRight":
840
- handleForword();
1130
+ e.preventDefault();
1131
+ handleForward();
841
1132
  break;
842
1133
  }
843
1134
  };
844
1135
  window.addEventListener("keydown", handleKeyDown);
845
1136
  return () => window.removeEventListener("keydown", handleKeyDown);
846
- }, [videoRef, isPlaying]);
1137
+ }, [
1138
+ videoElement,
1139
+ handlePlayPause,
1140
+ handleBackward,
1141
+ handleForward,
1142
+ isAdPlaying,
1143
+ ]);
1144
+ if (isAdPlaying) {
1145
+ return (React__default.createElement("div", { className: "flex justify-center items-center" },
1146
+ React__default.createElement(ControlButton, { onClick: handlePlayPause, className: "w-[10vw]", icon: isBuffering ? (React__default.createElement(Loader, { className: "w-24 h-24 lg:w-32 lg:h-32 animate-spin text-white" })) : isPlaying ? (React__default.createElement(PauseIcon, null)) : (React__default.createElement(PlayIcon, null)) })));
1147
+ }
847
1148
  return (React__default.createElement("div", { className: "flex justify-center items-center" },
848
- React__default.createElement("div", { onClick: handleBackword, className: "w-[15vw] flex justify-center items-center h-full cursor-pointer" },
849
- React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
850
- React__default.createElement("path", { 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.04zM33.98 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" }))),
851
- React__default.createElement("div", { onClick: handlePlayPause, className: "w-[10vw] flex justify-center items-center h-full cursor-pointer" }, isBuffering ? (React__default.createElement(Loader, { className: "w-24 h-24 lg:w-32 lg:h-32 animate-spin text-white" })) : isPlaying ? (React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
852
- React__default.createElement("path", { 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: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
853
- React__default.createElement("path", { 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" })))),
854
- React__default.createElement("div", { onClick: handleForword, className: "w-[15vw] flex justify-center items-center h-full cursor-pointer" },
855
- React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
856
- React__default.createElement("path", { 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-.04zM33.98 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" })))));
1149
+ React__default.createElement(ControlButton, { onClick: handleBackward, className: "w-[15vw]", icon: React__default.createElement(BackwardIcon, null) }),
1150
+ React__default.createElement(ControlButton, { onClick: handlePlayPause, className: "w-[10vw]", icon: isBuffering ? (React__default.createElement(Loader, { className: "w-24 h-24 lg:w-32 lg:h-32 animate-spin text-white" })) : isPlaying ? (React__default.createElement(PauseIcon, null)) : (React__default.createElement(PlayIcon, null)) }),
1151
+ React__default.createElement(ControlButton, { onClick: handleForward, className: "w-[15vw]", icon: React__default.createElement(ForwardIcon, null) })));
857
1152
  };
858
1153
 
859
- const VideoPlayerControls = ({ config, }) => {
1154
+ const VideoPlayerControls = ({ config }) => {
860
1155
  return (React.createElement("div", { className: "px-20" },
861
1156
  React.createElement("div", { className: "absolute top-0 left-0 h-full w-full bg-[rgba(0,0,0,0.5)] flex flex-col justify-between" },
862
1157
  React.createElement(ControlsHeader, { config: config?.headerConfig?.config }),
@@ -879,25 +1174,48 @@ const VideoActionButton = ({ text, onClick, icon, disabled = false, position = "
879
1174
 
880
1175
  const Overlay = ({ config }) => {
881
1176
  const controlsTimerRef = useRef(null);
882
- const { setControls, controls, showCountdown, countdownTime, setShowCountdown, setAutoPlayNext, setCurrentEpisodeIndex, episodeList, setCountdownTime, videoRef, currentEpisodeIndex, } = useVideoStore();
1177
+ const containerRef = useRef(null);
1178
+ const { setControls, controls, showCountdown, countdownTime, setShowCountdown, setAutoPlayNext, setCurrentEpisodeIndex, episodeList, setCountdownTime, videoRef, currentEpisodeIndex, isAdPlaying, } = useVideoStore(useShallow((state) => ({
1179
+ setControls: state.setControls,
1180
+ controls: state.controls,
1181
+ showCountdown: state.showCountdown,
1182
+ countdownTime: state.countdownTime,
1183
+ setShowCountdown: state.setShowCountdown,
1184
+ setAutoPlayNext: state.setAutoPlayNext,
1185
+ setCurrentEpisodeIndex: state.setCurrentEpisodeIndex,
1186
+ episodeList: state.episodeList,
1187
+ setCountdownTime: state.setCountdownTime,
1188
+ videoRef: state.videoRef,
1189
+ currentEpisodeIndex: state.currentEpisodeIndex,
1190
+ isAdPlaying: state.isAdPlaying,
1191
+ })));
883
1192
  const { onClose } = config?.headerConfig?.config || {};
884
- const handleMouseEnter = useCallback(() => {
885
- const videoPlayerControls = document?.getElementById("videoPlayerControls");
886
- if (videoPlayerControls) {
887
- videoPlayerControls.classList.remove("noCursor");
888
- }
889
- setControls(true);
1193
+ const hideControls = useCallback(() => {
1194
+ setControls(false);
1195
+ containerRef.current?.classList.add("noCursor");
1196
+ }, [setControls]);
1197
+ const resetControlsTimer = useCallback(() => {
890
1198
  if (controlsTimerRef.current) {
891
1199
  clearTimeout(controlsTimerRef.current);
892
1200
  }
893
- controlsTimerRef.current = setTimeout(() => {
894
- setControls(false);
895
- if (videoPlayerControls) {
896
- videoPlayerControls.classList.add("noCursor");
1201
+ controlsTimerRef.current = setTimeout(hideControls, CONTROLS_HIDE_DELAY_MS);
1202
+ }, [hideControls]);
1203
+ const handleControlsInteraction = useCallback(() => {
1204
+ containerRef.current?.classList.remove("noCursor");
1205
+ setControls(true);
1206
+ resetControlsTimer();
1207
+ }, [resetControlsTimer, setControls]);
1208
+ const handleMouseEnter = useCallback(() => {
1209
+ handleControlsInteraction();
1210
+ }, [handleControlsInteraction]);
1211
+ useEffect(() => {
1212
+ return () => {
1213
+ if (controlsTimerRef.current) {
1214
+ clearTimeout(controlsTimerRef.current);
897
1215
  }
898
- }, 2000);
899
- }, [setControls]);
900
- React.useEffect(() => {
1216
+ };
1217
+ }, []);
1218
+ useEffect(() => {
901
1219
  let timer;
902
1220
  if (showCountdown && countdownTime > 0 && episodeList.length > 0) {
903
1221
  timer = setInterval(() => {
@@ -909,27 +1227,38 @@ const Overlay = ({ config }) => {
909
1227
  clearInterval(timer);
910
1228
  };
911
1229
  }, [showCountdown, countdownTime, episodeList.length, setCountdownTime]);
1230
+ useEffect(() => {
1231
+ if (typeof window === "undefined") {
1232
+ return;
1233
+ }
1234
+ const handleExternalInteraction = () => {
1235
+ handleControlsInteraction();
1236
+ };
1237
+ window.addEventListener(CONTROL_INTERACTION_EVENT, handleExternalInteraction);
1238
+ return () => {
1239
+ window.removeEventListener(CONTROL_INTERACTION_EVENT, handleExternalInteraction);
1240
+ };
1241
+ }, [handleControlsInteraction]);
912
1242
  const handleNextEpisodeManually = () => {
913
1243
  const nextIndex = currentEpisodeIndex + 1;
914
1244
  if (nextIndex < episodeList.length && videoRef && episodeList[nextIndex]) {
915
1245
  setCurrentEpisodeIndex(nextIndex);
916
1246
  setAutoPlayNext(true);
917
1247
  videoRef.src = episodeList[nextIndex].url;
918
- videoRef
919
- .play()
920
- .catch((err) => console.error("Manual play failed:", err));
1248
+ videoRef.play().catch(() => undefined);
921
1249
  setShowCountdown(false);
922
1250
  setCountdownTime(10);
1251
+ handleControlsInteraction();
923
1252
  }
924
1253
  else if (onClose) {
925
1254
  onClose();
926
1255
  }
927
1256
  };
928
- return (React.createElement("div", { id: "videoPlayerControls", className: "absolute inset-0", onMouseMove: handleMouseEnter },
929
- controls && React.createElement(VideoPlayerControls, { config: config }),
1257
+ return (React__default.createElement("div", { id: "videoPlayerControls", ref: containerRef, className: "absolute inset-0", onMouseMove: handleMouseEnter },
1258
+ controls && !isAdPlaying && React__default.createElement(VideoPlayerControls, { config: config }),
930
1259
  showCountdown &&
931
1260
  episodeList.length > 0 &&
932
- currentEpisodeIndex + 1 < episodeList.length && (React.createElement(VideoActionButton, { text: "Next Episode", onClick: handleNextEpisodeManually, icon: React.createElement(ArrowRight, { className: "h-5 w-5 text-gray-900" }), disabled: currentEpisodeIndex + 1 >= episodeList.length, position: "right" }))));
1261
+ currentEpisodeIndex + 1 < episodeList.length && (React__default.createElement(VideoActionButton, { text: "Next Episode", onClick: handleNextEpisodeManually, icon: React__default.createElement(ArrowRight, { className: "h-5 w-5 text-gray-900" }), disabled: currentEpisodeIndex + 1 >= episodeList.length, position: "right" }))));
933
1262
  };
934
1263
 
935
1264
  const SubtitleOverlay = ({ styleConfig }) => {
@@ -947,13 +1276,7 @@ const SubtitleOverlay = ({ styleConfig }) => {
947
1276
  }
948
1277
  const currentTime = videoRef.currentTime;
949
1278
  const textTracks = Array.from(videoRef.textTracks);
950
- console.log("Available text tracks:", textTracks.map((t) => ({
951
- label: t.label,
952
- mode: t.mode,
953
- cues: t.cues?.length,
954
- })));
955
1279
  const activeTrack = textTracks.find((track) => track.mode === "showing" && track.label === activeSubtitle.label);
956
- console.log("Active track found:", !!activeTrack, "Current time:", currentTime);
957
1280
  if (activeTrack && activeTrack.cues) {
958
1281
  const activeCues = Array.from(activeTrack.cues).filter((cue) => currentTime >= cue.startTime && currentTime <= cue.endTime);
959
1282
  if (activeCues.length > 0) {
@@ -972,8 +1295,7 @@ const SubtitleOverlay = ({ styleConfig }) => {
972
1295
  cueText = cue.toString() || "";
973
1296
  }
974
1297
  }
975
- catch (error) {
976
- console.warn("Error getting subtitle text:", error);
1298
+ catch (_error) {
977
1299
  cueText = "";
978
1300
  }
979
1301
  setCurrentSubtitle(cueText);
@@ -1046,39 +1368,455 @@ const SubtitleOverlay = ({ styleConfig }) => {
1046
1368
  return React__default.createElement("div", { style: subtitleStyle }, currentSubtitle);
1047
1369
  };
1048
1370
 
1049
- const useVideoSource = (trackSrc, type) => {
1050
- const { videoRef, setQualityLevels, setHlsInstance } = useVideoStore();
1371
+ const HLS_CONFIG = {
1372
+ enableWorker: true,
1373
+ lowLatencyMode: false,
1374
+ backBufferLength: 90,
1375
+ liveSyncDurationCount: 3,
1376
+ maxBufferSize: 80 * 1000000,
1377
+ maxBufferLength: 30,
1378
+ manifestLoadingMaxRetry: 4,
1379
+ manifestLoadingRetryDelay: 1000,
1380
+ levelLoadingMaxRetry: 4,
1381
+ levelLoadingRetryDelay: 1000,
1382
+ fragLoadingMaxRetry: 6,
1383
+ fragLoadingRetryDelay: 750,
1384
+ startLevel: -1,
1385
+ startPosition: -1,
1386
+ capLevelToPlayerSize: true,
1387
+ };
1388
+ const DASH_SETTINGS = {
1389
+ streaming: {
1390
+ abr: {
1391
+ autoSwitchBitrate: {
1392
+ video: true,
1393
+ audio: true,
1394
+ },
1395
+ limitBitrateByPortal: true,
1396
+ ABRStrategy: "abrThroughput",
1397
+ bandwidthSafetyFactor: 0.9,
1398
+ },
1399
+ buffer: {
1400
+ fastSwitchEnabled: true,
1401
+ bufferTimeAtTopQuality: 28,
1402
+ bufferTimeAtTopQualityLongForm: 55,
1403
+ },
1404
+ lowLatencyEnabled: false,
1405
+ },
1406
+ debug: {
1407
+ logLevel: dashjs.Debug.LOG_LEVEL_NONE,
1408
+ },
1409
+ };
1410
+ const MAX_HLS_NETWORK_RETRIES = 4;
1411
+ const MAX_DASH_RESTARTS = 3;
1412
+ const sanitizeUrl = (url) => {
1413
+ if (!url)
1414
+ return "";
1415
+ return url.split("#")[0]?.split("?")[0] ?? url;
1416
+ };
1417
+ const resolveStreamType = (explicitType, source) => {
1418
+ if (explicitType === "hls" ||
1419
+ explicitType === "dash" ||
1420
+ explicitType === "mp4") {
1421
+ return explicitType;
1422
+ }
1423
+ if (explicitType === "youtube" || explicitType === "other") {
1424
+ return "other";
1425
+ }
1426
+ const sanitized = sanitizeUrl(source).toLowerCase();
1427
+ const extension = getExtensionFromUrl(sanitized);
1428
+ if (extension === "hls")
1429
+ return "hls";
1430
+ if (extension === "dash")
1431
+ return "dash";
1432
+ if (extension === "mp4")
1433
+ return "mp4";
1434
+ if (sanitized.includes(".m3u8"))
1435
+ return "hls";
1436
+ if (sanitized.includes(".mpd"))
1437
+ return "dash";
1438
+ if (sanitized.includes(".mp4"))
1439
+ return "mp4";
1440
+ return "other";
1441
+ };
1442
+ const useHlsEngine = ({ enabled, source, videoElement, setHlsInstance, setQualityLevels, setCurrentQuality, }) => {
1443
+ const networkRetryRef = useRef(0);
1444
+ const retryTimerRef = useRef(undefined);
1051
1445
  useEffect(() => {
1052
- if (!videoRef)
1446
+ if (!enabled || !videoElement) {
1053
1447
  return;
1054
- const getVideoExtension = getExtensionFromUrl(trackSrc);
1055
- const contentType = type || getVideoExtension;
1056
- if (contentType === "mp4") {
1057
- videoRef.src = trackSrc;
1448
+ }
1449
+ networkRetryRef.current = 0;
1450
+ setQualityLevels([]);
1451
+ setCurrentQuality("auto");
1452
+ const clearRetryTimer = () => {
1453
+ if (retryTimerRef.current) {
1454
+ window.clearTimeout(retryTimerRef.current);
1455
+ retryTimerRef.current = undefined;
1456
+ }
1457
+ };
1458
+ const attachNative = () => {
1459
+ setHlsInstance(null);
1460
+ videoElement.src = source;
1461
+ videoElement.load();
1462
+ const handleLoadedMetadata = () => {
1463
+ try {
1464
+ const mediaTracks = videoElement?.videoTracks;
1465
+ if (mediaTracks && mediaTracks.length > 0) {
1466
+ const levels = Array.from(mediaTracks).map((track, index) => ({
1467
+ height: track.height ?? 0,
1468
+ bitrate: track.bandwidth ?? 0,
1469
+ originalIndex: index,
1470
+ }));
1471
+ setQualityLevels(levels);
1472
+ }
1473
+ }
1474
+ catch (_error) {
1475
+ setQualityLevels([]);
1476
+ }
1477
+ };
1478
+ videoElement.addEventListener("loadedmetadata", handleLoadedMetadata);
1479
+ return () => {
1480
+ videoElement.removeEventListener("loadedmetadata", handleLoadedMetadata);
1481
+ };
1482
+ };
1483
+ if (!Hls.isSupported() &&
1484
+ videoElement.canPlayType("application/vnd.apple.mpegurl")) {
1485
+ return attachNative();
1486
+ }
1487
+ if (!Hls.isSupported()) {
1488
+ setHlsInstance(null);
1489
+ videoElement.src = source;
1490
+ videoElement.load();
1491
+ return;
1492
+ }
1493
+ const hls = new Hls(HLS_CONFIG);
1494
+ setHlsInstance(hls);
1495
+ const updateQualityLevels = () => {
1496
+ const levels = hls.levels ?? [];
1497
+ const parsedLevels = levels.map((level, index) => ({
1498
+ height: level.height ?? 0,
1499
+ bitrate: level.bitrate ?? 0,
1500
+ originalIndex: index,
1501
+ }));
1502
+ setQualityLevels(parsedLevels);
1503
+ };
1504
+ const handleManifestParsed = () => {
1505
+ networkRetryRef.current = 0;
1506
+ updateQualityLevels();
1507
+ const { activeQuality } = useVideoStore.getState();
1508
+ if (activeQuality && activeQuality.startsWith("hls-")) {
1509
+ const levelIndex = parseInt(activeQuality.replace("hls-", ""), 10);
1510
+ if (!Number.isNaN(levelIndex) && levelIndex >= 0) {
1511
+ hls.loadLevel = levelIndex;
1512
+ hls.nextLevel = levelIndex;
1513
+ hls.currentLevel = levelIndex;
1514
+ setCurrentQuality(activeQuality);
1515
+ return;
1516
+ }
1517
+ }
1518
+ setCurrentQuality("auto");
1519
+ hls.currentLevel = -1;
1520
+ hls.loadLevel = -1;
1521
+ hls.nextLevel = -1;
1522
+ };
1523
+ const handleLevelsUpdated = () => {
1524
+ updateQualityLevels();
1525
+ };
1526
+ const handleLevelSwitched = (_event, data) => {
1527
+ if (typeof data?.level === "number" && data.level >= 0) {
1528
+ setCurrentQuality(`hls-${data.level}`);
1529
+ }
1530
+ };
1531
+ const scheduleRestart = () => {
1532
+ if (networkRetryRef.current >= MAX_HLS_NETWORK_RETRIES) {
1533
+ return;
1534
+ }
1535
+ const delay = Math.min(2000 * (networkRetryRef.current + 1), 10000);
1536
+ clearRetryTimer();
1537
+ retryTimerRef.current = window.setTimeout(() => {
1538
+ try {
1539
+ hls.startLoad();
1540
+ }
1541
+ catch (_err) {
1542
+ // Ignore
1543
+ }
1544
+ }, delay);
1545
+ networkRetryRef.current += 1;
1546
+ };
1547
+ const handleError = (_event, data) => {
1548
+ if (!data)
1549
+ return;
1550
+ const HLS_ERROR_TYPES = Hls.ErrorTypes ?? {};
1551
+ if (data.fatal) {
1552
+ switch (data.type) {
1553
+ case HLS_ERROR_TYPES.NETWORK_ERROR ?? "networkError":
1554
+ scheduleRestart();
1555
+ break;
1556
+ case HLS_ERROR_TYPES.MEDIA_ERROR ?? "mediaError":
1557
+ try {
1558
+ hls.recoverMediaError();
1559
+ }
1560
+ catch (_err) {
1561
+ scheduleRestart();
1562
+ }
1563
+ break;
1564
+ default:
1565
+ clearRetryTimer();
1566
+ hls.destroy();
1567
+ setHlsInstance(null);
1568
+ break;
1569
+ }
1570
+ }
1571
+ else if (data.type === (HLS_ERROR_TYPES.NETWORK_ERROR ?? "networkError")) {
1572
+ scheduleRestart();
1573
+ }
1574
+ };
1575
+ hls.attachMedia(videoElement);
1576
+ hls.loadSource(source);
1577
+ const HLS_EVENTS = Hls.Events ?? {};
1578
+ hls.on(HLS_EVENTS.MANIFEST_PARSED ?? "manifestParsed", handleManifestParsed);
1579
+ hls.on(HLS_EVENTS.LEVELS_UPDATED ?? "levelsUpdated", handleLevelsUpdated);
1580
+ hls.on(HLS_EVENTS.LEVEL_SWITCHED ?? "levelSwitched", handleLevelSwitched);
1581
+ hls.on(HLS_EVENTS.ERROR ?? "error", handleError);
1582
+ return () => {
1583
+ clearRetryTimer();
1584
+ hls.off(HLS_EVENTS.MANIFEST_PARSED ?? "manifestParsed", handleManifestParsed);
1585
+ hls.off(HLS_EVENTS.LEVELS_UPDATED ?? "levelsUpdated", handleLevelsUpdated);
1586
+ hls.off(HLS_EVENTS.LEVEL_SWITCHED ?? "levelSwitched", handleLevelSwitched);
1587
+ hls.off(HLS_EVENTS.ERROR ?? "error", handleError);
1588
+ hls.destroy();
1589
+ setHlsInstance(null);
1590
+ setQualityLevels([]);
1591
+ setCurrentQuality("auto");
1592
+ };
1593
+ }, [
1594
+ enabled,
1595
+ source,
1596
+ videoElement,
1597
+ setHlsInstance,
1598
+ setQualityLevels,
1599
+ setCurrentQuality,
1600
+ ]);
1601
+ };
1602
+ const useDashEngine = ({ enabled, source, videoElement, setDashInstance, setQualityLevels, setCurrentQuality, }) => {
1603
+ const restartCountRef = useRef(0);
1604
+ const restartTimerRef = useRef(undefined);
1605
+ useEffect(() => {
1606
+ if (!enabled || !videoElement) {
1607
+ return;
1608
+ }
1609
+ if (!dashjs.supportsMediaSource()) {
1610
+ setDashInstance(null);
1058
1611
  setQualityLevels([]);
1612
+ videoElement.src = source;
1613
+ videoElement.load();
1614
+ return;
1059
1615
  }
1060
- else if (contentType === "hls") {
1061
- if (videoRef?.canPlayType("application/vnd.apple.mpegurl")) {
1062
- videoRef.src = trackSrc;
1063
- }
1064
- else if (Hls.isSupported()) {
1065
- const hls = new Hls();
1066
- hls.loadSource(trackSrc);
1067
- hls.attachMedia(videoRef);
1068
- setHlsInstance(hls);
1069
- hls.on(Hls.Events.MANIFEST_PARSED, () => {
1070
- setQualityLevels(hls.levels);
1071
- });
1072
- return () => {
1073
- hls.destroy();
1074
- };
1616
+ restartCountRef.current = 0;
1617
+ setQualityLevels([]);
1618
+ setCurrentQuality("auto");
1619
+ const player = dashjs.MediaPlayer().create();
1620
+ setDashInstance(player);
1621
+ const dashPlayer = player;
1622
+ const clearRestartTimer = () => {
1623
+ if (restartTimerRef.current) {
1624
+ window.clearTimeout(restartTimerRef.current);
1625
+ restartTimerRef.current = undefined;
1626
+ }
1627
+ };
1628
+ const applySettings = () => {
1629
+ player.updateSettings(DASH_SETTINGS);
1630
+ };
1631
+ const updateQualityLevels = () => {
1632
+ try {
1633
+ const levels = dashPlayer.getBitrateInfoListFor?.("video") ?? [];
1634
+ const mapped = Array.from(levels).map((info) => ({
1635
+ height: info.height ?? 0,
1636
+ bitrate: info.bitrate,
1637
+ originalIndex: info.qualityIndex ?? info.index ?? 0,
1638
+ id: info.id,
1639
+ }));
1640
+ setQualityLevels(mapped);
1641
+ }
1642
+ catch (_error) {
1643
+ setQualityLevels([]);
1644
+ }
1645
+ };
1646
+ const handleStreamInitialized = () => {
1647
+ restartCountRef.current = 0;
1648
+ updateQualityLevels();
1649
+ const { activeQuality } = useVideoStore.getState();
1650
+ if (activeQuality && activeQuality.startsWith("dash-")) {
1651
+ const levelIndex = parseInt(activeQuality.replace("dash-", ""), 10);
1652
+ if (!Number.isNaN(levelIndex) && levelIndex >= 0) {
1653
+ dashPlayer.setAutoSwitchQualityFor?.("video", false);
1654
+ dashPlayer.setQualityFor?.("video", levelIndex);
1655
+ setCurrentQuality(activeQuality);
1656
+ return;
1657
+ }
1658
+ }
1659
+ dashPlayer.setAutoSwitchQualityFor?.("video", true);
1660
+ const current = dashPlayer.getQualityFor?.("video");
1661
+ if (typeof current === "number" && current >= 0) {
1662
+ setCurrentQuality(`dash-${current}`);
1663
+ }
1664
+ else {
1665
+ setCurrentQuality("auto");
1666
+ }
1667
+ };
1668
+ const handleManifestLoaded = () => {
1669
+ updateQualityLevels();
1670
+ };
1671
+ const handleQualityRendered = (event) => {
1672
+ if (event?.mediaType === "video") {
1673
+ const current = dashPlayer.getQualityFor?.("video");
1674
+ if (typeof current === "number" && current >= 0) {
1675
+ setCurrentQuality(`dash-${current}`);
1676
+ }
1677
+ }
1678
+ };
1679
+ const bindEvents = () => {
1680
+ player.on(dashjs.MediaPlayer.events.STREAM_INITIALIZED, handleStreamInitialized);
1681
+ player.on(dashjs.MediaPlayer.events.MANIFEST_LOADED, handleManifestLoaded);
1682
+ player.on(dashjs.MediaPlayer.events.QUALITY_CHANGE_RENDERED, handleQualityRendered);
1683
+ player.on(dashjs.MediaPlayer.events.ERROR, handleError);
1684
+ };
1685
+ const detachEvents = () => {
1686
+ player.off(dashjs.MediaPlayer.events.STREAM_INITIALIZED, handleStreamInitialized);
1687
+ player.off(dashjs.MediaPlayer.events.MANIFEST_LOADED, handleManifestLoaded);
1688
+ player.off(dashjs.MediaPlayer.events.QUALITY_CHANGE_RENDERED, handleQualityRendered);
1689
+ player.off(dashjs.MediaPlayer.events.ERROR, handleError);
1690
+ };
1691
+ const restartPlayer = () => {
1692
+ if (restartCountRef.current >= MAX_DASH_RESTARTS) {
1693
+ return;
1075
1694
  }
1695
+ restartCountRef.current += 1;
1696
+ clearRestartTimer();
1697
+ const delay = Math.min(1500 * restartCountRef.current, 6000);
1698
+ restartTimerRef.current = window.setTimeout(() => {
1699
+ detachEvents();
1700
+ player.reset();
1701
+ setQualityLevels([]);
1702
+ applySettings();
1703
+ bindEvents();
1704
+ player.initialize(videoElement, source, videoElement.autoplay ?? false);
1705
+ }, delay);
1706
+ };
1707
+ const handleError = (event) => {
1708
+ if (!event)
1709
+ return;
1710
+ const errorToken = typeof event?.error === "string"
1711
+ ? event.error
1712
+ : typeof event?.event === "object" && event.event
1713
+ ? event.event.id
1714
+ : undefined;
1715
+ const normalized = errorToken?.toString().toLowerCase();
1716
+ const shouldRecover = normalized &&
1717
+ (normalized.includes("download") ||
1718
+ normalized.includes("manifest") ||
1719
+ normalized.includes("mediasource") ||
1720
+ normalized.includes("capability") ||
1721
+ normalized.includes("fragment"));
1722
+ if (shouldRecover) {
1723
+ restartPlayer();
1724
+ }
1725
+ };
1726
+ applySettings();
1727
+ bindEvents();
1728
+ player.initialize(videoElement, source, videoElement.autoplay ?? false);
1729
+ return () => {
1730
+ clearRestartTimer();
1731
+ detachEvents();
1732
+ player.reset();
1733
+ setDashInstance(null);
1734
+ setQualityLevels([]);
1735
+ setCurrentQuality("auto");
1736
+ };
1737
+ }, [
1738
+ enabled,
1739
+ source,
1740
+ videoElement,
1741
+ setDashInstance,
1742
+ setQualityLevels,
1743
+ setCurrentQuality,
1744
+ ]);
1745
+ };
1746
+ const useVideoSource = (trackSrc, type) => {
1747
+ const { videoRef, setQualityLevels, setHlsInstance, setDashInstance, setStreamType, setActiveQuality, setCurrentQuality, } = useVideoStore(useShallow((state) => ({
1748
+ videoRef: state.videoRef,
1749
+ setQualityLevels: state.setQualityLevels,
1750
+ setHlsInstance: state.setHlsInstance,
1751
+ setDashInstance: state.setDashInstance,
1752
+ setStreamType: state.setStreamType,
1753
+ setActiveQuality: state.setActiveQuality,
1754
+ setCurrentQuality: state.setCurrentQuality,
1755
+ })));
1756
+ const streamType = useMemo(() => resolveStreamType(type, trackSrc), [type, trackSrc]);
1757
+ useEffect(() => {
1758
+ if (!trackSrc)
1759
+ return;
1760
+ setStreamType(streamType);
1761
+ setActiveQuality("auto");
1762
+ setCurrentQuality("auto");
1763
+ setQualityLevels([]);
1764
+ }, [
1765
+ trackSrc,
1766
+ streamType,
1767
+ setStreamType,
1768
+ setActiveQuality,
1769
+ setCurrentQuality,
1770
+ setQualityLevels,
1771
+ ]);
1772
+ useEffect(() => {
1773
+ if (streamType !== "dash") {
1774
+ setDashInstance(null);
1076
1775
  }
1077
- else {
1776
+ if (streamType !== "hls") {
1777
+ setHlsInstance(null);
1778
+ }
1779
+ }, [streamType, setDashInstance, setHlsInstance]);
1780
+ useEffect(() => {
1781
+ if (!videoRef)
1782
+ return;
1783
+ if (streamType === "mp4" || streamType === "other") {
1078
1784
  videoRef.src = trackSrc;
1079
- setQualityLevels([]);
1785
+ videoRef.load();
1786
+ }
1787
+ else {
1788
+ // Adaptive engines will attach their own source; ensure no stale src lingers
1789
+ videoRef.removeAttribute("src");
1080
1790
  }
1081
- }, [trackSrc, videoRef, type, setQualityLevels, setHlsInstance]);
1791
+ }, [videoRef, trackSrc, streamType]);
1792
+ useHlsEngine({
1793
+ enabled: streamType === "hls",
1794
+ source: trackSrc,
1795
+ videoElement: videoRef,
1796
+ setHlsInstance,
1797
+ setQualityLevels,
1798
+ setCurrentQuality,
1799
+ });
1800
+ useDashEngine({
1801
+ enabled: streamType === "dash",
1802
+ source: trackSrc,
1803
+ videoElement: videoRef,
1804
+ setDashInstance,
1805
+ setQualityLevels,
1806
+ setCurrentQuality,
1807
+ });
1808
+ useEffect(() => {
1809
+ if (!videoRef)
1810
+ return;
1811
+ return () => {
1812
+ videoRef.pause();
1813
+ videoRef.removeAttribute("src");
1814
+ videoRef.load();
1815
+ const { setIsPlaying, setBufferedProgress } = useVideoStore.getState();
1816
+ setIsPlaying(false);
1817
+ setBufferedProgress(0);
1818
+ };
1819
+ }, [videoRef, trackSrc]);
1082
1820
  };
1083
1821
 
1084
1822
  const useSubtitles = (subtitles) => {
@@ -1107,7 +1845,6 @@ const useSubtitles = (subtitles) => {
1107
1845
  const textTrack = Array.from(videoRef.textTracks).find((track) => track.label === activeSubtitle.label);
1108
1846
  if (textTrack) {
1109
1847
  textTrack.mode = "showing";
1110
- console.log("Subtitle track loaded for custom rendering:", activeSubtitle.label);
1111
1848
  }
1112
1849
  };
1113
1850
  trackElement.addEventListener("load", handleTrackLoad);
@@ -1239,8 +1976,7 @@ const useVideoTracking = (tracking, episodeList, currentEpisodeIndex, onClose) =
1239
1976
  setShowCountdown,
1240
1977
  ]);
1241
1978
  useEffect(() => {
1242
- const handleUnload = (e) => {
1243
- e.preventDefault();
1979
+ const handleUnload = () => {
1244
1980
  if (startTime.current) {
1245
1981
  const elapsedTime = (Date.now() - startTime.current) / 1000;
1246
1982
  const getCurrentTime = localStorage.getItem("current_time");
@@ -1254,10 +1990,8 @@ const useVideoTracking = (tracking, episodeList, currentEpisodeIndex, onClose) =
1254
1990
  }
1255
1991
  localStorage.setItem("current_time", "0");
1256
1992
  };
1257
- window.addEventListener("beforeunload", handleUnload);
1258
1993
  window.addEventListener("unload", handleUnload);
1259
1994
  return () => {
1260
- window.removeEventListener("beforeunload", handleUnload);
1261
1995
  window.removeEventListener("unload", handleUnload);
1262
1996
  };
1263
1997
  }, [tracking]);
@@ -1333,24 +2067,100 @@ const useEpisodes = (episodeList, currentEpisodeIndex, nextEpisodeConfig) => {
1333
2067
  };
1334
2068
 
1335
2069
  const useVideoEvents = () => {
1336
- const { setCurrentTime, setDuration, setBufferedProgress, setIsPlaying } = useVideoStore();
2070
+ const { setCurrentTime, setDuration, setBufferedProgress, setIsPlaying } = useVideoStore(useShallow((state) => ({
2071
+ setCurrentTime: state.setCurrentTime,
2072
+ setDuration: state.setDuration,
2073
+ setBufferedProgress: state.setBufferedProgress,
2074
+ setIsPlaying: state.setIsPlaying,
2075
+ })));
2076
+ // Cache the most recent values so we can short-circuit duplicate store writes that were causing needless renders.
2077
+ const lastTimeUpdateRef = useRef(0);
2078
+ const lastBufferedProgressRef = useRef(0);
2079
+ const pendingTimeRef = useRef(null);
2080
+ const pendingBufferedRef = useRef(null);
2081
+ const timeUpdateRafRef = useRef(null);
2082
+ const bufferedRafRef = useRef(null);
2083
+ const stopMediaElement = (media) => {
2084
+ if (!media)
2085
+ return;
2086
+ try {
2087
+ media.pause();
2088
+ }
2089
+ catch (_error) {
2090
+ // Ignored: the element might already be paused or unavailable.
2091
+ }
2092
+ try {
2093
+ media.currentTime = 0;
2094
+ }
2095
+ catch (_error) {
2096
+ // Some streams throw while seeking when metadata is missing; safe to ignore.
2097
+ }
2098
+ media.removeAttribute("src");
2099
+ media.load();
2100
+ };
2101
+ const flushPendingTimeUpdate = (time) => {
2102
+ if (timeUpdateRafRef.current !== null) {
2103
+ cancelAnimationFrame(timeUpdateRafRef.current);
2104
+ timeUpdateRafRef.current = null;
2105
+ }
2106
+ pendingTimeRef.current = null;
2107
+ lastTimeUpdateRef.current = time;
2108
+ setCurrentTime(time);
2109
+ };
2110
+ const scheduleTimeUpdate = (time) => {
2111
+ pendingTimeRef.current = time;
2112
+ if (timeUpdateRafRef.current !== null) {
2113
+ return;
2114
+ }
2115
+ // Coalesce multiple rapid events into a single rAF-aligned update to reduce layout thrash.
2116
+ timeUpdateRafRef.current = requestAnimationFrame(() => {
2117
+ timeUpdateRafRef.current = null;
2118
+ const nextTime = pendingTimeRef.current;
2119
+ if (typeof nextTime === "number") {
2120
+ pendingTimeRef.current = null;
2121
+ lastTimeUpdateRef.current = nextTime;
2122
+ setCurrentTime(nextTime);
2123
+ }
2124
+ });
2125
+ };
2126
+ const scheduleBufferedUpdate = (bufferedProgress) => {
2127
+ pendingBufferedRef.current = bufferedProgress;
2128
+ if (bufferedRafRef.current !== null) {
2129
+ return;
2130
+ }
2131
+ bufferedRafRef.current = requestAnimationFrame(() => {
2132
+ bufferedRafRef.current = null;
2133
+ const nextBuffered = pendingBufferedRef.current;
2134
+ if (typeof nextBuffered === "number") {
2135
+ pendingBufferedRef.current = null;
2136
+ lastBufferedProgressRef.current = nextBuffered;
2137
+ setBufferedProgress(nextBuffered);
2138
+ }
2139
+ });
2140
+ };
1337
2141
  const onRightClick = (e) => {
1338
2142
  e.preventDefault();
1339
2143
  };
1340
2144
  const onSeeked = (e) => {
1341
- if (e?.currentTarget?.currentTime) {
1342
- setCurrentTime(e?.currentTarget?.currentTime);
2145
+ const time = e?.currentTarget?.currentTime;
2146
+ if (typeof time === "number" && !Number.isNaN(time)) {
2147
+ flushPendingTimeUpdate(time);
1343
2148
  }
1344
2149
  };
1345
2150
  const onTimeUpdate = (e) => {
1346
- if (e?.currentTarget?.currentTime) {
1347
- setCurrentTime(e?.currentTarget?.currentTime);
2151
+ const time = e?.currentTarget?.currentTime;
2152
+ if (typeof time === "number" && !Number.isNaN(time)) {
2153
+ if (Math.abs(time - lastTimeUpdateRef.current) >= 0.1 || time === 0) {
2154
+ // Reduce the frequency of global state updates and batch them on the next animation frame for smoother playback.
2155
+ scheduleTimeUpdate(time);
2156
+ }
1348
2157
  }
1349
2158
  };
1350
2159
  const onLoadedMetadata = (e) => {
1351
- if (e?.currentTarget?.duration) {
2160
+ const duration = e?.currentTarget?.duration;
2161
+ if (typeof duration === "number" && !Number.isNaN(duration)) {
1352
2162
  localStorage.setItem("current_time", "0");
1353
- setDuration(e?.currentTarget?.duration);
2163
+ setDuration(duration);
1354
2164
  }
1355
2165
  };
1356
2166
  const onProgress = (e) => {
@@ -1370,18 +2180,56 @@ const useVideoEvents = () => {
1370
2180
  bufferedEnd = video.buffered.end(video.buffered.length - 1);
1371
2181
  }
1372
2182
  const bufferedProgress = Math.min((bufferedEnd / video.duration) * 100, 100);
1373
- setBufferedProgress(bufferedProgress);
2183
+ if (Math.abs(bufferedProgress - lastBufferedProgressRef.current) >= 1) {
2184
+ // Skip tiny buffer deltas and dispatch the update on the next animation frame to avoid blocking the UI thread.
2185
+ scheduleBufferedUpdate(bufferedProgress);
2186
+ }
1374
2187
  }
1375
2188
  };
1376
2189
  const onPlay = () => {
1377
- setIsPlaying(true);
2190
+ const state = useVideoStore.getState();
2191
+ if (state.adVideoRef) {
2192
+ // Defensive guard: ensure any ad media tears down before the primary stream resumes so stray audio cannot continue.
2193
+ stopMediaElement(state.adVideoRef);
2194
+ state.setAdVideoRef(null);
2195
+ }
2196
+ if (state.isAdPlaying || state.currentAd) {
2197
+ state.setIsAdPlaying(false);
2198
+ state.setCurrentAd(null);
2199
+ state.setAdType(null);
2200
+ state.setAdCurrentTime(0);
2201
+ state.setCanSkipAd(false);
2202
+ state.setSkipCountdown(0);
2203
+ }
2204
+ if (!state.isPlaying) {
2205
+ setIsPlaying(true);
2206
+ }
1378
2207
  };
1379
2208
  const onPause = () => {
1380
- setIsPlaying(false);
2209
+ const state = useVideoStore.getState();
2210
+ if (state.isPlaying) {
2211
+ setIsPlaying(false);
2212
+ }
1381
2213
  };
1382
- const onEnded = () => {
1383
- setIsPlaying(false);
2214
+ const onEnded = (e) => {
2215
+ const state = useVideoStore.getState();
2216
+ if (state.isPlaying) {
2217
+ setIsPlaying(false);
2218
+ }
1384
2219
  };
2220
+ useEffect(() => {
2221
+ // Cancel any pending animation frame callbacks when the hook unmounts or dependencies change.
2222
+ return () => {
2223
+ if (timeUpdateRafRef.current !== null) {
2224
+ cancelAnimationFrame(timeUpdateRafRef.current);
2225
+ timeUpdateRafRef.current = null;
2226
+ }
2227
+ if (bufferedRafRef.current !== null) {
2228
+ cancelAnimationFrame(bufferedRafRef.current);
2229
+ bufferedRafRef.current = null;
2230
+ }
2231
+ };
2232
+ }, []);
1385
2233
  return {
1386
2234
  onRightClick,
1387
2235
  onSeeked,
@@ -1394,22 +2242,762 @@ const useVideoEvents = () => {
1394
2242
  };
1395
2243
  };
1396
2244
 
1397
- var css_248z = ".video-player video::cue {\n display: none !important;\n opacity: 0 !important;\n visibility: hidden !important;\n}\n\n.custom-subtitle-overlay {\n position: absolute;\n bottom: 10%;\n left: 50%;\n transform: translateX(-50%);\n\n font-size: 1.5rem;\n font-weight: 600;\n line-height: 1.4;\n text-align: center;\n\n color: #000;\n background: rgba(255, 255, 255, 0.6);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n\n padding: 10px 16px;\n border-radius: 10px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);\n\n max-width: 70%;\n min-width: fit-content;\n\n transition: all 0.2s ease-in-out;\n}\n\n.custom-subtitle-overlay:hover {\n transform: translateX(-50%) scale(1.02);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);\n}\n\n@media (max-width: 768px) {\n .custom-subtitle-overlay {\n font-size: 1.25rem;\n padding: 8px 14px;\n bottom: 8%;\n max-width: 85%;\n }\n}\n\n@media (max-width: 480px) {\n .custom-subtitle-overlay {\n font-size: 1rem;\n padding: 6px 10px;\n bottom: 6%;\n max-width: 90%;\n }\n}\n\n@media (prefers-contrast: high) {\n .custom-subtitle-overlay {\n background: #ffff00;\n color: #000;\n border: 3px solid #000;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-subtitle-overlay {\n transition: none;\n }\n\n .custom-subtitle-overlay:hover {\n transform: translateX(-50%);\n }\n}\n";
2245
+ const useAdManager = (adConfig) => {
2246
+ const { videoRef, setPlaying, setIsPlaying, currentTime, duration, isAdPlaying, setIsAdPlaying, currentAd, setCurrentAd, adType, setAdType, adVideoRef, setAdVideoRef, setAdCurrentTime, setCanSkipAd, setSkipCountdown, playedAdBreaks, addPlayedAdBreak, midRollQueue, setMidRollQueue, } = useVideoStore(useShallow((state) => ({
2247
+ videoRef: state.videoRef,
2248
+ setPlaying: state.setPlaying,
2249
+ setIsPlaying: state.setIsPlaying,
2250
+ currentTime: state.currentTime,
2251
+ duration: state.duration,
2252
+ isAdPlaying: state.isAdPlaying,
2253
+ setIsAdPlaying: state.setIsAdPlaying,
2254
+ currentAd: state.currentAd,
2255
+ setCurrentAd: state.setCurrentAd,
2256
+ adType: state.adType,
2257
+ setAdType: state.setAdType,
2258
+ adVideoRef: state.adVideoRef,
2259
+ setAdVideoRef: state.setAdVideoRef,
2260
+ setAdCurrentTime: state.setAdCurrentTime,
2261
+ setCanSkipAd: state.setCanSkipAd,
2262
+ setSkipCountdown: state.setSkipCountdown,
2263
+ playedAdBreaks: state.playedAdBreaks,
2264
+ addPlayedAdBreak: state.addPlayedAdBreak,
2265
+ midRollQueue: state.midRollQueue,
2266
+ setMidRollQueue: state.setMidRollQueue,
2267
+ })));
2268
+ const preRollPlayedRef = useRef(false);
2269
+ const postRollPlayedRef = useRef(false);
2270
+ const midRollCheckIntervalRef = useRef(null);
2271
+ const resumeAfterAdRef = useRef(false);
2272
+ const stopMediaElement = useCallback((media) => {
2273
+ if (!media)
2274
+ return;
2275
+ try {
2276
+ media.pause();
2277
+ }
2278
+ catch (_error) { }
2279
+ try {
2280
+ media.currentTime = 0;
2281
+ }
2282
+ catch (_error) { }
2283
+ media.removeAttribute("src");
2284
+ media.load();
2285
+ }, []);
2286
+ useEffect(() => {
2287
+ if (!adConfig?.midRoll || adConfig.midRoll.length === 0) {
2288
+ setMidRollQueue([]);
2289
+ return;
2290
+ }
2291
+ let sortedMidRolls = [...adConfig.midRoll].sort((a, b) => a.time - b.time);
2292
+ if (adConfig.smartPlacement?.enabled) {
2293
+ sortedMidRolls = applySmartPlacement(sortedMidRolls, duration, adConfig.smartPlacement);
2294
+ }
2295
+ setMidRollQueue(sortedMidRolls);
2296
+ }, [adConfig?.midRoll, adConfig?.smartPlacement, duration, setMidRollQueue]);
2297
+ const applySmartPlacement = (ads, videoDuration, options) => {
2298
+ const minVideoDuration = options.minVideoDuration || 60;
2299
+ const minGap = options.minGapBetweenAds || 30;
2300
+ const avoidNearEnd = options.avoidNearEnd || 10;
2301
+ if (videoDuration < minVideoDuration) {
2302
+ return [];
2303
+ }
2304
+ const validAds = [];
2305
+ let lastAdTime = 0;
2306
+ for (const ad of ads) {
2307
+ if (ad.time < minGap)
2308
+ continue;
2309
+ if (ad.time > videoDuration - avoidNearEnd)
2310
+ continue;
2311
+ if (ad.time - lastAdTime < minGap)
2312
+ continue;
2313
+ validAds.push(ad);
2314
+ const adLength = Number.isFinite(ad.duration)
2315
+ ? ad.duration
2316
+ : 0;
2317
+ lastAdTime = ad.time + adLength;
2318
+ }
2319
+ return validAds;
2320
+ };
2321
+ const playPreRollAd = async () => {
2322
+ if (!adConfig?.preRoll || preRollPlayedRef.current || !videoRef)
2323
+ return;
2324
+ const adBreak = adConfig.preRoll;
2325
+ preRollPlayedRef.current = true;
2326
+ resumeAfterAdRef.current = true;
2327
+ stopMediaElement(useVideoStore.getState().adVideoRef);
2328
+ videoRef.pause();
2329
+ setPlaying(false);
2330
+ setIsPlaying(false);
2331
+ setIsAdPlaying(true);
2332
+ setCurrentAd(adBreak);
2333
+ setAdType("pre-roll");
2334
+ adConfig.onAdStart?.(adBreak);
2335
+ };
2336
+ const playPostRollAd = async () => {
2337
+ if (!adConfig?.postRoll || postRollPlayedRef.current || !videoRef)
2338
+ return;
2339
+ const adBreak = adConfig.postRoll;
2340
+ postRollPlayedRef.current = true;
2341
+ resumeAfterAdRef.current = false;
2342
+ stopMediaElement(useVideoStore.getState().adVideoRef);
2343
+ setIsAdPlaying(true);
2344
+ setCurrentAd(adBreak);
2345
+ setAdType("post-roll");
2346
+ adConfig.onAdStart?.(adBreak);
2347
+ };
2348
+ const endAd = useCallback(() => {
2349
+ const currentAdState = useVideoStore.getState().currentAd;
2350
+ const adTypeState = useVideoStore.getState().adType;
2351
+ const videoRefState = useVideoStore.getState().videoRef;
2352
+ const adVideoRefState = useVideoStore.getState().adVideoRef;
2353
+ if (!videoRefState || !currentAdState)
2354
+ return;
2355
+ if (adVideoRefState) {
2356
+ stopMediaElement(adVideoRefState);
2357
+ setAdVideoRef(null);
2358
+ }
2359
+ setIsAdPlaying(false);
2360
+ setCurrentAd(null);
2361
+ setAdType(null);
2362
+ setAdCurrentTime(0);
2363
+ setCanSkipAd(false);
2364
+ setSkipCountdown(0);
2365
+ adConfig?.onAdEnd?.(currentAdState);
2366
+ if (resumeAfterAdRef.current && adTypeState !== "post-roll") {
2367
+ videoRefState.play().catch(() => undefined);
2368
+ setPlaying(true);
2369
+ setIsPlaying(true);
2370
+ }
2371
+ resumeAfterAdRef.current = false;
2372
+ }, [
2373
+ adConfig,
2374
+ setIsAdPlaying,
2375
+ setCurrentAd,
2376
+ setAdType,
2377
+ setAdCurrentTime,
2378
+ setCanSkipAd,
2379
+ setSkipCountdown,
2380
+ setAdVideoRef,
2381
+ setPlaying,
2382
+ setIsPlaying,
2383
+ stopMediaElement,
2384
+ ]);
2385
+ const skipAd = () => {
2386
+ if (!currentAd || !currentAd.skipable)
2387
+ return;
2388
+ adConfig?.onAdSkip?.(currentAd);
2389
+ endAd();
2390
+ };
2391
+ useEffect(() => {
2392
+ if (!adVideoRef || !isAdPlaying)
2393
+ return;
2394
+ const handleAdEnded = () => {
2395
+ endAd();
2396
+ };
2397
+ let forcedEndTriggered = false;
2398
+ const enforceTimedDuration = () => {
2399
+ if (!currentAd)
2400
+ return;
2401
+ const configuredDuration = Number(currentAd.duration);
2402
+ if (!Number.isFinite(configuredDuration) || configuredDuration <= 0) {
2403
+ return;
2404
+ }
2405
+ if (adVideoRef.currentTime >= configuredDuration) {
2406
+ if (adVideoRef.currentTime > configuredDuration) {
2407
+ try {
2408
+ adVideoRef.currentTime = configuredDuration;
2409
+ }
2410
+ catch (_error) { }
2411
+ }
2412
+ if (!forcedEndTriggered) {
2413
+ forcedEndTriggered = true;
2414
+ endAd();
2415
+ }
2416
+ }
2417
+ };
2418
+ adVideoRef.addEventListener("ended", handleAdEnded);
2419
+ adVideoRef.addEventListener("timeupdate", enforceTimedDuration);
2420
+ return () => {
2421
+ adVideoRef.removeEventListener("ended", handleAdEnded);
2422
+ adVideoRef.removeEventListener("timeupdate", enforceTimedDuration);
2423
+ };
2424
+ }, [adVideoRef, isAdPlaying, endAd, currentAd]);
2425
+ useEffect(() => {
2426
+ if (isAdPlaying || !adVideoRef) {
2427
+ return;
2428
+ }
2429
+ stopMediaElement(adVideoRef);
2430
+ setAdVideoRef(null);
2431
+ }, [isAdPlaying, adVideoRef, setAdVideoRef, stopMediaElement]);
2432
+ useEffect(() => {
2433
+ if (!videoRef || !adConfig?.preRoll || preRollPlayedRef.current)
2434
+ return;
2435
+ const handleCanPlay = () => {
2436
+ playPreRollAd();
2437
+ };
2438
+ videoRef.addEventListener("canplay", handleCanPlay, { once: true });
2439
+ if (videoRef.readyState >= 2) {
2440
+ playPreRollAd();
2441
+ }
2442
+ return () => {
2443
+ videoRef.removeEventListener("canplay", handleCanPlay);
2444
+ };
2445
+ }, [videoRef, adConfig?.preRoll]);
2446
+ useEffect(() => {
2447
+ if (!videoRef || !adConfig?.midRoll || isAdPlaying) {
2448
+ if (midRollCheckIntervalRef.current) {
2449
+ clearInterval(midRollCheckIntervalRef.current);
2450
+ midRollCheckIntervalRef.current = null;
2451
+ }
2452
+ return;
2453
+ }
2454
+ midRollCheckIntervalRef.current = setInterval(() => {
2455
+ const state = useVideoStore.getState();
2456
+ if (state.isAdPlaying ||
2457
+ !state.midRollQueue ||
2458
+ state.midRollQueue.length === 0) {
2459
+ return;
2460
+ }
2461
+ const nextAd = state.midRollQueue[0];
2462
+ if (nextAd &&
2463
+ state.currentTime >= nextAd.time &&
2464
+ !state.playedAdBreaks.includes(nextAd.id)) {
2465
+ const updatedQueue = state.midRollQueue.filter((ad) => ad.id !== nextAd.id);
2466
+ state.setMidRollQueue(updatedQueue);
2467
+ state.addPlayedAdBreak(nextAd.id);
2468
+ const wasPlaying = !state.videoRef?.paused;
2469
+ state.videoRef?.pause();
2470
+ state.setPlaying(false);
2471
+ state.setIsPlaying(false);
2472
+ resumeAfterAdRef.current = wasPlaying;
2473
+ state.setIsAdPlaying(true);
2474
+ state.setCurrentAd(nextAd);
2475
+ state.setAdType("mid-roll");
2476
+ adConfig?.onAdStart?.(nextAd);
2477
+ }
2478
+ }, 1000);
2479
+ return () => {
2480
+ if (midRollCheckIntervalRef.current) {
2481
+ clearInterval(midRollCheckIntervalRef.current);
2482
+ midRollCheckIntervalRef.current = null;
2483
+ }
2484
+ };
2485
+ }, [videoRef, isAdPlaying, adConfig]);
2486
+ useEffect(() => {
2487
+ if (!videoRef || !adConfig?.postRoll || postRollPlayedRef.current)
2488
+ return;
2489
+ const handleVideoEnded = () => {
2490
+ setTimeout(() => {
2491
+ playPostRollAd();
2492
+ }, 500);
2493
+ };
2494
+ videoRef.addEventListener("ended", handleVideoEnded);
2495
+ return () => {
2496
+ videoRef.removeEventListener("ended", handleVideoEnded);
2497
+ };
2498
+ }, [videoRef, adConfig?.postRoll]);
2499
+ useEffect(() => {
2500
+ if (!videoRef?.src)
2501
+ return;
2502
+ preRollPlayedRef.current = false;
2503
+ postRollPlayedRef.current = false;
2504
+ resumeAfterAdRef.current = false;
2505
+ setIsAdPlaying(false);
2506
+ setCurrentAd(null);
2507
+ setAdType(null);
2508
+ if (adConfig?.midRoll && adConfig.midRoll.length > 0) {
2509
+ const sortedMidRolls = [...adConfig.midRoll].sort((a, b) => a.time - b.time);
2510
+ setMidRollQueue(sortedMidRolls);
2511
+ }
2512
+ else {
2513
+ setMidRollQueue([]);
2514
+ }
2515
+ const lingeringAdRef = useVideoStore.getState().adVideoRef;
2516
+ if (lingeringAdRef) {
2517
+ stopMediaElement(lingeringAdRef);
2518
+ setAdVideoRef(null);
2519
+ }
2520
+ }, [
2521
+ videoRef?.src,
2522
+ adConfig?.midRoll,
2523
+ setIsAdPlaying,
2524
+ setCurrentAd,
2525
+ setAdType,
2526
+ setMidRollQueue,
2527
+ stopMediaElement,
2528
+ setAdVideoRef,
2529
+ ]);
2530
+ return {
2531
+ isAdPlaying,
2532
+ currentAd,
2533
+ adType,
2534
+ skipAd,
2535
+ endAd,
2536
+ };
2537
+ };
2538
+
2539
+ const usePrimaryVideoLifecycle = ({ hasPreRoll, trackSrc, }) => {
2540
+ const { videoRef, setVideoRef, isAdPlaying, currentAd, adType, setMuted, setPlaying, setIsPlaying, } = useVideoStore(useShallow((state) => ({
2541
+ videoRef: state.videoRef,
2542
+ setVideoRef: state.setVideoRef,
2543
+ isAdPlaying: state.isAdPlaying,
2544
+ currentAd: state.currentAd,
2545
+ adType: state.adType,
2546
+ setMuted: state.setMuted,
2547
+ setPlaying: state.setPlaying,
2548
+ setIsPlaying: state.setIsPlaying,
2549
+ })));
2550
+ const [initialAdStarted, setInitialAdStarted] = useState(!hasPreRoll);
2551
+ const [initialAdFinished, setInitialAdFinished] = useState(!hasPreRoll);
2552
+ const previousIsAdPlayingRef = useRef(isAdPlaying);
2553
+ useEffect(() => {
2554
+ if (hasPreRoll) {
2555
+ setInitialAdStarted(false);
2556
+ setInitialAdFinished(false);
2557
+ }
2558
+ else {
2559
+ setInitialAdStarted(true);
2560
+ setInitialAdFinished(true);
2561
+ }
2562
+ }, [hasPreRoll, trackSrc]);
2563
+ useEffect(() => {
2564
+ if (hasPreRoll &&
2565
+ !initialAdStarted &&
2566
+ isAdPlaying &&
2567
+ adType === "pre-roll") {
2568
+ setInitialAdStarted(true);
2569
+ }
2570
+ }, [hasPreRoll, initialAdStarted, isAdPlaying, adType]);
2571
+ useEffect(() => {
2572
+ const previouslyPlaying = previousIsAdPlayingRef.current;
2573
+ if (hasPreRoll &&
2574
+ initialAdStarted &&
2575
+ previouslyPlaying &&
2576
+ !isAdPlaying &&
2577
+ !initialAdFinished) {
2578
+ setInitialAdFinished(true);
2579
+ }
2580
+ previousIsAdPlayingRef.current = isAdPlaying;
2581
+ }, [hasPreRoll, initialAdStarted, initialAdFinished, isAdPlaying]);
2582
+ useEffect(() => {
2583
+ if (!videoRef) {
2584
+ return;
2585
+ }
2586
+ if (hasPreRoll && !initialAdFinished) {
2587
+ videoRef.pause();
2588
+ return;
2589
+ }
2590
+ }, [videoRef, hasPreRoll, initialAdFinished]);
2591
+ useEffect(() => {
2592
+ if (!videoRef)
2593
+ return;
2594
+ const syncMutedState = () => {
2595
+ setMuted(videoRef.muted);
2596
+ };
2597
+ syncMutedState();
2598
+ videoRef.addEventListener("volumechange", syncMutedState);
2599
+ return () => {
2600
+ videoRef.removeEventListener("volumechange", syncMutedState);
2601
+ };
2602
+ }, [videoRef, setMuted]);
2603
+ useEffect(() => {
2604
+ if (!videoRef)
2605
+ return;
2606
+ videoRef.preload = "auto";
2607
+ }, [videoRef]);
2608
+ useEffect(() => {
2609
+ const element = videoRef;
2610
+ return () => {
2611
+ if (!element)
2612
+ return;
2613
+ try {
2614
+ element.pause();
2615
+ }
2616
+ catch (_error) { }
2617
+ element.removeAttribute("src");
2618
+ element.load();
2619
+ };
2620
+ }, [videoRef]);
2621
+ useEffect(() => {
2622
+ if (!videoRef) {
2623
+ return;
2624
+ }
2625
+ if (hasPreRoll && !initialAdFinished) {
2626
+ return;
2627
+ }
2628
+ if (isAdPlaying) {
2629
+ return;
2630
+ }
2631
+ let cancelled = false;
2632
+ const markPlaying = () => {
2633
+ if (cancelled)
2634
+ return;
2635
+ setPlaying(true);
2636
+ setIsPlaying(true);
2637
+ };
2638
+ const attemptPlayback = () => {
2639
+ if (!videoRef || cancelled)
2640
+ return;
2641
+ if (!videoRef.paused) {
2642
+ markPlaying();
2643
+ return;
2644
+ }
2645
+ const playPromise = videoRef.play();
2646
+ if (playPromise && typeof playPromise.then === "function") {
2647
+ playPromise.then(markPlaying).catch((error) => {
2648
+ if (cancelled)
2649
+ return;
2650
+ const maybeNotAllowed = typeof error === "object" &&
2651
+ error !== null &&
2652
+ "name" in error &&
2653
+ error.name === "NotAllowedError";
2654
+ if (maybeNotAllowed && videoRef.muted === false) {
2655
+ videoRef.muted = true;
2656
+ const retryPromise = videoRef.play();
2657
+ if (retryPromise && typeof retryPromise.then === "function") {
2658
+ retryPromise.then(markPlaying).catch(() => {
2659
+ if (cancelled)
2660
+ return;
2661
+ setPlaying(false);
2662
+ setIsPlaying(false);
2663
+ });
2664
+ return;
2665
+ }
2666
+ }
2667
+ setPlaying(false);
2668
+ setIsPlaying(false);
2669
+ });
2670
+ }
2671
+ else {
2672
+ markPlaying();
2673
+ }
2674
+ };
2675
+ if (videoRef.readyState >= 2) {
2676
+ attemptPlayback();
2677
+ }
2678
+ else {
2679
+ const onCanPlay = () => {
2680
+ attemptPlayback();
2681
+ };
2682
+ videoRef.addEventListener("canplay", onCanPlay, { once: true });
2683
+ return () => {
2684
+ cancelled = true;
2685
+ videoRef.removeEventListener("canplay", onCanPlay);
2686
+ };
2687
+ }
2688
+ return () => {
2689
+ cancelled = true;
2690
+ };
2691
+ }, [
2692
+ videoRef,
2693
+ hasPreRoll,
2694
+ initialAdFinished,
2695
+ isAdPlaying,
2696
+ setPlaying,
2697
+ setIsPlaying,
2698
+ ]);
2699
+ useEffect(() => {
2700
+ if (!videoRef)
2701
+ return;
2702
+ const wrapper = useVideoStore.getState().videoWrapperRef;
2703
+ if (wrapper) {
2704
+ wrapper.dataset.ready = (!hasPreRoll || initialAdFinished).toString();
2705
+ }
2706
+ }, [videoRef, hasPreRoll, initialAdFinished]);
2707
+ const registerVideoRef = useCallback((node) => {
2708
+ setVideoRef(node);
2709
+ }, [setVideoRef]);
2710
+ const shouldCoverMainVideo = useMemo(() => hasPreRoll && !initialAdFinished, [hasPreRoll, initialAdFinished]);
2711
+ const shouldShowPlaceholder = useMemo(() => shouldCoverMainVideo && !isAdPlaying, [shouldCoverMainVideo, isAdPlaying]);
2712
+ return {
2713
+ registerVideoRef,
2714
+ videoRef,
2715
+ isAdPlaying,
2716
+ currentAd,
2717
+ adType,
2718
+ initialAdFinished,
2719
+ shouldCoverMainVideo,
2720
+ shouldShowPlaceholder,
2721
+ };
2722
+ };
2723
+
2724
+ const AdOverlay = ({ adBreak, onSkip, config }) => {
2725
+ const { adVideoRef, setAdVideoRef, adCurrentTime, setAdCurrentTime, canSkipAd, setCanSkipAd, skipCountdown, setSkipCountdown, videoRef, muted, setIsPlaying, } = useVideoStore();
2726
+ const [showControls, setShowControls] = useState(true);
2727
+ const [isHovered, setIsHovered] = useState(false);
2728
+ const [adDuration, setAdDuration] = useState(0);
2729
+ const [requiresInteraction, setRequiresInteraction] = useState(false);
2730
+ const controlsTimeoutRef = useRef(null);
2731
+ const safelySetCanSkipAd = useCallback((value) => {
2732
+ if (useVideoStore.getState().canSkipAd !== value) {
2733
+ setCanSkipAd(value);
2734
+ }
2735
+ }, [setCanSkipAd]);
2736
+ const safelySetSkipCountdown = useCallback((value) => {
2737
+ if (useVideoStore.getState().skipCountdown !== value) {
2738
+ setSkipCountdown(value);
2739
+ }
2740
+ }, [setSkipCountdown]);
2741
+ useEffect(() => {
2742
+ if (isHovered) {
2743
+ setShowControls(true);
2744
+ if (controlsTimeoutRef.current) {
2745
+ clearTimeout(controlsTimeoutRef.current);
2746
+ }
2747
+ return;
2748
+ }
2749
+ controlsTimeoutRef.current = setTimeout(() => {
2750
+ setShowControls(false);
2751
+ }, 3000);
2752
+ return () => {
2753
+ if (controlsTimeoutRef.current) {
2754
+ clearTimeout(controlsTimeoutRef.current);
2755
+ }
2756
+ };
2757
+ }, [isHovered]);
2758
+ const skipAfter = useMemo(() => {
2759
+ const rawSkipAfter = Number.isFinite(adBreak.skipAfter)
2760
+ ? Math.max(0, Number(adBreak.skipAfter))
2761
+ : 0;
2762
+ if (adDuration > 0) {
2763
+ return Math.min(rawSkipAfter, adDuration);
2764
+ }
2765
+ return rawSkipAfter;
2766
+ }, [adBreak.skipAfter, adDuration]);
2767
+ const sponsoredUrl = adBreak.sponsoredUrl;
2768
+ useEffect(() => {
2769
+ setAdDuration(0);
2770
+ setRequiresInteraction(false);
2771
+ }, [adBreak.id]);
2772
+ useEffect(() => {
2773
+ if (!adBreak.skipable) {
2774
+ safelySetCanSkipAd(false);
2775
+ safelySetSkipCountdown(0);
2776
+ return;
2777
+ }
2778
+ safelySetCanSkipAd(false);
2779
+ safelySetSkipCountdown(Math.max(Math.ceil(skipAfter), 0));
2780
+ if (skipAfter <= 0) {
2781
+ safelySetCanSkipAd(true);
2782
+ safelySetSkipCountdown(0);
2783
+ }
2784
+ }, [
2785
+ adBreak.id,
2786
+ adBreak.skipable,
2787
+ skipAfter,
2788
+ safelySetCanSkipAd,
2789
+ safelySetSkipCountdown,
2790
+ ]);
2791
+ const attemptAdPlayback = useCallback(() => {
2792
+ if (!adVideoRef)
2793
+ return;
2794
+ setRequiresInteraction(false);
2795
+ const playPromise = adVideoRef.play();
2796
+ if (playPromise && "catch" in playPromise) {
2797
+ playPromise.catch(() => {
2798
+ setRequiresInteraction(true);
2799
+ setIsPlaying(false);
2800
+ });
2801
+ }
2802
+ }, [adVideoRef, setIsPlaying]);
2803
+ useEffect(() => {
2804
+ if (!adVideoRef)
2805
+ return;
2806
+ const handleTimeUpdate = () => {
2807
+ const currentTime = adVideoRef.currentTime;
2808
+ setAdCurrentTime(currentTime);
2809
+ if (adBreak.skipable) {
2810
+ const remaining = skipAfter - currentTime;
2811
+ if (remaining <= 0) {
2812
+ safelySetCanSkipAd(true);
2813
+ safelySetSkipCountdown(0);
2814
+ }
2815
+ else {
2816
+ const remainingForDisplay = Math.max(Math.ceil(remaining), 0);
2817
+ safelySetSkipCountdown(remainingForDisplay);
2818
+ if (canSkipAd) {
2819
+ safelySetCanSkipAd(false);
2820
+ }
2821
+ }
2822
+ }
2823
+ };
2824
+ const handleLoadedMetadata = () => {
2825
+ const duration = Number.isFinite(adVideoRef.duration)
2826
+ ? adVideoRef.duration
2827
+ : 0;
2828
+ setAdDuration(duration);
2829
+ setIsPlaying(!adVideoRef.paused);
2830
+ attemptAdPlayback();
2831
+ };
2832
+ const handlePlay = () => {
2833
+ setIsPlaying(true);
2834
+ setRequiresInteraction(false);
2835
+ };
2836
+ const handlePause = () => {
2837
+ setIsPlaying(false);
2838
+ };
2839
+ const handleWaiting = () => {
2840
+ setIsPlaying(false);
2841
+ };
2842
+ const handlePlaying = () => {
2843
+ setIsPlaying(true);
2844
+ setRequiresInteraction(false);
2845
+ };
2846
+ const handleError = () => {
2847
+ setRequiresInteraction(true);
2848
+ setIsPlaying(false);
2849
+ };
2850
+ adVideoRef.addEventListener("timeupdate", handleTimeUpdate);
2851
+ adVideoRef.addEventListener("loadedmetadata", handleLoadedMetadata);
2852
+ adVideoRef.addEventListener("play", handlePlay);
2853
+ adVideoRef.addEventListener("pause", handlePause);
2854
+ adVideoRef.addEventListener("waiting", handleWaiting);
2855
+ adVideoRef.addEventListener("playing", handlePlaying);
2856
+ adVideoRef.addEventListener("error", handleError);
2857
+ return () => {
2858
+ adVideoRef.removeEventListener("timeupdate", handleTimeUpdate);
2859
+ adVideoRef.removeEventListener("loadedmetadata", handleLoadedMetadata);
2860
+ adVideoRef.removeEventListener("play", handlePlay);
2861
+ adVideoRef.removeEventListener("pause", handlePause);
2862
+ adVideoRef.removeEventListener("waiting", handleWaiting);
2863
+ adVideoRef.removeEventListener("playing", handlePlaying);
2864
+ adVideoRef.removeEventListener("error", handleError);
2865
+ };
2866
+ }, [
2867
+ adVideoRef,
2868
+ adBreak.skipable,
2869
+ skipAfter,
2870
+ canSkipAd,
2871
+ setAdCurrentTime,
2872
+ setIsPlaying,
2873
+ safelySetSkipCountdown,
2874
+ safelySetCanSkipAd,
2875
+ attemptAdPlayback,
2876
+ ]);
2877
+ useEffect(() => {
2878
+ if (!adVideoRef || !videoRef)
2879
+ return;
2880
+ if (adVideoRef.readyState === 0) {
2881
+ adVideoRef.volume = videoRef.volume;
2882
+ adVideoRef.muted = muted;
2883
+ adVideoRef.load();
2884
+ }
2885
+ const handleCanPlay = () => {
2886
+ if (adVideoRef && !adVideoRef.paused)
2887
+ return;
2888
+ attemptAdPlayback();
2889
+ };
2890
+ adVideoRef.addEventListener("canplay", handleCanPlay);
2891
+ if (adVideoRef.readyState >= 3) {
2892
+ attemptAdPlayback();
2893
+ }
2894
+ return () => {
2895
+ adVideoRef.removeEventListener("canplay", handleCanPlay);
2896
+ };
2897
+ }, [adVideoRef, videoRef, attemptAdPlayback]);
2898
+ useEffect(() => {
2899
+ if (adVideoRef) {
2900
+ adVideoRef.muted = muted;
2901
+ }
2902
+ }, [adVideoRef, muted]);
2903
+ const handleSkip = () => {
2904
+ if (canSkipAd && onSkip) {
2905
+ onSkip();
2906
+ }
2907
+ };
2908
+ const formatTime = (seconds) => {
2909
+ if (isNaN(seconds) || seconds < 0)
2910
+ return "0:00";
2911
+ const mins = Math.floor(seconds / 60);
2912
+ const secs = Math.floor(seconds % 60);
2913
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
2914
+ };
2915
+ const progressPercent = adDuration > 0 ? (adCurrentTime / adDuration) * 100 : 0;
2916
+ return (React__default.createElement("div", { className: "absolute inset-0 bg-black z-50 flex flex-col overflow-hidden transition-opacity duration-300", onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), onMouseMove: () => {
2917
+ setIsHovered(true);
2918
+ setShowControls(true);
2919
+ } },
2920
+ React__default.createElement("div", { className: "relative flex-1 w-full flex items-center justify-center" },
2921
+ React__default.createElement("video", { ref: (ref) => {
2922
+ if (ref && ref !== adVideoRef) {
2923
+ ref.muted = muted;
2924
+ setAdVideoRef(ref);
2925
+ if (ref.src !== adBreak.adUrl) {
2926
+ ref.src = adBreak.adUrl;
2927
+ ref.load();
2928
+ }
2929
+ }
2930
+ }, className: "w-full h-full object-contain", autoPlay: true, playsInline: true, muted: muted, preload: "auto", key: adBreak.id }),
2931
+ requiresInteraction && (React__default.createElement("div", { className: "absolute inset-0 flex items-center justify-center bg-black/60 backdrop-blur-sm" },
2932
+ React__default.createElement("button", { onClick: attemptAdPlayback, className: "px-5 py-3 rounded bg-white/20 text-white font-semibold border border-white/40 hover:bg-white/30 transition" }, "Tap to Play Ad")))),
2933
+ React__default.createElement("div", { className: `absolute inset-0 transition-all duration-300 ${showControls ? "opacity-100" : "opacity-0 pointer-events-none"}` },
2934
+ React__default.createElement("div", { className: "absolute inset-0 bg-linear-to-b from-black/80 via-transparent to-black/90 flex flex-col justify-between" },
2935
+ React__default.createElement("div", { className: "shrink-0 relative" },
2936
+ React__default.createElement(ControlsHeader, { config: {
2937
+ title: adBreak.title ||
2938
+ config?.config?.headerConfig?.config?.title ||
2939
+ "Advertisement",
2940
+ isTrailer: config?.config?.headerConfig?.config?.isTrailer,
2941
+ onClose: config?.config?.headerConfig?.config?.onClose,
2942
+ } })),
2943
+ React__default.createElement("div", { className: "flex-1 flex items-center justify-center" },
2944
+ React__default.createElement(MiddleControls, null)),
2945
+ React__default.createElement("div", { className: "shrink-0 relative" },
2946
+ adBreak.skipable && (React__default.createElement("div", { className: "px-10 pb-3 flex justify-end" },
2947
+ React__default.createElement("button", { onClick: handleSkip, disabled: !canSkipAd, className: `flex items-center gap-2 px-4 py-2 rounded transition-all duration-200 ${canSkipAd
2948
+ ? "bg-white/20 hover:bg-white/30 text-white cursor-pointer hover:scale-105 active:scale-95 shadow-md hover:shadow-lg border border-white/30 hover:border-white/50 backdrop-blur-md"
2949
+ : "bg-black/60 text-gray-400 cursor-not-allowed border border-gray-700/60"}`, style: { borderRadius: "4px" } },
2950
+ React__default.createElement(SkipForward, { className: "w-4 h-4" }),
2951
+ React__default.createElement("span", { className: "text-sm font-medium" }, canSkipAd
2952
+ ? "Skip Ad"
2953
+ : `Skip in ${Math.max(skipCountdown, 0)}s`)))),
2954
+ React__default.createElement("div", { className: "px-10 pb-4" },
2955
+ React__default.createElement("div", { className: "relative h-1 bg-white/20 rounded-full overflow-hidden pointer-events-none select-none" },
2956
+ React__default.createElement("div", { className: "absolute left-0 top-0 h-full bg-white rounded-full transition-all duration-300 ease-out", style: { width: `${progressPercent}%` } }),
2957
+ React__default.createElement("div", { className: "absolute top-1/2 -translate-y-1/2 w-3 h-3 bg-white rounded-full shadow-lg transition-all duration-300 ease-out pointer-events-none", style: { left: `calc(${progressPercent}% - 6px)` } }))),
2958
+ React__default.createElement("div", { className: "px-10 pb-6 flex items-center justify-between" },
2959
+ React__default.createElement("div", { className: "flex items-center gap-4 text-white" },
2960
+ React__default.createElement("span", { className: "text-lg lg:text-2xl font-semibold" }, formatTime(adCurrentTime)),
2961
+ React__default.createElement("span", { className: "text-lg lg:text-3xl font-semibold text-gray-500" }, "/"),
2962
+ React__default.createElement("span", { className: "text-lg lg:text-2xl font-semibold text-gray-400" }, formatTime(adDuration))),
2963
+ sponsoredUrl && (React__default.createElement("a", { href: sponsoredUrl, target: "_blank", rel: "noopener noreferrer", className: "text-sm font-semibold text-sky-300 hover:text-white transition-colors" }, "Learn More"))))))));
2964
+ };
2965
+
2966
+ var css_248z$1 = ".video-player video::cue {\n display: none !important;\n opacity: 0 !important;\n visibility: hidden !important;\n}\n\n.custom-subtitle-overlay {\n position: absolute;\n bottom: 10%;\n left: 50%;\n transform: translateX(-50%);\n\n font-size: 1.5rem;\n font-weight: 600;\n line-height: 1.4;\n text-align: center;\n\n color: #000;\n background: rgba(255, 255, 255, 0.6);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n\n padding: 10px 16px;\n border-radius: 10px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);\n\n max-width: 70%;\n min-width: fit-content;\n\n transition: all 0.2s ease-in-out;\n}\n\n.custom-subtitle-overlay:hover {\n transform: translateX(-50%) scale(1.02);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);\n}\n\n@media (max-width: 768px) {\n .custom-subtitle-overlay {\n font-size: 1.25rem;\n padding: 8px 14px;\n bottom: 8%;\n max-width: 85%;\n }\n}\n\n@media (max-width: 480px) {\n .custom-subtitle-overlay {\n font-size: 1rem;\n padding: 6px 10px;\n bottom: 6%;\n max-width: 90%;\n }\n}\n\n@media (prefers-contrast: high) {\n .custom-subtitle-overlay {\n background: #ffff00;\n color: #000;\n border: 3px solid #000;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-subtitle-overlay {\n transition: none;\n }\n\n .custom-subtitle-overlay:hover {\n transform: translateX(-50%);\n }\n}\n";
2967
+ styleInject(css_248z$1,{"insertAt":"top"});
2968
+
2969
+ var css_248z = "@keyframes fade-in {\n from {\n opacity: 0;\n transform: translateY(-10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes slide-up {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes pulse-glow {\n 0%,\n 100% {\n box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);\n }\n 50% {\n box-shadow: 0 0 20px rgba(239, 68, 68, 0.8);\n }\n}\n\n.animate-fade-in {\n animation: fade-in 0.3s ease-out;\n}\n\n.animate-slide-up {\n animation: slide-up 0.3s ease-out;\n}\n\n.ad-progress-bar {\n position: relative;\n overflow: hidden;\n}\n\n.ad-progress-bar::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: linear-gradient(\n 90deg,\n transparent,\n rgba(255, 255, 255, 0.3),\n transparent\n );\n animation: shimmer 2s infinite;\n}\n\n@keyframes shimmer {\n 0% {\n transform: translateX(-100%);\n }\n 100% {\n transform: translateX(100%);\n }\n}\n\n.ad-badge {\n background: linear-gradient(\n 135deg,\n rgba(239, 68, 68, 0.9),\n rgba(249, 115, 22, 0.9)\n );\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n}\n\n.ad-button {\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.ad-button:hover {\n transform: translateY(-2px);\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);\n}\n\n.ad-button:active {\n transform: translateY(0);\n}\n\n.skip-button-enabled {\n background: linear-gradient(\n 135deg,\n rgba(255, 255, 255, 0.2),\n rgba(255, 255, 255, 0.1)\n );\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n}\n\n.skip-button-enabled:hover {\n background: linear-gradient(\n 135deg,\n rgba(255, 255, 255, 0.3),\n rgba(255, 255, 255, 0.2)\n );\n box-shadow: 0 0 20px rgba(255, 255, 255, 0.2);\n}\n\n.skip-button-disabled {\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n}\n\n.loader {\n width: 64px;\n height: 64px;\n border-radius: 50%;\n display: inline-block;\n border-top: 3px solid #fff;\n border-right: 3px solid transparent;\n box-sizing: border-box;\n animation: rotation 1s linear infinite;\n}\n\n@keyframes rotation {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n@media (max-width: 768px) {\n .ad-controls {\n padding: 1rem;\n gap: 0.75rem;\n }\n\n .ad-badge {\n font-size: 0.625rem;\n padding: 0.5rem 0.75rem;\n }\n\n .skip-button {\n padding: 0.5rem 1rem;\n font-size: 0.75rem;\n }\n}\n\n@media (max-width: 480px) {\n .ad-controls {\n padding: 0.75rem;\n gap: 0.5rem;\n }\n\n .ad-badge {\n font-size: 0.5rem;\n padding: 0.375rem 0.625rem;\n }\n\n .skip-button {\n padding: 0.375rem 0.75rem;\n font-size: 0.625rem;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .animate-fade-in,\n .animate-slide-up,\n .ad-progress-bar::after {\n animation: none;\n }\n\n .ad-button {\n transition: none;\n }\n}\n\n@media (prefers-contrast: high) {\n .ad-badge {\n background: #000;\n border: 2px solid #fff;\n }\n\n .skip-button-enabled {\n background: #fff;\n color: #000;\n border: 2px solid #000;\n }\n}\n";
1398
2970
  styleInject(css_248z,{"insertAt":"top"});
1399
2971
 
1400
- const VideoPlayer = ({ trackSrc, trackTitle, intro, onClose, trackPoster, isTrailer, className, type, height, width, timeCodes, getPreviewScreenUrl, tracking, subtitles, episodeList, currentEpisodeIndex = 0, nextEpisodeConfig, subtitleStyle, }) => {
1401
- const { setVideoRef, setVideoWrapperRef, videoRef } = useVideoStore();
2972
+ const VideoPlayer = ({ trackSrc, trackTitle, intro, onClose, onError, trackPoster, isTrailer, className, type, height, width, timeCodes, getPreviewScreenUrl, tracking, subtitles, episodeList, currentEpisodeIndex = 0, onEnded, nextEpisodeConfig, subtitleStyle, showControls = true, isMute = false, ads, }) => {
2973
+ const { setVideoWrapperRef } = useVideoStore(useShallow((state) => ({
2974
+ setVideoWrapperRef: state.setVideoWrapperRef,
2975
+ })));
2976
+ const effectiveAds = React__default.useMemo(() => (isTrailer ? undefined : ads), [ads, isTrailer]);
2977
+ const hasPreRoll = React__default.useMemo(() => Boolean(effectiveAds?.preRoll), [effectiveAds?.preRoll]);
2978
+ const { registerVideoRef, videoRef, isAdPlaying, currentAd, initialAdFinished, shouldCoverMainVideo, shouldShowPlaceholder, } = usePrimaryVideoLifecycle({
2979
+ hasPreRoll,
2980
+ trackSrc,
2981
+ });
1402
2982
  useVideoSource(trackSrc, type);
1403
2983
  useSubtitles(subtitles);
1404
2984
  useSubtitleStyling(subtitleStyle);
1405
2985
  useVideoTracking(tracking, episodeList, currentEpisodeIndex, onClose);
1406
2986
  const { showSkipIntro, handleSkipIntro } = useIntroSkip(intro);
1407
2987
  useEpisodes(episodeList, currentEpisodeIndex, nextEpisodeConfig);
1408
- const { onSeeked, onTimeUpdate, onLoadedMetadata, onProgress, onPlay, onPause, onEnded } = useVideoEvents();
2988
+ const { onSeeked, onTimeUpdate, onLoadedMetadata, onProgress, onPlay, onPause, onEnded: onEndedHook, } = useVideoEvents();
2989
+ const { skipAd } = useAdManager(effectiveAds);
1409
2990
  return (React__default.createElement("div", { ref: setVideoWrapperRef, className: `video-player ${height || "h-full"} ${width || "w-full"} mx-auto absolute` },
1410
2991
  trackPoster && (React__default.createElement("div", { className: "pip-poster absolute inset-0 bg-center bg-cover hidden", style: { backgroundImage: `url(${trackPoster})` } })),
1411
- React__default.createElement("video", { autoPlay: true, playsInline: true, preload: "metadata", ref: setVideoRef, onSeeked: onSeeked, poster: trackPoster, crossOrigin: "anonymous", controls: false, disableRemotePlayback: true, controlsList: "nodownload", onContextMenu: (e) => e.preventDefault(), onTimeUpdate: onTimeUpdate, onLoadedMetadata: onLoadedMetadata, onProgress: onProgress, onPlay: onPlay, onPause: onPause, onEnded: onEnded, className: `w-full h-full relative ${className}` }),
1412
- React__default.createElement(Overlay, { config: {
2992
+ React__default.createElement("video", { playsInline: true, preload: hasPreRoll ? "metadata" : "auto", ref: registerVideoRef, onSeeked: onSeeked, poster: trackPoster, crossOrigin: "anonymous", controls: false, disableRemotePlayback: true, controlsList: "nodownload", onContextMenu: (e) => e.preventDefault(), onTimeUpdate: onTimeUpdate, onLoadedMetadata: onLoadedMetadata, onProgress: onProgress, onPlay: onPlay, onPause: onPause, onEnded: (e) => {
2993
+ onEndedHook(e);
2994
+ onEnded?.(e);
2995
+ }, onError: (e) => {
2996
+ onError?.(e);
2997
+ }, autoPlay: !hasPreRoll, muted: isMute, className: `w-full h-full relative ${className || ""} ${shouldCoverMainVideo ? "opacity-0" : "opacity-100"} transition-opacity duration-200 ease-out` }),
2998
+ shouldShowPlaceholder && (React__default.createElement("div", { className: "absolute inset-0 z-40 flex items-center justify-center bg-black" },
2999
+ React__default.createElement("span", { className: "loader" }))),
3000
+ showControls && initialAdFinished && (React__default.createElement(Overlay, { config: {
1413
3001
  headerConfig: {
1414
3002
  config: {
1415
3003
  isTrailer: isTrailer,
@@ -1427,9 +3015,29 @@ const VideoPlayer = ({ trackSrc, trackTitle, intro, onClose, trackPoster, isTrai
1427
3015
  },
1428
3016
  },
1429
3017
  },
1430
- } }),
3018
+ } })),
1431
3019
  React__default.createElement(SubtitleOverlay, { styleConfig: subtitleStyle }),
1432
- showSkipIntro && (React__default.createElement(VideoActionButton, { text: "Skip Intro", onClick: handleSkipIntro, position: "left" }))));
3020
+ showSkipIntro && !isAdPlaying && initialAdFinished && (React__default.createElement(VideoActionButton, { text: "Skip Intro", onClick: handleSkipIntro, position: "left" })),
3021
+ isAdPlaying && currentAd && (React__default.createElement(AdOverlay, { adBreak: currentAd, onSkip: skipAd, config: {
3022
+ config: {
3023
+ headerConfig: {
3024
+ config: {
3025
+ isTrailer: isTrailer,
3026
+ title: trackTitle,
3027
+ onClose: onClose,
3028
+ },
3029
+ },
3030
+ bottomConfig: {
3031
+ config: {
3032
+ seekBarConfig: {
3033
+ timeCodes: timeCodes,
3034
+ trackColor: "red",
3035
+ getPreviewScreenUrl,
3036
+ },
3037
+ },
3038
+ },
3039
+ },
3040
+ } }))));
1433
3041
  };
1434
3042
 
1435
3043
  export { VideoPlayer, useVideoStore };