@zezosoft/react-player 0.0.8 → 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 (38) hide show
  1. package/dist/VideoPlayer/VideoPlayer.d.ts +1 -0
  2. package/dist/VideoPlayer/components/AdOverlay.d.ts +10 -0
  3. package/dist/VideoPlayer/components/Overlay.d.ts +4 -0
  4. package/dist/VideoPlayer/components/SubtitleOverlay.d.ts +7 -0
  5. package/dist/VideoPlayer/components/controls/BottomControls.d.ts +5 -0
  6. package/dist/VideoPlayer/components/controls/ControlsHeader.d.ts +5 -0
  7. package/dist/VideoPlayer/components/controls/MiddleControls.d.ts +3 -0
  8. package/dist/VideoPlayer/components/controls/VideoPlayerControls.d.ts +4 -0
  9. package/dist/VideoPlayer/components/controls/index.d.ts +4 -0
  10. package/dist/VideoPlayer/components/time-line/TimeLine.d.ts +21 -0
  11. package/dist/VideoPlayer/components/time-line/components/HoverTimeWithPreview.d.ts +16 -0
  12. package/dist/VideoPlayer/components/time-line/components/Thumb.d.ts +9 -0
  13. package/dist/VideoPlayer/components/time-line/components/TimeCodeItem.d.ts +21 -0
  14. package/dist/VideoPlayer/components/time-line/components/TimeCodes.d.ts +15 -0
  15. package/dist/VideoPlayer/components/time-line/utils/getEndTimeByIndex.d.ts +2 -0
  16. package/dist/VideoPlayer/components/time-line/utils/getHoverTimePosition.d.ts +3 -0
  17. package/dist/VideoPlayer/components/time-line/utils/getPositionPercent.d.ts +1 -0
  18. package/dist/VideoPlayer/components/time-line/utils/getTimeScale.d.ts +1 -0
  19. package/dist/VideoPlayer/components/time-line/utils/isInRange.d.ts +1 -0
  20. package/dist/VideoPlayer/components/time-line/utils/positionToMs.d.ts +1 -0
  21. package/dist/VideoPlayer/components/time-line/utils/secondsToTime.d.ts +6 -0
  22. package/dist/VideoPlayer/components/time-line/utils/timeToTimeString.d.ts +1 -0
  23. package/dist/VideoPlayer/constants.d.ts +3 -0
  24. package/dist/VideoPlayer/hooks/index.d.ts +2 -0
  25. package/dist/VideoPlayer/hooks/useAdManager.d.ts +8 -0
  26. package/dist/VideoPlayer/hooks/usePrimaryVideoLifecycle.d.ts +17 -0
  27. package/dist/VideoPlayer/hooks/useVideoSource.d.ts +1 -14
  28. package/dist/VideoPlayer/types/AdTypes.d.ts +36 -0
  29. package/dist/VideoPlayer/types/VideoPlayerTypes.d.ts +4 -2
  30. package/dist/VideoPlayer/utils/index.d.ts +1 -1
  31. package/dist/VideoPlayer/utils/qualityManager.d.ts +6 -32
  32. package/dist/components/ui/FullScreenToggle.d.ts +1 -1
  33. package/dist/components/ui/PiPictureInPictureToggle.d.ts +1 -1
  34. package/dist/index.js +1784 -345
  35. package/dist/store/slices/adsSlice.d.ts +24 -0
  36. package/dist/store/slices/index.d.ts +1 -0
  37. package/dist/store/types/StoreTypes.d.ts +31 -9
  38. package/package.json +7 -6
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
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';
9
10
  import * as dashjs from 'dashjs';
@@ -35,8 +36,8 @@ function styleInject(css, ref) {
35
36
  }
36
37
  }
37
38
 
