@zezosoft/react-player 0.0.6 → 0.0.8
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/MediaControls/BottomControls.d.ts +5 -0
- package/dist/VideoPlayer/MediaControls/ControlsHeader.d.ts +5 -0
- package/dist/VideoPlayer/MediaControls/MiddleControls.d.ts +3 -0
- package/dist/VideoPlayer/MediaControls/VideoPlayerControls.d.ts +4 -0
- package/dist/VideoPlayer/VideoPlayer.d.ts +1 -1
- package/dist/VideoPlayer/hooks/useVideoEvents.d.ts +4 -0
- package/dist/VideoPlayer/hooks/useVideoSource.d.ts +14 -1
- package/dist/VideoPlayer/types/VideoPlayerTypes.d.ts +4 -0
- package/dist/VideoPlayer/utils/index.d.ts +1 -0
- package/dist/VideoPlayer/utils/qualityManager.d.ts +78 -0
- package/dist/components/ui/tooltip.d.ts +1 -1
- package/dist/index.js +374 -165
- package/dist/store/slices/index.d.ts +1 -0
- package/dist/store/slices/resetSlice.d.ts +5 -0
- package/dist/store/types/StoreTypes.d.ts +29 -8
- package/package.json +10 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { VideoPlayerProps } from "./types/VideoPlayerTypes";
|
|
3
|
-
import "
|
|
3
|
+
import "../index.css";
|
|
4
4
|
import "./styles/subtitles.css";
|
|
5
5
|
declare const VideoPlayer: React.FC<VideoPlayerProps>;
|
|
6
6
|
export default VideoPlayer;
|
|
@@ -3,4 +3,8 @@ export declare const useVideoEvents: () => {
|
|
|
3
3
|
onSeeked: (e: React.SyntheticEvent<HTMLVideoElement>) => void;
|
|
4
4
|
onTimeUpdate: (e: React.SyntheticEvent<HTMLVideoElement>) => void;
|
|
5
5
|
onLoadedMetadata: (e: React.SyntheticEvent<HTMLVideoElement>) => void;
|
|
6
|
+
onProgress: (e: React.SyntheticEvent<HTMLVideoElement>) => void;
|
|
7
|
+
onPlay: () => void;
|
|
8
|
+
onPause: () => void;
|
|
9
|
+
onEnded: (e: unknown) => void;
|
|
6
10
|
};
|
|
@@ -1 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Video Source Hook
|
|
3
|
+
*
|
|
4
|
+
* Manages video source loading and streaming technology detection
|
|
5
|
+
* Supports HLS.js, DASH.js, and native HTML5 video
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Automatic stream type detection
|
|
9
|
+
* - HLS.js fallback for older browsers
|
|
10
|
+
* - DASH.js support with proper initialization
|
|
11
|
+
* - Quality level extraction for all stream types
|
|
12
|
+
* - Error handling and cleanup
|
|
13
|
+
*/
|
|
14
|
+
export declare const useVideoSource: (trackSrc: string, type?: "hls" | "mp4" | "dash" | "other" | "youtube" | undefined) => void;
|
|
@@ -3,6 +3,10 @@ import { IOnWatchTimeUpdated } from "../../types";
|
|
|
3
3
|
import { SubtitleStyleConfig } from "../hooks/useSubtitleStyling";
|
|
4
4
|
export interface VideoPlayerProps {
|
|
5
5
|
trackSrc: string;
|
|
6
|
+
showControls?: boolean;
|
|
7
|
+
isMute?: boolean;
|
|
8
|
+
onEnded?: (e: React.SyntheticEvent<HTMLVideoElement>) => void;
|
|
9
|
+
onError?: (e?: React.SyntheticEvent<HTMLVideoElement, Event>) => void;
|
|
6
10
|
trackTitle?: string;
|
|
7
11
|
trackPoster?: string;
|
|
8
12
|
isTrailer?: boolean;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import Hls from 'hls.js';
|
|
2
|
+
import * as dashjs from 'dashjs';
|
|
3
|
+
export type StreamType = 'hls' | 'dash' | 'mp4' | 'other';
|
|
4
|
+
/**
|
|
5
|
+
* Video Quality Management Utility
|
|
6
|
+
* Provides a unified interface for quality switching across HLS.js and DASH.js
|
|
7
|
+
*
|
|
8
|
+
* This utility follows OTT-grade best practices for smooth quality switching:
|
|
9
|
+
* - Immediate quality changes without playback interruption
|
|
10
|
+
* - Proper ABR (Adaptive Bitrate) control
|
|
11
|
+
* - Error handling and fallback mechanisms
|
|
12
|
+
* - Support for both manual and auto quality modes
|
|
13
|
+
*/
|
|
14
|
+
export declare class QualityManager {
|
|
15
|
+
/**
|
|
16
|
+
* Set video quality for HLS streams with OTT-grade smoothness
|
|
17
|
+
*
|
|
18
|
+
* Best practices implemented:
|
|
19
|
+
* 1. Use currentLevel for immediate quality change
|
|
20
|
+
* 2. Use autoLevelCapping to prevent ABR from switching back
|
|
21
|
+
* 3. Use nextLevel to ensure next segment uses selected quality
|
|
22
|
+
* 4. Handle edge cases and errors gracefully
|
|
23
|
+
*
|
|
24
|
+
* @param hlsInstance HLS.js instance (null for native HLS, undefined when not available)
|
|
25
|
+
* @param levelIndex Quality level index (-1 for auto)
|
|
26
|
+
*/
|
|
27
|
+
static setHlsQuality(hlsInstance: Hls | null | undefined, levelIndex: number): void;
|
|
28
|
+
/**
|
|
29
|
+
* Set video quality for DASH streams with OTT-grade smoothness
|
|
30
|
+
*
|
|
31
|
+
* Best practices implemented:
|
|
32
|
+
* 1. Use autoSwitchBitrate settings to control ABR behavior
|
|
33
|
+
* 2. Use setRepresentationForTypeById for immediate quality change
|
|
34
|
+
* 3. Handle representation discovery and selection properly
|
|
35
|
+
* 4. Provide visual feedback through console logs
|
|
36
|
+
*
|
|
37
|
+
* @param dashInstance DASH.js instance
|
|
38
|
+
* @param qualityId Quality level ID (undefined/null for auto)
|
|
39
|
+
*/
|
|
40
|
+
static setDashQuality(dashInstance: dashjs.MediaPlayerClass, qualityId: string | null | undefined): void;
|
|
41
|
+
/**
|
|
42
|
+
* Get available quality levels for HLS with proper error handling
|
|
43
|
+
*
|
|
44
|
+
* @param hlsInstance HLS.js instance
|
|
45
|
+
* @returns Array of quality level objects
|
|
46
|
+
*/
|
|
47
|
+
static getHlsQualityLevels(hlsInstance: Hls): Array<{
|
|
48
|
+
height: number;
|
|
49
|
+
bitrate?: number;
|
|
50
|
+
originalIndex: number;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Get available quality levels for DASH with proper error handling
|
|
54
|
+
*
|
|
55
|
+
* @param dashInstance DASH.js instance
|
|
56
|
+
* @returns Array of quality level objects
|
|
57
|
+
*/
|
|
58
|
+
static getDashQualityLevels(dashInstance: dashjs.MediaPlayerClass): Array<{
|
|
59
|
+
height: number;
|
|
60
|
+
bitrate?: number;
|
|
61
|
+
id: string;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Unified quality setting function that works with both HLS and DASH
|
|
65
|
+
* Provides a single interface for quality management across streaming technologies
|
|
66
|
+
*
|
|
67
|
+
* @param streamType Type of stream (hls, dash, etc.)
|
|
68
|
+
* @param qualityIdentifier Quality level identifier (index for HLS, ID for DASH)
|
|
69
|
+
*/
|
|
70
|
+
static setQuality(streamType: StreamType, qualityIdentifier: string | number): void;
|
|
71
|
+
/**
|
|
72
|
+
* Enable auto quality switching for the current stream type
|
|
73
|
+
*
|
|
74
|
+
* @param streamType Type of stream (hls, dash, etc.)
|
|
75
|
+
*/
|
|
76
|
+
static setAutoQuality(streamType: StreamType): void;
|
|
77
|
+
}
|
|
78
|
+
export default QualityManager;
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,10 @@ import React__default, { memo, useCallback, useEffect, useRef, useState } from '
|
|
|
3
3
|
import { create } from 'zustand';
|
|
4
4
|
import { IoVolumeMuteOutline, IoVolumeHighOutline } from 'react-icons/io5';
|
|
5
5
|
import { IoMdClose } from 'react-icons/io';
|
|
6
|
-
import { Settings as Settings$1, ChevronRight, Check, Loader } from 'lucide-react';
|
|
7
|
-
import
|
|
6
|
+
import { Settings as Settings$1, ChevronRight, Check, Loader, ArrowRight } from 'lucide-react';
|
|
7
|
+
import screenfull from 'screenfull';
|
|
8
8
|
import Hls from 'hls.js';
|
|
9
|
+
import * as dashjs from 'dashjs';
|
|
9
10
|
|
|
10
11
|
function styleInject(css, ref) {
|
|
11
12
|
if ( ref === void 0 ) ref = {};
|
|
@@ -34,7 +35,7 @@ function styleInject(css, ref) {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
var css_248z$3 = "/*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --font-sans: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\",\n \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\",\n \"Courier New\", monospace;\n --color-green-500: oklch(72.3% 0.219 149.579);\n --color-blue-500: oklch(62.3% 0.214 259.815);\n --color-purple-500: oklch(62.7% 0.265 303.9);\n --color-gray-200: oklch(92.8% 0.006 264.531);\n --color-gray-300: oklch(87.2% 0.01 258.338);\n --color-gray-400: oklch(70.7% 0.022 261.325);\n --color-gray-500: oklch(55.1% 0.027 264.364);\n --color-gray-600: oklch(44.6% 0.03 256.802);\n --color-gray-900: oklch(21% 0.034 264.665);\n --color-black: #000;\n --color-white: #fff;\n --spacing: 0.25rem;\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-base: 1rem;\n --text-base--line-height: calc(1.5 / 1);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --text-xl: 1.25rem;\n --text-xl--line-height: calc(1.75 / 1.25);\n --text-2xl: 1.5rem;\n --text-2xl--line-height: calc(2 / 1.5);\n --text-3xl: 1.875rem;\n --text-3xl--line-height: calc(2.25 / 1.875);\n --font-weight-normal: 400;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --radius-md: 0.375rem;\n --radius-lg: 0.5rem;\n --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);\n --animate-spin: spin 1s linear infinite;\n --blur-sm: 8px;\n --default-transition-duration: 150ms;\n --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --default-font-family: var(--font-sans);\n --default-mono-font-family: var(--font-mono);\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n ::-webkit-calendar-picker-indicator {\n line-height: 1;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type=\"button\"], [type=\"reset\"], [type=\"submit\"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden=\"until-found\"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .pointer-events-none {\n pointer-events: none;\n }\n .visible {\n visibility: visible;\n }\n .absolute {\n position: absolute;\n }\n .relative {\n position: relative;\n }\n .inset-0 {\n inset: calc(var(--spacing) * 0);\n }\n .-top-2 {\n top: calc(var(--spacing) * -2);\n }\n .top-0 {\n top: calc(var(--spacing) * 0);\n }\n .top-1\\/2 {\n top: calc(1/2 * 100%);\n }\n .top-full {\n top: 100%;\n }\n .right-0 {\n right: calc(var(--spacing) * 0);\n }\n .right-32 {\n right: calc(var(--spacing) * 32);\n }\n .right-full {\n right: 100%;\n }\n .bottom-36 {\n bottom: calc(var(--spacing) * 36);\n }\n .bottom-full {\n bottom: 100%;\n }\n .left-0 {\n left: calc(var(--spacing) * 0);\n }\n .left-1\\/2 {\n left: calc(1/2 * 100%);\n }\n .left-32 {\n left: calc(var(--spacing) * 32);\n }\n .left-full {\n left: 100%;\n }\n .z-50 {\n z-index: 50;\n }\n .z-\\[-1\\] {\n z-index: -1;\n }\n .mx-2 {\n margin-inline: calc(var(--spacing) * 2);\n }\n .mx-auto {\n margin-inline: auto;\n }\n .mt-1 {\n margin-top: calc(var(--spacing) * 1);\n }\n .mt-2 {\n margin-top: calc(var(--spacing) * 2);\n }\n .mr-2 {\n margin-right: calc(var(--spacing) * 2);\n }\n .mb-1 {\n margin-bottom: calc(var(--spacing) * 1);\n }\n .mb-2 {\n margin-bottom: calc(var(--spacing) * 2);\n }\n .mb-4 {\n margin-bottom: calc(var(--spacing) * 4);\n }\n .ml-2 {\n margin-left: calc(var(--spacing) * 2);\n }\n .block {\n display: block;\n }\n .flex {\n display: flex;\n }\n .hidden {\n display: none;\n }\n .inline {\n display: inline;\n }\n .inline-block {\n display: inline-block;\n }\n .h-3 {\n height: calc(var(--spacing) * 3);\n }\n .h-5 {\n height: calc(var(--spacing) * 5);\n }\n .h-6 {\n height: calc(var(--spacing) * 6);\n }\n .h-10 {\n height: calc(var(--spacing) * 10);\n }\n .h-24 {\n height: calc(var(--spacing) * 24);\n }\n .h-full {\n height: 100%;\n }\n .max-h-80 {\n max-height: calc(var(--spacing) * 80);\n }\n .w-3 {\n width: calc(var(--spacing) * 3);\n }\n .w-5 {\n width: calc(var(--spacing) * 5);\n }\n .w-6 {\n width: calc(var(--spacing) * 6);\n }\n .w-24 {\n width: calc(var(--spacing) * 24);\n }\n .w-80 {\n width: calc(var(--spacing) * 80);\n }\n .w-\\[2px\\] {\n width: 2px;\n }\n .w-\\[10vw\\] {\n width: 10vw;\n }\n .w-\\[15vw\\] {\n width: 15vw;\n }\n .w-\\[720px\\] {\n width: 720px;\n }\n .w-fit {\n width: fit-content;\n }\n .w-full {\n width: 100%;\n }\n .-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .rotate-45 {\n rotate: 45deg;\n }\n .rotate-180 {\n rotate: 180deg;\n }\n .transform {\n transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);\n }\n .animate-spin {\n animation: var(--animate-spin);\n }\n .cursor-not-allowed {\n cursor: not-allowed;\n }\n .cursor-pointer {\n cursor: pointer;\n }\n .flex-col {\n flex-direction: column;\n }\n .items-center {\n align-items: center;\n }\n .items-start {\n align-items: flex-start;\n }\n .justify-between {\n justify-content: space-between;\n }\n .justify-center {\n justify-content: center;\n }\n .gap-3 {\n gap: calc(var(--spacing) * 3);\n }\n .gap-4 {\n gap: calc(var(--spacing) * 4);\n }\n .gap-7 {\n gap: calc(var(--spacing) * 7);\n }\n .space-y-0 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-3 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .overflow-hidden {\n overflow: hidden;\n }\n .overflow-y-auto {\n overflow-y: auto;\n }\n .rounded {\n border-radius: 0.25rem;\n }\n .rounded-\\[5px\\] {\n border-radius: 5px;\n }\n .rounded-\\[7px\\] {\n border-radius: 7px;\n }\n .rounded-lg {\n border-radius: var(--radius-lg);\n }\n .rounded-md {\n border-radius: var(--radius-md);\n }\n .border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n }\n .border-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n }\n .border-b {\n border-bottom-style: var(--tw-border-style);\n border-bottom-width: 1px;\n }\n .border-l {\n border-left-style: var(--tw-border-style);\n border-left-width: 1px;\n }\n .border-gray-600 {\n border-color: var(--color-gray-600);\n }\n .border-white\\/10 {\n border-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n .bg-\\[\\#3a4049\\] {\n background-color: #3a4049;\n }\n .bg-\\[\\#454545\\] {\n background-color: #454545;\n }\n .bg-\\[rgba\\(0\\,0\\,0\\,0\\.5\\)\\] {\n background-color: rgba(0,0,0,0.5);\n }\n .bg-blue-500 {\n background-color: var(--color-blue-500);\n }\n .bg-gray-500 {\n background-color: var(--color-gray-500);\n }\n .bg-gray-900 {\n background-color: var(--color-gray-900);\n }\n .bg-green-500 {\n background-color: var(--color-green-500);\n }\n .bg-purple-500 {\n background-color: var(--color-purple-500);\n }\n .bg-white\\/10 {\n background-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n .bg-white\\/80 {\n background-color: color-mix(in srgb, #fff 80%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 80%, transparent);\n }\n }\n .bg-gradient-to-b {\n --tw-gradient-position: to bottom in oklab;\n background-image: linear-gradient(var(--tw-gradient-stops));\n }\n .from-black {\n --tw-gradient-from: var(--color-black);\n --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));\n }\n .bg-cover {\n background-size: cover;\n }\n .bg-center {\n background-position: center;\n }\n .p-0 {\n padding: calc(var(--spacing) * 0);\n }\n .p-1 {\n padding: calc(var(--spacing) * 1);\n }\n .p-2 {\n padding: calc(var(--spacing) * 2);\n }\n .p-4 {\n padding: calc(var(--spacing) * 4);\n }\n .p-10 {\n padding: calc(var(--spacing) * 10);\n }\n .px-3 {\n padding-inline: calc(var(--spacing) * 3);\n }\n .px-4 {\n padding-inline: calc(var(--spacing) * 4);\n }\n .px-6 {\n padding-inline: calc(var(--spacing) * 6);\n }\n .px-10 {\n padding-inline: calc(var(--spacing) * 10);\n }\n .px-20 {\n padding-inline: calc(var(--spacing) * 20);\n }\n .py-1 {\n padding-block: calc(var(--spacing) * 1);\n }\n .py-2 {\n padding-block: calc(var(--spacing) * 2);\n }\n .py-3 {\n padding-block: calc(var(--spacing) * 3);\n }\n .py-4 {\n padding-block: calc(var(--spacing) * 4);\n }\n .pt-6 {\n padding-top: calc(var(--spacing) * 6);\n }\n .pb-10 {\n padding-bottom: calc(var(--spacing) * 10);\n }\n .pb-16 {\n padding-bottom: calc(var(--spacing) * 16);\n }\n .text-left {\n text-align: left;\n }\n .text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n }\n .text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n }\n .text-xl {\n font-size: var(--text-xl);\n line-height: var(--tw-leading, var(--text-xl--line-height));\n }\n .font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n }\n .font-normal {\n --tw-font-weight: var(--font-weight-normal);\n font-weight: var(--font-weight-normal);\n }\n .font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n }\n .text-black {\n color: var(--color-black);\n }\n .text-gray-200 {\n color: var(--color-gray-200);\n }\n .text-gray-300 {\n color: var(--color-gray-300);\n }\n .text-gray-400 {\n color: var(--color-gray-400);\n }\n .text-gray-500 {\n color: var(--color-gray-500);\n }\n .text-gray-900 {\n color: var(--color-gray-900);\n }\n .text-white {\n color: var(--color-white);\n }\n .opacity-50 {\n opacity: 50%;\n }\n .shadow-2xl {\n --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .shadow-md {\n --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .backdrop-blur-sm {\n --tw-backdrop-blur: blur(var(--blur-sm));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .transition {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, visibility, content-visibility, overlay, pointer-events;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-all {\n transition-property: all;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-colors {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-opacity {\n transition-property: opacity;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .duration-200 {\n --tw-duration: 200ms;\n transition-duration: 200ms;\n }\n .ease-in-out {\n --tw-ease: var(--ease-in-out);\n transition-timing-function: var(--ease-in-out);\n }\n .hover\\:bg-gray-300 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-gray-300);\n }\n }\n }\n .hover\\:bg-white\\/5 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 5%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 5%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/10 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/90 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 90%, transparent);\n }\n }\n }\n }\n .hover\\:text-gray-200 {\n &:hover {\n @media (hover: hover) {\n color: var(--color-gray-200);\n }\n }\n }\n .focus\\:ring-2 {\n &:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n .focus\\:ring-gray-400 {\n &:focus {\n --tw-ring-color: var(--color-gray-400);\n }\n }\n .focus\\:ring-offset-1 {\n &:focus {\n --tw-ring-offset-width: 1px;\n --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n }\n }\n .focus\\:outline-none {\n &:focus {\n --tw-outline-style: none;\n outline-style: none;\n }\n }\n .disabled\\:cursor-not-allowed {\n &:disabled {\n cursor: not-allowed;\n }\n }\n .disabled\\:bg-white\\/50 {\n &:disabled {\n background-color: color-mix(in srgb, #fff 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 50%, transparent);\n }\n }\n }\n .disabled\\:opacity-50 {\n &:disabled {\n opacity: 50%;\n }\n }\n .lg\\:h-32 {\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 32);\n }\n }\n .lg\\:w-32 {\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 32);\n }\n }\n .lg\\:pb-12 {\n @media (width >= 64rem) {\n padding-bottom: calc(var(--spacing) * 12);\n }\n }\n .lg\\:text-2xl {\n @media (width >= 64rem) {\n font-size: var(--text-2xl);\n line-height: var(--tw-leading, var(--text-2xl--line-height));\n }\n }\n .lg\\:text-3xl {\n @media (width >= 64rem) {\n font-size: var(--text-3xl);\n line-height: var(--tw-leading, var(--text-3xl--line-height));\n }\n }\n .lg\\:text-base {\n @media (width >= 64rem) {\n font-size: var(--text-base);\n line-height: var(--tw-leading, var(--text-base--line-height));\n }\n }\n}\n.noCursor {\n cursor: none !important;\n}\n.icon-class {\n height: calc(var(--spacing) * 14);\n width: calc(var(--spacing) * 14);\n cursor: pointer;\n color: var(--color-gray-400);\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n --tw-duration: 200ms;\n transition-duration: 200ms;\n &:hover {\n @media (hover: hover) {\n color: var(--color-gray-200);\n }\n }\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 18);\n }\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 18);\n }\n}\n@property --tw-translate-x {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-rotate-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-z {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-space-y-reverse {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: \"*\";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-gradient-position {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-from {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-via {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-to {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-stops {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-via-stops {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-from-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 0%;\n}\n@property --tw-gradient-via-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 50%;\n}\n@property --tw-gradient-to-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-font-weight {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-inset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: \"<length>\";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: \"*\";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-backdrop-blur {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-brightness {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-contrast {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-grayscale {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-hue-rotate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-invert {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-opacity {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-saturate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-sepia {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-duration {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ease {\n syntax: \"*\";\n inherits: false;\n}\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-rotate-x: initial;\n --tw-rotate-y: initial;\n --tw-rotate-z: initial;\n --tw-skew-x: initial;\n --tw-skew-y: initial;\n --tw-space-y-reverse: 0;\n --tw-border-style: solid;\n --tw-gradient-position: initial;\n --tw-gradient-from: #0000;\n --tw-gradient-via: #0000;\n --tw-gradient-to: #0000;\n --tw-gradient-stops: initial;\n --tw-gradient-via-stops: initial;\n --tw-gradient-from-position: 0%;\n --tw-gradient-via-position: 50%;\n --tw-gradient-to-position: 100%;\n --tw-font-weight: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-backdrop-blur: initial;\n --tw-backdrop-brightness: initial;\n --tw-backdrop-contrast: initial;\n --tw-backdrop-grayscale: initial;\n --tw-backdrop-hue-rotate: initial;\n --tw-backdrop-invert: initial;\n --tw-backdrop-opacity: initial;\n --tw-backdrop-saturate: initial;\n --tw-backdrop-sepia: initial;\n --tw-duration: initial;\n --tw-ease: initial;\n }\n }\n}\n";
|
|
38
|
+
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";
|
|
38
39
|
styleInject(css_248z$3,{"insertAt":"top"});
|
|
39
40
|
|
|
40
41
|
const createVideoRefsSlice = (set) => ({
|
|
@@ -62,6 +63,8 @@ const createVideoTimingSlice = (set) => ({
|
|
|
62
63
|
setCurrentTime: (currentTime) => set({ currentTime }),
|
|
63
64
|
duration: 0,
|
|
64
65
|
setDuration: (duration) => set({ duration }),
|
|
66
|
+
bufferedProgress: 0,
|
|
67
|
+
setBufferedProgress: (progress) => set({ bufferedProgress: progress }),
|
|
65
68
|
});
|
|
66
69
|
|
|
67
70
|
const createVideoControlsSlice = (set) => ({
|
|
@@ -69,15 +72,21 @@ const createVideoControlsSlice = (set) => ({
|
|
|
69
72
|
setControls: (controls) => set({ controls }),
|
|
70
73
|
isFullscreen: false,
|
|
71
74
|
setIsFullscreen: (isFullscreen) => set({ isFullscreen }),
|
|
75
|
+
controlsVisible: true,
|
|
76
|
+
setControlsVisible: (visible) => set({ controlsVisible: visible }),
|
|
72
77
|
});
|
|
73
78
|
|
|
74
79
|
const createVideoQualitySlice = (set) => ({
|
|
75
80
|
hlsInstance: undefined,
|
|
76
81
|
setHlsInstance: (hlsInstance) => set({ hlsInstance }),
|
|
82
|
+
dashInstance: undefined,
|
|
83
|
+
setDashInstance: (dashInstance) => set({ dashInstance }),
|
|
77
84
|
qualityLevels: undefined,
|
|
78
85
|
setQualityLevels: (qualityLevels) => set({ qualityLevels }),
|
|
79
86
|
activeQuality: "auto",
|
|
80
87
|
setActiveQuality: (activeQuality) => set({ activeQuality }),
|
|
88
|
+
streamType: "mp4",
|
|
89
|
+
setStreamType: (streamType) => set({ streamType }),
|
|
81
90
|
});
|
|
82
91
|
|
|
83
92
|
const createSubtitlesSlice = (set) => ({
|
|
@@ -105,22 +114,47 @@ const createIntroSlice = (set) => ({
|
|
|
105
114
|
setShowIntroSkip: (show) => set({ showIntroSkip: show }),
|
|
106
115
|
});
|
|
107
116
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
const createResetSlice = (set, get) => ({
|
|
118
|
+
resetStore: () => {
|
|
119
|
+
set({
|
|
120
|
+
videoRef: null,
|
|
121
|
+
videoWrapperRef: null,
|
|
122
|
+
playing: false,
|
|
123
|
+
isBuffering: false,
|
|
124
|
+
isPlaying: false,
|
|
125
|
+
muted: false,
|
|
126
|
+
volume: 1,
|
|
127
|
+
currentTime: 0,
|
|
128
|
+
duration: 0,
|
|
129
|
+
bufferedProgress: 0,
|
|
130
|
+
controls: false,
|
|
131
|
+
isFullscreen: false,
|
|
132
|
+
hlsInstance: undefined,
|
|
133
|
+
qualityLevels: undefined,
|
|
134
|
+
activeQuality: "auto",
|
|
135
|
+
activeSubtitle: null,
|
|
136
|
+
subtitles: [],
|
|
137
|
+
episodeList: [],
|
|
138
|
+
currentEpisodeIndex: 0,
|
|
139
|
+
showCountdown: false,
|
|
140
|
+
countdownTime: 10,
|
|
141
|
+
autoPlayNext: false,
|
|
142
|
+
showIntroSkip: false,
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const useVideoStore = create()((set, get, store) => ({
|
|
148
|
+
...createVideoRefsSlice(set),
|
|
149
|
+
...createVideoPlaybackSlice(set),
|
|
150
|
+
...createVideoTimingSlice(set),
|
|
151
|
+
...createVideoControlsSlice(set),
|
|
152
|
+
...createVideoQualitySlice(set),
|
|
153
|
+
...createSubtitlesSlice(set),
|
|
154
|
+
...createEpisodesSlice(set),
|
|
155
|
+
...createIntroSlice(set),
|
|
156
|
+
...createResetSlice(set),
|
|
117
157
|
}));
|
|
118
|
-
// Previous version before recent edits code changes in panding
|
|
119
|
-
// ...createVideoRefsSlice(...a),
|
|
120
|
-
// ...createVideoPlaybackSlice(...a),
|
|
121
|
-
// ...createVideoTimingSlice(...a),
|
|
122
|
-
// ...createEpisodesSlice(...a),
|
|
123
|
-
// ...createIntroSlice(...a),
|
|
124
158
|
|
|
125
159
|
/**
|
|
126
160
|
* @description
|
|
@@ -158,6 +192,9 @@ const getExtensionFromUrl = (url) => {
|
|
|
158
192
|
if (extension === "m3u8") {
|
|
159
193
|
return "hls";
|
|
160
194
|
}
|
|
195
|
+
if (extension === "mpd") {
|
|
196
|
+
return "dash";
|
|
197
|
+
}
|
|
161
198
|
return extension;
|
|
162
199
|
};
|
|
163
200
|
|
|
@@ -395,14 +432,14 @@ const VideoSeekSlider = ({ max = 1000, currentTime = 0, bufferTime = 0, hideThum
|
|
|
395
432
|
React__default.createElement(Thumb, { max: max, currentTime: currentTime, isThumbActive: isThumbActive, trackColor: trackColor })));
|
|
396
433
|
};
|
|
397
434
|
|
|
398
|
-
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;\n transform-origin: 0 0;\n}\n.ui-video-seek-slider .track .main:focus {\n border: 0;\n outline: none;\n}\n.ui-video-seek-slider .track .main .buffered {\n background-color: rgba(255, 255, 255, 0.3);\n z-index: 2;\n}\n.ui-video-seek-slider .track .main .seek-hover {\n background-color: rgba(255, 255, 255, 0.5);\n z-index: 1;\n}\n.ui-video-seek-slider .track .main .connect {\n background-color: #ff0000;\n z-index: 3;\n transform-origin: 0 0;\n}\n.ui-video-seek-slider .track .main.with-gap .inner-seek-block,\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";
|
|
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";
|
|
399
436
|
styleInject(css_248z$2,{"insertAt":"top"});
|
|
400
437
|
|
|
401
438
|
const BottomControls = ({ config }) => {
|
|
402
|
-
const { videoRef, currentTime, isFullscreen } = useVideoStore();
|
|
439
|
+
const { videoRef, currentTime, isFullscreen, bufferedProgress } = useVideoStore();
|
|
403
440
|
const duration = videoRef?.duration;
|
|
404
441
|
return (React__default.createElement("div", { className: "px-10" },
|
|
405
|
-
React__default.createElement(VideoSeekSlider, { max: secondsToMilliseconds(duration || 0), currentTime: secondsToMilliseconds(currentTime || 0), bufferTime: secondsToMilliseconds(0), onChange: (currentTime) => {
|
|
442
|
+
React__default.createElement(VideoSeekSlider, { max: secondsToMilliseconds(duration || 0), currentTime: secondsToMilliseconds(currentTime || 0), bufferTime: secondsToMilliseconds((duration || 0) * (bufferedProgress / 100)), onChange: (currentTime) => {
|
|
406
443
|
if (videoRef) {
|
|
407
444
|
videoRef.currentTime = currentTime / 1000;
|
|
408
445
|
}
|
|
@@ -423,7 +460,7 @@ const Tooltip = ({ children, title, position = "top", }) => {
|
|
|
423
460
|
};
|
|
424
461
|
return (React__default.createElement("div", { className: "relative inline-block cursor-pointer", onMouseEnter: () => setVisible(true), onMouseLeave: () => setVisible(false) },
|
|
425
462
|
children,
|
|
426
|
-
visible && (React__default.createElement("div", { className: `absolute z-50 px-3 py-1 text-sm text-white bg-gray-900 rounded-md shadow-md transition-opacity duration-200 ease-in-out ${positionStyles[position]}` }, title))));
|
|
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))));
|
|
427
464
|
};
|
|
428
465
|
|
|
429
466
|
var css_248z$1 = ".icon-button {\n width: 20px;\n height: 20px;\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.icon-button:hover {\n color: #e5e7eb;\n}\n\n@media (min-width: 1024px) {\n .icon-button {\n width: 32px;\n height: 32px;\n }\n}\n\n/* styles/fullscreen-toggle.css */\n.fullscreen-icon {\n width: 20px;\n height: 20px;\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.fullscreen-icon:hover {\n color: #e5e7eb;\n}\n\n@media (min-width: 1024px) {\n .fullscreen-icon {\n width: 32px;\n height: 32px;\n }\n}\n\n/* styles/pi-picture-in-picture-toggle.css */\n.pip-toggle {\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.pip-toggle:hover {\n color: #e5e7eb;\n}\n\n.pip-icon {\n width: 15px;\n height: 15px;\n}\n\n@media (min-width: 1024px) {\n .pip-icon {\n width: 28px;\n height: 28px;\n }\n}\n";
|
|
@@ -655,50 +692,52 @@ const Settings = ({ iconClassName }) => {
|
|
|
655
692
|
|
|
656
693
|
const ControlsHeader = ({ config }) => {
|
|
657
694
|
const iconClassName = "icon-button";
|
|
658
|
-
const { videoWrapperRef, videoRef, episodeList, currentEpisodeIndex } = useVideoStore();
|
|
695
|
+
const { videoWrapperRef, videoRef, episodeList, currentEpisodeIndex, resetStore, } = useVideoStore();
|
|
659
696
|
const [isPipActive, setIsPipActive] = React.useState(false);
|
|
660
|
-
const isFullscreen =
|
|
697
|
+
const [isFullscreen, setIsFullscreen] = React.useState(false);
|
|
661
698
|
const handleFullscreen = () => {
|
|
662
|
-
if (isPipActive)
|
|
699
|
+
if (!screenfull.isEnabled || isPipActive)
|
|
663
700
|
return;
|
|
664
|
-
if (
|
|
665
|
-
|
|
701
|
+
if (screenfull.isFullscreen) {
|
|
702
|
+
screenfull.exit();
|
|
666
703
|
}
|
|
667
|
-
else {
|
|
668
|
-
videoWrapperRef
|
|
704
|
+
else if (videoWrapperRef) {
|
|
705
|
+
screenfull.request(videoWrapperRef);
|
|
669
706
|
}
|
|
670
707
|
};
|
|
708
|
+
React.useEffect(() => {
|
|
709
|
+
if (!screenfull.isEnabled)
|
|
710
|
+
return;
|
|
711
|
+
const changeHandler = () => setIsFullscreen(screenfull.isFullscreen);
|
|
712
|
+
screenfull.on("change", changeHandler);
|
|
713
|
+
return () => {
|
|
714
|
+
screenfull.off("change", changeHandler);
|
|
715
|
+
};
|
|
716
|
+
}, []);
|
|
671
717
|
const handleMute = () => {
|
|
672
718
|
if (videoRef) {
|
|
673
719
|
videoRef.muted = !videoRef.muted;
|
|
674
720
|
}
|
|
675
721
|
};
|
|
676
722
|
const handlePipToggle = async () => {
|
|
677
|
-
if (videoRef)
|
|
723
|
+
if (!videoRef)
|
|
724
|
+
return;
|
|
725
|
+
try {
|
|
678
726
|
if (!document.pictureInPictureElement && !isPipActive) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
setIsPipActive(true);
|
|
682
|
-
}
|
|
683
|
-
catch (error) {
|
|
684
|
-
console.error("PiP mode failed:", error);
|
|
685
|
-
}
|
|
727
|
+
await videoRef.requestPictureInPicture();
|
|
728
|
+
setIsPipActive(true);
|
|
686
729
|
}
|
|
687
730
|
else if (document.pictureInPictureElement && isPipActive) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
setIsPipActive(false);
|
|
691
|
-
}
|
|
692
|
-
catch (error) {
|
|
693
|
-
console.error("Exit PiP failed:", error);
|
|
694
|
-
}
|
|
731
|
+
await document.exitPictureInPicture();
|
|
732
|
+
setIsPipActive(false);
|
|
695
733
|
}
|
|
696
734
|
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
console.error("PiP toggle failed:", error);
|
|
737
|
+
}
|
|
697
738
|
};
|
|
698
739
|
React.useEffect(() => {
|
|
699
|
-
const handlePipChange = () =>
|
|
700
|
-
setIsPipActive(!!document.pictureInPictureElement);
|
|
701
|
-
};
|
|
740
|
+
const handlePipChange = () => setIsPipActive(!!document.pictureInPictureElement);
|
|
702
741
|
document.addEventListener("enterpictureinpicture", handlePipChange);
|
|
703
742
|
document.addEventListener("leavepictureinpicture", handlePipChange);
|
|
704
743
|
return () => {
|
|
@@ -706,6 +745,12 @@ const ControlsHeader = ({ config }) => {
|
|
|
706
745
|
document.removeEventListener("leavepictureinpicture", handlePipChange);
|
|
707
746
|
};
|
|
708
747
|
}, []);
|
|
748
|
+
const handleClose = () => {
|
|
749
|
+
resetStore();
|
|
750
|
+
if (config?.onClose) {
|
|
751
|
+
config.onClose();
|
|
752
|
+
}
|
|
753
|
+
};
|
|
709
754
|
return (React.createElement("div", { className: "flex items-center justify-between p-10 bg-gradient-to-b from-black" },
|
|
710
755
|
React.createElement("div", { className: "flex" },
|
|
711
756
|
React.createElement("div", null,
|
|
@@ -721,79 +766,64 @@ const ControlsHeader = ({ config }) => {
|
|
|
721
766
|
React.createElement(Tooltip, { title: isPipActive
|
|
722
767
|
? "Disabled in PiP"
|
|
723
768
|
: isFullscreen
|
|
724
|
-
? "Exit"
|
|
769
|
+
? "Exit Fullscreen"
|
|
725
770
|
: "Fullscreen", className: `${iconClassName} ${isPipActive ? "opacity-50 cursor-not-allowed" : ""}` },
|
|
726
771
|
React.createElement("div", { onClick: handleFullscreen, className: isPipActive ? "pointer-events-none" : "" },
|
|
727
772
|
React.createElement(FullScreenToggle, { isFullScreen: isFullscreen, className: iconClassName }))),
|
|
728
|
-
React.createElement(Tooltip, { title: isPipActive ? "Exit PiP" : "Enter PiP" },
|
|
773
|
+
React.createElement(Tooltip, { className: "whitespace-nowrap", title: isPipActive ? "Exit PiP" : "Enter PiP" },
|
|
729
774
|
React.createElement("div", { onClick: handlePipToggle },
|
|
730
775
|
React.createElement(PiPictureInPictureToggle, { className: iconClassName }))),
|
|
731
776
|
config?.onClose && (React.createElement(React.Fragment, null,
|
|
732
777
|
React.createElement("div", { className: "w-[2px] h-10 bg-gray-500 hover:bg-gray-300 mx-2" }),
|
|
733
|
-
React.createElement("div", { onClick:
|
|
778
|
+
React.createElement("div", { onClick: handleClose },
|
|
734
779
|
React.createElement(Tooltip, { title: "Close" },
|
|
735
780
|
React.createElement(IoMdClose, { className: iconClassName }))))))));
|
|
736
781
|
};
|
|
737
782
|
|
|
783
|
+
const ControlButton = ({ onClick, icon, className, }) => (React__default.createElement("button", { onClick: onClick, className: `flex justify-center items-center h-full cursor-pointer ${className}` }, icon));
|
|
738
784
|
const MiddleControls = () => {
|
|
739
|
-
const { videoRef, isPlaying, setIsPlaying
|
|
740
|
-
const
|
|
741
|
-
|
|
785
|
+
const { videoRef, isPlaying, setIsPlaying } = useVideoStore();
|
|
786
|
+
const [isBuffering, setIsBuffering] = useState(false);
|
|
787
|
+
const videoElement = videoRef;
|
|
788
|
+
const handlePlayPause = useCallback(() => {
|
|
789
|
+
if (!videoElement)
|
|
742
790
|
return;
|
|
743
|
-
if (
|
|
744
|
-
|
|
791
|
+
if (videoElement.paused) {
|
|
792
|
+
videoElement
|
|
745
793
|
.play()
|
|
746
|
-
.
|
|
747
|
-
|
|
794
|
+
.then(() => setIsPlaying(true))
|
|
795
|
+
.catch((err) => console.error("Error playing video:", err));
|
|
748
796
|
}
|
|
749
797
|
else {
|
|
750
|
-
|
|
798
|
+
videoElement.pause();
|
|
751
799
|
setIsPlaying(false);
|
|
752
800
|
}
|
|
753
|
-
};
|
|
754
|
-
const
|
|
755
|
-
if (!
|
|
801
|
+
}, [videoElement, setIsPlaying]);
|
|
802
|
+
const handleBackward = useCallback(() => {
|
|
803
|
+
if (!videoElement)
|
|
756
804
|
return;
|
|
757
|
-
|
|
758
|
-
};
|
|
759
|
-
const
|
|
760
|
-
if (!
|
|
761
|
-
return;
|
|
762
|
-
videoRef.currentTime += 10;
|
|
763
|
-
};
|
|
764
|
-
// Handle buffering state
|
|
765
|
-
useEffect(() => {
|
|
766
|
-
if (!videoRef)
|
|
805
|
+
videoElement.currentTime = Math.max(0, videoElement.currentTime - 10);
|
|
806
|
+
}, [videoElement]);
|
|
807
|
+
const handleForward = useCallback(() => {
|
|
808
|
+
if (!videoElement)
|
|
767
809
|
return;
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
};
|
|
771
|
-
const handlePlaying = () => {
|
|
772
|
-
setIsBuffering(false);
|
|
773
|
-
};
|
|
774
|
-
videoRef.addEventListener("waiting", handleWaiting);
|
|
775
|
-
videoRef.addEventListener("playing", handlePlaying);
|
|
776
|
-
return () => {
|
|
777
|
-
videoRef.removeEventListener("waiting", handleWaiting);
|
|
778
|
-
videoRef.removeEventListener("playing", handlePlaying);
|
|
779
|
-
};
|
|
780
|
-
}, [videoRef, setIsBuffering]);
|
|
810
|
+
videoElement.currentTime = Math.min(videoElement.duration, videoElement.currentTime + 10);
|
|
811
|
+
}, [videoElement]);
|
|
781
812
|
useEffect(() => {
|
|
782
|
-
if (!
|
|
813
|
+
if (!videoElement)
|
|
783
814
|
return;
|
|
784
815
|
const handleWaiting = () => setIsBuffering(true);
|
|
785
816
|
const handlePlaying = () => setIsBuffering(false);
|
|
786
|
-
|
|
787
|
-
|
|
817
|
+
videoElement.addEventListener("waiting", handleWaiting);
|
|
818
|
+
videoElement.addEventListener("playing", handlePlaying);
|
|
788
819
|
return () => {
|
|
789
|
-
|
|
790
|
-
|
|
820
|
+
videoElement.removeEventListener("waiting", handleWaiting);
|
|
821
|
+
videoElement.removeEventListener("playing", handlePlaying);
|
|
791
822
|
};
|
|
792
|
-
}, [
|
|
793
|
-
// keyboard controls
|
|
823
|
+
}, [videoElement]);
|
|
794
824
|
useEffect(() => {
|
|
795
825
|
const handleKeyDown = (e) => {
|
|
796
|
-
if (!
|
|
826
|
+
if (!videoElement)
|
|
797
827
|
return;
|
|
798
828
|
switch (e.code) {
|
|
799
829
|
case "Space":
|
|
@@ -801,31 +831,31 @@ const MiddleControls = () => {
|
|
|
801
831
|
handlePlayPause();
|
|
802
832
|
break;
|
|
803
833
|
case "ArrowLeft":
|
|
804
|
-
|
|
834
|
+
e.preventDefault();
|
|
835
|
+
handleBackward();
|
|
805
836
|
break;
|
|
806
837
|
case "ArrowRight":
|
|
807
|
-
|
|
838
|
+
e.preventDefault();
|
|
839
|
+
handleForward();
|
|
808
840
|
break;
|
|
809
841
|
}
|
|
810
842
|
};
|
|
811
843
|
window.addEventListener("keydown", handleKeyDown);
|
|
812
844
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
813
|
-
}, [
|
|
845
|
+
}, [videoElement, handlePlayPause, handleBackward, handleForward]);
|
|
814
846
|
return (React__default.createElement("div", { className: "flex justify-center items-center" },
|
|
815
|
-
React__default.createElement(
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
|
|
823
|
-
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" })))));
|
|
847
|
+
React__default.createElement(ControlButton, { onClick: handleBackward, className: "w-[15vw]", icon: React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
|
|
848
|
+
React__default.createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M33.5 0C52 0 67 15 67 33.5S52 67 33.5 67 0 52 0 33.5c.03-1.4 1.17-2.53 2.58-2.53 1.4 0 2.55 1.13 2.57 2.53 0 15.65 12.7 28.35 28.35 28.35 15.66 0 28.35-12.7 28.35-28.35 0-15.66-12.69-28.35-28.35-28.35h-.04c-7 0-13.76 2.61-18.94 7.3-.46.42-.91.85-1.34 1.29h6.58c1.42 0 2.57 1.16 2.57 2.58 0 1.42-1.15 2.58-2.57 2.58H6.01c-1.42 0-2.57-1.16-2.57-2.58V2.58C3.44 1.15 4.59 0 6.01 0c1.43 0 2.58 1.15 2.58 2.58v8.52c.78-.86 1.61-1.7 2.47-2.47A33.407 33.407 0 0 1 33.46 0h.04zM33.98 41.34c-1.6-2.21-2-5.2-2-7.85 0-2.65.4-5.63 2-7.83 1.44-1.97 3.47-2.84 5.88-2.84 2.41 0 4.42.87 5.86 2.84 1.61 2.21 2.03 5.16 2.03 7.83 0 2.66-.4 5.64-2 7.85-1.43 1.97-3.47 2.84-5.89 2.84-2.41 0-4.45-.86-5.88-2.84zm-9.73-12.77l-5 1.58v-4.21l5.87-2.65h4.28v20.47h-5.15V28.57zm17.61 9.96c.61-1.33.68-3.6.68-5.04s-.07-3.7-.68-5.02c-.4-.86-1.04-1.29-2-1.29-.95 0-1.59.42-1.99 1.29-.61 1.32-.68 3.58-.68 5.02 0 1.44.07 3.71.68 5.04.4.87 1.04 1.29 1.99 1.29.96 0 1.6-.42 2-1.29z" })) }),
|
|
849
|
+
React__default.createElement(ControlButton, { onClick: handlePlayPause, className: "w-[10vw]", icon: isBuffering ? (React__default.createElement(Loader, { className: "w-24 h-24 lg:w-32 lg:h-32 animate-spin text-white" })) : isPlaying ? (React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
|
|
850
|
+
React__default.createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M46.332 5.773a4.125 4.125 0 0 0-4.125 4.125v46.75a4.127 4.127 0 0 0 4.125 4.125 4.127 4.127 0 0 0 4.125-4.125V9.898a4.125 4.125 0 0 0-4.125-4.125zM25.707 9.898v46.75a4.125 4.125 0 1 1-8.25 0V9.898a4.123 4.123 0 0 1 4.125-4.125 4.123 4.123 0 0 1 4.125 4.125z" }))) : (React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
|
|
851
|
+
React__default.createElement("path", { d: "M20.28 9.65c-2.205-1.268-4.026-.228-4.026 2.307v43.805c0 2.535 1.82 3.574 4.027 2.307l38.471-21.903a2.556 2.556 0 0 0 1.094-.935 2.514 2.514 0 0 0 0-2.743 2.556 2.556 0 0 0-1.093-.936L20.28 9.65z" }))) }),
|
|
852
|
+
React__default.createElement(ControlButton, { onClick: handleForward, className: "w-[15vw]", icon: React__default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon-class", fill: "currentColor", viewBox: "0 0 67 67" },
|
|
853
|
+
React__default.createElement("path", { fillRule: "evenodd", d: "M33.5 0C15 0 0 15 0 33.5S15 67 33.5 67 67 52 67 33.5a2.583 2.583 0 0 0-2.58-2.53c-1.4 0-2.55 1.13-2.57 2.53 0 15.66-12.69 28.35-28.35 28.35-15.65 0-28.35-12.7-28.35-28.35 0-15.66 12.7-28.35 28.35-28.35 7.3 0 13.96 2.76 18.99 7.3.46.42.9.85 1.34 1.29h-6.59a2.58 2.58 0 0 0 0 5.16h13.75c1.42 0 2.57-1.16 2.57-2.58V2.58c0-1.43-1.15-2.58-2.57-2.58-1.43 0-2.58 1.15-2.58 2.58v8.52c-.78-.87-1.61-1.7-2.47-2.48A33.446 33.446 0 0 0 33.54 0h-.04zM33.98 41.34c-1.6-2.21-2-5.2-2-7.85 0-2.65.4-5.63 2-7.83 1.44-1.97 3.47-2.84 5.88-2.84 2.41 0 4.42.87 5.86 2.84 1.61 2.21 2.03 5.16 2.03 7.83 0 2.66-.4 5.64-2 7.85-1.43 1.97-3.47 2.84-5.89 2.84-2.41 0-4.45-.87-5.88-2.84zm-9.73-12.77l-5 1.58v-4.21l5.87-2.65h4.28v20.47h-5.15V28.57zm17.61 9.96c.61-1.33.68-3.6.68-5.04s-.07-3.7-.68-5.02c-.4-.87-1.04-1.29-2-1.29-.95 0-1.59.42-1.99 1.29-.61 1.32-.68 3.58-.68 5.02 0 1.44.07 3.71.68 5.04.4.86 1.04 1.28 1.99 1.28.96 0 1.6-.42 2-1.28z" })) })));
|
|
824
854
|
};
|
|
825
855
|
|
|
826
|
-
const VideoPlayerControls = ({ config
|
|
856
|
+
const VideoPlayerControls = ({ config }) => {
|
|
827
857
|
return (React.createElement("div", { className: "px-20" },
|
|
828
|
-
React.createElement("div", { className:
|
|
858
|
+
React.createElement("div", { className: "absolute top-0 left-0 h-full w-full bg-[rgba(0,0,0,0.5)] flex flex-col justify-between" },
|
|
829
859
|
React.createElement(ControlsHeader, { config: config?.headerConfig?.config }),
|
|
830
860
|
React.createElement(MiddleControls, null),
|
|
831
861
|
React.createElement(BottomControls, { config: config?.bottomConfig?.config }))));
|
|
@@ -848,71 +878,59 @@ const Overlay = ({ config }) => {
|
|
|
848
878
|
const controlsTimerRef = useRef(null);
|
|
849
879
|
const { setControls, controls, showCountdown, countdownTime, setShowCountdown, setAutoPlayNext, setCurrentEpisodeIndex, episodeList, setCountdownTime, videoRef, currentEpisodeIndex, } = useVideoStore();
|
|
850
880
|
const { onClose } = config?.headerConfig?.config || {};
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
if (videoPlayerControls) {
|
|
854
|
-
videoPlayerControls.classList.remove("noCursor");
|
|
855
|
-
}
|
|
856
|
-
setControls(true);
|
|
881
|
+
const HIDE_DELAY = 2000;
|
|
882
|
+
const resetControlsTimer = useCallback(() => {
|
|
857
883
|
if (controlsTimerRef.current) {
|
|
858
884
|
clearTimeout(controlsTimerRef.current);
|
|
859
885
|
}
|
|
860
886
|
controlsTimerRef.current = setTimeout(() => {
|
|
861
887
|
setControls(false);
|
|
888
|
+
const videoPlayerControls = document?.getElementById("videoPlayerControls");
|
|
862
889
|
if (videoPlayerControls) {
|
|
863
890
|
videoPlayerControls.classList.add("noCursor");
|
|
864
891
|
}
|
|
865
|
-
},
|
|
892
|
+
}, HIDE_DELAY);
|
|
866
893
|
}, [setControls]);
|
|
867
|
-
|
|
894
|
+
const handleMouseEnter = useCallback(() => {
|
|
895
|
+
const videoPlayerControls = document?.getElementById("videoPlayerControls");
|
|
896
|
+
if (videoPlayerControls) {
|
|
897
|
+
videoPlayerControls.classList.remove("noCursor");
|
|
898
|
+
}
|
|
899
|
+
setControls(true);
|
|
900
|
+
resetControlsTimer();
|
|
901
|
+
}, [setControls, resetControlsTimer]);
|
|
902
|
+
useEffect(() => {
|
|
903
|
+
return () => {
|
|
904
|
+
if (controlsTimerRef.current) {
|
|
905
|
+
clearTimeout(controlsTimerRef.current);
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
}, []);
|
|
909
|
+
useEffect(() => {
|
|
868
910
|
let timer;
|
|
869
911
|
if (showCountdown && countdownTime > 0 && episodeList.length > 0) {
|
|
870
912
|
timer = setInterval(() => {
|
|
871
913
|
setCountdownTime(countdownTime - 1);
|
|
872
914
|
}, 1000);
|
|
873
915
|
}
|
|
874
|
-
else if (showCountdown && countdownTime === 0 && episodeList.length > 0) {
|
|
875
|
-
const nextIndex = currentEpisodeIndex + 1;
|
|
876
|
-
if (nextIndex < episodeList.length) {
|
|
877
|
-
setCurrentEpisodeIndex(nextIndex);
|
|
878
|
-
setAutoPlayNext(true);
|
|
879
|
-
if (videoRef && episodeList[nextIndex]) {
|
|
880
|
-
videoRef.src = episodeList[nextIndex].url;
|
|
881
|
-
videoRef
|
|
882
|
-
.play()
|
|
883
|
-
.catch((err) => console.error("Auto-play failed:", err));
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
else {
|
|
887
|
-
if (onClose)
|
|
888
|
-
onClose();
|
|
889
|
-
}
|
|
890
|
-
setShowCountdown(false);
|
|
891
|
-
}
|
|
892
916
|
return () => {
|
|
893
917
|
if (timer)
|
|
894
918
|
clearInterval(timer);
|
|
895
919
|
};
|
|
896
|
-
}, [
|
|
897
|
-
showCountdown,
|
|
898
|
-
countdownTime,
|
|
899
|
-
episodeList.length,
|
|
900
|
-
setCountdownTime,
|
|
901
|
-
setCurrentEpisodeIndex,
|
|
902
|
-
currentEpisodeIndex,
|
|
903
|
-
setAutoPlayNext,
|
|
904
|
-
videoRef,
|
|
905
|
-
episodeList,
|
|
906
|
-
onClose,
|
|
907
|
-
]);
|
|
920
|
+
}, [showCountdown, countdownTime, episodeList.length, setCountdownTime]);
|
|
908
921
|
const handleNextEpisodeManually = () => {
|
|
909
922
|
const nextIndex = currentEpisodeIndex + 1;
|
|
910
923
|
if (nextIndex < episodeList.length && videoRef && episodeList[nextIndex]) {
|
|
911
924
|
setCurrentEpisodeIndex(nextIndex);
|
|
912
925
|
setAutoPlayNext(true);
|
|
913
926
|
videoRef.src = episodeList[nextIndex].url;
|
|
914
|
-
videoRef
|
|
927
|
+
videoRef
|
|
928
|
+
.play()
|
|
929
|
+
.catch((err) => console.error("Manual play failed:", err));
|
|
915
930
|
setShowCountdown(false);
|
|
931
|
+
setCountdownTime(10);
|
|
932
|
+
setControls(true);
|
|
933
|
+
resetControlsTimer();
|
|
916
934
|
}
|
|
917
935
|
else if (onClose) {
|
|
918
936
|
onClose();
|
|
@@ -922,7 +940,7 @@ const Overlay = ({ config }) => {
|
|
|
922
940
|
controls && React.createElement(VideoPlayerControls, { config: config }),
|
|
923
941
|
showCountdown &&
|
|
924
942
|
episodeList.length > 0 &&
|
|
925
|
-
currentEpisodeIndex + 1 < episodeList.length && (React.createElement(VideoActionButton, { text: "Next Episode", onClick: handleNextEpisodeManually, icon: React.createElement(
|
|
943
|
+
currentEpisodeIndex + 1 < episodeList.length && (React.createElement(VideoActionButton, { text: "Next Episode", onClick: handleNextEpisodeManually, icon: React.createElement(ArrowRight, { className: "h-5 w-5 text-gray-900" }), disabled: currentEpisodeIndex + 1 >= episodeList.length, position: "right" }))));
|
|
926
944
|
};
|
|
927
945
|
|
|
928
946
|
const SubtitleOverlay = ({ styleConfig }) => {
|
|
@@ -1039,39 +1057,195 @@ const SubtitleOverlay = ({ styleConfig }) => {
|
|
|
1039
1057
|
return React__default.createElement("div", { style: subtitleStyle }, currentSubtitle);
|
|
1040
1058
|
};
|
|
1041
1059
|
|
|
1060
|
+
/**
|
|
1061
|
+
* Video Source Hook
|
|
1062
|
+
*
|
|
1063
|
+
* Manages video source loading and streaming technology detection
|
|
1064
|
+
* Supports HLS.js, DASH.js, and native HTML5 video
|
|
1065
|
+
*
|
|
1066
|
+
* Features:
|
|
1067
|
+
* - Automatic stream type detection
|
|
1068
|
+
* - HLS.js fallback for older browsers
|
|
1069
|
+
* - DASH.js support with proper initialization
|
|
1070
|
+
* - Quality level extraction for all stream types
|
|
1071
|
+
* - Error handling and cleanup
|
|
1072
|
+
*/
|
|
1042
1073
|
const useVideoSource = (trackSrc, type) => {
|
|
1043
|
-
const { videoRef, setQualityLevels, setHlsInstance } = useVideoStore();
|
|
1074
|
+
const { videoRef, setQualityLevels, setHlsInstance, setDashInstance, setStreamType } = useVideoStore();
|
|
1044
1075
|
useEffect(() => {
|
|
1045
1076
|
if (!videoRef)
|
|
1046
1077
|
return;
|
|
1047
1078
|
const getVideoExtension = getExtensionFromUrl(trackSrc);
|
|
1048
1079
|
const contentType = type || getVideoExtension;
|
|
1049
|
-
|
|
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") {
|
|
1050
1084
|
videoRef.src = trackSrc;
|
|
1051
1085
|
setQualityLevels([]);
|
|
1086
|
+
return;
|
|
1052
1087
|
}
|
|
1053
|
-
|
|
1088
|
+
// Handle HLS streams
|
|
1089
|
+
if (contentType === "hls") {
|
|
1090
|
+
// Native HLS support (Safari/iOS)
|
|
1054
1091
|
if (videoRef?.canPlayType("application/vnd.apple.mpegurl")) {
|
|
1092
|
+
console.log('📱 Using native HLS support');
|
|
1055
1093
|
videoRef.src = trackSrc;
|
|
1094
|
+
// For native HLS, we can't control quality directly, but we can still extract info
|
|
1095
|
+
const handleLoadedMetadata = () => {
|
|
1096
|
+
const videoElement = videoRef;
|
|
1097
|
+
if (videoElement.videoTracks && videoElement.videoTracks.length > 0) {
|
|
1098
|
+
// Extract quality levels from native HLS
|
|
1099
|
+
const tracks = Array.from(videoElement.videoTracks).map((track, index) => ({
|
|
1100
|
+
height: track.height || 720,
|
|
1101
|
+
bitrate: track.bandwidth || 0,
|
|
1102
|
+
originalIndex: index
|
|
1103
|
+
}));
|
|
1104
|
+
setQualityLevels(tracks);
|
|
1105
|
+
console.log('✅ Native HLS quality levels:', tracks);
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
// Fallback quality levels for native HLS
|
|
1109
|
+
const defaultLevels = [
|
|
1110
|
+
{ height: 360, bitrate: 800000, originalIndex: 0 },
|
|
1111
|
+
{ height: 480, bitrate: 1400000, originalIndex: 1 },
|
|
1112
|
+
{ height: 720, bitrate: 2800000, originalIndex: 2 },
|
|
1113
|
+
{ height: 1080, bitrate: 5000000, originalIndex: 3 },
|
|
1114
|
+
];
|
|
1115
|
+
setQualityLevels(defaultLevels);
|
|
1116
|
+
console.log('✅ Native HLS fallback quality levels:', defaultLevels);
|
|
1117
|
+
}
|
|
1118
|
+
// Even for native HLS, set a mock HLS instance to indicate it's HLS
|
|
1119
|
+
// This allows the quality manager to know we're dealing with HLS
|
|
1120
|
+
setHlsInstance(null); // null indicates native HLS, not HLS.js
|
|
1121
|
+
};
|
|
1122
|
+
videoRef.addEventListener('loadedmetadata', handleLoadedMetadata);
|
|
1123
|
+
return () => {
|
|
1124
|
+
videoRef.removeEventListener('loadedmetadata', handleLoadedMetadata);
|
|
1125
|
+
};
|
|
1056
1126
|
}
|
|
1127
|
+
// HLS.js support (Chrome/Firefox/etc)
|
|
1057
1128
|
else if (Hls.isSupported()) {
|
|
1058
|
-
|
|
1129
|
+
console.log('🔧 Using HLS.js for HLS streaming');
|
|
1130
|
+
const hls = new Hls({
|
|
1131
|
+
// HLS.js configuration for optimal performance
|
|
1132
|
+
enableWorker: true,
|
|
1133
|
+
lowLatencyMode: true,
|
|
1134
|
+
backBufferLength: 90
|
|
1135
|
+
});
|
|
1059
1136
|
hls.loadSource(trackSrc);
|
|
1060
1137
|
hls.attachMedia(videoRef);
|
|
1138
|
+
console.log('✅ HLS.js instance created and attached');
|
|
1061
1139
|
setHlsInstance(hls);
|
|
1140
|
+
// Extract quality levels when manifest is parsed
|
|
1062
1141
|
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
1063
|
-
|
|
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);
|
|
1064
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
|
|
1065
1159
|
return () => {
|
|
1066
1160
|
hls.destroy();
|
|
1161
|
+
console.log('🧹 HLS.js instance destroyed');
|
|
1067
1162
|
};
|
|
1068
1163
|
}
|
|
1164
|
+
else {
|
|
1165
|
+
// Fallback when HLS.js is not supported
|
|
1166
|
+
console.log('📱 Using fallback HLS (direct src)');
|
|
1167
|
+
videoRef.src = trackSrc;
|
|
1168
|
+
setHlsInstance(null); // null indicates native HLS fallback
|
|
1169
|
+
// Set fallback quality levels
|
|
1170
|
+
const defaultLevels = [
|
|
1171
|
+
{ height: 360, bitrate: 800000, originalIndex: 0 },
|
|
1172
|
+
{ height: 480, bitrate: 1400000, originalIndex: 1 },
|
|
1173
|
+
{ height: 720, bitrate: 2800000, originalIndex: 2 },
|
|
1174
|
+
{ height: 1080, bitrate: 5000000, originalIndex: 3 },
|
|
1175
|
+
];
|
|
1176
|
+
setQualityLevels(defaultLevels);
|
|
1177
|
+
console.log('✅ HLS fallback quality levels:', defaultLevels);
|
|
1178
|
+
}
|
|
1069
1179
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1180
|
+
// Handle DASH streams
|
|
1181
|
+
else if (contentType === "dash") {
|
|
1182
|
+
// DASH.js support
|
|
1183
|
+
if (dashjs.supportsMediaSource()) {
|
|
1184
|
+
console.log('🔧 Using DASH.js for DASH streaming');
|
|
1185
|
+
const player = dashjs.MediaPlayer().create();
|
|
1186
|
+
// DASH.js configuration for optimal performance
|
|
1187
|
+
player.updateSettings({
|
|
1188
|
+
streaming: {
|
|
1189
|
+
buffer: {
|
|
1190
|
+
fastSwitchEnabled: true, // Enable fast quality switching
|
|
1191
|
+
bufferTimeAtTopQuality: 30, // Buffer 30s at top quality
|
|
1192
|
+
bufferTimeAtTopQualityLongForm: 60 // Buffer 60s for long content
|
|
1193
|
+
},
|
|
1194
|
+
// Note: Some ABR settings may vary by DASH.js version
|
|
1195
|
+
// Check documentation for your specific version
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
player.initialize(videoRef, trackSrc, true);
|
|
1199
|
+
console.log('✅ DASH.js instance created and initialized');
|
|
1200
|
+
setDashInstance(player);
|
|
1201
|
+
// Extract quality levels when manifest is loaded
|
|
1202
|
+
const handleManifestLoaded = () => {
|
|
1203
|
+
try {
|
|
1204
|
+
const representations = player.getRepresentationsByType('video');
|
|
1205
|
+
if (representations && representations.length > 0) {
|
|
1206
|
+
const levels = representations.map((rep, index) => ({
|
|
1207
|
+
height: rep.height || Math.round(rep.bandwidth / 1000) || 0,
|
|
1208
|
+
bitrate: rep.bandwidth,
|
|
1209
|
+
originalIndex: index,
|
|
1210
|
+
id: rep.id
|
|
1211
|
+
}));
|
|
1212
|
+
setQualityLevels(levels);
|
|
1213
|
+
console.log('✅ DASH.js quality levels:', levels);
|
|
1214
|
+
}
|
|
1215
|
+
else {
|
|
1216
|
+
console.warn('⚠️ No DASH video representations found');
|
|
1217
|
+
setQualityLevels([]);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
catch (error) {
|
|
1221
|
+
console.error('❌ Error getting DASH quality levels:', error);
|
|
1222
|
+
setQualityLevels([]);
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
// Listen for manifest loaded event
|
|
1226
|
+
player.on('manifestLoaded', handleManifestLoaded);
|
|
1227
|
+
// Log quality changes for debugging
|
|
1228
|
+
player.on('qualityChange', (e) => {
|
|
1229
|
+
console.log('🔄 DASH quality changed to:', e.newQuality, e);
|
|
1230
|
+
});
|
|
1231
|
+
// Error handling
|
|
1232
|
+
player.on('error', (e) => {
|
|
1233
|
+
console.error('❌ DASH.js error:', e);
|
|
1234
|
+
});
|
|
1235
|
+
// Cleanup
|
|
1236
|
+
return () => {
|
|
1237
|
+
player.reset();
|
|
1238
|
+
console.log('🧹 DASH.js instance reset');
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
else {
|
|
1242
|
+
console.warn('⚠️ DASH.js not supported in this browser');
|
|
1243
|
+
}
|
|
1073
1244
|
}
|
|
1074
|
-
|
|
1245
|
+
// Fallback for unsupported formats
|
|
1246
|
+
videoRef.src = trackSrc;
|
|
1247
|
+
setQualityLevels([]);
|
|
1248
|
+
}, [trackSrc, videoRef, type, setQualityLevels, setHlsInstance, setDashInstance, setStreamType]);
|
|
1075
1249
|
};
|
|
1076
1250
|
|
|
1077
1251
|
const useSubtitles = (subtitles) => {
|
|
@@ -1232,8 +1406,7 @@ const useVideoTracking = (tracking, episodeList, currentEpisodeIndex, onClose) =
|
|
|
1232
1406
|
setShowCountdown,
|
|
1233
1407
|
]);
|
|
1234
1408
|
useEffect(() => {
|
|
1235
|
-
const handleUnload = (
|
|
1236
|
-
e.preventDefault();
|
|
1409
|
+
const handleUnload = () => {
|
|
1237
1410
|
if (startTime.current) {
|
|
1238
1411
|
const elapsedTime = (Date.now() - startTime.current) / 1000;
|
|
1239
1412
|
const getCurrentTime = localStorage.getItem("current_time");
|
|
@@ -1247,10 +1420,8 @@ const useVideoTracking = (tracking, episodeList, currentEpisodeIndex, onClose) =
|
|
|
1247
1420
|
}
|
|
1248
1421
|
localStorage.setItem("current_time", "0");
|
|
1249
1422
|
};
|
|
1250
|
-
window.addEventListener("beforeunload", handleUnload);
|
|
1251
1423
|
window.addEventListener("unload", handleUnload);
|
|
1252
1424
|
return () => {
|
|
1253
|
-
window.removeEventListener("beforeunload", handleUnload);
|
|
1254
1425
|
window.removeEventListener("unload", handleUnload);
|
|
1255
1426
|
};
|
|
1256
1427
|
}, [tracking]);
|
|
@@ -1326,7 +1497,7 @@ const useEpisodes = (episodeList, currentEpisodeIndex, nextEpisodeConfig) => {
|
|
|
1326
1497
|
};
|
|
1327
1498
|
|
|
1328
1499
|
const useVideoEvents = () => {
|
|
1329
|
-
const { setCurrentTime, setDuration } = useVideoStore();
|
|
1500
|
+
const { setCurrentTime, setDuration, setBufferedProgress, setIsPlaying } = useVideoStore();
|
|
1330
1501
|
const onRightClick = (e) => {
|
|
1331
1502
|
e.preventDefault();
|
|
1332
1503
|
};
|
|
@@ -1346,18 +1517,51 @@ const useVideoEvents = () => {
|
|
|
1346
1517
|
setDuration(e?.currentTarget?.duration);
|
|
1347
1518
|
}
|
|
1348
1519
|
};
|
|
1520
|
+
const onProgress = (e) => {
|
|
1521
|
+
const video = e.currentTarget;
|
|
1522
|
+
if (video.buffered.length > 0 &&
|
|
1523
|
+
video.duration > 0 &&
|
|
1524
|
+
!isNaN(video.duration)) {
|
|
1525
|
+
let bufferedEnd = 0;
|
|
1526
|
+
for (let i = 0; i < video.buffered.length; i++) {
|
|
1527
|
+
if (video.currentTime >= video.buffered.start(i) &&
|
|
1528
|
+
video.currentTime <= video.buffered.end(i)) {
|
|
1529
|
+
bufferedEnd = video.buffered.end(i);
|
|
1530
|
+
break;
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
if (bufferedEnd === 0 && video.buffered.length > 0) {
|
|
1534
|
+
bufferedEnd = video.buffered.end(video.buffered.length - 1);
|
|
1535
|
+
}
|
|
1536
|
+
const bufferedProgress = Math.min((bufferedEnd / video.duration) * 100, 100);
|
|
1537
|
+
setBufferedProgress(bufferedProgress);
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
const onPlay = () => {
|
|
1541
|
+
setIsPlaying(true);
|
|
1542
|
+
};
|
|
1543
|
+
const onPause = () => {
|
|
1544
|
+
setIsPlaying(false);
|
|
1545
|
+
};
|
|
1546
|
+
const onEnded = (e) => {
|
|
1547
|
+
setIsPlaying(false);
|
|
1548
|
+
};
|
|
1349
1549
|
return {
|
|
1350
1550
|
onRightClick,
|
|
1351
1551
|
onSeeked,
|
|
1352
1552
|
onTimeUpdate,
|
|
1353
1553
|
onLoadedMetadata,
|
|
1554
|
+
onProgress,
|
|
1555
|
+
onPlay,
|
|
1556
|
+
onPause,
|
|
1557
|
+
onEnded,
|
|
1354
1558
|
};
|
|
1355
1559
|
};
|
|
1356
1560
|
|
|
1357
1561
|
var css_248z = ".video-player video::cue {\n display: none !important;\n opacity: 0 !important;\n visibility: hidden !important;\n}\n\n.custom-subtitle-overlay {\n position: absolute;\n bottom: 10%;\n left: 50%;\n transform: translateX(-50%);\n\n font-size: 1.5rem;\n font-weight: 600;\n line-height: 1.4;\n text-align: center;\n\n color: #000;\n background: rgba(255, 255, 255, 0.6);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n\n padding: 10px 16px;\n border-radius: 10px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);\n\n max-width: 70%;\n min-width: fit-content;\n\n transition: all 0.2s ease-in-out;\n}\n\n.custom-subtitle-overlay:hover {\n transform: translateX(-50%) scale(1.02);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);\n}\n\n@media (max-width: 768px) {\n .custom-subtitle-overlay {\n font-size: 1.25rem;\n padding: 8px 14px;\n bottom: 8%;\n max-width: 85%;\n }\n}\n\n@media (max-width: 480px) {\n .custom-subtitle-overlay {\n font-size: 1rem;\n padding: 6px 10px;\n bottom: 6%;\n max-width: 90%;\n }\n}\n\n@media (prefers-contrast: high) {\n .custom-subtitle-overlay {\n background: #ffff00;\n color: #000;\n border: 3px solid #000;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-subtitle-overlay {\n transition: none;\n }\n\n .custom-subtitle-overlay:hover {\n transform: translateX(-50%);\n }\n}\n";
|
|
1358
1562
|
styleInject(css_248z,{"insertAt":"top"});
|
|
1359
1563
|
|
|
1360
|
-
const VideoPlayer = ({ trackSrc, trackTitle, intro, onClose, trackPoster, isTrailer, className, type, height, width, timeCodes, getPreviewScreenUrl, tracking, subtitles, episodeList, currentEpisodeIndex = 0, nextEpisodeConfig, subtitleStyle, }) => {
|
|
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, }) => {
|
|
1361
1565
|
const { setVideoRef, setVideoWrapperRef, videoRef } = useVideoStore();
|
|
1362
1566
|
useVideoSource(trackSrc, type);
|
|
1363
1567
|
useSubtitles(subtitles);
|
|
@@ -1365,11 +1569,16 @@ const VideoPlayer = ({ trackSrc, trackTitle, intro, onClose, trackPoster, isTrai
|
|
|
1365
1569
|
useVideoTracking(tracking, episodeList, currentEpisodeIndex, onClose);
|
|
1366
1570
|
const { showSkipIntro, handleSkipIntro } = useIntroSkip(intro);
|
|
1367
1571
|
useEpisodes(episodeList, currentEpisodeIndex, nextEpisodeConfig);
|
|
1368
|
-
const { onSeeked, onTimeUpdate, onLoadedMetadata } = useVideoEvents();
|
|
1572
|
+
const { onSeeked, onTimeUpdate, onLoadedMetadata, onProgress, onPlay, onPause, onEnded: onEndedHook, } = useVideoEvents();
|
|
1369
1573
|
return (React__default.createElement("div", { ref: setVideoWrapperRef, className: `video-player ${height || "h-full"} ${width || "w-full"} mx-auto absolute` },
|
|
1370
1574
|
trackPoster && (React__default.createElement("div", { className: "pip-poster absolute inset-0 bg-center bg-cover hidden", style: { backgroundImage: `url(${trackPoster})` } })),
|
|
1371
|
-
React__default.createElement("video", { autoPlay: true,
|
|
1372
|
-
|
|
1575
|
+
React__default.createElement("video", { autoPlay: true, playsInline: true, preload: "metadata", ref: setVideoRef, onSeeked: onSeeked, poster: trackPoster, crossOrigin: "anonymous", controls: false, disableRemotePlayback: true, controlsList: "nodownload", onContextMenu: (e) => e.preventDefault(), onTimeUpdate: onTimeUpdate, onLoadedMetadata: onLoadedMetadata, onProgress: onProgress, onPlay: onPlay, onPause: onPause, onEnded: (e) => {
|
|
1576
|
+
onEndedHook(e);
|
|
1577
|
+
onEnded?.(e);
|
|
1578
|
+
}, onError: (e) => {
|
|
1579
|
+
onError?.(e);
|
|
1580
|
+
}, muted: isMute, className: `w-full h-full relative ${className}` }),
|
|
1581
|
+
showControls && (React__default.createElement(Overlay, { config: {
|
|
1373
1582
|
headerConfig: {
|
|
1374
1583
|
config: {
|
|
1375
1584
|
isTrailer: isTrailer,
|
|
@@ -1387,7 +1596,7 @@ const VideoPlayer = ({ trackSrc, trackTitle, intro, onClose, trackPoster, isTrai
|
|
|
1387
1596
|
},
|
|
1388
1597
|
},
|
|
1389
1598
|
},
|
|
1390
|
-
} }),
|
|
1599
|
+
} })),
|
|
1391
1600
|
React__default.createElement(SubtitleOverlay, { styleConfig: subtitleStyle }),
|
|
1392
1601
|
showSkipIntro && (React__default.createElement(VideoActionButton, { text: "Skip Intro", onClick: handleSkipIntro, position: "left" }))));
|
|
1393
1602
|
};
|
|
@@ -6,3 +6,4 @@ export { createVideoQualitySlice } from "./videoQualitySlice";
|
|
|
6
6
|
export { createSubtitlesSlice } from "./subtitlesSlice";
|
|
7
7
|
export { createEpisodesSlice } from "./episodesSlice";
|
|
8
8
|
export { createIntroSlice } from "./introSlice";
|
|
9
|
+
export { createResetSlice } from "./resetSlice";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Hls from "hls.js";
|
|
2
|
-
import
|
|
2
|
+
import * as dashjs from "dashjs";
|
|
3
3
|
export interface VideoRefsState {
|
|
4
4
|
videoRef: HTMLVideoElement | null;
|
|
5
5
|
setVideoRef: (ref: HTMLVideoElement) => void;
|
|
@@ -7,8 +7,8 @@ export interface VideoRefsState {
|
|
|
7
7
|
setVideoWrapperRef: (ref: HTMLDivElement) => void;
|
|
8
8
|
}
|
|
9
9
|
export interface VideoPlaybackState {
|
|
10
|
-
playing: boolean
|
|
11
|
-
setPlaying:
|
|
10
|
+
playing: boolean;
|
|
11
|
+
setPlaying: (playing: boolean) => void;
|
|
12
12
|
isBuffering: boolean;
|
|
13
13
|
setIsBuffering: (isBuffering: boolean) => void;
|
|
14
14
|
isPlaying: boolean;
|
|
@@ -23,20 +23,38 @@ export interface VideoTimingState {
|
|
|
23
23
|
setCurrentTime: (currentTime: number) => void;
|
|
24
24
|
duration: number;
|
|
25
25
|
setDuration: (duration: number) => void;
|
|
26
|
+
bufferedProgress: number;
|
|
27
|
+
setBufferedProgress: (progress: number) => void;
|
|
26
28
|
}
|
|
27
29
|
export interface VideoControlsState {
|
|
28
30
|
controls: boolean;
|
|
29
31
|
setControls: (controls: boolean) => void;
|
|
30
32
|
isFullscreen: boolean;
|
|
31
33
|
setIsFullscreen: (isFullscreen: boolean) => void;
|
|
34
|
+
controlsVisible: boolean;
|
|
35
|
+
setControlsVisible: (visible: boolean) => void;
|
|
32
36
|
}
|
|
33
37
|
export interface VideoQualityState {
|
|
34
|
-
hlsInstance?: Hls;
|
|
35
|
-
setHlsInstance: (hlsInstance: Hls) => void;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
hlsInstance?: Hls | null;
|
|
39
|
+
setHlsInstance: (hlsInstance: Hls | null) => void;
|
|
40
|
+
dashInstance?: dashjs.MediaPlayerClass;
|
|
41
|
+
setDashInstance: (dashInstance: dashjs.MediaPlayerClass) => void;
|
|
42
|
+
qualityLevels?: Array<{
|
|
43
|
+
height: number;
|
|
44
|
+
bitrate?: number;
|
|
45
|
+
originalIndex: number;
|
|
46
|
+
id?: string;
|
|
47
|
+
}>;
|
|
48
|
+
setQualityLevels: (qualityLevels: Array<{
|
|
49
|
+
height: number;
|
|
50
|
+
bitrate?: number;
|
|
51
|
+
originalIndex: number;
|
|
52
|
+
id?: string;
|
|
53
|
+
}>) => void;
|
|
38
54
|
activeQuality: string;
|
|
39
55
|
setActiveQuality: (activeQuality: string) => void;
|
|
56
|
+
streamType: "hls" | "dash" | "mp4" | "other";
|
|
57
|
+
setStreamType: (streamType: "hls" | "dash" | "mp4" | "other") => void;
|
|
40
58
|
}
|
|
41
59
|
export interface SubtitleTrack {
|
|
42
60
|
lang: string;
|
|
@@ -70,5 +88,8 @@ export interface IntroState {
|
|
|
70
88
|
showIntroSkip: boolean;
|
|
71
89
|
setShowIntroSkip: (show: boolean) => void;
|
|
72
90
|
}
|
|
73
|
-
export interface
|
|
91
|
+
export interface StoreResetState {
|
|
92
|
+
resetStore: () => void;
|
|
93
|
+
}
|
|
94
|
+
export interface VideoState extends VideoRefsState, VideoPlaybackState, VideoTimingState, VideoControlsState, VideoQualityState, SubtitlesState, EpisodesState, IntroState, StoreResetState {
|
|
74
95
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zezosoft/react-player",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "npx rollup -c",
|
|
9
|
-
"dev": "npx rollup -c -w",
|
|
10
|
-
"prepare": "npm run build"
|
|
11
|
-
},
|
|
12
7
|
"files": [
|
|
13
8
|
"dist"
|
|
14
9
|
],
|
|
@@ -36,8 +31,8 @@
|
|
|
36
31
|
"devDependencies": {
|
|
37
32
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
38
33
|
"@tailwindcss/postcss": "^4.0.14",
|
|
39
|
-
"@types/react": "^19.0.10",
|
|
40
34
|
"@types/node": "^24.4.0",
|
|
35
|
+
"@types/react": "^19.0.10",
|
|
41
36
|
"hls.js": "^1.5.20",
|
|
42
37
|
"lucide-react": "^0.481.0",
|
|
43
38
|
"postcss": "^8.5.3",
|
|
@@ -57,6 +52,12 @@
|
|
|
57
52
|
"zustand": "^5.0.3"
|
|
58
53
|
},
|
|
59
54
|
"dependencies": {
|
|
60
|
-
"
|
|
55
|
+
"dashjs": "^5.0.3",
|
|
56
|
+
"react-icons": "^5.5.0",
|
|
57
|
+
"screenfull": "^6.0.2"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "npx rollup -c",
|
|
61
|
+
"dev": "npx rollup -c -w"
|
|
61
62
|
}
|
|
62
|
-
}
|
|
63
|
+
}
|