@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.
- package/dist/VideoPlayer/VideoPlayer.d.ts +1 -0
- package/dist/VideoPlayer/components/AdOverlay.d.ts +10 -0
- package/dist/VideoPlayer/components/Overlay.d.ts +4 -0
- package/dist/VideoPlayer/components/SubtitleOverlay.d.ts +7 -0
- package/dist/VideoPlayer/components/controls/BottomControls.d.ts +5 -0
- package/dist/VideoPlayer/components/controls/ControlsHeader.d.ts +5 -0
- package/dist/VideoPlayer/components/controls/MiddleControls.d.ts +3 -0
- package/dist/VideoPlayer/components/controls/VideoPlayerControls.d.ts +4 -0
- package/dist/VideoPlayer/components/controls/index.d.ts +4 -0
- package/dist/VideoPlayer/components/time-line/TimeLine.d.ts +21 -0
- package/dist/VideoPlayer/components/time-line/components/HoverTimeWithPreview.d.ts +16 -0
- package/dist/VideoPlayer/components/time-line/components/Thumb.d.ts +9 -0
- package/dist/VideoPlayer/components/time-line/components/TimeCodeItem.d.ts +21 -0
- package/dist/VideoPlayer/components/time-line/components/TimeCodes.d.ts +15 -0
- package/dist/VideoPlayer/components/time-line/utils/getEndTimeByIndex.d.ts +2 -0
- package/dist/VideoPlayer/components/time-line/utils/getHoverTimePosition.d.ts +3 -0
- package/dist/VideoPlayer/components/time-line/utils/getPositionPercent.d.ts +1 -0
- package/dist/VideoPlayer/components/time-line/utils/getTimeScale.d.ts +1 -0
- package/dist/VideoPlayer/components/time-line/utils/isInRange.d.ts +1 -0
- package/dist/VideoPlayer/components/time-line/utils/positionToMs.d.ts +1 -0
- package/dist/VideoPlayer/components/time-line/utils/secondsToTime.d.ts +6 -0
- package/dist/VideoPlayer/components/time-line/utils/timeToTimeString.d.ts +1 -0
- package/dist/VideoPlayer/constants.d.ts +3 -0
- package/dist/VideoPlayer/hooks/index.d.ts +2 -0
- package/dist/VideoPlayer/hooks/useAdManager.d.ts +8 -0
- package/dist/VideoPlayer/hooks/usePrimaryVideoLifecycle.d.ts +17 -0
- package/dist/VideoPlayer/hooks/useVideoSource.d.ts +1 -14
- package/dist/VideoPlayer/types/AdTypes.d.ts +36 -0
- package/dist/VideoPlayer/types/VideoPlayerTypes.d.ts +4 -2
- package/dist/VideoPlayer/utils/index.d.ts +1 -1
- package/dist/VideoPlayer/utils/qualityManager.d.ts +6 -32
- package/dist/components/ui/FullScreenToggle.d.ts +1 -1
- package/dist/components/ui/PiPictureInPictureToggle.d.ts +1 -1
- package/dist/index.js +1784 -345
- package/dist/store/slices/adsSlice.d.ts +24 -0
- package/dist/store/slices/index.d.ts +1 -0
- package/dist/store/types/StoreTypes.d.ts +31 -9
- 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$
|
|
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:
|
|
79
|
+
hlsInstance: null,
|
|
81
80
|
setHlsInstance: (hlsInstance) => set({ hlsInstance }),
|
|
82
|
-
dashInstance:
|
|
81
|
+
dashInstance: null,
|
|
83
82
|
setDashInstance: (dashInstance) => set({ dashInstance }),
|
|
84
|
-
qualityLevels:
|
|
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
|
-
...
|
|
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
|
-
|
|
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;
|
|
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:
|
|
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
|
-
|
|
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
|
|
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" },
|
|
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" },
|
|
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$
|
|
467
|
-
styleInject(css_248z$
|
|
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,
|
|
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
|
|
545
|
-
|
|
765
|
+
const isAdaptiveStream = streamType === "hls" || streamType === "dash";
|
|
766
|
+
const qualityOptions = React.useMemo(() => {
|
|
767
|
+
if (!qualityLevels || !isAdaptiveStream) {
|
|
546
768
|
return [];
|
|
547
|
-
|
|
548
|
-
const
|
|
549
|
-
qualityLevels
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
|
|
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 (
|
|
807
|
+
if (!isAdaptiveStream)
|
|
566
808
|
return "Auto";
|
|
567
|
-
|
|
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
|
-
|
|
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 (
|
|
643
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
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 ===
|
|
668
|
-
|
|
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
|
-
|
|
719
|
-
|
|
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 (
|
|
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
|
-
|
|
755
|
-
React.createElement("
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
config?.
|
|
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 },
|
|
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
|
|
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(() =>
|
|
795
|
-
|
|
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 -
|
|
806
|
-
|
|
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 +
|
|
811
|
-
|
|
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
|
-
}, [
|
|
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(
|
|
848
|
-
|
|
849
|
-
React__default.createElement(ControlButton, { onClick:
|
|
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
|
|
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
|
|
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
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
933
|
-
resetControlsTimer();
|
|
1251
|
+
handleControlsInteraction();
|
|
934
1252
|
}
|
|
935
1253
|
else if (onClose) {
|
|
936
1254
|
onClose();
|
|
937
1255
|
}
|
|
938
1256
|
};
|
|
939
|
-
return (
|
|
940
|
-
controls &&
|
|
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 && (
|
|
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 (
|
|
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
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
*
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
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 (!
|
|
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
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
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(
|
|
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
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
hls.
|
|
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
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
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
|
-
|
|
1216
|
-
|
|
1217
|
-
setQualityLevels([]);
|
|
1560
|
+
catch (_err) {
|
|
1561
|
+
scheduleRestart();
|
|
1218
1562
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1246
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1506
|
-
|
|
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
|
-
|
|
1511
|
-
|
|
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
|
-
|
|
2160
|
+
const duration = e?.currentTarget?.duration;
|
|
2161
|
+
if (typeof duration === "number" && !Number.isNaN(duration)) {
|
|
1516
2162
|
localStorage.setItem("current_time", "0");
|
|
1517
|
-
setDuration(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2209
|
+
const state = useVideoStore.getState();
|
|
2210
|
+
if (state.isPlaying) {
|
|
2211
|
+
setIsPlaying(false);
|
|
2212
|
+
}
|
|
1545
2213
|
};
|
|
1546
2214
|
const onEnded = (e) => {
|
|
1547
|
-
|
|
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
|
-
|
|
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 {
|
|
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", {
|
|
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
|
-
|
|
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 };
|