38
- 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 .visible {\n visibility: visible;\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-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 .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-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";
39
- 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"});
40
41
 
41
42
  const createVideoRefsSlice = (set) => ({
42
43
  videoRef: null,
@@ -72,19 +73,19 @@ const createVideoControlsSlice = (set) => ({
72
73
  setControls: (controls) => set({ controls }),
73
74
  isFullscreen: false,
74
75
  setIsFullscreen: (isFullscreen) => set({ isFullscreen }),
75
- controlsVisible: true,
76
- setControlsVisible: (visible) => set({ controlsVisible: visible }),
77
76
  });
78
77
 
79
78
  const createVideoQualitySlice = (set) => ({
80
- hlsInstance: undefined,
79
+ hlsInstance: null,
81
80
  setHlsInstance: (hlsInstance) => set({ hlsInstance }),
82
- dashInstance: undefined,
81
+ dashInstance: null,
83
82
  setDashInstance: (dashInstance) => set({ dashInstance }),
84
- qualityLevels: undefined,
83
+ qualityLevels: [],
85
84
  setQualityLevels: (qualityLevels) => set({ qualityLevels }),
86
85
  activeQuality: "auto",
87
86
  setActiveQuality: (activeQuality) => set({ activeQuality }),
87
+ currentQuality: "auto",
88
+ setCurrentQuality: (currentQuality) => set({ currentQuality }),
88
89
  streamType: "mp4",
89
90
  setStreamType: (streamType) => set({ streamType }),
90
91
  });
@@ -114,8 +115,54 @@ const createIntroSlice = (set) => ({
114
115
  setShowIntroSkip: (show) => set({ showIntroSkip: show }),
115
116
  });
116
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
+
117
141
  const createResetSlice = (set, get) => ({
118
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
+ }
119
166
  set({
120
167
  videoRef: null,
121
168
  videoWrapperRef: null,
@@ -130,6 +177,7 @@ const createResetSlice = (set, get) => ({
130
177
  controls: false,
131
178
  isFullscreen: false,
132
179
  hlsInstance: undefined,
180
+ dashInstance: undefined,
133
181
  qualityLevels: undefined,
134
182
  activeQuality: "auto",
135
183
  activeSubtitle: null,
@@ -140,6 +188,15 @@ const createResetSlice = (set, get) => ({
140
188
  countdownTime: 10,
141
189
  autoPlayNext: false,
142
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,
143
200
  });
144
201
  },
145
202
  });
@@ -153,9 +210,145 @@ const useVideoStore = create()((set, get, store) => ({
153
210
  ...createSubtitlesSlice(set),
154
211
  ...createEpisodesSlice(set),
155
212
  ...createIntroSlice(set),
156
- ...createResetSlice(set),
213
+ ...createAdsSlice(set),
214
+ ...createResetSlice(set, get),
157
215
  }));
158
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
+
159
352
  /**
160
353
  * @description
161
354
  * @param seconds
@@ -188,18 +381,25 @@ const secondsToMilliseconds = (seconds) => {
188
381
  * @returns
189
382
  */
190
383
  const getExtensionFromUrl = (url) => {
191
- 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();
192
389
  if (extension === "m3u8") {
193
390
  return "hls";
194
391
  }
195
392
  if (extension === "mpd") {
196
393
  return "dash";
197
394
  }
395
+ if (extension === "mp4") {
396
+ return "mp4";
397
+ }
198
398
  return extension;
199
399
  };
200
400
 
201
401
  function getPositionPercent(max, current) {
202
- const divider = max || -1; // prevent division by zero
402
+ const divider = max || -1;
203
403
  return (current * 100) / divider;
204
404
  }
205
405
 
@@ -247,7 +447,7 @@ const TimeCodes = ({ max = 1000, currentTime = 0, bufferTime = 0, seekHoverPosit
247
447
  if (label !== currentLabel) {
248
448
  setLabel(currentLabel);
249
449
  }
250
- }, [label]);
450
+ }, [label, setLabel]);
251
451
  useEffect(() => {
252
452
  if (!mobileSeeking) {
253
453
  return;
@@ -259,7 +459,7 @@ const TimeCodes = ({ max = 1000, currentTime = 0, bufferTime = 0, seekHoverPosit
259
459
  if (currentCode?.description !== label) {
260
460
  setLabel(currentCode?.description || "");
261
461
  }
262
- }, [currentTime, label, max, timeCodes]);
462
+ }, [currentTime, label, max, timeCodes, mobileSeeking, setLabel]);
263
463
  return (React__default.createElement(React__default.Fragment, null, timeCodes?.map(({ fromMs, description }, index) => {
264
464
  const endTime = getEndTimeByIndex(timeCodes, index, max);
265
465
  const isTimePassed = endTime <= currentTime;
@@ -336,12 +536,15 @@ const Thumb = ({ max, currentTime, isThumbActive, trackColor, }) => {
336
536
  left: `calc(${leftPosition}% + ${thumbConstantOffset}px)`,
337
537
  };
338
538
  };
339
- 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() },
340
540
  React__default.createElement("div", { className: "handler", style: {
341
541
  backgroundColor: trackColor || "#ff0000",
342
542
  } })));
343
543
  };
344
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
+
345
548
  const VideoSeekSlider = ({ max = 1000, currentTime = 0, bufferTime = 0, hideThumbTooltip = false, offset = 0, secondsPrefix = "", minutesPrefix = "", limitTimeTooltipBySides = true, timeCodes, onChange = () => undefined, getPreviewScreenUrl, trackColor, }) => {
346
549
  const [seekHoverPosition, setSeekHoverPosition] = useState(0);
347
550
  const [label, setLabel] = useState("");
@@ -432,39 +635,44 @@ const VideoSeekSlider = ({ max = 1000, currentTime = 0, bufferTime = 0, hideThum
432
635
  React__default.createElement(Thumb, { max: max, currentTime: currentTime, isThumbActive: isThumbActive, trackColor: trackColor })));
433
636
  };
434
637
 
435
- 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";
436
- styleInject(css_248z$2,{"insertAt":"top"});
437
-
438
638
  const BottomControls = ({ config }) => {
439
- const { videoRef, currentTime, isFullscreen, bufferedProgress } = useVideoStore();
440
- 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
+ }
441
666
  return (React__default.createElement("div", { className: "px-10" },
442
- React__default.createElement(VideoSeekSlider, { max: secondsToMilliseconds(duration || 0), currentTime: secondsToMilliseconds(currentTime || 0), bufferTime: secondsToMilliseconds((duration || 0) * (bufferedProgress / 100)), onChange: (currentTime) => {
443
- if (videoRef) {
444
- videoRef.currentTime = currentTime / 1000;
445
- }
446
- }, 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 }),
447
668
  React__default.createElement("div", { className: `pt-6 ${isFullscreen ? "pb-10" : "pb-16"} lg:pb-12 flex items-center gap-4 text-white` },
448
- 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),
449
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" }, "/"),
450
- 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)))));
451
- };
452
-
453
- const Tooltip = ({ children, title, position = "top", }) => {
454
- const [visible, setVisible] = useState(false);
455
- const positionStyles = {
456
- top: "bottom-full left-1/2 transform -translate-x-1/2 mb-2",
457
- bottom: "top-full left-1/2 transform -translate-x-1/2 mt-2",
458
- left: "right-full top-1/2 transform -translate-y-1/2 mr-2",
459
- right: "left-full top-1/2 transform -translate-y-1/2 ml-2",
460
- };
461
- return (React__default.createElement("div", { className: "relative inline-block cursor-pointer", onMouseEnter: () => setVisible(true), onMouseLeave: () => setVisible(false) },
462
- children,
463
- 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))));
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))));
464
672
  };
465
673
 
466
- 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";
467
- 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"});
468
676
 
469
677
  const FullScreenToggle = ({ isFullScreen, onClick, className = "fullscreen-icon", }) => {
470
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 },
@@ -531,8 +739,21 @@ const Popover = ({ button, children, closeOnButtonClick = false, className = "",
531
739
  children))));
532
740
  };
533
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
+
534
755
  const Settings = ({ iconClassName }) => {
535
- const { qualityLevels, hlsInstance, setActiveQuality, activeQuality, subtitles, activeSubtitle, setActiveSubtitle, videoRef, } = useVideoStore();
756
+ const { qualityLevels, activeQuality, currentQuality, subtitles, activeSubtitle, setActiveSubtitle, videoRef, streamType, } = useVideoStore();
536
757
  const [speed, setSpeed] = React.useState(1);
537
758
  const [activeMenu, setActiveMenu] = React.useState("main");
538
759
  const handleSpeedChange = (newSpeed) => {
@@ -541,37 +762,57 @@ const Settings = ({ iconClassName }) => {
541
762
  videoRef.playbackRate = newSpeed;
542
763
  }
543
764
  };
544
- const uniqueQualityLevels = React.useMemo(() => {
545
- if (!qualityLevels)
765
+ const isAdaptiveStream = streamType === "hls" || streamType === "dash";
766
+ const qualityOptions = React.useMemo(() => {
767
+ if (!qualityLevels || !isAdaptiveStream) {
546
768
  return [];
547
- const seenHeights = new Set();
548
- const unique = [];
549
- qualityLevels.forEach((level, originalIndex) => {
550
- if (seenHeights.has(level.height))
551
- return;
552
- seenHeights.add(level.height);
553
- unique.push({
554
- height: level.height,
555
- bitrate: level.bitrate,
556
- originalIndex,
557
- });
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;
558
786
  });
559
- return unique;
560
- }, [qualityLevels]);
787
+ }, [qualityLevels, isAdaptiveStream, streamType]);
561
788
  const speedOptions = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
562
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
+ };
563
805
  // Get quality label for display
564
806
  const getQualityLabel = () => {
565
- if (activeQuality === "auto")
807
+ if (!isAdaptiveStream)
566
808
  return "Auto";
567
- const level = uniqueQualityLevels.find((l) => String(l.height) === activeQuality);
568
- return level ? `${level.height}p` : "Auto";
569
- };
570
- // Get quality label: show explicit resolution to avoid duplicates
571
- const getQualityName = (height) => {
572
- if (!height || height <= 0)
809
+ if (currentQuality === "auto")
573
810
  return "Auto";
574
- 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;
575
816
  };
576
817
  // Get estimated data usage using bitrate when available
577
818
  const getDataUsage = (height, bitrate) => {
@@ -639,33 +880,28 @@ const Settings = ({ iconClassName }) => {
639
880
  React.createElement("h3", { className: "text-white font-bold text-xl" }, "Video Quality")),
640
881
  React.createElement("div", { className: "space-y-3" },
641
882
  React.createElement("button", { onClick: () => {
642
- if (hlsInstance) {
643
- hlsInstance.currentLevel = -1;
644
- setActiveQuality("auto");
883
+ if (isAdaptiveStream) {
884
+ QualityManager.setQuality(streamType, "auto");
645
885
  }
646
- }, 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"
647
887
  ? "bg-white/10"
648
- : "hover:bg-white/5"}` },
888
+ : isAdaptiveStream
889
+ ? "hover:bg-white/5"
890
+ : "opacity-50 cursor-not-allowed"}` },
649
891
  React.createElement("div", { className: "flex items-start justify-between" },
650
892
  React.createElement("div", null,
651
893
  React.createElement("div", { className: "text-white font-semibold text-lg mb-1" }, "Auto"),
652
894
  React.createElement("div", { className: "text-gray-400 text-sm" }, "Adjust to your connection")),
653
895
  activeQuality === "auto" && (React.createElement(Check, { className: "w-6 h-6 text-white mt-1" })))),
654
- uniqueQualityLevels
655
- .map((level) => (React.createElement("button", { key: level.originalIndex, onClick: () => {
656
- if (hlsInstance) {
657
- hlsInstance.currentLevel = level.originalIndex;
658
- setActiveQuality(String(level.height));
659
- }
660
- }, 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
661
897
  ? "bg-white/10"
662
898
  : "hover:bg-white/5"}` },
663
899
  React.createElement("div", { className: "flex items-start justify-between" },
664
900
  React.createElement("div", null,
665
- 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)),
666
902
  React.createElement("div", { className: "text-gray-400 text-sm" }, getDataUsage(level.height, level.bitrate))),
667
- activeQuality === String(level.height) && (React.createElement(Check, { className: "w-6 h-6 text-white mt-1" }))))))
668
- .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."))))),
669
905
  activeMenu === "subtitles" && (React.createElement("div", { className: "p-4" },
670
906
  React.createElement("div", { className: "flex items-center gap-3 mb-4" },
671
907
  React.createElement("button", { onClick: handleBack, className: "p-1 hover:bg-white/10 rounded-md transition-colors" },
@@ -692,7 +928,17 @@ const Settings = ({ iconClassName }) => {
692
928
 
693
929
  const ControlsHeader = ({ config }) => {
694
930
  const iconClassName = "icon-button";
695
- 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
+ })));
696
942
  const [isPipActive, setIsPipActive] = React.useState(false);
697
943
  const [isFullscreen, setIsFullscreen] = React.useState(false);
698
944
  const handleFullscreen = () => {
@@ -715,9 +961,17 @@ const ControlsHeader = ({ config }) => {
715
961
  };
716
962
  }, []);
717
963
  const handleMute = () => {
718
- if (videoRef) {
719
- 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;
720
970
  }
971
+ if (adVideoRef && adVideoRef.muted !== nextMuted) {
972
+ adVideoRef.muted = nextMuted;
973
+ }
974
+ setMuted(nextMuted);
721
975
  };
722
976
  const handlePipToggle = async () => {
723
977
  if (!videoRef)
@@ -732,9 +986,7 @@ const ControlsHeader = ({ config }) => {
732
986
  setIsPipActive(false);
733
987
  }
734
988
  }
735
- catch (error) {
736
- console.error("PiP toggle failed:", error);
737
- }
989
+ catch (_error) { }
738
990
  };
739
991
  React.useEffect(() => {
740
992
  const handlePipChange = () => setIsPipActive(!!document.pictureInPictureElement);
@@ -751,16 +1003,19 @@ const ControlsHeader = ({ config }) => {
751
1003
  config.onClose();
752
1004
  }
753
1005
  };
754
- return (React.createElement("div", { className: "flex items-center justify-between p-10 bg-gradient-to-b from-black" },
755
- React.createElement("div", { className: "flex" },
756
- React.createElement("div", null,
757
- React.createElement("h1", { className: "text-gray-200 text-lg lg:text-2xl font-semibold" }, episodeList.length > 0
758
- ? episodeList[currentEpisodeIndex]?.title
759
- : config?.title),
760
- 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(),
761
1016
  React.createElement("div", { className: "flex items-center gap-7 text-white" },
762
- React.createElement(Settings, { iconClassName: iconClassName }),
763
- 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" },
764
1019
  React.createElement(IoVolumeMuteOutline, { className: iconClassName }))) : (React.createElement(Tooltip, { title: "Mute" },
765
1020
  React.createElement(IoVolumeHighOutline, { className: iconClassName })))),
766
1021
  React.createElement(Tooltip, { title: isPipActive
@@ -770,9 +1025,9 @@ const ControlsHeader = ({ config }) => {
770
1025
  : "Fullscreen", className: `${iconClassName} ${isPipActive ? "opacity-50 cursor-not-allowed" : ""}` },
771
1026
  React.createElement("div", { onClick: handleFullscreen, className: isPipActive ? "pointer-events-none" : "" },
772
1027
  React.createElement(FullScreenToggle, { isFullScreen: isFullscreen, className: iconClassName }))),
773
- React.createElement(Tooltip, { className: "whitespace-nowrap", title: isPipActive ? "Exit PiP" : "Enter PiP" },
1028
+ !isAdPlaying && (React.createElement(Tooltip, { className: "whitespace-nowrap", title: isPipActive ? "Exit PiP" : "Enter PiP" },
774
1029
  React.createElement("div", { onClick: handlePipToggle },
775
- React.createElement(PiPictureInPictureToggle, { className: iconClassName }))),
1030
+ React.createElement(PiPictureInPictureToggle, { className: iconClassName })))),
776
1031
  config?.onClose && (React.createElement(React.Fragment, null,
777
1032
  React.createElement("div", { className: "w-[2px] h-10 bg-gray-500 hover:bg-gray-300 mx-2" }),
778
1033
  React.createElement("div", { onClick: handleClose },
@@ -780,35 +1035,71 @@ const ControlsHeader = ({ config }) => {
780
1035
  React.createElement(IoMdClose, { className: iconClassName }))))))));
781
1036
  };
782
1037
 
783
- const ControlButton = ({ onClick, icon, className, }) => (React__default.createElement("button", { onClick: onClick, className: `flex justify-center items-center h-full cursor-pointer ${className}` }, icon));
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";
784
1057
  const MiddleControls = () => {
785
- const { videoRef, isPlaying, setIsPlaying } = useVideoStore();
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
+ })));
786
1065
  const [isBuffering, setIsBuffering] = useState(false);
787
- const videoElement = videoRef;
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
+ }, []);
788
1073
  const handlePlayPause = useCallback(() => {
789
1074
  if (!videoElement)
790
1075
  return;
791
1076
  if (videoElement.paused) {
792
1077
  videoElement
793
1078
  .play()
794
- .then(() => setIsPlaying(true))
795
- .catch((err) => console.error("Error playing video:", err));
1079
+ .then(() => {
1080
+ setIsPlaying(true);
1081
+ resetControlsVisibility();
1082
+ })
1083
+ .catch(() => undefined);
796
1084
  }
797
1085
  else {
798
1086
  videoElement.pause();
799
1087
  setIsPlaying(false);
1088
+ resetControlsVisibility();
800
1089
  }
801
- }, [videoElement, setIsPlaying]);
1090
+ }, [videoElement, setIsPlaying, resetControlsVisibility]);
802
1091
  const handleBackward = useCallback(() => {
803
1092
  if (!videoElement)
804
1093
  return;
805
- videoElement.currentTime = Math.max(0, videoElement.currentTime - 10);
806
- }, [videoElement]);
1094
+ videoElement.currentTime = Math.max(0, videoElement.currentTime - SKIP_INTERVAL_SECONDS);
1095
+ resetControlsVisibility();
1096
+ }, [videoElement, resetControlsVisibility]);
807
1097
  const handleForward = useCallback(() => {
808
1098
  if (!videoElement)
809
1099
  return;
810
- videoElement.currentTime = Math.min(videoElement.duration, videoElement.currentTime + 10);
811
- }, [videoElement]);
1100
+ videoElement.currentTime = Math.min(videoElement.duration, videoElement.currentTime + SKIP_INTERVAL_SECONDS);
1101
+ resetControlsVisibility();
1102
+ }, [videoElement, resetControlsVisibility]);
812
1103
  useEffect(() => {
813
1104
  if (!videoElement)
814
1105
  return;
@@ -820,15 +1111,16 @@ const MiddleControls = () => {
820
1111
  videoElement.removeEventListener("waiting", handleWaiting);
821
1112
  videoElement.removeEventListener("playing", handlePlaying);
822
1113
  };
823
- }, [videoElement]);
1114
+ }, [videoElement, isAdPlaying]);
824
1115
  useEffect(() => {
825
1116
  const handleKeyDown = (e) => {
826
- if (!videoElement)
1117
+ if (!videoElement || isAdPlaying)
827
1118
  return;
828
1119
  switch (e.code) {
829
1120
  case "Space":
830
1121
  e.preventDefault();
831
1122
  handlePlayPause();
1123
+ resetControlsVisibility();
832
1124
  break;
833
1125
  case "ArrowLeft":
834
1126
  e.preventDefault();
@@ -842,15 +1134,21 @@ const MiddleControls = () => {
842
1134
  };
843
1135
  window.addEventListener("keydown", handleKeyDown);
844
1136
  return () => window.removeEventListener("keydown", handleKeyDown);
845
- }, [videoElement, handlePlayPause, handleBackward, handleForward]);
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
+ }
846
1148
  return (React__default.createElement("div", { className: "flex justify-center items-center" },
847
- React__default.createElement(ControlButton, { onClick: handleBackward, className: "w-[15vw]", icon: React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
848
- 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" })) }),
849
- 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("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: "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" }))) : (React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
851
- 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" }))) }),
852
- React__default.createElement(ControlButton, { onClick: handleForward, className: "w-[15vw]", icon: 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", { 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) })));
854
1152
  };
855
1153
 
856
1154
  const VideoPlayerControls = ({ config }) => {
@@ -876,29 +1174,40 @@ const VideoActionButton = ({ text, onClick, icon, disabled = false, position = "
876
1174
 
877
1175
  const Overlay = ({ config }) => {
878
1176
  const controlsTimerRef = useRef(null);
879
- 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
+ })));
880
1192
  const { onClose } = config?.headerConfig?.config || {};
881
- const HIDE_DELAY = 2000;
1193
+ const hideControls = useCallback(() => {
1194
+ setControls(false);
1195
+ containerRef.current?.classList.add("noCursor");
1196
+ }, [setControls]);
882
1197
  const resetControlsTimer = useCallback(() => {
883
1198
  if (controlsTimerRef.current) {
884
1199
  clearTimeout(controlsTimerRef.current);
885
1200
  }
886
- controlsTimerRef.current = setTimeout(() => {
887
- setControls(false);
888
- const videoPlayerControls = document?.getElementById("videoPlayerControls");
889
- if (videoPlayerControls) {
890
- videoPlayerControls.classList.add("noCursor");
891
- }
892
- }, HIDE_DELAY);
893
- }, [setControls]);
894
- const handleMouseEnter = useCallback(() => {
895
- const videoPlayerControls = document?.getElementById("videoPlayerControls");
896
- if (videoPlayerControls) {
897
- videoPlayerControls.classList.remove("noCursor");
898
- }
1201
+ controlsTimerRef.current = setTimeout(hideControls, CONTROLS_HIDE_DELAY_MS);
1202
+ }, [hideControls]);
1203
+ const handleControlsInteraction = useCallback(() => {
1204
+ containerRef.current?.classList.remove("noCursor");
899
1205
  setControls(true);
900
1206
  resetControlsTimer();
901
- }, [setControls, resetControlsTimer]);
1207
+ }, [resetControlsTimer, setControls]);
1208
+ const handleMouseEnter = useCallback(() => {
1209
+ handleControlsInteraction();
1210
+ }, [handleControlsInteraction]);
902
1211
  useEffect(() => {
903
1212
  return () => {
904
1213
  if (controlsTimerRef.current) {
@@ -918,29 +1227,38 @@ const Overlay = ({ config }) => {
918
1227
  clearInterval(timer);
919
1228
  };
920
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]);
921
1242
  const handleNextEpisodeManually = () => {
922
1243
  const nextIndex = currentEpisodeIndex + 1;
923
1244
  if (nextIndex < episodeList.length && videoRef && episodeList[nextIndex]) {
924
1245
  setCurrentEpisodeIndex(nextIndex);
925
1246
  setAutoPlayNext(true);
926
1247
  videoRef.src = episodeList[nextIndex].url;
927
- videoRef
928
- .play()
929
- .catch((err) => console.error("Manual play failed:", err));
1248
+ videoRef.play().catch(() => undefined);
930
1249
  setShowCountdown(false);
931
1250
  setCountdownTime(10);
932
- setControls(true);
933
- resetControlsTimer();
1251
+ handleControlsInteraction();
934
1252
  }
935
1253
  else if (onClose) {
936
1254
  onClose();
937
1255
  }
938
1256
  };
939
- return (React.createElement("div", { id: "videoPlayerControls", className: "absolute inset-0", onMouseMove: handleMouseEnter },
940
- 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 }),
941
1259
  showCountdown &&
942
1260
  episodeList.length > 0 &&
943
- 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" }))));
944
1262
  };
945
1263
 
946
1264
  const SubtitleOverlay = ({ styleConfig }) => {
@@ -958,13 +1276,7 @@ const SubtitleOverlay = ({ styleConfig }) => {
958
1276
  }
959
1277
  const currentTime = videoRef.currentTime;
960
1278
  const textTracks = Array.from(videoRef.textTracks);
961
- console.log("Available text tracks:", textTracks.map((t) => ({
962
- label: t.label,
963
- mode: t.mode,
964
- cues: t.cues?.length,
965
- })));
966
1279
  const activeTrack = textTracks.find((track) => track.mode === "showing" && track.label === activeSubtitle.label);
967
- console.log("Active track found:", !!activeTrack, "Current time:", currentTime);
968
1280
  if (activeTrack && activeTrack.cues) {
969
1281
  const activeCues = Array.from(activeTrack.cues).filter((cue) => currentTime >= cue.startTime && currentTime <= cue.endTime);
970
1282
  if (activeCues.length > 0) {
@@ -983,8 +1295,7 @@ const SubtitleOverlay = ({ styleConfig }) => {
983
1295
  cueText = cue.toString() || "";
984
1296
  }
985
1297
  }
986
- catch (error) {
987
- console.warn("Error getting subtitle text:", error);
1298
+ catch (_error) {
988
1299
  cueText = "";
989
1300
  }
990
1301
  setCurrentSubtitle(cueText);
@@ -1057,195 +1368,455 @@ const SubtitleOverlay = ({ styleConfig }) => {
1057
1368
  return React__default.createElement("div", { style: subtitleStyle }, currentSubtitle);
1058
1369
  };
1059
1370
 
1060
- /**
1061
- * Video Source Hook
1062
- *
1063
- * Manages video source loading and streaming technology detection
1064
- * Supports HLS.js, DASH.js, and native HTML5 video
1065
- *
1066
- * Features:
1067
- * - Automatic stream type detection
1068
- * - HLS.js fallback for older browsers
1069
- * - DASH.js support with proper initialization
1070
- * - Quality level extraction for all stream types
1071
- * - Error handling and cleanup
1072
- */
1073
- const useVideoSource = (trackSrc, type) => {
1074
- const { videoRef, setQualityLevels, setHlsInstance, setDashInstance, setStreamType } = 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);
1075
1445
  useEffect(() => {
1076
- if (!videoRef)
1077
- return;
1078
- const getVideoExtension = getExtensionFromUrl(trackSrc);
1079
- const contentType = type || getVideoExtension;
1080
- // Set stream type in store for quality manager
1081
- setStreamType(contentType);
1082
- // Handle MP4 and other simple formats
1083
- if (contentType === "mp4" || contentType === "other") {
1084
- videoRef.src = trackSrc;
1085
- setQualityLevels([]);
1446
+ if (!enabled || !videoElement) {
1086
1447
  return;
1087
1448
  }
1088
- // Handle HLS streams
1089
- if (contentType === "hls") {
1090
- // Native HLS support (Safari/iOS)
1091
- if (videoRef?.canPlayType("application/vnd.apple.mpegurl")) {
1092
- console.log('📱 Using native HLS support');
1093
- videoRef.src = trackSrc;
1094
- // For native HLS, we can't control quality directly, but we can still extract info
1095
- const handleLoadedMetadata = () => {
1096
- const videoElement = videoRef;
1097
- if (videoElement.videoTracks && videoElement.videoTracks.length > 0) {
1098
- // Extract quality levels from native HLS
1099
- const tracks = Array.from(videoElement.videoTracks).map((track, index) => ({
1100
- height: track.height || 720,
1101
- bitrate: track.bandwidth || 0,
1102
- originalIndex: index
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,
1103
1470
  }));
1104
- setQualityLevels(tracks);
1105
- console.log('✅ Native HLS quality levels:', tracks);
1106
- }
1107
- else {
1108
- // Fallback quality levels for native HLS
1109
- const defaultLevels = [
1110
- { height: 360, bitrate: 800000, originalIndex: 0 },
1111
- { height: 480, bitrate: 1400000, originalIndex: 1 },
1112
- { height: 720, bitrate: 2800000, originalIndex: 2 },
1113
- { height: 1080, bitrate: 5000000, originalIndex: 3 },
1114
- ];
1115
- setQualityLevels(defaultLevels);
1116
- console.log('✅ Native HLS fallback quality levels:', defaultLevels);
1471
+ setQualityLevels(levels);
1117
1472
  }
1118
- // Even for native HLS, set a mock HLS instance to indicate it's HLS
1119
- // This allows the quality manager to know we're dealing with HLS
1120
- setHlsInstance(null); // null indicates native HLS, not HLS.js
1121
- };
1122
- videoRef.addEventListener('loadedmetadata', handleLoadedMetadata);
1123
- return () => {
1124
- videoRef.removeEventListener('loadedmetadata', handleLoadedMetadata);
1125
- };
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
+ }
1126
1517
  }
1127
- // HLS.js support (Chrome/Firefox/etc)
1128
- else if (Hls.isSupported()) {
1129
- console.log('🔧 Using HLS.js for HLS streaming');
1130
- const hls = new Hls({
1131
- // HLS.js configuration for optimal performance
1132
- enableWorker: true,
1133
- lowLatencyMode: true,
1134
- backBufferLength: 90
1135
- });
1136
- hls.loadSource(trackSrc);
1137
- hls.attachMedia(videoRef);
1138
- console.log('✅ HLS.js instance created and attached');
1139
- setHlsInstance(hls);
1140
- // Extract quality levels when manifest is parsed
1141
- hls.on(Hls.Events.MANIFEST_PARSED, () => {
1142
- const levels = hls.levels.map((level, index) => ({
1143
- height: level.height,
1144
- bitrate: level.bitrate,
1145
- originalIndex: index
1146
- }));
1147
- setQualityLevels(levels);
1148
- console.log('✅ HLS.js quality levels:', levels);
1149
- });
1150
- // Log level switches for debugging
1151
- hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
1152
- console.log('🔄 HLS level switched to:', data.level, hls.levels?.[data.level]);
1153
- });
1154
- // Error handling
1155
- hls.on(Hls.Events.ERROR, (event, data) => {
1156
- console.error('❌ HLS.js error:', data);
1157
- });
1158
- // Cleanup
1159
- return () => {
1160
- hls.destroy();
1161
- console.log('🧹 HLS.js instance destroyed');
1162
- };
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}`);
1163
1529
  }
1164
- else {
1165
- // Fallback when HLS.js is not supported
1166
- console.log('📱 Using fallback HLS (direct src)');
1167
- videoRef.src = trackSrc;
1168
- setHlsInstance(null); // null indicates native HLS fallback
1169
- // Set fallback quality levels
1170
- const defaultLevels = [
1171
- { height: 360, bitrate: 800000, originalIndex: 0 },
1172
- { height: 480, bitrate: 1400000, originalIndex: 1 },
1173
- { height: 720, bitrate: 2800000, originalIndex: 2 },
1174
- { height: 1080, bitrate: 5000000, originalIndex: 3 },
1175
- ];
1176
- setQualityLevels(defaultLevels);
1177
- console.log('✅ HLS fallback quality levels:', defaultLevels);
1178
- }
1179
- }
1180
- // Handle DASH streams
1181
- else if (contentType === "dash") {
1182
- // DASH.js support
1183
- if (dashjs.supportsMediaSource()) {
1184
- console.log('🔧 Using DASH.js for DASH streaming');
1185
- const player = dashjs.MediaPlayer().create();
1186
- // DASH.js configuration for optimal performance
1187
- player.updateSettings({
1188
- streaming: {
1189
- buffer: {
1190
- fastSwitchEnabled: true, // Enable fast quality switching
1191
- bufferTimeAtTopQuality: 30, // Buffer 30s at top quality
1192
- bufferTimeAtTopQualityLongForm: 60 // Buffer 60s for long content
1193
- },
1194
- // Note: Some ABR settings may vary by DASH.js version
1195
- // Check documentation for your specific version
1196
- }
1197
- });
1198
- player.initialize(videoRef, trackSrc, true);
1199
- console.log('✅ DASH.js instance created and initialized');
1200
- setDashInstance(player);
1201
- // Extract quality levels when manifest is loaded
1202
- const handleManifestLoaded = () => {
1203
- try {
1204
- const representations = player.getRepresentationsByType('video');
1205
- if (representations && representations.length > 0) {
1206
- const levels = representations.map((rep, index) => ({
1207
- height: rep.height || Math.round(rep.bandwidth / 1000) || 0,
1208
- bitrate: rep.bandwidth,
1209
- originalIndex: index,
1210
- id: rep.id
1211
- }));
1212
- setQualityLevels(levels);
1213
- console.log('✅ DASH.js quality levels:', levels);
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();
1214
1559
  }
1215
- else {
1216
- console.warn('⚠️ No DASH video representations found');
1217
- setQualityLevels([]);
1560
+ catch (_err) {
1561
+ scheduleRestart();
1218
1562
  }
1219
- }
1220
- catch (error) {
1221
- console.error('❌ Error getting DASH quality levels:', error);
1222
- setQualityLevels([]);
1223
- }
1224
- };
1225
- // Listen for manifest loaded event
1226
- player.on('manifestLoaded', handleManifestLoaded);
1227
- // Log quality changes for debugging
1228
- player.on('qualityChange', (e) => {
1229
- console.log('🔄 DASH quality changed to:', e.newQuality, e);
1230
- });
1231
- // Error handling
1232
- player.on('error', (e) => {
1233
- console.error('❌ DASH.js error:', e);
1234
- });
1235
- // Cleanup
1236
- return () => {
1237
- player.reset();
1238
- console.log('🧹 DASH.js instance reset');
1239
- };
1563
+ break;
1564
+ default:
1565
+ clearRetryTimer();
1566
+ hls.destroy();
1567
+ setHlsInstance(null);
1568
+ break;
1569
+ }
1240
1570
  }
1241
- else {
1242
- console.warn('⚠️ DASH.js not supported in this browser');
1571
+ else if (data.type === (HLS_ERROR_TYPES.NETWORK_ERROR ?? "networkError")) {
1572
+ scheduleRestart();
1243
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;
1244
1608
  }
1245
- // Fallback for unsupported formats
1246
- videoRef.src = trackSrc;
1609
+ if (!dashjs.supportsMediaSource()) {
1610
+ setDashInstance(null);
1611
+ setQualityLevels([]);
1612
+ videoElement.src = source;
1613
+ videoElement.load();
1614
+ return;
1615
+ }
1616
+ restartCountRef.current = 0;
1247
1617
  setQualityLevels([]);
1248
- }, [trackSrc, videoRef, type, setQualityLevels, setHlsInstance, setDashInstance, setStreamType]);
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;
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);
1775
+ }
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") {
1784
+ videoRef.src = trackSrc;
1785
+ videoRef.load();
1786
+ }
1787
+ else {
1788
+ // Adaptive engines will attach their own source; ensure no stale src lingers
1789
+ videoRef.removeAttribute("src");
1790
+ }
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]);
1249
1820
  };
1250
1821
 
1251
1822
  const useSubtitles = (subtitles) => {
@@ -1274,7 +1845,6 @@ const useSubtitles = (subtitles) => {
1274
1845
  const textTrack = Array.from(videoRef.textTracks).find((track) => track.label === activeSubtitle.label);
1275
1846
  if (textTrack) {
1276
1847
  textTrack.mode = "showing";
1277
- console.log("Subtitle track loaded for custom rendering:", activeSubtitle.label);
1278
1848
  }
1279
1849
  };
1280
1850
  trackElement.addEventListener("load", handleTrackLoad);
@@ -1497,24 +2067,100 @@ const useEpisodes = (episodeList, currentEpisodeIndex, nextEpisodeConfig) => {
1497
2067
  };
1498
2068
 
1499
2069
  const useVideoEvents = () => {
1500
- 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
+ };
1501
2141
  const onRightClick = (e) => {
1502
2142
  e.preventDefault();
1503
2143
  };
1504
2144
  const onSeeked = (e) => {
1505
- if (e?.currentTarget?.currentTime) {
1506
- setCurrentTime(e?.currentTarget?.currentTime);
2145
+ const time = e?.currentTarget?.currentTime;
2146
+ if (typeof time === "number" && !Number.isNaN(time)) {
2147
+ flushPendingTimeUpdate(time);
1507
2148
  }
1508
2149
  };
1509
2150
  const onTimeUpdate = (e) => {
1510
- if (e?.currentTarget?.currentTime) {
1511
- 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
+ }
1512
2157
  }
1513
2158
  };
1514
2159
  const onLoadedMetadata = (e) => {
1515
- if (e?.currentTarget?.duration) {
2160
+ const duration = e?.currentTarget?.duration;
2161
+ if (typeof duration === "number" && !Number.isNaN(duration)) {
1516
2162
  localStorage.setItem("current_time", "0");
1517
- setDuration(e?.currentTarget?.duration);
2163
+ setDuration(duration);
1518
2164
  }
1519
2165
  };
1520
2166
  const onProgress = (e) => {
@@ -1534,18 +2180,56 @@ const useVideoEvents = () => {
1534
2180
  bufferedEnd = video.buffered.end(video.buffered.length - 1);
1535
2181
  }
1536
2182
  const bufferedProgress = Math.min((bufferedEnd / video.duration) * 100, 100);
1537
- 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
+ }
1538
2187
  }
1539
2188
  };
1540
2189
  const onPlay = () => {
1541
- 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
+ }
1542
2207
  };
1543
2208
  const onPause = () => {
1544
- setIsPlaying(false);
2209
+ const state = useVideoStore.getState();
2210
+ if (state.isPlaying) {
2211
+ setIsPlaying(false);
2212
+ }
1545
2213
  };
1546
2214
  const onEnded = (e) => {
1547
- setIsPlaying(false);
2215
+ const state = useVideoStore.getState();
2216
+ if (state.isPlaying) {
2217
+ setIsPlaying(false);
2218
+ }
1548
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
+ }, []);
1549
2233
  return {
1550
2234
  onRightClick,
1551
2235
  onSeeked,
@@ -1558,11 +2242,743 @@ const useVideoEvents = () => {
1558
2242
  };
1559
2243
  };
1560
2244
 
1561
- 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";
1562
2970
  styleInject(css_248z,{"insertAt":"top"});
1563
2971
 
1564
- 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, }) => {
1565
- 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
+ });
1566
2982
  useVideoSource(trackSrc, type);
1567
2983
  useSubtitles(subtitles);
1568
2984
  useSubtitleStyling(subtitleStyle);
@@ -1570,15 +2986,18 @@ const VideoPlayer = ({ trackSrc, trackTitle, intro, onClose, onError, trackPoste
1570
2986
  const { showSkipIntro, handleSkipIntro } = useIntroSkip(intro);
1571
2987
  useEpisodes(episodeList, currentEpisodeIndex, nextEpisodeConfig);
1572
2988
  const { onSeeked, onTimeUpdate, onLoadedMetadata, onProgress, onPlay, onPause, onEnded: onEndedHook, } = useVideoEvents();
2989
+ const { skipAd } = useAdManager(effectiveAds);
1573
2990
  return (React__default.createElement("div", { ref: setVideoWrapperRef, className: `video-player ${height || "h-full"} ${width || "w-full"} mx-auto absolute` },
1574
2991
  trackPoster && (React__default.createElement("div", { className: "pip-poster absolute inset-0 bg-center bg-cover hidden", style: { backgroundImage: `url(${trackPoster})` } })),
1575
- 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: (e) => {
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) => {
1576
2993
  onEndedHook(e);
1577
2994
  onEnded?.(e);
1578
2995
  }, onError: (e) => {
1579
2996
  onError?.(e);
1580
- }, muted: isMute, className: `w-full h-full relative ${className}` }),
1581
- showControls && (React__default.createElement(Overlay, { config: {
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: {
1582
3001
  headerConfig: {
1583
3002
  config: {
1584
3003
  isTrailer: isTrailer,
@@ -1598,7 +3017,27 @@ const VideoPlayer = ({ trackSrc, trackTitle, intro, onClose, onError, trackPoste
1598
3017
  },
1599
3018
  } })),
1600
3019
  React__default.createElement(SubtitleOverlay, { styleConfig: subtitleStyle }),
1601
- 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
+ } }))));
1602
3041
  };
1603
3042
 
1604
3043
  export { VideoPlayer, useVideoStore };