ts-video-player 0.1.0
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/LICENSE.md +21 -0
- package/README.md +438 -0
- package/dist/cdn.d.ts +10 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.min.js +1046 -0
- package/dist/cdn.min.js.map +54 -0
- package/dist/cdn.test.d.ts +2 -0
- package/dist/cdn.test.d.ts.map +1 -0
- package/dist/chunk-36x4yhjn.js +765 -0
- package/dist/chunk-36x4yhjn.js.map +12 -0
- package/dist/chunk-eajts2jv.js +4243 -0
- package/dist/chunk-eajts2jv.js.map +42 -0
- package/dist/chunk-z1ehbz9f.js +380 -0
- package/dist/chunk-z1ehbz9f.js.map +19 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/core/audio-gain.d.ts +67 -0
- package/dist/core/audio-gain.d.ts.map +1 -0
- package/dist/core/caption-styles.d.ts +121 -0
- package/dist/core/caption-styles.d.ts.map +1 -0
- package/dist/core/chapters.d.ts +150 -0
- package/dist/core/chapters.d.ts.map +1 -0
- package/dist/core/error-overlay.d.ts +71 -0
- package/dist/core/error-overlay.d.ts.map +1 -0
- package/dist/core/events.d.ts +119 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/features.d.ts +64 -0
- package/dist/core/features.d.ts.map +1 -0
- package/dist/core/i18n.d.ts +132 -0
- package/dist/core/i18n.d.ts.map +1 -0
- package/dist/core/index.d.ts +37 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/live.d.ts +130 -0
- package/dist/core/live.d.ts.map +1 -0
- package/dist/core/media-session.d.ts +38 -0
- package/dist/core/media-session.d.ts.map +1 -0
- package/dist/core/orientation.d.ts +22 -0
- package/dist/core/orientation.d.ts.map +1 -0
- package/dist/core/popover.d.ts +42 -0
- package/dist/core/popover.d.ts.map +1 -0
- package/dist/core/remote-playback.d.ts +195 -0
- package/dist/core/remote-playback.d.ts.map +1 -0
- package/dist/core/settings-menu.d.ts +132 -0
- package/dist/core/settings-menu.d.ts.map +1 -0
- package/dist/core/spinner.d.ts +67 -0
- package/dist/core/spinner.d.ts.map +1 -0
- package/dist/core/state.d.ts +142 -0
- package/dist/core/state.d.ts.map +1 -0
- package/dist/core/text-tracks.d.ts +162 -0
- package/dist/core/text-tracks.d.ts.map +1 -0
- package/dist/core/themes.d.ts +213 -0
- package/dist/core/themes.d.ts.map +1 -0
- package/dist/core/thumbnails.d.ts +124 -0
- package/dist/core/thumbnails.d.ts.map +1 -0
- package/dist/core/tooltips.d.ts +100 -0
- package/dist/core/tooltips.d.ts.map +1 -0
- package/dist/elements/base.d.ts +3 -0
- package/dist/elements/base.d.ts.map +1 -0
- package/dist/elements/index.d.ts +33 -0
- package/dist/elements/index.d.ts.map +1 -0
- package/dist/elements/index.js +55 -0
- package/dist/elements/index.js.map +9 -0
- package/dist/elements/media-airplay-button.d.ts +13 -0
- package/dist/elements/media-airplay-button.d.ts.map +1 -0
- package/dist/elements/media-error-display.d.ts +13 -0
- package/dist/elements/media-error-display.d.ts.map +1 -0
- package/dist/elements/media-fullscreen-button.d.ts +13 -0
- package/dist/elements/media-fullscreen-button.d.ts.map +1 -0
- package/dist/elements/media-live-button.d.ts +13 -0
- package/dist/elements/media-live-button.d.ts.map +1 -0
- package/dist/elements/media-mute-button.d.ts +13 -0
- package/dist/elements/media-mute-button.d.ts.map +1 -0
- package/dist/elements/media-pip-button.d.ts +13 -0
- package/dist/elements/media-pip-button.d.ts.map +1 -0
- package/dist/elements/media-play-button.d.ts +14 -0
- package/dist/elements/media-play-button.d.ts.map +1 -0
- package/dist/elements/media-playback-rate-button.d.ts +10 -0
- package/dist/elements/media-playback-rate-button.d.ts.map +1 -0
- package/dist/elements/media-preview-time.d.ts +18 -0
- package/dist/elements/media-preview-time.d.ts.map +1 -0
- package/dist/elements/media-progress-bar.d.ts +16 -0
- package/dist/elements/media-progress-bar.d.ts.map +1 -0
- package/dist/elements/media-remote-playback-button.d.ts +16 -0
- package/dist/elements/media-remote-playback-button.d.ts.map +1 -0
- package/dist/elements/media-seek-button.d.ts +9 -0
- package/dist/elements/media-seek-button.d.ts.map +1 -0
- package/dist/elements/media-settings-menu.d.ts +32 -0
- package/dist/elements/media-settings-menu.d.ts.map +1 -0
- package/dist/elements/media-time-display.d.ts +21 -0
- package/dist/elements/media-time-display.d.ts.map +1 -0
- package/dist/elements/media-time-group.d.ts +22 -0
- package/dist/elements/media-time-group.d.ts.map +1 -0
- package/dist/elements/media-time-separator.d.ts +13 -0
- package/dist/elements/media-time-separator.d.ts.map +1 -0
- package/dist/elements/media-volume-slider.d.ts +16 -0
- package/dist/elements/media-volume-slider.d.ts.map +1 -0
- package/dist/elements/utils.d.ts +27 -0
- package/dist/elements/utils.d.ts.map +1 -0
- package/dist/elements/video-player.d.ts +23 -0
- package/dist/elements/video-player.d.ts.map +1 -0
- package/dist/elements/video-skin.d.ts +23 -0
- package/dist/elements/video-skin.d.ts.map +1 -0
- package/dist/features/captions.d.ts +8 -0
- package/dist/features/captions.d.ts.map +1 -0
- package/dist/features/create-player.d.ts +45 -0
- package/dist/features/create-player.d.ts.map +1 -0
- package/dist/features/fullscreen.d.ts +8 -0
- package/dist/features/fullscreen.d.ts.map +1 -0
- package/dist/features/gestures.d.ts +8 -0
- package/dist/features/gestures.d.ts.map +1 -0
- package/dist/features/index.d.ts +26 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +31 -0
- package/dist/features/index.js.map +9 -0
- package/dist/features/keyboard.d.ts +8 -0
- package/dist/features/keyboard.d.ts.map +1 -0
- package/dist/features/pip.d.ts +8 -0
- package/dist/features/pip.d.ts.map +1 -0
- package/dist/features/playback.d.ts +8 -0
- package/dist/features/playback.d.ts.map +1 -0
- package/dist/features/quality.d.ts +8 -0
- package/dist/features/quality.d.ts.map +1 -0
- package/dist/features/types.d.ts +26 -0
- package/dist/features/types.d.ts.map +1 -0
- package/dist/features/volume.d.ts +8 -0
- package/dist/features/volume.d.ts.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8149 -0
- package/dist/index.js.map +31 -0
- package/dist/layouts/index.d.ts +58 -0
- package/dist/layouts/index.d.ts.map +1 -0
- package/dist/player.d.ts +90 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/plugins/ads.d.ts +311 -0
- package/dist/plugins/ads.d.ts.map +1 -0
- package/dist/plugins/analytics.d.ts +203 -0
- package/dist/plugins/analytics.d.ts.map +1 -0
- package/dist/plugins/end-screen.d.ts +184 -0
- package/dist/plugins/end-screen.d.ts.map +1 -0
- package/dist/plugins/index.d.ts +13 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +20 -0
- package/dist/plugins/index.js.map +9 -0
- package/dist/plugins/skip-segments.d.ts +159 -0
- package/dist/plugins/skip-segments.d.ts.map +1 -0
- package/dist/plugins/watermark.d.ts +157 -0
- package/dist/plugins/watermark.d.ts.map +1 -0
- package/dist/providers/base.d.ts +56 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/dash.d.ts +94 -0
- package/dist/providers/dash.d.ts.map +1 -0
- package/dist/providers/hls.d.ts +182 -0
- package/dist/providers/hls.d.ts.map +1 -0
- package/dist/providers/html5.d.ts +64 -0
- package/dist/providers/html5.d.ts.map +1 -0
- package/dist/providers/index.d.ts +41 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/vimeo.d.ts +157 -0
- package/dist/providers/vimeo.d.ts.map +1 -0
- package/dist/providers/youtube.d.ts +134 -0
- package/dist/providers/youtube.d.ts.map +1 -0
- package/dist/stx/index.d.ts +59 -0
- package/dist/stx/index.d.ts.map +1 -0
- package/dist/stx/index.js +480 -0
- package/dist/stx/index.js.map +11 -0
- package/dist/types.d.ts +724 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/ui/components.d.ts +121 -0
- package/dist/ui/components.d.ts.map +1 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +16 -0
- package/dist/ui/index.js.map +9 -0
- package/package.json +109 -0
- package/src/cdn.test.ts +10 -0
- package/src/cdn.ts +60 -0
- package/src/config.ts +25 -0
- package/src/core/audio-gain.ts +171 -0
- package/src/core/caption-styles.ts +568 -0
- package/src/core/chapters.ts +551 -0
- package/src/core/error-overlay.ts +354 -0
- package/src/core/events.ts +552 -0
- package/src/core/features.ts +287 -0
- package/src/core/i18n.ts +590 -0
- package/src/core/index.ts +249 -0
- package/src/core/live.ts +472 -0
- package/src/core/media-session.ts +137 -0
- package/src/core/orientation.ts +57 -0
- package/src/core/popover.ts +143 -0
- package/src/core/remote-playback.ts +661 -0
- package/src/core/settings-menu.ts +630 -0
- package/src/core/spinner.ts +310 -0
- package/src/core/state.ts +476 -0
- package/src/core/text-tracks.ts +599 -0
- package/src/core/themes.ts +607 -0
- package/src/core/thumbnails.ts +457 -0
- package/src/core/tooltips.ts +370 -0
- package/src/elements/base.ts +2 -0
- package/src/elements/index.ts +82 -0
- package/src/elements/media-airplay-button.ts +78 -0
- package/src/elements/media-error-display.ts +47 -0
- package/src/elements/media-fullscreen-button.ts +72 -0
- package/src/elements/media-live-button.ts +65 -0
- package/src/elements/media-mute-button.ts +69 -0
- package/src/elements/media-pip-button.ts +66 -0
- package/src/elements/media-play-button.ts +69 -0
- package/src/elements/media-playback-rate-button.ts +59 -0
- package/src/elements/media-preview-time.ts +75 -0
- package/src/elements/media-progress-bar.ts +200 -0
- package/src/elements/media-remote-playback-button.ts +108 -0
- package/src/elements/media-seek-button.ts +50 -0
- package/src/elements/media-settings-menu.ts +302 -0
- package/src/elements/media-time-display.ts +99 -0
- package/src/elements/media-time-group.ts +41 -0
- package/src/elements/media-time-separator.ts +35 -0
- package/src/elements/media-volume-slider.ts +140 -0
- package/src/elements/utils.ts +82 -0
- package/src/elements/video-player.ts +175 -0
- package/src/elements/video-skin.ts +127 -0
- package/src/features/captions.ts +56 -0
- package/src/features/create-player.ts +91 -0
- package/src/features/fullscreen.ts +44 -0
- package/src/features/gestures.ts +44 -0
- package/src/features/index.ts +44 -0
- package/src/features/keyboard.ts +89 -0
- package/src/features/pip.ts +47 -0
- package/src/features/playback.ts +68 -0
- package/src/features/quality.ts +19 -0
- package/src/features/types.ts +28 -0
- package/src/features/volume.ts +46 -0
- package/src/index.ts +635 -0
- package/src/layouts/index.ts +672 -0
- package/src/player.ts +821 -0
- package/src/plugins/ads.ts +1045 -0
- package/src/plugins/analytics.ts +673 -0
- package/src/plugins/end-screen.ts +793 -0
- package/src/plugins/index.ts +61 -0
- package/src/plugins/skip-segments.ts +537 -0
- package/src/plugins/watermark.ts +537 -0
- package/src/providers/base.ts +197 -0
- package/src/providers/dash.ts +588 -0
- package/src/providers/hls.ts +624 -0
- package/src/providers/html5.ts +473 -0
- package/src/providers/index.ts +97 -0
- package/src/providers/vimeo.ts +533 -0
- package/src/providers/youtube.ts +542 -0
- package/src/stx/index.ts +606 -0
- package/src/types.ts +874 -0
- package/src/ui/components.ts +910 -0
- package/src/ui/index.ts +29 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/state.ts", "../src/core/events.ts", "../src/core/media-session.ts", "../src/core/orientation.ts", "../src/core/features.ts", "../src/providers/base.ts", "../src/providers/html5.ts", "../src/providers/youtube.ts", "../src/providers/vimeo.ts", "../src/providers/hls.ts", "../src/providers/dash.ts", "../src/providers/index.ts", "../src/player.ts", "../src/core/themes.ts", "../src/core/i18n.ts", "../src/layouts/index.ts", "../src/plugins/analytics.ts", "../src/plugins/ads.ts", "../src/plugins/skip-segments.ts", "../src/plugins/end-screen.ts", "../src/plugins/watermark.ts", "../src/elements/base.ts", "../src/elements/video-player.ts", "../src/elements/utils.ts", "../src/elements/video-skin.ts", "../src/elements/media-play-button.ts", "../src/elements/media-mute-button.ts", "../src/elements/media-fullscreen-button.ts", "../src/elements/media-pip-button.ts", "../src/elements/media-time-display.ts", "../src/elements/media-time-group.ts", "../src/elements/media-time-separator.ts", "../src/elements/media-preview-time.ts", "../src/elements/media-progress-bar.ts", "../src/elements/media-volume-slider.ts", "../src/core/popover.ts", "../src/elements/media-settings-menu.ts", "../src/elements/media-seek-button.ts", "../src/elements/media-playback-rate-button.ts", "../src/elements/media-airplay-button.ts", "../src/elements/media-live-button.ts", "../src/elements/media-error-display.ts", "../src/elements/media-remote-playback-button.ts", "../src/elements/index.ts", "../src/cdn.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * ts-video-player State Management\n *\n * Simple reactive state system for the video player.\n *\n * @module core/state\n */\n\nimport type { PlayerState } from '../types'\n\n// =============================================================================\n// Utilities\n// =============================================================================\n\n/**\n * Shallow equality comparison for plain objects.\n * Returns true if both objects have the same keys with === equal values.\n * Used to optimize selector subscriptions — skip notifications when\n * derived state hasn't meaningfully changed.\n */\nexport function shallowEqual<T extends Record<string, unknown>>(a: T, b: T): boolean {\n if (a === b) return true\n if (!a || !b) return false\n\n const keysA = Object.keys(a)\n const keysB = Object.keys(b)\n\n if (keysA.length !== keysB.length) return false\n\n for (const key of keysA) {\n if (a[key] !== b[key]) return false\n }\n\n return true\n}\n\n// =============================================================================\n// Reactive Signal System\n// =============================================================================\n\n// eslint-disable-next-line pickier/no-unused-vars\ntype Listener<T> = (_value: T, _prev: T) => void\ntype Unsubscribe = () => void\n\n/**\n * Create a reactive signal\n */\nexport function createSignal<T>(initial: T): [() => T, (value: T | ((prev: T) => T)) => void] {\n let value = initial\n const listeners = new Set<Listener<T>>()\n\n const get = () => value\n\n const set = (newValue: T | ((prev: T) => T)) => {\n const prev = value\n value = typeof newValue === 'function' ? (newValue as (prev: T) => T)(prev) : newValue\n if (value !== prev) {\n listeners.forEach((listener) => listener(value, prev))\n }\n }\n\n // Attach subscribe method to getter\n ;(get as any).subscribe = (listener: Listener<T>): Unsubscribe => {\n listeners.add(listener)\n return () => listeners.delete(listener)\n }\n\n return [get, set]\n}\n\n/**\n * Create a computed value from signals\n */\nexport function computed<T>(fn: () => T, deps: Array<() => unknown>): () => T {\n let value: T\n let dirty = true\n\n // Subscribe to all dependencies\n deps.forEach((dep) => {\n if ((dep as any).subscribe) {\n (dep as any).subscribe(() => {\n dirty = true\n })\n }\n })\n\n return () => {\n if (dirty) {\n value = fn()\n dirty = false\n }\n return value\n }\n}\n\n// =============================================================================\n// Player State Store\n// =============================================================================\n\n/**\n * Default player state\n */\nexport function createDefaultState(): PlayerState {\n return {\n // Source\n src: null,\n sources: [],\n currentSourceIndex: 0,\n mediaType: 'unknown',\n streamType: 'unknown',\n providerType: 'unknown',\n\n // Loading\n loadingState: 'idle',\n preload: 'metadata',\n canPlay: false,\n canPlayThrough: false,\n error: null,\n\n // Playback\n playbackState: 'idle',\n paused: true,\n playing: false,\n started: false,\n ended: false,\n seeking: false,\n waiting: false,\n currentTime: 0,\n duration: 0,\n playbackRate: 1,\n loop: false,\n autoplay: false,\n playsinline: true,\n\n // Volume\n muted: false,\n volume: 1,\n\n // Buffering\n buffered: [],\n bufferedAmount: 0,\n seekable: [],\n\n // Dimensions\n videoWidth: 0,\n videoHeight: 0,\n aspectRatio: 16 / 9,\n\n // Tracks\n qualities: [],\n autoQuality: true,\n audioTracks: [],\n textTracks: [],\n\n // Fullscreen/PiP\n fullscreen: false,\n pictureInPicture: false,\n canFullscreen: false,\n canPictureInPicture: false,\n\n // Feature Availability\n volumeAvailability: 'unavailable',\n fullscreenAvailability: 'unavailable',\n pipAvailability: 'unavailable',\n\n // UI\n controlsVisible: true,\n userActive: true,\n pointerOver: false,\n\n // Metadata\n title: '',\n poster: '',\n }\n}\n\n/**\n * State store for the player\n *\n * Uses microtask-coalesced notifications: multiple rapid set() calls within\n * the same microtask are batched into a single notification cycle. This prevents\n * redundant re-renders when many state fields change in quick succession\n * (e.g., timeupdate + progress + buffered all firing together).\n */\nexport class StateStore {\n private state: PlayerState\n private listeners = new Map<keyof PlayerState | '*', Set<(state: PlayerState, key?: keyof PlayerState) => void>>()\n private _pendingKeys: Set<keyof PlayerState> | null = null\n private _flushScheduled = false\n\n constructor(initial?: Partial<PlayerState>) {\n this.state = { ...createDefaultState(), ...initial }\n }\n\n /**\n * Get the current state\n */\n getState(): Readonly<PlayerState> {\n return this.state\n }\n\n /**\n * Get a specific state value\n */\n get<K extends keyof PlayerState>(key: K): PlayerState[K] {\n return this.state[key]\n }\n\n /**\n * Set a single state value. Notifications are coalesced via microtask.\n */\n set<K extends keyof PlayerState>(key: K, value: PlayerState[K]): void {\n if (this.state[key] === value) return\n\n this.state = { ...this.state, [key]: value }\n this.scheduleNotify(key)\n }\n\n /**\n * Batch update multiple state values. Notifications are coalesced via microtask.\n */\n batch(updates: Partial<PlayerState>): void {\n for (const [key, value] of Object.entries(updates)) {\n if (this.state[key as keyof PlayerState] !== value) {\n ;(this.state as any)[key] = value\n this.scheduleNotify(key as keyof PlayerState)\n }\n }\n }\n\n /**\n * Subscribe to state changes\n */\n subscribe(\n // eslint-disable-next-line pickier/no-unused-vars\n keyOrListener: keyof PlayerState | '*' | ((_state: PlayerState, _key?: keyof PlayerState) => void),\n // eslint-disable-next-line pickier/no-unused-vars\n listener?: (_state: PlayerState, _key?: keyof PlayerState) => void,\n ): Unsubscribe {\n if (typeof keyOrListener === 'function') {\n // Subscribe to all changes\n const key = '*'\n if (!this.listeners.has(key)) {\n this.listeners.set(key, new Set())\n }\n this.listeners.get(key)!.add(keyOrListener)\n return () => this.listeners.get(key)?.delete(keyOrListener)\n }\n\n // Subscribe to specific key\n const key = keyOrListener\n if (!listener) return () => {}\n\n if (!this.listeners.has(key)) {\n this.listeners.set(key, new Set())\n }\n this.listeners.get(key)!.add(listener)\n return () => this.listeners.get(key)?.delete(listener)\n }\n\n /**\n * Reset state to defaults\n */\n reset(): void {\n this.state = createDefaultState()\n this._pendingKeys = null\n this._flushScheduled = false\n this.notifyAll()\n }\n\n /**\n * Force synchronous flush of pending notifications.\n * Useful in tests or when immediate consistency is required.\n */\n flush(): void {\n if (this._pendingKeys) {\n const keys = this._pendingKeys\n this._pendingKeys = null\n this._flushScheduled = false\n for (const key of keys) {\n this.notifyKey(key)\n }\n this.notifyAll()\n }\n }\n\n private scheduleNotify(key: keyof PlayerState): void {\n if (!this._pendingKeys) {\n this._pendingKeys = new Set()\n }\n this._pendingKeys.add(key)\n\n if (!this._flushScheduled) {\n this._flushScheduled = true\n queueMicrotask(() => this.flush())\n }\n }\n\n private notifyKey(key: keyof PlayerState): void {\n this.listeners.get(key)?.forEach((listener) => listener(this.state, key))\n }\n\n private notifyAll(): void {\n this.listeners.get('*')?.forEach((listener) => listener(this.state))\n }\n}\n\n// =============================================================================\n// State Selectors (Computed Values)\n// =============================================================================\n\n/**\n * Calculate buffered percentage\n */\nexport function selectBufferedAmount(state: PlayerState): number {\n if (state.duration === 0 || state.buffered.length === 0) return 0\n\n let buffered = 0\n for (const range of state.buffered) {\n buffered += range.end - range.start\n }\n return Math.min(buffered / state.duration, 1)\n}\n\n/**\n * Calculate progress percentage\n */\nexport function selectProgress(state: PlayerState): number {\n if (state.duration === 0) return 0\n return Math.min(state.currentTime / state.duration, 1)\n}\n\n/**\n * Get remaining time\n */\nexport function selectRemainingTime(state: PlayerState): number {\n return Math.max(state.duration - state.currentTime, 0)\n}\n\n/**\n * Check if player is in idle state\n */\nexport function selectIsIdle(state: PlayerState): boolean {\n return state.loadingState === 'idle' && state.playbackState === 'idle'\n}\n\n/**\n * Check if player is loading\n */\nexport function selectIsLoading(state: PlayerState): boolean {\n return state.loadingState === 'loading' || state.waiting\n}\n\n/**\n * Check if live stream\n */\nexport function selectIsLive(state: PlayerState): boolean {\n return state.streamType.includes('live')\n}\n\n/**\n * Check if DVR enabled\n */\nexport function selectIsDVR(state: PlayerState): boolean {\n return state.streamType === 'live:dvr'\n}\n\n/**\n * Get current quality\n */\nexport function selectCurrentQuality(state: PlayerState): string | null {\n const selected = state.qualities.find((q) => q.selected)\n if (!selected) return state.autoQuality ? 'auto' : null\n return `${selected.height}p`\n}\n\n/**\n * Get current text track\n */\nexport function selectCurrentTextTrack(state: PlayerState): string | null {\n const showing = state.textTracks.find((t) => t.mode === 'showing')\n return showing?.label || null\n}\n\n/**\n * Get current audio track\n */\nexport function selectCurrentAudioTrack(state: PlayerState): string | null {\n const selected = state.audioTracks.find((t) => t.selected)\n return selected?.label || null\n}\n\n// =============================================================================\n// Deprecated Selectors (use availability states instead)\n// =============================================================================\n\n/** @deprecated Use `state.fullscreenAvailability === 'available'` instead */\nexport function selectCanFullscreen(state: PlayerState): boolean {\n return state.fullscreenAvailability === 'available'\n}\n\n/** @deprecated Use `state.pipAvailability === 'available'` instead */\nexport function selectCanPiP(state: PlayerState): boolean {\n return state.pipAvailability === 'available'\n}\n\n/** @deprecated Use `state.volumeAvailability === 'available'` instead */\nexport function selectCanSetVolume(state: PlayerState): boolean {\n return state.volumeAvailability === 'available'\n}\n\n// =============================================================================\n// Storage Integration\n// =============================================================================\n\nconst STORAGE_KEY = 'ts-video-player'\n\ninterface StoredState {\n volume: number\n muted: boolean\n playbackRate: number\n captions: boolean\n quality: string\n}\n\n/**\n * Load persisted state from storage\n */\nexport function loadPersistedState(storage: Storage = localStorage): Partial<StoredState> {\n try {\n const data = storage.getItem(STORAGE_KEY)\n if (data) {\n return JSON.parse(data)\n }\n } catch {\n // Ignore storage errors\n }\n return {}\n}\n\n/**\n * Save state to storage\n */\nexport function savePersistedState(state: Partial<StoredState>, storage: Storage = localStorage): void {\n try {\n const existing = loadPersistedState(storage)\n storage.setItem(STORAGE_KEY, JSON.stringify({ ...existing, ...state }))\n } catch {\n // Ignore storage errors\n }\n}\n\n/**\n * Create storage sync for state store\n */\nexport function createStorageSync(\n store: StateStore,\n keys: (keyof StoredState)[] = ['volume', 'muted', 'playbackRate'],\n storage: Storage = localStorage,\n): Unsubscribe {\n // Load initial values\n const persisted = loadPersistedState(storage)\n if (persisted.volume !== undefined) store.set('volume', persisted.volume)\n if (persisted.muted !== undefined) store.set('muted', persisted.muted)\n if (persisted.playbackRate !== undefined) store.set('playbackRate', persisted.playbackRate)\n\n // Subscribe to changes\n const unsubscribes = keys.map((key) =>\n store.subscribe(key as keyof PlayerState, (state) => {\n const value = state[key as keyof PlayerState]\n savePersistedState({ [key]: value } as Partial<StoredState>, storage)\n }),\n )\n\n return () => unsubscribes.forEach((unsub) => unsub())\n}\n",
|
|
6
|
+
"/**\n * ts-video-player Event System\n *\n * Event emitter and media event normalization.\n *\n * @module core/events\n */\n\nimport type { ProviderEventMap, MediaError, TimeRange } from '../types'\n\n/**\n * Type-safe event emitter\n */\nexport class EventEmitter<T extends { [K in keyof T]: (...args: any[]) => void }> {\n private listeners = new Map<keyof T, Set<T[keyof T]>>()\n private onceListeners = new Map<keyof T, Set<T[keyof T]>>()\n\n /**\n * Add an event listener\n */\n on<K extends keyof T>(event: K, handler: T[K]): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set())\n }\n this.listeners.get(event)!.add(handler)\n\n return () => this.off(event, handler)\n }\n\n /**\n * Add a one-time event listener\n */\n once<K extends keyof T>(event: K, handler: T[K]): () => void {\n if (!this.onceListeners.has(event)) {\n this.onceListeners.set(event, new Set())\n }\n this.onceListeners.get(event)!.add(handler)\n\n return () => this.onceListeners.get(event)?.delete(handler)\n }\n\n /**\n * Remove an event listener\n */\n off<K extends keyof T>(event: K, handler: T[K]): void {\n this.listeners.get(event)?.delete(handler)\n this.onceListeners.get(event)?.delete(handler)\n }\n\n /**\n * Emit an event\n */\n emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>): void {\n // Call regular listeners\n this.listeners.get(event)?.forEach((handler) => {\n try {\n ;(handler as (...args: any[]) => void)(...args)\n } catch (error) {\n console.error(`[ts-video-player] Error in event handler for \"${String(event)}\":`, error)\n }\n })\n\n // Call and remove once listeners\n const onceHandlers = this.onceListeners.get(event)\n if (onceHandlers) {\n onceHandlers.forEach((handler) => {\n try {\n ;(handler as (...args: any[]) => void)(...args)\n } catch (error) {\n console.error(`[ts-video-player] Error in once handler for \"${String(event)}\":`, error)\n }\n })\n onceHandlers.clear()\n }\n }\n\n /**\n * Remove all listeners for an event (or all events)\n */\n removeAllListeners(event?: keyof T): void {\n if (event) {\n this.listeners.delete(event)\n this.onceListeners.delete(event)\n } else {\n this.listeners.clear()\n this.onceListeners.clear()\n }\n }\n\n /**\n * Get listener count for an event\n */\n listenerCount(event: keyof T): number {\n return (this.listeners.get(event)?.size || 0) + (this.onceListeners.get(event)?.size || 0)\n }\n}\n\n// =============================================================================\n// Media Events Normalizer\n// =============================================================================\n\n/**\n * Normalize HTMLMediaElement events to our event system\n */\nexport class MediaEventsNormalizer {\n private media: HTMLMediaElement\n private emitter: EventEmitter<ProviderEventMap>\n private listeners: Array<() => void> = []\n\n constructor(media: HTMLMediaElement, emitter: EventEmitter<ProviderEventMap>) {\n this.media = media\n this.emitter = emitter\n this.attachListeners()\n }\n\n private attachListeners(): void {\n const on = (event: string, handler: (e: Event) => void) => {\n this.media.addEventListener(event, handler)\n this.listeners.push(() => this.media.removeEventListener(event, handler))\n }\n\n // Loading events\n on('loadstart', () => this.emitter.emit('loadstart'))\n on('loadedmetadata', () => this.emitter.emit('loadedmetadata'))\n on('loadeddata', () => this.emitter.emit('loadeddata'))\n on('canplay', () => this.emitter.emit('canplay'))\n on('canplaythrough', () => this.emitter.emit('canplaythrough'))\n\n // Playback events\n on('play', () => this.emitter.emit('play'))\n on('pause', () => this.emitter.emit('pause'))\n on('playing', () => this.emitter.emit('playing'))\n on('waiting', () => this.emitter.emit('waiting'))\n on('ended', () => this.emitter.emit('ended'))\n\n // Seeking events\n on('seeking', () => this.emitter.emit('seeking'))\n on('seeked', () => this.emitter.emit('seeked'))\n\n // Time/duration events\n on('timeupdate', () => this.emitter.emit('timeupdate', this.media.currentTime))\n on('durationchange', () => this.emitter.emit('durationchange', this.media.duration))\n\n // Volume events\n on('volumechange', () => this.emitter.emit('volumechange', this.media.volume, this.media.muted))\n\n // Rate events\n on('ratechange', () => this.emitter.emit('ratechange', this.media.playbackRate))\n\n // Progress events\n on('progress', () => {\n const buffered = this.getTimeRanges(this.media.buffered)\n this.emitter.emit('progress', buffered)\n })\n\n // Error events\n on('error', () => {\n const error = this.normalizeError(this.media.error)\n this.emitter.emit('error', error)\n })\n }\n\n private getTimeRanges(ranges: TimeRanges): TimeRange[] {\n const result: TimeRange[] = []\n for (let i = 0; i < ranges.length; i++) {\n result.push({\n start: ranges.start(i),\n end: ranges.end(i),\n })\n }\n return result\n }\n\n private normalizeError(error: globalThis.MediaError | null): MediaError {\n if (!error) {\n return { code: 0, message: 'Unknown error' }\n }\n\n const messages: Record<number, string> = {\n 1: 'The fetching of the media resource was aborted by the user agent.',\n 2: 'A network error occurred while fetching the media resource.',\n 3: 'An error occurred while decoding the media resource.',\n 4: 'The media resource is not supported.',\n }\n\n return {\n code: error.code,\n message: messages[error.code] || error.message || 'Unknown error',\n }\n }\n\n /**\n * Destroy the normalizer\n */\n destroy(): void {\n this.listeners.forEach((remove) => remove())\n this.listeners = []\n }\n}\n\n// =============================================================================\n// Fullscreen Events\n// =============================================================================\n\n/**\n * Normalize fullscreen change events\n */\nexport function onFullscreenChange(handler: (fullscreen: boolean) => void): () => void {\n const events = ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange']\n\n const listener = () => {\n const fullscreen = !!(\n document.fullscreenElement ||\n (document as any).webkitFullscreenElement ||\n (document as any).mozFullScreenElement ||\n (document as any).msFullscreenElement\n )\n handler(fullscreen)\n }\n\n events.forEach((event) => document.addEventListener(event, listener))\n\n return () => {\n events.forEach((event) => document.removeEventListener(event, listener))\n }\n}\n\n// =============================================================================\n// Picture-in-Picture Events\n// =============================================================================\n\n/**\n * Normalize PiP change events\n */\nexport function onPiPChange(video: HTMLVideoElement, handler: (pip: boolean) => void): () => void {\n const enterHandler = () => handler(true)\n const leaveHandler = () => handler(false)\n\n video.addEventListener('enterpictureinpicture', enterHandler)\n video.addEventListener('leavepictureinpicture', leaveHandler)\n\n return () => {\n video.removeEventListener('enterpictureinpicture', enterHandler)\n video.removeEventListener('leavepictureinpicture', leaveHandler)\n }\n}\n\n// =============================================================================\n// Keyboard Events\n// =============================================================================\n\n/**\n * Default keyboard shortcuts\n */\nexport const DEFAULT_KEYBOARD_SHORTCUTS: Record<string, string> = {\n ' ': 'togglePlay',\n k: 'togglePlay',\n m: 'toggleMute',\n f: 'toggleFullscreen',\n i: 'togglePiP',\n c: 'toggleCaptions',\n ArrowLeft: 'seekBackward',\n ArrowRight: 'seekForward',\n j: 'seekBackward',\n l: 'seekForward',\n ArrowUp: 'volumeUp',\n ArrowDown: 'volumeDown',\n '>': 'speedUp',\n '<': 'speedDown',\n Home: 'seekStart',\n End: 'seekEnd',\n '0': 'seek0',\n '1': 'seek10',\n '2': 'seek20',\n '3': 'seek30',\n '4': 'seek40',\n '5': 'seek50',\n '6': 'seek60',\n '7': 'seek70',\n '8': 'seek80',\n '9': 'seek90',\n}\n\n/**\n * Action handlers for keyboard shortcuts\n */\nexport interface KeyboardActions {\n togglePlay(): void\n toggleMute(): void\n toggleFullscreen(): void\n togglePiP(): void\n toggleCaptions(): void\n seekBackward(): void\n seekForward(): void\n volumeUp(): void\n volumeDown(): void\n speedUp(): void\n speedDown(): void\n seekStart(): void\n seekEnd(): void\n seekPercent(percent: number): void\n}\n\n/**\n * Create keyboard event handler\n */\nexport function createKeyboardHandler(\n actions: KeyboardActions,\n options: { seekStep?: number; volumeStep?: number; speedStep?: number } = {},\n): (event: KeyboardEvent) => void {\n const { seekStep: _seekStep = 5, volumeStep: _volumeStep = 0.1, speedStep: _speedStep = 0.25 } = options\n\n return (event: KeyboardEvent) => {\n // Ignore if in input field\n if (\n event.target instanceof HTMLInputElement ||\n event.target instanceof HTMLTextAreaElement ||\n event.target instanceof HTMLSelectElement ||\n (event.target as HTMLElement)?.isContentEditable\n ) {\n return\n }\n\n const key = event.key\n const action = DEFAULT_KEYBOARD_SHORTCUTS[key]\n\n if (!action) return\n\n // Prevent default for handled keys\n event.preventDefault()\n\n switch (action) {\n case 'togglePlay':\n actions.togglePlay()\n break\n case 'toggleMute':\n actions.toggleMute()\n break\n case 'toggleFullscreen':\n actions.toggleFullscreen()\n break\n case 'togglePiP':\n actions.togglePiP()\n break\n case 'toggleCaptions':\n actions.toggleCaptions()\n break\n case 'seekBackward':\n actions.seekBackward()\n break\n case 'seekForward':\n actions.seekForward()\n break\n case 'volumeUp':\n actions.volumeUp()\n break\n case 'volumeDown':\n actions.volumeDown()\n break\n case 'speedUp':\n actions.speedUp()\n break\n case 'speedDown':\n actions.speedDown()\n break\n case 'seekStart':\n actions.seekStart()\n break\n case 'seekEnd':\n actions.seekEnd()\n break\n default:\n // Handle number keys for percentage seeking\n if (action.startsWith('seek')) {\n const percent = parseInt(action.replace('seek', ''), 10)\n if (!isNaN(percent)) {\n actions.seekPercent(percent)\n }\n }\n }\n }\n}\n\n// =============================================================================\n// User Activity Detection\n// =============================================================================\n\n/**\n * Create user activity detector\n */\nexport function createActivityDetector(\n element: HTMLElement,\n options: { timeout?: number; onActive?: () => void; onInactive?: () => void } = {},\n): () => void {\n const { timeout = 3000, onActive, onInactive } = options\n\n let timer: ReturnType<typeof setTimeout> | null = null\n let active = true\n\n const setActive = () => {\n if (!active) {\n active = true\n onActive?.()\n }\n resetTimer()\n }\n\n const setInactive = () => {\n if (active) {\n active = false\n onInactive?.()\n }\n }\n\n const resetTimer = () => {\n if (timer) clearTimeout(timer)\n timer = setTimeout(setInactive, timeout)\n }\n\n // Events that indicate activity\n const events = ['mousemove', 'mouseenter', 'mouseleave', 'mousedown', 'keydown', 'touchstart', 'touchmove']\n\n events.forEach((event) => element.addEventListener(event, setActive))\n\n // Start timer\n resetTimer()\n\n // Return cleanup function\n return () => {\n if (timer) clearTimeout(timer)\n events.forEach((event) => element.removeEventListener(event, setActive))\n }\n}\n\n// =============================================================================\n// Gesture Detection\n// =============================================================================\n\nexport interface GestureHandlers {\n onTap?: (x: number, y: number) => void\n onDoubleTap?: (x: number, y: number) => void\n onSwipeLeft?: () => void\n onSwipeRight?: () => void\n onSwipeUp?: () => void\n onSwipeDown?: () => void\n onScrub?: (progress: number) => void\n onScrubEnd?: () => void\n}\n\n/**\n * Create touch gesture detector\n */\nexport function createGestureDetector(element: HTMLElement, handlers: GestureHandlers): () => void {\n let startX = 0\n let startY = 0\n let startTime = 0\n let lastTap = 0\n let isScrubbing = false\n let cachedRect: DOMRect | null = null\n\n const SWIPE_THRESHOLD = 50\n const TAP_THRESHOLD = 200\n const DOUBLE_TAP_THRESHOLD = 300\n\n const onTouchStart = (e: TouchEvent) => {\n const touch = e.touches[0]\n startX = touch.clientX\n startY = touch.clientY\n startTime = Date.now()\n isScrubbing = false\n cachedRect = element.getBoundingClientRect()\n }\n\n const onTouchMove = (e: TouchEvent) => {\n if (!startTime) return\n\n const touch = e.touches[0]\n const deltaX = touch.clientX - startX\n const deltaY = touch.clientY - startY\n\n // Horizontal scrubbing\n if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 20) {\n isScrubbing = true\n const rect = cachedRect || element.getBoundingClientRect()\n const progress = Math.max(0, Math.min(1, (touch.clientX - rect.left) / rect.width))\n handlers.onScrub?.(progress)\n }\n }\n\n const onTouchEnd = (e: TouchEvent) => {\n const endTime = Date.now()\n const duration = endTime - startTime\n\n if (isScrubbing) {\n handlers.onScrubEnd?.()\n isScrubbing = false\n startTime = 0\n return\n }\n\n const touch = e.changedTouches[0]\n const deltaX = touch.clientX - startX\n const deltaY = touch.clientY - startY\n\n // Check for swipe\n if (duration < 500) {\n if (Math.abs(deltaX) > SWIPE_THRESHOLD && Math.abs(deltaX) > Math.abs(deltaY)) {\n if (deltaX > 0) {\n handlers.onSwipeRight?.()\n } else {\n handlers.onSwipeLeft?.()\n }\n startTime = 0\n return\n }\n\n if (Math.abs(deltaY) > SWIPE_THRESHOLD && Math.abs(deltaY) > Math.abs(deltaX)) {\n if (deltaY > 0) {\n handlers.onSwipeDown?.()\n } else {\n handlers.onSwipeUp?.()\n }\n startTime = 0\n return\n }\n }\n\n // Check for tap\n if (duration < TAP_THRESHOLD && Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10) {\n const now = Date.now()\n if (now - lastTap < DOUBLE_TAP_THRESHOLD) {\n handlers.onDoubleTap?.(touch.clientX, touch.clientY)\n lastTap = 0\n } else {\n handlers.onTap?.(touch.clientX, touch.clientY)\n lastTap = now\n }\n }\n\n startTime = 0\n }\n\n element.addEventListener('touchstart', onTouchStart, { passive: true })\n element.addEventListener('touchmove', onTouchMove, { passive: true })\n element.addEventListener('touchend', onTouchEnd, { passive: true })\n\n return () => {\n element.removeEventListener('touchstart', onTouchStart)\n element.removeEventListener('touchmove', onTouchMove)\n element.removeEventListener('touchend', onTouchEnd)\n }\n}\n",
|
|
7
|
+
"/**\n * ts-video-player Media Session API Integration\n *\n * Provides lock screen / notification controls via navigator.mediaSession.\n *\n * @module core/media-session\n */\n\nimport type { StateStore } from './state'\n\nexport interface MediaSessionOptions {\n /** Title to display */\n title?: string\n /** Artist name */\n artist?: string\n /** Album name */\n album?: string\n /** Artwork images for lock screen */\n artwork?: MediaImage[]\n}\n\n/**\n * Check if Media Session API is available\n */\nexport function isMediaSessionSupported(): boolean {\n return typeof navigator !== 'undefined' && 'mediaSession' in navigator\n}\n\n/**\n * Create a Media Session controller that syncs player state to the browser's\n * media session (lock screen controls, notification area, etc.)\n */\nexport function createMediaSession(\n store: StateStore,\n actions: {\n play: () => void\n pause: () => void\n seekTo: (time: number) => void\n seekBy: (offset: number) => void\n stop: () => void\n },\n options?: MediaSessionOptions,\n): () => void {\n if (!isMediaSessionSupported()) return () => {}\n\n const session = navigator.mediaSession\n\n // Set metadata\n if (options) {\n updateMetadata(options)\n }\n\n // Action handlers\n const handlers: Array<[MediaSessionAction, MediaSessionActionHandler]> = [\n ['play', () => actions.play()],\n ['pause', () => actions.pause()],\n ['stop', () => actions.stop()],\n ['seekbackward', (details) => {\n const offset = (details as MediaSessionActionDetails & { seekOffset?: number }).seekOffset || -10\n actions.seekBy(offset)\n }],\n ['seekforward', (details) => {\n const offset = (details as MediaSessionActionDetails & { seekOffset?: number }).seekOffset || 10\n actions.seekBy(offset)\n }],\n ['seekto', (details) => {\n const seekTime = (details as MediaSessionActionDetails & { seekTime?: number }).seekTime\n if (seekTime !== undefined) actions.seekTo(seekTime)\n }],\n ]\n\n for (const [action, handler] of handlers) {\n try {\n session.setActionHandler(action, handler)\n } catch {\n // Some actions may not be supported on all platforms\n }\n }\n\n // Sync playback state\n const unsubPaused = store.subscribe('paused', (state) => {\n session.playbackState = state.paused ? 'paused' : 'playing'\n })\n\n // Sync position state\n let positionTimer: ReturnType<typeof setInterval> | null = null\n\n const updatePosition = () => {\n const state = store.getState()\n if (!isFinite(state.duration) || state.duration <= 0) return\n try {\n session.setPositionState({\n duration: state.duration,\n playbackRate: state.playbackRate,\n position: Math.min(state.currentTime, state.duration),\n })\n } catch {\n // Position state may fail if values are invalid\n }\n }\n\n const unsubPlaying = store.subscribe('playing', (state) => {\n if (state.playing) {\n updatePosition()\n if (!positionTimer) {\n positionTimer = setInterval(updatePosition, 1000)\n }\n } else {\n if (positionTimer) { clearInterval(positionTimer); positionTimer = null }\n updatePosition()\n }\n })\n\n return () => {\n if (positionTimer) clearInterval(positionTimer)\n unsubPaused()\n unsubPlaying()\n\n // Clear action handlers\n for (const [action] of handlers) {\n try { session.setActionHandler(action, null) } catch { /* ignore */ }\n }\n }\n}\n\n/**\n * Update media session metadata\n */\nexport function updateMetadata(options: MediaSessionOptions): void {\n if (!isMediaSessionSupported()) return\n navigator.mediaSession.metadata = new MediaMetadata({\n title: options.title || '',\n artist: options.artist || '',\n album: options.album || '',\n artwork: options.artwork || [],\n })\n}\n",
|
|
8
|
+
"/**\n * ts-video-player Orientation Lock\n *\n * Auto-lock screen orientation to landscape when entering fullscreen on mobile.\n *\n * @module core/orientation\n */\n\ntype OrientationType = 'landscape' | 'portrait' | 'any'\n\ninterface ScreenOrientationAPI {\n lock(orientation: string): Promise<void>\n unlock(): void\n}\n\n/**\n * Check if Screen Orientation API is available\n */\nexport function isOrientationLockSupported(): boolean {\n return (\n typeof screen !== 'undefined' &&\n 'orientation' in screen &&\n typeof (screen.orientation as unknown as ScreenOrientationAPI).lock === 'function'\n )\n}\n\n/**\n * Lock the screen orientation (best-effort, silently fails if unsupported)\n */\nexport function lockOrientation(type: OrientationType): void {\n if (!isOrientationLockSupported()) return\n\n const orientationMap: Record<OrientationType, string> = {\n landscape: 'landscape',\n portrait: 'portrait',\n any: 'any',\n }\n\n ;(screen.orientation as unknown as ScreenOrientationAPI)\n .lock(orientationMap[type])\n .catch(() => {\n // Orientation lock may fail if not in fullscreen or unsupported\n })\n}\n\n/**\n * Unlock the screen orientation\n */\nexport function unlockOrientation(): void {\n if (!isOrientationLockSupported()) return\n\n try {\n ;(screen.orientation as unknown as ScreenOrientationAPI).unlock()\n } catch {\n // Unlock may fail if lock was never acquired\n }\n}\n",
|
|
9
|
+
"/**\n * ts-video-player Feature Availability Detection\n *\n * Detects platform capabilities for volume, fullscreen, and PiP.\n *\n * @module core/features\n */\n\nimport type { FeatureAvailability } from '../types'\n\n// =============================================================================\n// Platform Detection Helpers\n// =============================================================================\n\n/**\n * Detect iOS Safari (including iPadOS Safari)\n */\nexport function isIOSSafari(): boolean {\n if (typeof navigator === 'undefined') return false\n const ua = navigator.userAgent\n // iPhone/iPad/iPod with Safari\n if (/iPad|iPhone|iPod/.test(ua) && !(window as any).MSStream) return true\n // iPadOS reports as Mac but has touch\n if (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) return true\n return false\n}\n\n/**\n * Detect Safari browser (desktop or mobile)\n */\nexport function isSafari(): boolean {\n if (typeof navigator === 'undefined') return false\n return /^(?:(?!chrome|android).)*safari/i.test(navigator.userAgent)\n}\n\n/**\n * Detect standalone PWA mode\n */\nexport function isStandalonePWA(): boolean {\n if (typeof window === 'undefined') return false\n return (navigator as any).standalone === true\n || window.matchMedia('(display-mode: standalone)').matches\n}\n\n// =============================================================================\n// Volume Availability\n// =============================================================================\n\n/**\n * Detect volume control availability.\n * iOS Safari silently ignores programmatic volume changes on media elements.\n */\nexport function detectVolumeAvailability(\n media?: HTMLMediaElement | null,\n): FeatureAvailability {\n if (isIOSSafari()) return 'unsupported'\n if (!media) return 'unavailable'\n return 'available'\n}\n\n/**\n * Async volume probe: attempts to set volume and reads it back.\n * This is the authoritative check - the sync check is a fast heuristic.\n */\nexport async function probeVolumeAvailability(\n media: HTMLMediaElement,\n): Promise<FeatureAvailability> {\n const original = media.volume\n try {\n const testValue = original === 1 ? 0.5 : 1\n media.volume = testValue\n // Some browsers apply volume asynchronously\n await new Promise((r) => setTimeout(r, 0))\n const changed = media.volume !== original\n media.volume = original\n return changed ? 'available' : 'unsupported'\n } catch {\n try { media.volume = original } catch { /* ignore */ }\n return 'unsupported'\n }\n}\n\n// =============================================================================\n// Fullscreen Availability\n// =============================================================================\n\ninterface WebKitDocument extends Document {\n webkitFullscreenEnabled?: boolean\n webkitFullscreenElement?: Element\n webkitExitFullscreen?: () => Promise<void>\n webkitCancelFullScreen?: () => Promise<void>\n mozFullScreenEnabled?: boolean\n msFullscreenEnabled?: boolean\n}\n\ninterface WebKitVideoElement extends HTMLVideoElement {\n webkitEnterFullscreen?: () => void\n webkitExitFullscreen?: () => void\n webkitSupportsFullscreen?: boolean\n webkitDisplayingFullscreen?: boolean\n webkitPresentationMode?: string\n webkitSetPresentationMode?: (mode: string) => void\n webkitSupportsPresentationMode?: (mode: string) => boolean\n}\n\ninterface WebKitFullscreenElement extends HTMLElement {\n webkitRequestFullscreen?: () => Promise<void>\n webkitRequestFullScreen?: () => Promise<void>\n}\n\n/**\n * Detect fullscreen API availability.\n */\nexport function detectFullscreenAvailability(\n container?: HTMLElement | null,\n videoElement?: HTMLVideoElement | null,\n): FeatureAvailability {\n const doc = typeof document !== 'undefined' ? document as WebKitDocument : null\n if (!doc) return 'unsupported'\n\n // Standard API or WebKit prefix\n if (doc.fullscreenEnabled || doc.webkitFullscreenEnabled) return 'available'\n // Moz/MS prefixes\n if (doc.mozFullScreenEnabled || doc.msFullscreenEnabled) return 'available'\n\n // iOS Safari: video-only fullscreen via webkitEnterFullscreen\n if (videoElement) {\n const video = videoElement as WebKitVideoElement\n if (video.webkitSupportsFullscreen === true) return 'available'\n if (typeof video.webkitEnterFullscreen === 'function') return 'available'\n }\n\n if (!container && !videoElement) return 'unavailable'\n return 'unsupported'\n}\n\n/**\n * Whether this environment only supports video-element fullscreen (iOS Safari),\n * not container-level fullscreen.\n */\nexport function isVideoOnlyFullscreen(): boolean {\n if (typeof document === 'undefined') return false\n const doc = document as WebKitDocument\n return isIOSSafari()\n && !doc.fullscreenEnabled\n && !doc.webkitFullscreenEnabled\n}\n\n/**\n * Enter fullscreen with cross-browser support including iOS Safari.\n */\nexport async function enterFullscreen(\n container: HTMLElement | null,\n media: HTMLMediaElement | null,\n): Promise<void> {\n const video = media as WebKitVideoElement | null\n\n // Try container first (standard and WebKit APIs)\n if (container) {\n const el = container as WebKitFullscreenElement\n if (typeof el.requestFullscreen === 'function') return el.requestFullscreen()\n if (typeof el.webkitRequestFullscreen === 'function') return el.webkitRequestFullscreen()\n if (typeof el.webkitRequestFullScreen === 'function') return el.webkitRequestFullScreen()\n }\n\n // Fall back to video element (iOS Safari)\n if (video && typeof video.webkitEnterFullscreen === 'function') {\n video.webkitEnterFullscreen()\n return\n }\n\n // Last resort: try media element with standard API\n if (media && typeof media.requestFullscreen === 'function') {\n return media.requestFullscreen()\n }\n}\n\n/**\n * Exit fullscreen with cross-browser support.\n */\nexport async function exitFullscreen(media?: HTMLMediaElement | null): Promise<void> {\n const doc = document as WebKitDocument\n const video = media as WebKitVideoElement | null\n\n if (typeof doc.exitFullscreen === 'function') return doc.exitFullscreen()\n if (typeof doc.webkitExitFullscreen === 'function') return doc.webkitExitFullscreen()\n if (typeof doc.webkitCancelFullScreen === 'function') return doc.webkitCancelFullScreen()\n\n // iOS Safari video fullscreen\n if (video && typeof video.webkitExitFullscreen === 'function') {\n video.webkitExitFullscreen()\n }\n}\n\n// =============================================================================\n// Picture-in-Picture Availability\n// =============================================================================\n\n/**\n * Detect Picture-in-Picture availability.\n */\nexport function detectPipAvailability(\n videoElement?: HTMLVideoElement | null,\n): FeatureAvailability {\n if (!videoElement) return 'unavailable'\n\n // Standard PiP API\n if ('requestPictureInPicture' in videoElement) {\n if (typeof document !== 'undefined' && document.pictureInPictureEnabled === false) {\n return 'unavailable'\n }\n // Safari PWAs have the API but it doesn't work\n if (isSafari() && isStandalonePWA()) return 'unavailable'\n return 'available'\n }\n\n // iOS Safari WebKit presentation mode\n const video = videoElement as WebKitVideoElement\n if (typeof video.webkitSetPresentationMode === 'function') {\n if (isStandalonePWA()) return 'unavailable'\n if (typeof video.webkitSupportsPresentationMode === 'function') {\n return video.webkitSupportsPresentationMode('picture-in-picture')\n ? 'available'\n : 'unsupported'\n }\n return 'available'\n }\n\n return 'unsupported'\n}\n\n/**\n * Enter Picture-in-Picture with cross-browser support.\n */\nexport async function enterPiP(media: HTMLMediaElement): Promise<void> {\n const video = media as HTMLVideoElement & WebKitVideoElement\n\n // Standard PiP API\n if (typeof video.requestPictureInPicture === 'function') {\n await video.requestPictureInPicture()\n return\n }\n\n // iOS Safari WebKit presentation mode\n if (typeof video.webkitSetPresentationMode === 'function') {\n video.webkitSetPresentationMode('picture-in-picture')\n }\n}\n\n/**\n * Exit Picture-in-Picture with cross-browser support.\n */\nexport async function exitPiP(media?: HTMLMediaElement | null): Promise<void> {\n // Standard PiP API\n if (document.pictureInPictureElement && typeof document.exitPictureInPicture === 'function') {\n await document.exitPictureInPicture()\n return\n }\n\n // iOS Safari WebKit presentation mode\n if (media) {\n const video = media as WebKitVideoElement\n if (video.webkitPresentationMode === 'picture-in-picture'\n && typeof video.webkitSetPresentationMode === 'function') {\n video.webkitSetPresentationMode('inline')\n }\n }\n}\n\n// =============================================================================\n// Aggregate Detection\n// =============================================================================\n\n/**\n * Detect all feature availabilities at once.\n */\nexport function detectAllFeatures(\n container?: HTMLElement | null,\n videoElement?: HTMLVideoElement | null,\n mediaElement?: HTMLMediaElement | null,\n): Record<'volume' | 'fullscreen' | 'pip', FeatureAvailability> {\n return {\n volume: detectVolumeAvailability(mediaElement),\n fullscreen: detectFullscreenAvailability(container, videoElement),\n pip: detectPipAvailability(videoElement),\n }\n}\n",
|
|
10
|
+
"/**\n * ts-video-player Base Provider\n *\n * Abstract base class for media providers.\n *\n * @module providers/base\n */\n\nimport type {\n Provider,\n ProviderType,\n Src,\n PlayerOptions,\n ProviderEventMap,\n VideoQuality,\n AudioTrack,\n TextTrack,\n MediaError,\n FeatureAvailability,\n} from '../types'\nimport { EventEmitter } from '../core/events'\nimport {\n detectVolumeAvailability,\n detectFullscreenAvailability,\n detectPipAvailability,\n enterFullscreen as fsEnter,\n exitFullscreen as fsExit,\n enterPiP as pipEnter,\n exitPiP as pipExit,\n} from '../core/features'\n\n/**\n * Abstract base provider implementation\n */\nexport abstract class BaseProvider implements Provider {\n abstract readonly name: string\n abstract readonly type: ProviderType\n\n protected container: HTMLElement | null = null\n protected options: PlayerOptions = {}\n protected events: EventEmitter<ProviderEventMap> = new EventEmitter<ProviderEventMap>()\n protected _ready = false\n\n get mediaElement(): HTMLVideoElement | HTMLAudioElement | HTMLIFrameElement | null {\n return null\n }\n\n get ready(): boolean {\n return this._ready\n }\n\n // === Lifecycle ===\n\n async setup(container: HTMLElement, options: PlayerOptions): Promise<void> {\n this.container = container\n this.options = options\n await this.initialize()\n this._ready = true\n this.events.emit('ready')\n }\n\n protected abstract initialize(): Promise<void>\n\n destroy(): void {\n this._ready = false\n this.cleanup()\n this.events.removeAllListeners()\n }\n\n protected abstract cleanup(): void\n\n // === Loading ===\n\n abstract load(src: Src | Src[]): Promise<void>\n abstract canPlay(src: Src): boolean\n\n // === Playback ===\n\n abstract play(): Promise<void>\n abstract pause(): void\n\n stop(): void {\n this.pause()\n this.seekTo(0)\n }\n\n // === Seeking ===\n\n abstract seekTo(time: number): void\n abstract getCurrentTime(): number\n abstract getDuration(): number\n\n // === Volume ===\n\n abstract setVolume(volume: number): void\n abstract getVolume(): number\n abstract setMuted(muted: boolean): void\n abstract getMuted(): boolean\n\n // === Playback Rate ===\n\n abstract setPlaybackRate(rate: number): void\n abstract getPlaybackRate(): number\n\n // === Quality ===\n\n getQualities(): VideoQuality[] {\n return []\n }\n\n setQuality(_quality: VideoQuality | 'auto'): void {\n // Override in subclass if supported\n }\n\n // === Tracks ===\n\n getTextTracks(): TextTrack[] {\n return []\n }\n\n setTextTrackMode(_trackId: string, _mode: 'disabled' | 'hidden' | 'showing'): void {\n // Override in subclass if supported\n }\n\n getAudioTracks(): AudioTrack[] {\n return []\n }\n\n setAudioTrack(_trackId: string): void {\n // Override in subclass if supported\n }\n\n // === Feature Availability ===\n\n getFeatureAvailability(feature: 'volume' | 'fullscreen' | 'pip'): FeatureAvailability {\n // Only pass media element if it's actually an HTMLMediaElement (not an iframe)\n const el = this.mediaElement\n const media = el instanceof HTMLMediaElement ? el : null\n const video = el instanceof HTMLVideoElement ? el : null\n\n switch (feature) {\n case 'volume':\n // Iframe providers (YouTube/Vimeo) handle volume via their own API\n return el && !(el instanceof HTMLMediaElement) ? 'available' : detectVolumeAvailability(media)\n case 'fullscreen':\n return detectFullscreenAvailability(this.container, video)\n case 'pip':\n // PiP is not available for iframe embeds\n return el && !(el instanceof HTMLMediaElement) ? 'unsupported' : detectPipAvailability(video)\n default:\n return 'unsupported'\n }\n }\n\n // === Fullscreen ===\n\n async enterFullscreen(): Promise<void> {\n await fsEnter(this.container, this.mediaElement as HTMLMediaElement | null)\n }\n\n async exitFullscreen(): Promise<void> {\n await fsExit(this.mediaElement as HTMLMediaElement | null)\n }\n\n protected getFullscreenElement(): HTMLElement | null {\n return this.container\n }\n\n // === Picture-in-Picture ===\n\n async enterPiP(): Promise<void> {\n const media = this.mediaElement as HTMLMediaElement | null\n if (!media) return\n await pipEnter(media)\n }\n\n async exitPiP(): Promise<void> {\n await pipExit(this.mediaElement as HTMLMediaElement | null)\n }\n\n // === Events ===\n\n on<K extends keyof ProviderEventMap>(event: K, handler: ProviderEventMap[K]): void {\n this.events.on(event, handler)\n }\n\n off<K extends keyof ProviderEventMap>(event: K, handler: ProviderEventMap[K]): void {\n this.events.off(event, handler)\n }\n\n // === Helpers ===\n\n protected emitError(code: number, message: string, details?: unknown): void {\n const error: MediaError = { code, message, details }\n this.events.emit('error', error)\n }\n}\n",
|
|
11
|
+
"/**\n * ts-video-player HTML5 Provider\n *\n * Native HTML5 video/audio element provider.\n *\n * @module providers/html5\n */\n\nimport type {\n ProviderType,\n Src,\n MediaSource,\n PlayerOptions,\n AudioTrack,\n TextTrack,\n} from '../types'\nimport { BaseProvider } from './base'\nimport { MediaEventsNormalizer, onFullscreenChange, onPiPChange } from '../core/events'\nimport { probeVolumeAvailability } from '../core/features'\n\n/**\n * Check if a source is an HTML5 compatible source\n */\nexport function isHTML5Source(src: Src): boolean {\n if (typeof src === 'string') {\n const extension = getExtension(src)\n // Check for supported extensions\n if (\n extension === 'mp4' ||\n extension === 'webm' ||\n extension === 'ogg' ||\n extension === 'ogv' ||\n extension === 'mov' ||\n extension === 'mp3' ||\n extension === 'wav' ||\n extension === 'flac' ||\n extension === 'm4a' ||\n extension === 'aac' ||\n extension === 'oga' ||\n extension === 'opus'\n ) {\n return true\n }\n // Check for MIME types\n return false\n }\n\n if (typeof src === 'object' && 'type' in src) {\n const type = (src as MediaSource).type?.toLowerCase() || ''\n return (\n type.startsWith('video/') ||\n type.startsWith('audio/') ||\n type === 'application/ogg'\n )\n }\n\n return false\n}\n\nfunction getExtension(src: string): string {\n const path = src.split(/[?#]/, 1)[0]\n return path.slice(path.lastIndexOf('.') + 1).toLowerCase()\n}\n\n/**\n * Get MIME type for a source\n */\nfunction getMimeType(src: string): string {\n const ext = getExtension(src)\n const mimeTypes: Record<string, string> = {\n mp4: 'video/mp4',\n webm: 'video/webm',\n ogg: 'video/ogg',\n ogv: 'video/ogg',\n mov: 'video/quicktime',\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n flac: 'audio/flac',\n m4a: 'audio/mp4',\n aac: 'audio/aac',\n oga: 'audio/ogg',\n opus: 'audio/ogg; codecs=opus',\n }\n return mimeTypes[ext || ''] || 'video/mp4'\n}\n\n/**\n * HTML5 Video/Audio Provider\n */\nexport class HTML5Provider extends BaseProvider {\n readonly name = 'html5'\n readonly type: ProviderType = 'video'\n\n private media: HTMLVideoElement | HTMLAudioElement | null = null\n private eventsNormalizer: MediaEventsNormalizer | null = null\n private cleanupFullscreen: (() => void) | null = null\n private cleanupPiP: (() => void) | null = null\n private isAudio = false\n\n get mediaElement(): HTMLVideoElement | HTMLAudioElement | null {\n return this.media\n }\n\n // === Lifecycle ===\n\n protected async initialize(): Promise<void> {\n if (!this.container) {\n throw new Error('Container element is required')\n }\n\n // Determine if audio or video based on options\n this.isAudio = this.options.mediaType === 'audio'\n\n // Create media element\n this.media = document.createElement(this.isAudio ? 'audio' : 'video')\n this.media.className = 'ts-video-player__media'\n\n // Set attributes\n if (this.options.autoplay) this.media.autoplay = true\n if (this.options.loop) this.media.loop = true\n if (this.options.muted) this.media.muted = true\n if (this.options.playsinline && !this.isAudio) {\n (this.media as HTMLVideoElement).playsInline = true\n }\n if (this.options.preload) this.media.preload = this.options.preload\n if (this.options.crossorigin) this.media.crossOrigin = this.options.crossorigin\n if (this.options.controlsList?.length) this.media.setAttribute('controlslist', this.options.controlsList.join(' '))\n if (!this.isAudio && this.options.disablePictureInPicture) {\n (this.media as HTMLVideoElement).disablePictureInPicture = true\n }\n if (this.options.poster && !this.isAudio) {\n (this.media as HTMLVideoElement).poster = this.options.poster\n }\n\n // Set initial volume\n if (this.options.volume !== undefined) {\n this.media.volume = this.options.volume\n }\n\n // Append to container\n this.container.appendChild(this.media)\n for (const track of this.options.tracks ?? []) this.addTextTrack(track)\n\n // Setup event normalization\n this.eventsNormalizer = new MediaEventsNormalizer(this.media, this.events)\n\n // Setup fullscreen listener\n this.cleanupFullscreen = onFullscreenChange((fullscreen) => {\n this.events.emit('fullscreenchange', fullscreen)\n })\n\n // Setup PiP listener for video\n if (!this.isAudio && this.media instanceof HTMLVideoElement) {\n this.cleanupPiP = onPiPChange(this.media, (pip) => {\n this.events.emit('pipchange', pip)\n })\n }\n\n // Emit state for dimensions and feature availability\n if (!this.isAudio) {\n this.media.addEventListener('loadedmetadata', () => {\n const video = this.media as HTMLVideoElement\n const fullscreenAvailability = this.getFeatureAvailability('fullscreen')\n const pipAvailability = this.getFeatureAvailability('pip')\n const volumeAvailability = this.getFeatureAvailability('volume')\n this.events.emit('statechange', {\n videoWidth: video.videoWidth,\n videoHeight: video.videoHeight,\n aspectRatio: (video.videoHeight > 0 ? video.videoWidth / video.videoHeight : 0) || 16 / 9,\n canFullscreen: fullscreenAvailability === 'available',\n canPictureInPicture: pipAvailability === 'available',\n fullscreenAvailability,\n pipAvailability,\n volumeAvailability,\n })\n this.events.emit('availabilitychange', 'fullscreen', fullscreenAvailability)\n this.events.emit('availabilitychange', 'pip', pipAvailability)\n this.events.emit('availabilitychange', 'volume', volumeAvailability)\n })\n }\n\n // Async volume probe after loadeddata\n this.media.addEventListener('loadeddata', () => {\n if (!this.media) return\n probeVolumeAvailability(this.media).then((availability) => {\n this.events.emit('statechange', {\n volumeAvailability: availability,\n })\n this.events.emit('availabilitychange', 'volume', availability)\n }).catch(() => {\n // Volume probe failed — keep current availability state\n })\n })\n\n // iOS Safari PiP via webkitpresentationmodechanged\n if (!this.isAudio && this.media instanceof HTMLVideoElement) {\n this.media.addEventListener('webkitpresentationmodechanged', () => {\n const video = this.media as any\n if (video?.webkitPresentationMode) {\n const isPiP = video.webkitPresentationMode === 'picture-in-picture'\n this.events.emit('pipchange', isPiP)\n }\n })\n }\n }\n\n protected cleanup(): void {\n this.eventsNormalizer?.destroy()\n this.cleanupFullscreen?.()\n this.cleanupPiP?.()\n\n if (this.media) {\n this.media.pause()\n this.media.removeAttribute('src')\n this.media.load()\n this.media.remove()\n this.media = null\n }\n }\n\n // === Loading ===\n\n canPlay(src: Src): boolean {\n return isHTML5Source(src)\n }\n\n async load(src: Src): Promise<void> {\n if (!this.media) return\n\n // Clear existing sources\n this.media.innerHTML = ''\n this.media.removeAttribute('src')\n\n // Normalize source\n const sources = Array.isArray(src) ? src : [src]\n\n for (const source of sources) {\n if (typeof source === 'string') {\n // Simple URL\n const sourceEl = document.createElement('source')\n sourceEl.src = source\n sourceEl.type = getMimeType(source)\n this.media.appendChild(sourceEl)\n } else if ('src' in source && typeof source.src === 'string') {\n // MediaSource object\n const mediaSource = source as MediaSource\n const sourceEl = document.createElement('source')\n sourceEl.src = mediaSource.src\n sourceEl.type = mediaSource.type || getMimeType(mediaSource.src)\n this.media.appendChild(sourceEl)\n }\n }\n\n // Load the media\n this.media.load()\n }\n\n // === Playback ===\n\n async play(): Promise<void> {\n if (!this.media) return\n try {\n await this.media.play()\n } catch (error) {\n // Handle autoplay blocked\n if ((error as Error).name === 'NotAllowedError') {\n this.emitError(1, 'Playback was blocked. User interaction required.', error)\n } else {\n throw error\n }\n }\n }\n\n pause(): void {\n this.media?.pause()\n }\n\n // === Seeking ===\n\n seekTo(time: number): void {\n if (this.media) {\n this.media.currentTime = Math.max(0, Math.min(time, this.getDuration()))\n }\n }\n\n getCurrentTime(): number {\n return this.media?.currentTime || 0\n }\n\n getDuration(): number {\n return this.media?.duration || 0\n }\n\n // === Volume ===\n\n setVolume(volume: number): void {\n if (this.media) {\n this.media.volume = Math.max(0, Math.min(1, volume))\n }\n }\n\n getVolume(): number {\n return this.media?.volume || 1\n }\n\n setMuted(muted: boolean): void {\n if (this.media) {\n this.media.muted = muted\n }\n }\n\n getMuted(): boolean {\n return this.media?.muted || false\n }\n\n // === Playback Rate ===\n\n setPlaybackRate(rate: number): void {\n if (this.media) {\n this.media.playbackRate = Math.max(0.25, Math.min(4, rate))\n }\n }\n\n getPlaybackRate(): number {\n return this.media?.playbackRate || 1\n }\n\n // === Text Tracks ===\n\n getTextTracks(): TextTrack[] {\n if (!this.media) return []\n\n const tracks: TextTrack[] = []\n const textTracks = this.media.textTracks\n\n for (let i = 0; i < textTracks.length; i++) {\n const track = textTracks[i]\n tracks.push({\n id: String(i),\n kind: track.kind as TextTrack['kind'],\n label: track.label || `Track ${i + 1}`,\n language: track.language || '',\n mode: track.mode as TextTrack['mode'],\n cues: this.getTrackCues(track),\n })\n }\n\n return tracks\n }\n\n private getTrackCues(track: globalThis.TextTrack): TextTrack['cues'] {\n if (!track.cues) return []\n\n const cues: TextTrack['cues'] = []\n for (let i = 0; i < track.cues.length; i++) {\n const cue = track.cues[i] as VTTCue\n cues.push({\n id: cue.id || String(i),\n startTime: cue.startTime,\n endTime: cue.endTime,\n text: cue.text,\n })\n }\n return cues\n }\n\n setTextTrackMode(trackId: string, mode: 'disabled' | 'hidden' | 'showing'): void {\n if (!this.media) return\n\n const index = parseInt(trackId, 10)\n const track = this.media.textTracks[index]\n if (track) {\n track.mode = mode\n this.events.emit('texttrackchange', mode === 'showing' ? this.getTextTracks()[index] : null)\n }\n }\n\n // === Audio Tracks ===\n\n getAudioTracks(): AudioTrack[] {\n if (!this.media || this.isAudio) return []\n\n const video = this.media as HTMLVideoElement\n if (!('audioTracks' in video)) return []\n\n const audioTracks = (video as any).audioTracks\n const tracks: AudioTrack[] = []\n\n for (let i = 0; i < audioTracks.length; i++) {\n const track = audioTracks[i]\n tracks.push({\n id: track.id || String(i),\n label: track.label || `Track ${i + 1}`,\n language: track.language || '',\n kind: track.kind || '',\n selected: track.enabled,\n })\n }\n\n return tracks\n }\n\n setAudioTrack(trackId: string): void {\n if (!this.media || this.isAudio) return\n\n const video = this.media as HTMLVideoElement\n if (!('audioTracks' in video)) return\n\n const audioTracks = (video as any).audioTracks\n for (let i = 0; i < audioTracks.length; i++) {\n const isTarget = audioTracks[i].id === trackId || String(i) === trackId\n audioTracks[i].enabled = isTarget\n }\n\n this.events.emit('audiotrackchange', this.getAudioTracks().find((t) => t.selected) || null)\n }\n\n // === Add Text Track ===\n\n addTextTrack(options: {\n src?: string\n kind?: TextTrack['kind']\n label?: string\n language?: string\n default?: boolean\n }): void {\n if (!this.media) return\n\n const track = document.createElement('track')\n if (options.src) track.src = options.src\n track.kind = options.kind || 'subtitles'\n track.label = options.label || ''\n track.srclang = options.language || ''\n if (options.default) track.default = true\n\n this.media.appendChild(track)\n }\n}\n\n/**\n * HTML5 Provider Loader\n */\nexport const html5Loader = {\n name: 'html5',\n type: 'video' as ProviderType,\n\n canPlay(src: Src): boolean {\n return isHTML5Source(src)\n },\n\n mediaType(src: Src): 'video' | 'audio' | 'unknown' {\n if (typeof src === 'string') {\n const extension = getExtension(src)\n if (['mp3', 'wav', 'flac', 'm4a', 'aac', 'oga', 'opus'].includes(extension)) {\n return 'audio'\n }\n return 'video'\n }\n\n if (typeof src === 'object' && 'type' in src) {\n const type = (src as MediaSource).type?.toLowerCase() || ''\n if (type.startsWith('audio/')) return 'audio'\n }\n\n return 'video'\n },\n\n async load(container: HTMLElement, options: PlayerOptions): Promise<HTML5Provider> {\n const provider = new HTML5Provider()\n await provider.setup(container, options)\n return provider\n },\n}\n",
|
|
12
|
+
"/**\n * ts-video-player YouTube Provider\n *\n * YouTube embed provider using the iframe API.\n *\n * @module providers/youtube\n */\n\nimport type { ProviderType, Src, YouTubeSource, PlayerOptions, VideoQuality } from '../types'\nimport { BaseProvider } from './base'\n\n// =============================================================================\n// YouTube URL Parsing\n// =============================================================================\n\n/**\n * YouTube URL patterns\n */\nconst YOUTUBE_PATTERNS = [\n /(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/|youtube\\.com\\/embed\\/)([a-zA-Z0-9_-]{11})/,\n /youtube\\.com\\/v\\/([a-zA-Z0-9_-]{11})/,\n /youtube\\.com\\/shorts\\/([a-zA-Z0-9_-]{11})/,\n]\n\n/**\n * Extract YouTube video ID from URL\n */\nexport function extractYouTubeId(url: string): string | null {\n for (const pattern of YOUTUBE_PATTERNS) {\n const match = url.match(pattern)\n if (match) return match[1]\n }\n return null\n}\n\n/**\n * Check if source is a YouTube source\n */\nexport function isYouTubeSource(src: Src): boolean {\n if (typeof src === 'string') {\n return extractYouTubeId(src) !== null\n }\n\n if (typeof src === 'object' && 'type' in src) {\n return (src as YouTubeSource).type === 'youtube'\n }\n\n return false\n}\n\n/**\n * Parse YouTube source\n */\nfunction parseYouTubeSource(src: Src): { videoId: string; startTime?: number; endTime?: number } | null {\n if (typeof src === 'string') {\n const videoId = extractYouTubeId(src)\n if (!videoId) return null\n\n // Parse start/end time from URL params\n const url = new URL(src, 'https://youtube.com')\n const start = url.searchParams.get('t') || url.searchParams.get('start')\n const end = url.searchParams.get('end')\n\n return {\n videoId,\n startTime: start ? parseTime(start) : undefined,\n endTime: end ? parseTime(end) : undefined,\n }\n }\n\n if (typeof src === 'object' && 'type' in src && (src as YouTubeSource).type === 'youtube') {\n const ytSrc = src as YouTubeSource\n return {\n videoId: ytSrc.videoId || extractYouTubeId(ytSrc.src) || '',\n startTime: ytSrc.startTime,\n endTime: ytSrc.endTime,\n }\n }\n\n return null\n}\n\n/**\n * Parse time string (e.g., \"1m30s\", \"90\", \"1:30\")\n */\nfunction parseTime(time: string): number {\n // Already a number in seconds\n if (/^\\d+$/.test(time)) return parseInt(time, 10)\n\n // Format: 1m30s\n const match1 = time.match(/(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?/)\n if (match1) {\n const h = parseInt(match1[1] || '0', 10)\n const m = parseInt(match1[2] || '0', 10)\n const s = parseInt(match1[3] || '0', 10)\n return h * 3600 + m * 60 + s\n }\n\n // Format: 1:30 or 1:30:00\n const parts = time.split(':').map((p) => parseInt(p, 10))\n if (parts.length === 2) return parts[0] * 60 + parts[1]\n if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2]\n\n return 0\n}\n\n// =============================================================================\n// YouTube API Types\n// =============================================================================\n\ninterface YTPlayer {\n playVideo(): void\n pauseVideo(): void\n stopVideo(): void\n seekTo(seconds: number, allowSeekAhead: boolean): void\n getCurrentTime(): number\n getDuration(): number\n setVolume(volume: number): void\n getVolume(): number\n mute(): void\n unMute(): void\n isMuted(): boolean\n setPlaybackRate(rate: number): void\n getPlaybackRate(): number\n getAvailablePlaybackRates(): number[]\n setPlaybackQuality(quality: string): void\n getPlaybackQuality(): string\n getAvailableQualityLevels(): string[]\n loadVideoById(options: { videoId: string; startSeconds?: number; endSeconds?: number }): void\n cueVideoById(options: { videoId: string; startSeconds?: number; endSeconds?: number }): void\n getIframe(): HTMLIFrameElement\n getPlayerState(): number\n destroy(): void\n}\n\ninterface YTPlayerEvent {\n target: YTPlayer\n data: number\n}\n\ndeclare global {\n interface Window {\n YT?: {\n Player: new (\n elementId: string | HTMLElement,\n options: {\n videoId?: string\n width?: number | string\n height?: number | string\n playerVars?: Record<string, string | number>\n events?: {\n onReady?: (event: YTPlayerEvent) => void\n onStateChange?: (event: YTPlayerEvent) => void\n onError?: (event: YTPlayerEvent) => void\n onPlaybackQualityChange?: (event: YTPlayerEvent) => void\n onPlaybackRateChange?: (event: YTPlayerEvent) => void\n }\n },\n ) => YTPlayer\n PlayerState: {\n UNSTARTED: -1\n ENDED: 0\n PLAYING: 1\n PAUSED: 2\n BUFFERING: 3\n CUED: 5\n }\n }\n onYouTubeIframeAPIReady?: () => void\n }\n}\n\n// =============================================================================\n// YouTube Provider\n// =============================================================================\n\n/**\n * YouTube embed provider\n */\nexport class YouTubeProvider extends BaseProvider {\n readonly name = 'youtube'\n readonly type: ProviderType = 'youtube'\n\n private player: YTPlayer | null = null\n private iframe: HTMLIFrameElement | null = null\n private pendingVideoId: string | null = null\n private startTime?: number\n private endTime?: number\n private timeUpdateInterval: ReturnType<typeof setInterval> | null = null\n private lastState = -1\n\n get mediaElement(): HTMLIFrameElement | null {\n return this.iframe\n }\n\n // === Lifecycle ===\n\n protected async initialize(): Promise<void> {\n if (!this.container) {\n throw new Error('Container element is required')\n }\n\n // Load YouTube API\n await this.loadYouTubeAPI()\n\n // Create container div for YouTube player\n const playerDiv = document.createElement('div')\n playerDiv.id = `ts-video-player-yt-${Date.now()}`\n playerDiv.className = 'ts-video-player__youtube'\n this.container.appendChild(playerDiv)\n\n // Build player vars\n const playerVars: Record<string, string | number> = {\n autoplay: this.options.autoplay ? 1 : 0,\n controls: 0, // We use our own controls\n disablekb: 1, // Disable keyboard (we handle it)\n enablejsapi: 1,\n iv_load_policy: 3, // Disable annotations\n modestbranding: 1,\n playsinline: this.options.playsinline ? 1 : 0,\n rel: 0, // Don't show related videos\n origin: window.location.origin,\n }\n\n // Apply custom YouTube params\n if (this.options.youtubeParams) {\n Object.assign(playerVars, this.options.youtubeParams)\n }\n\n // Use privacy-enhanced mode\n playerVars.host = 'https://www.youtube-nocookie.com'\n\n // Create player\n this.player = new window.YT!.Player(playerDiv.id, {\n width: '100%',\n height: '100%',\n playerVars,\n events: {\n onReady: () => this.onReady(),\n onStateChange: (e) => this.onStateChange(e),\n onError: (e) => this.onError(e),\n onPlaybackQualityChange: () => this.onQualityChange(),\n onPlaybackRateChange: () => this.onRateChange(),\n },\n })\n\n // Get iframe reference\n this.iframe = this.player.getIframe()\n this.iframe.className = 'ts-video-player__media'\n }\n\n private async loadYouTubeAPI(): Promise<void> {\n if (window.YT?.Player) return\n\n return new Promise((resolve) => {\n // Check if script already loading\n if (document.querySelector('script[src*=\"youtube.com/iframe_api\"]')) {\n const check = () => {\n if (window.YT?.Player) {\n resolve()\n } else {\n setTimeout(check, 100)\n }\n }\n check()\n return\n }\n\n // Load script\n const script = document.createElement('script')\n script.src = 'https://www.youtube.com/iframe_api'\n script.async = true\n\n window.onYouTubeIframeAPIReady = () => {\n resolve()\n }\n\n document.head.appendChild(script)\n })\n }\n\n protected cleanup(): void {\n if (this.timeUpdateInterval) {\n clearInterval(this.timeUpdateInterval)\n this.timeUpdateInterval = null\n }\n\n this.player?.destroy()\n this.player = null\n this.iframe = null\n }\n\n // === Event Handlers ===\n\n private onReady(): void {\n // Start time update polling\n this.timeUpdateInterval = setInterval(() => {\n if (this.player && this.lastState === 1) {\n this.events.emit('timeupdate', this.getCurrentTime())\n }\n }, 250)\n\n // Emit initial state\n this.events.emit('loadedmetadata')\n this.events.emit('canplay')\n\n // Load pending video if any\n if (this.pendingVideoId) {\n this.loadVideo(this.pendingVideoId, this.startTime, this.endTime)\n this.pendingVideoId = null\n }\n }\n\n private onStateChange(event: YTPlayerEvent): void {\n const state = event.data\n this.lastState = state\n\n const PlayerState = window.YT!.PlayerState\n\n switch (state) {\n case PlayerState.UNSTARTED:\n this.events.emit('loadstart')\n break\n case PlayerState.PLAYING:\n this.events.emit('play')\n this.events.emit('playing')\n this.events.emit('statechange', { paused: false, playing: true, waiting: false })\n break\n case PlayerState.PAUSED:\n this.events.emit('pause')\n this.events.emit('statechange', { paused: true, playing: false })\n break\n case PlayerState.BUFFERING:\n this.events.emit('waiting')\n this.events.emit('statechange', { waiting: true })\n break\n case PlayerState.ENDED:\n this.events.emit('ended')\n this.events.emit('statechange', { ended: true, playing: false })\n break\n case PlayerState.CUED:\n this.events.emit('loadeddata')\n this.events.emit('durationchange', this.getDuration())\n break\n }\n }\n\n private onError(event: YTPlayerEvent): void {\n const errorMessages: Record<number, string> = {\n 2: 'Invalid video ID',\n 5: 'HTML5 player error',\n 100: 'Video not found or removed',\n 101: 'Video cannot be embedded',\n 150: 'Video cannot be embedded',\n }\n\n this.emitError(event.data, errorMessages[event.data] || 'Unknown YouTube error')\n }\n\n private onQualityChange(): void {\n const quality = this.player?.getPlaybackQuality() || 'auto'\n const qualities = this.getQualities()\n this.events.emit('qualitychange', qualities.find((q) => q.id === quality) || null)\n }\n\n private onRateChange(): void {\n this.events.emit('ratechange', this.getPlaybackRate())\n }\n\n // === Loading ===\n\n canPlay(src: Src): boolean {\n return isYouTubeSource(src)\n }\n\n async load(src: Src): Promise<void> {\n const parsed = parseYouTubeSource(src)\n if (!parsed) {\n this.emitError(2, 'Invalid YouTube source')\n return\n }\n\n this.startTime = parsed.startTime\n this.endTime = parsed.endTime\n\n if (!this.player || !this._ready) {\n this.pendingVideoId = parsed.videoId\n return\n }\n\n this.loadVideo(parsed.videoId, parsed.startTime, parsed.endTime)\n }\n\n private loadVideo(videoId: string, startTime?: number, endTime?: number): void {\n if (!this.player) return\n\n if (this.options.autoplay) {\n this.player.loadVideoById({\n videoId,\n startSeconds: startTime,\n endSeconds: endTime,\n })\n } else {\n this.player.cueVideoById({\n videoId,\n startSeconds: startTime,\n endSeconds: endTime,\n })\n }\n }\n\n // === Playback ===\n\n async play(): Promise<void> {\n this.player?.playVideo()\n }\n\n pause(): void {\n this.player?.pauseVideo()\n }\n\n stop(): void {\n this.player?.stopVideo()\n }\n\n // === Seeking ===\n\n seekTo(time: number): void {\n this.player?.seekTo(time, true)\n this.events.emit('seeking')\n // YouTube doesn't emit seeked, so we fake it\n setTimeout(() => this.events.emit('seeked'), 100)\n }\n\n getCurrentTime(): number {\n return this.player?.getCurrentTime() || 0\n }\n\n getDuration(): number {\n return this.player?.getDuration() || 0\n }\n\n // === Volume ===\n\n setVolume(volume: number): void {\n this.player?.setVolume(Math.round(volume * 100))\n this.events.emit('volumechange', volume, this.getMuted())\n }\n\n getVolume(): number {\n return (this.player?.getVolume() || 100) / 100\n }\n\n setMuted(muted: boolean): void {\n if (muted) {\n this.player?.mute()\n } else {\n this.player?.unMute()\n }\n this.events.emit('volumechange', this.getVolume(), muted)\n }\n\n getMuted(): boolean {\n return this.player?.isMuted() || false\n }\n\n // === Playback Rate ===\n\n setPlaybackRate(rate: number): void {\n this.player?.setPlaybackRate(rate)\n }\n\n getPlaybackRate(): number {\n return this.player?.getPlaybackRate() || 1\n }\n\n // === Quality ===\n\n getQualities(): VideoQuality[] {\n const levels = this.player?.getAvailableQualityLevels() || []\n const heights: Record<string, number> = {\n highres: 2160,\n hd2160: 2160,\n hd1440: 1440,\n hd1080: 1080,\n hd720: 720,\n large: 480,\n medium: 360,\n small: 240,\n tiny: 144,\n }\n\n const current = this.player?.getPlaybackQuality()\n\n return levels.map((level) => ({\n id: level,\n width: Math.round((heights[level] || 480) * (16 / 9)),\n height: heights[level] || 480,\n bitrate: 0,\n selected: level === current,\n }))\n }\n\n setQuality(quality: VideoQuality | 'auto'): void {\n if (quality === 'auto') {\n this.player?.setPlaybackQuality('default')\n } else {\n this.player?.setPlaybackQuality(quality.id)\n }\n }\n}\n\n/**\n * YouTube Provider Loader\n */\nexport const youtubeLoader = {\n name: 'youtube',\n type: 'youtube' as ProviderType,\n\n canPlay(src: Src): boolean {\n return isYouTubeSource(src)\n },\n\n mediaType(): 'video' {\n return 'video'\n },\n\n preconnect(): string[] {\n return [\n 'https://www.youtube-nocookie.com',\n 'https://www.youtube.com',\n 'https://i.ytimg.com',\n 'https://s.ytimg.com',\n ]\n },\n\n async load(container: HTMLElement, options: PlayerOptions): Promise<YouTubeProvider> {\n const provider = new YouTubeProvider()\n await provider.setup(container, options)\n return provider\n },\n}\n",
|
|
13
|
+
"/**\n * ts-video-player Vimeo Provider\n *\n * Vimeo embed provider using the Vimeo Player API.\n *\n * @module providers/vimeo\n */\n\nimport type { ProviderType, Src, VimeoSource, PlayerOptions, VideoQuality } from '../types'\nimport { BaseProvider } from './base'\n\n// =============================================================================\n// Vimeo URL Parsing\n// =============================================================================\n\n/**\n * Vimeo URL patterns\n */\nconst VIMEO_PATTERNS = [\n /vimeo\\.com\\/(\\d+)/,\n /vimeo\\.com\\/video\\/(\\d+)/,\n /player\\.vimeo\\.com\\/video\\/(\\d+)/,\n /vimeo\\.com\\/channels\\/[\\w]+\\/(\\d+)/,\n /vimeo\\.com\\/groups\\/[\\w]+\\/videos\\/(\\d+)/,\n]\n\n/**\n * Extract Vimeo video ID from URL\n */\nexport function extractVimeoId(url: string): string | null {\n for (const pattern of VIMEO_PATTERNS) {\n const match = url.match(pattern)\n if (match) return match[1]\n }\n return null\n}\n\n/**\n * Extract hash for private videos\n */\nexport function extractVimeoHash(url: string): string | null {\n // Format: vimeo.com/123456789/abcdef123\n const match = url.match(/vimeo\\.com\\/\\d+\\/([a-f0-9]+)/)\n return match ? match[1] : null\n}\n\n/**\n * Check if source is a Vimeo source\n */\nexport function isVimeoSource(src: Src): boolean {\n if (typeof src === 'string') {\n return extractVimeoId(src) !== null\n }\n\n if (typeof src === 'object' && 'type' in src) {\n return (src as VimeoSource).type === 'vimeo'\n }\n\n return false\n}\n\n/**\n * Parse Vimeo source\n */\nfunction parseVimeoSource(src: Src): { videoId: string; hash?: string } | null {\n if (typeof src === 'string') {\n const videoId = extractVimeoId(src)\n if (!videoId) return null\n return { videoId, hash: extractVimeoHash(src) || undefined }\n }\n\n if (typeof src === 'object' && 'type' in src && (src as VimeoSource).type === 'vimeo') {\n const vSrc = src as VimeoSource\n return {\n videoId: vSrc.videoId || extractVimeoId(vSrc.src) || '',\n hash: vSrc.hash,\n }\n }\n\n return null\n}\n\n// =============================================================================\n// Vimeo Player Types\n// =============================================================================\n\ninterface VimeoPlayerOptions {\n id?: number\n url?: string\n autopause?: boolean\n autoplay?: boolean\n background?: boolean\n byline?: boolean\n color?: string\n controls?: boolean\n dnt?: boolean\n height?: number\n loop?: boolean\n maxheight?: number\n maxwidth?: number\n muted?: boolean\n playsinline?: boolean\n portrait?: boolean\n responsive?: boolean\n speed?: boolean\n title?: boolean\n transparent?: boolean\n width?: number\n}\n\ninterface VimeoPlayer {\n ready(): Promise<void>\n play(): Promise<void>\n pause(): Promise<void>\n unload(): Promise<void>\n destroy(): Promise<void>\n getCurrentTime(): Promise<number>\n setCurrentTime(seconds: number): Promise<number>\n getDuration(): Promise<number>\n getVolume(): Promise<number>\n setVolume(volume: number): Promise<number>\n getMuted(): Promise<boolean>\n setMuted(muted: boolean): Promise<boolean>\n getLoop(): Promise<boolean>\n setLoop(loop: boolean): Promise<boolean>\n getPlaybackRate(): Promise<number>\n setPlaybackRate(rate: number): Promise<number>\n getPaused(): Promise<boolean>\n getEnded(): Promise<boolean>\n getVideoTitle(): Promise<string>\n getVideoWidth(): Promise<number>\n getVideoHeight(): Promise<number>\n getVideoId(): Promise<number>\n getVideoUrl(): Promise<string>\n getQualities(): Promise<Array<{ id: string; label: string; active: boolean }>>\n setQuality(quality: string): Promise<string>\n getTextTracks(): Promise<Array<{ label: string; language: string; kind: string; mode: string }>>\n enableTextTrack(language: string, kind?: string): Promise<void>\n disableTextTrack(): Promise<void>\n on(event: string, callback: (...args: any[]) => void): void\n off(event: string, callback?: (...args: any[]) => void): void\n getBuffered(): Promise<Array<{ start: number; end: number }>>\n getChapters(): Promise<Array<{ title: string; startTime: number; index: number }>>\n}\n\ninterface VimeoStatic {\n Player: new (element: HTMLElement | HTMLIFrameElement, options?: VimeoPlayerOptions) => VimeoPlayer\n}\n\ndeclare global {\n interface Window {\n Vimeo?: VimeoStatic\n }\n}\n\n// =============================================================================\n// Vimeo Provider\n// =============================================================================\n\nconst VIMEO_SDK_URL = 'https://player.vimeo.com/api/player.js'\n\n/**\n * Vimeo embed provider\n */\nexport class VimeoProvider extends BaseProvider {\n readonly name = 'vimeo'\n readonly type: ProviderType = 'vimeo'\n\n private container_el: HTMLElement | null = null\n private player: VimeoPlayer | null = null\n private iframe: HTMLIFrameElement | null = null\n private timeUpdateInterval: ReturnType<typeof setInterval> | null = null\n private _qualities: VideoQuality[] = []\n private _duration = 0\n get mediaElement(): HTMLIFrameElement | null {\n return this.iframe\n }\n\n // === Lifecycle ===\n\n protected async initialize(): Promise<void> {\n if (!this.container) {\n throw new Error('Container element is required')\n }\n\n // Load Vimeo SDK\n await this.loadVimeoSDK()\n\n // Create container for Vimeo player\n this.container_el = document.createElement('div')\n this.container_el.className = 'ts-video-player__vimeo'\n this.container.appendChild(this.container_el)\n }\n\n private async loadVimeoSDK(): Promise<void> {\n if (window.Vimeo) return\n\n return new Promise((resolve, reject) => {\n if (document.querySelector(`script[src=\"${VIMEO_SDK_URL}\"]`)) {\n const check = () => {\n if (window.Vimeo) resolve()\n else setTimeout(check, 100)\n }\n check()\n return\n }\n\n const script = document.createElement('script')\n script.src = VIMEO_SDK_URL\n script.async = true\n script.onload = () => resolve()\n script.onerror = () => reject(new Error('Failed to load Vimeo SDK'))\n document.head.appendChild(script)\n })\n }\n\n protected cleanup(): void {\n if (this.timeUpdateInterval) {\n clearInterval(this.timeUpdateInterval)\n this.timeUpdateInterval = null\n }\n\n this.player?.destroy()\n this.player = null\n this.iframe = null\n this.container_el?.remove()\n this.container_el = null\n }\n\n // === Loading ===\n\n canPlay(src: Src): boolean {\n return isVimeoSource(src)\n }\n\n async load(src: Src): Promise<void> {\n const parsed = parseVimeoSource(src)\n if (!parsed || !this.container_el) {\n this.emitError(2, 'Invalid Vimeo source')\n return\n }\n\n // Destroy existing player\n if (this.player) {\n await this.player.destroy()\n this.player = null\n }\n\n // Build player options\n const playerOptions: VimeoPlayerOptions = {\n id: parseInt(parsed.videoId, 10),\n autopause: false,\n autoplay: this.options.autoplay || false,\n controls: false, // We use our own controls\n loop: this.options.loop || false,\n muted: this.options.muted || false,\n playsinline: this.options.playsinline !== false,\n responsive: true,\n dnt: true, // Do Not Track\n }\n\n // Apply custom Vimeo params\n if (this.options.vimeoParams) {\n Object.assign(playerOptions, this.options.vimeoParams)\n }\n\n // Create player\n this.player = new window.Vimeo!.Player(this.container_el, playerOptions)\n\n // Wait for ready\n await this.player.ready()\n\n // Get iframe\n this.iframe = this.container_el.querySelector('iframe')\n if (this.iframe) {\n this.iframe.className = 'ts-video-player__media'\n }\n\n // Setup event listeners\n this.attachVimeoEvents()\n\n // Emit ready\n this.events.emit('loadedmetadata')\n this.events.emit('canplay')\n\n // Get duration\n this._duration = await this.player.getDuration()\n this.events.emit('durationchange', this._duration)\n\n // Get initial qualities\n await this.updateQualities()\n\n // Emit dimensions\n const [width, height] = await Promise.all([this.player.getVideoWidth(), this.player.getVideoHeight()])\n this.events.emit('statechange', {\n videoWidth: width,\n videoHeight: height,\n aspectRatio: width / height || 16 / 9,\n canFullscreen: true,\n canPictureInPicture: false,\n })\n }\n\n private attachVimeoEvents(): void {\n if (!this.player) return\n\n // Play\n this.player.on('play', () => {\n this.events.emit('play')\n this.events.emit('playing')\n this.events.emit('statechange', { paused: false, playing: true })\n })\n\n // Pause\n this.player.on('pause', () => {\n this.events.emit('pause')\n this.events.emit('statechange', { paused: true, playing: false })\n })\n\n // Ended\n this.player.on('ended', () => {\n this.events.emit('ended')\n this.events.emit('statechange', { ended: true, playing: false })\n })\n\n // Time update\n this.player.on('timeupdate', (data: { seconds: number }) => {\n this.events.emit('timeupdate', data.seconds)\n })\n\n // Seeking\n this.player.on('seeking', () => {\n this.events.emit('seeking')\n })\n\n this.player.on('seeked', () => {\n this.events.emit('seeked')\n })\n\n // Buffering\n this.player.on('bufferstart', () => {\n this.events.emit('waiting')\n this.events.emit('statechange', { waiting: true })\n })\n\n this.player.on('bufferend', () => {\n this.events.emit('statechange', { waiting: false })\n })\n\n // Progress\n this.player.on('progress', (data: { percent: number }) => {\n const buffered = [{ start: 0, end: data.percent * this._duration }]\n this.events.emit('progress', buffered)\n })\n\n // Volume\n this.player.on('volumechange', async () => {\n const [volume, muted] = await Promise.all([this.player!.getVolume(), this.player!.getMuted()])\n this.events.emit('volumechange', volume, muted)\n })\n\n // Playback rate\n this.player.on('playbackratechange', (data: { playbackRate: number }) => {\n this.events.emit('ratechange', data.playbackRate)\n })\n\n // Quality change\n this.player.on('qualitychange', async () => {\n await this.updateQualities()\n const current = this._qualities.find((q) => q.selected)\n this.events.emit('qualitychange', current || null)\n })\n\n // Error\n this.player.on('error', (error: { name: string; message: string }) => {\n this.emitError(5, error.message, error)\n })\n }\n\n private async updateQualities(): Promise<void> {\n if (!this.player) return\n\n try {\n const qualities = await this.player.getQualities()\n this._qualities = qualities.map((q) => {\n // Parse resolution from label (e.g., \"1080p\", \"720p\")\n const match = q.label.match(/(\\d+)p/)\n const height = match ? parseInt(match[1], 10) : 0\n\n return {\n id: q.id,\n width: Math.round(height * (16 / 9)),\n height,\n bitrate: 0,\n selected: q.active,\n }\n })\n\n this.events.emit('statechange', { qualities: this._qualities })\n } catch {\n // Quality API not available for all videos\n }\n }\n\n // === Playback ===\n\n async play(): Promise<void> {\n try {\n await this.player?.play()\n } catch (error) {\n if ((error as Error).name === 'NotAllowedError') {\n this.emitError(1, 'Playback was blocked. User interaction required.', error)\n } else {\n throw error\n }\n }\n }\n\n pause(): void {\n this.player?.pause()\n }\n\n stop(): void {\n this.pause()\n this.seekTo(0)\n }\n\n // === Seeking ===\n\n seekTo(time: number): void {\n this.player?.setCurrentTime(time)\n }\n\n getCurrentTime(): number {\n // Sync call not available, return cached or 0\n return 0\n }\n\n getDuration(): number {\n return this._duration\n }\n\n // === Volume ===\n\n setVolume(volume: number): void {\n this.player?.setVolume(Math.max(0, Math.min(1, volume)))\n }\n\n getVolume(): number {\n return 1 // Async, return default\n }\n\n setMuted(muted: boolean): void {\n this.player?.setMuted(muted)\n }\n\n getMuted(): boolean {\n return false // Async, return default\n }\n\n // === Playback Rate ===\n\n setPlaybackRate(rate: number): void {\n this.player?.setPlaybackRate(rate)\n }\n\n getPlaybackRate(): number {\n return 1 // Async, return default\n }\n\n // === Quality ===\n\n getQualities(): VideoQuality[] {\n return this._qualities\n }\n\n setQuality(quality: VideoQuality | 'auto'): void {\n if (!this.player) return\n\n if (quality === 'auto') {\n this.player.setQuality('auto')\n } else {\n this.player.setQuality(quality.id)\n }\n }\n\n // === Text Tracks ===\n\n async enableTextTrack(language: string): Promise<void> {\n await this.player?.enableTextTrack(language)\n }\n\n async disableTextTrack(): Promise<void> {\n await this.player?.disableTextTrack()\n }\n\n // === Chapters ===\n\n async getChapters(): Promise<Array<{ title: string; startTime: number; index: number }>> {\n if (!this.player) return []\n try {\n return await this.player.getChapters()\n } catch {\n return []\n }\n }\n}\n\n/**\n * Vimeo Provider Loader\n */\nexport const vimeoLoader = {\n name: 'vimeo',\n type: 'vimeo' as ProviderType,\n\n canPlay(src: Src): boolean {\n return isVimeoSource(src)\n },\n\n mediaType(): 'video' {\n return 'video'\n },\n\n preconnect(): string[] {\n return ['https://player.vimeo.com', 'https://i.vimeocdn.com', 'https://f.vimeocdn.com']\n },\n\n async load(container: HTMLElement, options: PlayerOptions): Promise<VimeoProvider> {\n const provider = new VimeoProvider()\n await provider.setup(container, options)\n return provider\n },\n}\n",
|
|
14
|
+
"/**\n * ts-video-player HLS Provider\n *\n * HLS streaming provider using hls.js library.\n * Supports adaptive bitrate streaming, quality selection, and live streams.\n *\n * @module providers/hls\n */\n\nimport type {\n ProviderType,\n Src,\n HLSSource,\n PlayerOptions,\n VideoQuality,\n AudioTrack,\n TextTrack,\n} from '../types'\nimport { BaseProvider } from './base'\nimport { MediaEventsNormalizer, onFullscreenChange, onPiPChange } from '../core/events'\n\n// =============================================================================\n// HLS Detection\n// =============================================================================\n\n/**\n * Check if source is an HLS source\n */\nexport function isHLSSource(src: Src): boolean {\n if (typeof src === 'string') {\n const url = src.toLowerCase()\n return url.endsWith('.m3u8') || url.includes('application/x-mpegurl') || url.includes('application/vnd.apple.mpegurl')\n }\n\n if (typeof src === 'object' && 'type' in src) {\n const type = (src as HLSSource).type?.toLowerCase() || ''\n return type === 'application/x-mpegurl' || type === 'application/vnd.apple.mpegurl'\n }\n\n return false\n}\n\n/**\n * Check if native HLS is supported (Safari, iOS)\n */\nexport function isNativeHLSSupported(): boolean {\n const video = document.createElement('video')\n return video.canPlayType('application/vnd.apple.mpegurl') !== ''\n}\n\n// =============================================================================\n// HLS.js Types\n// =============================================================================\n\ninterface HlsConfig {\n enableWorker?: boolean\n lowLatencyMode?: boolean\n backBufferLength?: number\n maxBufferLength?: number\n maxMaxBufferLength?: number\n startLevel?: number\n autoStartLoad?: boolean\n debug?: boolean\n [key: string]: unknown\n}\n\ninterface HlsLevel {\n bitrate: number\n width: number\n height: number\n codecSet: string\n url: string[]\n audioCodec?: string\n videoCodec?: string\n}\n\ninterface HlsAudioTrack {\n id: number\n name: string\n lang?: string\n default: boolean\n autoselect: boolean\n forced: boolean\n url: string\n}\n\ninterface HlsSubtitleTrack {\n id: number\n name: string\n lang?: string\n default: boolean\n autoselect: boolean\n forced: boolean\n url: string\n}\n\ninterface HlsEvents {\n MEDIA_ATTACHED: string\n MEDIA_DETACHED: string\n MANIFEST_LOADED: string\n MANIFEST_PARSED: string\n LEVEL_LOADED: string\n LEVEL_SWITCHED: string\n AUDIO_TRACK_LOADED: string\n AUDIO_TRACK_SWITCHED: string\n SUBTITLE_TRACK_LOADED: string\n SUBTITLE_TRACK_SWITCH: string\n FRAG_LOADED: string\n FRAG_BUFFERED: string\n ERROR: string\n}\n\ninterface Hls {\n config: HlsConfig\n media: HTMLVideoElement | null\n currentLevel: number\n nextLevel: number\n loadLevel: number\n autoLevelEnabled: boolean\n levels: HlsLevel[]\n audioTracks: HlsAudioTrack[]\n audioTrack: number\n subtitleTracks: HlsSubtitleTrack[]\n subtitleTrack: number\n liveSyncPosition: number | null\n\n loadSource(src: string): void\n attachMedia(media: HTMLVideoElement): void\n detachMedia(): void\n startLoad(startPosition?: number): void\n stopLoad(): void\n recoverMediaError(): void\n destroy(): void\n on(event: string, handler: (...args: any[]) => void): void\n off(event: string, handler: (...args: any[]) => void): void\n}\n\ninterface HlsStatic {\n new (config?: HlsConfig): Hls\n isSupported(): boolean\n Events: HlsEvents\n ErrorTypes: {\n NETWORK_ERROR: string\n MEDIA_ERROR: string\n KEY_SYSTEM_ERROR: string\n MUX_ERROR: string\n OTHER_ERROR: string\n }\n ErrorDetails: Record<string, string>\n}\n\ndeclare global {\n interface Window {\n Hls?: HlsStatic\n }\n}\n\n// =============================================================================\n// HLS Provider\n// =============================================================================\n\nconst HLS_CDN_DEFAULT = 'https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js'\n\n/**\n * HLS streaming provider\n */\nexport class HLSProvider extends BaseProvider {\n readonly name = 'hls'\n readonly type: ProviderType = 'hls'\n\n private video: HTMLVideoElement | null = null\n private hls: Hls | null = null\n private eventsNormalizer: MediaEventsNormalizer | null = null\n private cleanupFullscreen: (() => void) | null = null\n private cleanupPiP: (() => void) | null = null\n private useNativeHLS = false\n private _qualities: VideoQuality[] = []\n private _audioTracks: AudioTrack[] = []\n private _textTracks: TextTrack[] = []\n private currentQualityIndex = -1 // -1 = auto\n\n get mediaElement(): HTMLVideoElement | null {\n return this.video\n }\n\n /**\n * Whether live sync is supported\n */\n get canLiveSync(): boolean {\n return !this.useNativeHLS && this.hls !== null\n }\n\n /**\n * Get HLS.js config\n */\n get hlsConfig(): HlsConfig {\n return this.hls?.config || {}\n }\n\n // === Lifecycle ===\n\n protected async initialize(): Promise<void> {\n if (!this.container) {\n throw new Error('Container element is required')\n }\n\n // Create video element\n this.video = document.createElement('video')\n this.video.className = 'ts-video-player__media'\n\n // Set attributes\n if (this.options.autoplay) this.video.autoplay = true\n if (this.options.loop) this.video.loop = true\n if (this.options.muted) this.video.muted = true\n if (this.options.playsinline) this.video.playsInline = true\n if (this.options.preload) this.video.preload = this.options.preload\n if (this.options.crossorigin) this.video.crossOrigin = this.options.crossorigin\n if (this.options.poster) this.video.poster = this.options.poster\n if (this.options.volume !== undefined) this.video.volume = this.options.volume\n\n this.container.appendChild(this.video)\n\n // Setup event normalization\n this.eventsNormalizer = new MediaEventsNormalizer(this.video, this.events)\n\n // Setup fullscreen/PiP listeners\n this.cleanupFullscreen = onFullscreenChange((fullscreen) => {\n this.events.emit('fullscreenchange', fullscreen)\n })\n\n this.cleanupPiP = onPiPChange(this.video, (pip) => {\n this.events.emit('pipchange', pip)\n })\n\n // Check for native HLS support\n this.useNativeHLS = isNativeHLSSupported()\n\n // Load HLS.js if needed\n if (!this.useNativeHLS) {\n await this.loadHLSLibrary()\n }\n\n // Emit dimensions and feature availability on load\n this.video.addEventListener('loadedmetadata', () => {\n const fullscreenAvailability = this.getFeatureAvailability('fullscreen')\n const pipAvailability = this.getFeatureAvailability('pip')\n const volumeAvailability = this.getFeatureAvailability('volume')\n this.events.emit('statechange', {\n videoWidth: this.video!.videoWidth,\n videoHeight: this.video!.videoHeight,\n aspectRatio: (this.video!.videoHeight > 0 ? this.video!.videoWidth / this.video!.videoHeight : 0) || 16 / 9,\n canFullscreen: fullscreenAvailability === 'available',\n canPictureInPicture: pipAvailability === 'available',\n fullscreenAvailability,\n pipAvailability,\n volumeAvailability,\n })\n this.events.emit('availabilitychange', 'fullscreen', fullscreenAvailability)\n this.events.emit('availabilitychange', 'pip', pipAvailability)\n this.events.emit('availabilitychange', 'volume', volumeAvailability)\n })\n }\n\n private async loadHLSLibrary(): Promise<void> {\n if (window.Hls) return\n\n return new Promise((resolve, reject) => {\n const script = document.createElement('script')\n script.src = this.options.hlsCdnUrl || HLS_CDN_DEFAULT\n script.async = true\n script.onload = () => resolve()\n script.onerror = () => reject(new Error('Failed to load HLS.js'))\n document.head.appendChild(script)\n })\n }\n\n protected cleanup(): void {\n this.eventsNormalizer?.destroy()\n this.cleanupFullscreen?.()\n this.cleanupPiP?.()\n\n if (this.hls) {\n this.hls.destroy()\n this.hls = null\n }\n\n if (this.video) {\n this.video.pause()\n this.video.removeAttribute('src')\n this.video.load()\n this.video.remove()\n this.video = null\n }\n\n this._qualities = []\n this._audioTracks = []\n this._textTracks = []\n }\n\n // === Loading ===\n\n canPlay(src: Src): boolean {\n return isHLSSource(src)\n }\n\n async load(src: Src): Promise<void> {\n if (!this.video) return\n\n const url = typeof src === 'string' ? src : (src as HLSSource).src\n\n if (this.useNativeHLS) {\n // Use native HLS (Safari/iOS)\n this.video.src = url\n this.video.load()\n return\n }\n\n // Use HLS.js\n if (!window.Hls) {\n await this.loadHLSLibrary()\n }\n\n if (!window.Hls?.isSupported()) {\n this.emitError(4, 'HLS is not supported in this browser')\n return\n }\n\n // Destroy existing instance\n if (this.hls) {\n this.hls.destroy()\n }\n\n // Build config\n const config: HlsConfig = {\n enableWorker: true,\n lowLatencyMode: false,\n ...this.options.hlsConfig,\n }\n\n // Create new HLS instance\n this.hls = new window.Hls(config)\n this.attachHLSEvents()\n\n // Attach and load\n this.hls.attachMedia(this.video)\n this.hls.loadSource(url)\n }\n\n private attachHLSEvents(): void {\n if (!this.hls || !window.Hls) return\n\n const Hls = window.Hls\n\n // Manifest loaded\n this.hls.on(Hls.Events.MANIFEST_PARSED, (_event: string, data: { levels: HlsLevel[] }) => {\n this.updateQualities(data.levels)\n\n // Auto-start load\n if (this.options.autoplay) {\n this.video?.play().catch(() => {})\n }\n })\n\n // Level switched\n this.hls.on(Hls.Events.LEVEL_SWITCHED, (_event: string, data: { level: number }) => {\n this.currentQualityIndex = data.level\n this.updateQualitySelection()\n this.events.emit('qualitychange', this._qualities[data.level] || null)\n })\n\n // Audio tracks loaded\n this.hls.on(Hls.Events.AUDIO_TRACK_LOADED, () => {\n this.updateAudioTracks()\n })\n\n // Audio track switched\n this.hls.on(Hls.Events.AUDIO_TRACK_SWITCHED, () => {\n this.updateAudioTracks()\n const current = this._audioTracks.find((t) => t.selected)\n this.events.emit('audiotrackchange', current || null)\n })\n\n // Subtitle tracks loaded\n this.hls.on(Hls.Events.SUBTITLE_TRACK_LOADED, () => {\n this.updateTextTracks()\n })\n\n // Error handling\n this.hls.on(Hls.Events.ERROR, (_event: string, data: { type: string; details: string; fatal: boolean }) => {\n if (data.fatal) {\n switch (data.type) {\n case Hls.ErrorTypes.NETWORK_ERROR:\n // Try to recover from network error\n this.hls?.startLoad()\n break\n case Hls.ErrorTypes.MEDIA_ERROR:\n // Try to recover from media error\n this.hls?.recoverMediaError()\n break\n default:\n // Unrecoverable error\n this.emitError(5, `HLS fatal error: ${data.details}`, data)\n this.hls?.destroy()\n break\n }\n }\n })\n }\n\n private updateQualities(levels: HlsLevel[]): void {\n this._qualities = levels.map((level, index) => ({\n id: String(index),\n width: level.width,\n height: level.height,\n bitrate: level.bitrate,\n codec: level.codecSet || level.videoCodec || undefined,\n selected: index === this.currentQualityIndex,\n }))\n\n this.events.emit('statechange', { qualities: this._qualities })\n }\n\n private updateQualitySelection(): void {\n this._qualities = this._qualities.map((q, i) => ({\n ...q,\n selected: i === this.currentQualityIndex,\n }))\n }\n\n private updateAudioTracks(): void {\n if (!this.hls) return\n\n this._audioTracks = this.hls.audioTracks.map((track, index) => ({\n id: String(track.id),\n label: track.name || `Track ${index + 1}`,\n language: track.lang || '',\n kind: track.default ? 'main' : 'alternative',\n selected: index === this.hls!.audioTrack,\n }))\n\n this.events.emit('statechange', { audioTracks: this._audioTracks })\n }\n\n private updateTextTracks(): void {\n if (!this.hls) return\n\n this._textTracks = this.hls.subtitleTracks.map((track, index) => ({\n id: String(track.id),\n kind: 'subtitles' as const,\n label: track.name || `Subtitle ${index + 1}`,\n language: track.lang || '',\n mode: (index === this.hls!.subtitleTrack ? 'showing' : 'disabled') as TextTrack['mode'],\n cues: [],\n }))\n\n this.events.emit('statechange', { textTracks: this._textTracks })\n }\n\n // === Playback ===\n\n async play(): Promise<void> {\n if (!this.video) return\n try {\n await this.video.play()\n } catch (error) {\n if ((error as Error).name === 'NotAllowedError') {\n this.emitError(1, 'Playback was blocked. User interaction required.', error)\n } else {\n throw error\n }\n }\n }\n\n pause(): void {\n this.video?.pause()\n }\n\n // === Seeking ===\n\n seekTo(time: number): void {\n if (this.video) {\n this.video.currentTime = Math.max(0, Math.min(time, this.getDuration()))\n }\n }\n\n getCurrentTime(): number {\n return this.video?.currentTime || 0\n }\n\n getDuration(): number {\n return this.video?.duration || 0\n }\n\n /**\n * Sync to live edge (for live streams)\n */\n syncToLive(): void {\n if (this.hls?.liveSyncPosition != null) {\n this.seekTo(this.hls.liveSyncPosition)\n }\n }\n\n // === Volume ===\n\n setVolume(volume: number): void {\n if (this.video) {\n this.video.volume = Math.max(0, Math.min(1, volume))\n }\n }\n\n getVolume(): number {\n return this.video?.volume || 1\n }\n\n setMuted(muted: boolean): void {\n if (this.video) {\n this.video.muted = muted\n }\n }\n\n getMuted(): boolean {\n return this.video?.muted || false\n }\n\n // === Playback Rate ===\n\n setPlaybackRate(rate: number): void {\n if (this.video) {\n this.video.playbackRate = Math.max(0.25, Math.min(4, rate))\n }\n }\n\n getPlaybackRate(): number {\n return this.video?.playbackRate || 1\n }\n\n // === Quality ===\n\n getQualities(): VideoQuality[] {\n return this._qualities\n }\n\n setQuality(quality: VideoQuality | 'auto'): void {\n if (!this.hls) return\n\n if (quality === 'auto') {\n this.hls.currentLevel = -1 // Auto\n this.currentQualityIndex = -1\n this.events.emit('statechange', { autoQuality: true })\n } else {\n const index = parseInt(quality.id, 10)\n if (!isNaN(index) && index >= 0 && index < this._qualities.length) {\n this.hls.currentLevel = index\n this.currentQualityIndex = index\n this.events.emit('statechange', { autoQuality: false })\n }\n }\n }\n\n // === Audio Tracks ===\n\n getAudioTracks(): AudioTrack[] {\n return this._audioTracks\n }\n\n setAudioTrack(trackId: string): void {\n if (!this.hls) return\n\n const index = parseInt(trackId, 10)\n if (!isNaN(index) && index >= 0 && index < this.hls.audioTracks.length) {\n this.hls.audioTrack = index\n }\n }\n\n // === Text Tracks ===\n\n getTextTracks(): TextTrack[] {\n return this._textTracks\n }\n\n setTextTrackMode(trackId: string, mode: 'disabled' | 'hidden' | 'showing'): void {\n if (!this.hls) return\n\n if (mode === 'showing') {\n const index = parseInt(trackId, 10)\n if (!isNaN(index)) {\n this.hls.subtitleTrack = index\n }\n } else {\n this.hls.subtitleTrack = -1\n }\n\n this.updateTextTracks()\n const current = this._textTracks.find((t) => t.mode === 'showing')\n this.events.emit('texttrackchange', current || null)\n }\n\n}\n\n/**\n * HLS Provider Loader\n */\nexport const hlsLoader = {\n name: 'hls',\n type: 'hls' as ProviderType,\n\n canPlay(src: Src): boolean {\n return isHLSSource(src)\n },\n\n mediaType(): 'video' {\n return 'video'\n },\n\n preconnect(): string[] {\n return ['https://cdn.jsdelivr.net']\n },\n\n async load(container: HTMLElement, options: PlayerOptions): Promise<HLSProvider> {\n const provider = new HLSProvider()\n await provider.setup(container, options)\n return provider\n },\n}\n",
|
|
15
|
+
"/**\n * DASH Provider\n *\n * Media provider for MPEG-DASH streaming using dash.js.\n *\n * @module providers/dash\n */\n\nimport { BaseProvider } from './base'\nimport type {\n Src,\n ProviderLoader,\n VideoQuality,\n AudioTrack,\n TextTrack,\n PlayerOptions,\n DRMConfig,\n} from '../types'\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface DASHSource {\n src: string\n type: 'dash'\n /** DRM configuration */\n drm?: DRMConfig\n}\n\nexport interface DASHProviderConfig {\n /** Custom dash.js settings */\n dashConfig?: Record<string, any>\n /** Enable low latency mode */\n lowLatency?: boolean\n /** Retry parameters */\n retry?: {\n maxAttempts: number\n delay: number\n }\n}\n\n// =============================================================================\n// DASH.js Loading\n// =============================================================================\n\nlet dashjs: any = null\nlet dashLoading: Promise<any> | null = null\n\nconst DASH_CDN_DEFAULT = 'https://cdn.dashjs.org/latest/dash.all.min.js'\n\n/**\n * Load dash.js from CDN\n */\nasync function loadDashJS(cdnUrl?: string): Promise<any> {\n if (dashjs) return dashjs\n\n if (dashLoading) return dashLoading\n\n dashLoading = new Promise((resolve, reject) => {\n // Check if already loaded globally\n if (typeof window !== 'undefined' && (window as any).dashjs) {\n dashjs = (window as any).dashjs\n resolve(dashjs)\n return\n }\n\n const script = document.createElement('script')\n script.src = cdnUrl || DASH_CDN_DEFAULT\n script.async = true\n\n script.onload = () => {\n dashjs = (window as any).dashjs\n resolve(dashjs)\n }\n\n script.onerror = () => {\n reject(new Error('Failed to load dash.js'))\n }\n\n document.head.appendChild(script)\n })\n\n return dashLoading\n}\n\n// =============================================================================\n// Source Detection\n// =============================================================================\n\n/**\n * Check if source is a DASH manifest\n */\nexport function isDASHSource(src: Src): src is DASHSource | string {\n if (typeof src === 'object' && 'type' in src && src.type === 'dash') {\n return true\n }\n\n if (typeof src === 'string') {\n return src.includes('.mpd') || src.includes('manifest(format=mpd')\n }\n\n return false\n}\n\n// =============================================================================\n// DASH Provider\n// =============================================================================\n\nexport class DASHProvider extends BaseProvider {\n readonly name = 'dash'\n readonly type = 'dash' as const\n\n private dash: any = null\n private video: HTMLVideoElement | null = null\n private source: DASHSource | null = null\n private config: DASHProviderConfig = {}\n private _volume = 1\n private _muted = false\n private _playbackRate = 1\n\n get mediaElement(): HTMLVideoElement | null {\n return this.video\n }\n\n /**\n * Initialize the provider\n */\n protected async initialize(): Promise<void> {\n // Create video element\n this.video = document.createElement('video')\n this.video.style.width = '100%'\n this.video.style.height = '100%'\n this.video.style.display = 'block'\n\n // Load dash.js\n await loadDashJS(this.options.dashCdnUrl)\n\n // Create player instance\n this.dash = dashjs.MediaPlayer().create()\n\n // Apply custom config from options\n if (this.options.dashConfig) {\n this.config.dashConfig = this.options.dashConfig as Record<string, any>\n }\n\n if (this.config.dashConfig) {\n Object.entries(this.config.dashConfig).forEach(([key, value]) => {\n this.dash.updateSettings({ [key]: value })\n })\n }\n\n // Enable low latency mode\n if (this.config.lowLatency) {\n this.dash.updateSettings({\n streaming: {\n lowLatencyEnabled: true,\n liveDelay: 3,\n liveCatchUpMinDrift: 0.05,\n liveCatchUpPlaybackRate: 0.5,\n },\n })\n }\n\n // Attach dash.js events\n this.attachDashEvents()\n\n // Append to container\n if (this.container) {\n this.container.appendChild(this.video)\n }\n\n // Add video element events\n this.attachVideoEvents()\n }\n\n /**\n * Attach video element event listeners\n */\n private attachVideoEvents(): void {\n if (!this.video) return\n\n this.video.addEventListener('play', () => this.events.emit('play'))\n this.video.addEventListener('pause', () => this.events.emit('pause'))\n this.video.addEventListener('ended', () => this.events.emit('ended'))\n this.video.addEventListener('timeupdate', () => this.events.emit('timeupdate', this.getCurrentTime()))\n this.video.addEventListener('durationchange', () => this.events.emit('durationchange', this.getDuration()))\n this.video.addEventListener('volumechange', () => {\n this.events.emit('volumechange', this.getVolume(), this.getMuted())\n })\n this.video.addEventListener('waiting', () => this.events.emit('waiting'))\n this.video.addEventListener('playing', () => this.events.emit('playing'))\n this.video.addEventListener('seeking', () => this.events.emit('seeking'))\n this.video.addEventListener('seeked', () => this.events.emit('seeked'))\n this.video.addEventListener('progress', () => {\n const ranges: Array<{ start: number, end: number }> = []\n for (let i = 0; i < this.video!.buffered.length; i++) {\n ranges.push({\n start: this.video!.buffered.start(i),\n end: this.video!.buffered.end(i),\n })\n }\n this.events.emit('progress', ranges)\n })\n this.video.addEventListener('loadedmetadata', () => {\n this.events.emit('loadedmetadata')\n const fullscreenAvailability = this.getFeatureAvailability('fullscreen')\n const pipAvailability = this.getFeatureAvailability('pip')\n const volumeAvailability = this.getFeatureAvailability('volume')\n this.events.emit('statechange', {\n videoWidth: this.video!.videoWidth,\n videoHeight: this.video!.videoHeight,\n aspectRatio: (this.video!.videoHeight > 0 ? this.video!.videoWidth / this.video!.videoHeight : 0) || 16 / 9,\n canFullscreen: fullscreenAvailability === 'available',\n canPictureInPicture: pipAvailability === 'available',\n fullscreenAvailability,\n pipAvailability,\n volumeAvailability,\n })\n this.events.emit('availabilitychange', 'fullscreen', fullscreenAvailability)\n this.events.emit('availabilitychange', 'pip', pipAvailability)\n this.events.emit('availabilitychange', 'volume', volumeAvailability)\n })\n this.video.addEventListener('loadeddata', () => this.events.emit('loadeddata'))\n this.video.addEventListener('canplay', () => this.events.emit('canplay'))\n this.video.addEventListener('canplaythrough', () => this.events.emit('canplaythrough'))\n this.video.addEventListener('ratechange', () => this.events.emit('ratechange', this.getPlaybackRate()))\n }\n\n /**\n * Attach dash.js event listeners\n */\n private attachDashEvents(): void {\n if (!dashjs) return\n\n const MediaPlayer = dashjs.MediaPlayer\n\n // Quality change\n this.dash.on(MediaPlayer.events.QUALITY_CHANGE_RENDERED, () => {\n this.events.emit('qualitychange', this.getQualities().find(q => q.selected) || null)\n })\n\n // Manifest loaded\n this.dash.on(MediaPlayer.events.MANIFEST_LOADED, () => {\n // Qualities and tracks are now available\n })\n\n // Error handling\n this.dash.on(MediaPlayer.events.ERROR, (event: any) => {\n this.emitError(\n event.error?.code || 0,\n event.error?.message || 'DASH playback error',\n event.error\n )\n })\n }\n\n /**\n * Configure DRM protection\n */\n private configureDRM(drm: DRMConfig): void {\n const protectionData: Record<string, any> = {}\n\n if (drm.widevine) {\n protectionData['com.widevine.alpha'] = {\n serverURL: drm.widevine.url,\n httpRequestHeaders: drm.widevine.headers,\n }\n }\n\n if (drm.playready) {\n protectionData['com.microsoft.playready'] = {\n serverURL: drm.playready.url,\n httpRequestHeaders: drm.playready.headers,\n }\n }\n\n if (drm.fairplay) {\n protectionData['com.apple.fps.1_0'] = {\n serverURL: drm.fairplay.url,\n serverCertificateURL: drm.fairplay.certificateUrl,\n httpRequestHeaders: drm.fairplay.headers,\n }\n }\n\n this.dash.setProtectionData(protectionData)\n }\n\n /**\n * Load a source\n */\n async load(src: Src): Promise<void> {\n if (!this.video || !this.dash) return\n\n // Parse source\n if (typeof src === 'string') {\n this.source = { src, type: 'dash' }\n }\n else if (isDASHSource(src)) {\n this.source = src as DASHSource\n }\n else {\n this.emitError(4, 'Invalid DASH source')\n return\n }\n\n // Configure DRM if provided\n if (this.source.drm) {\n this.configureDRM(this.source.drm)\n }\n\n // Initialize with video element and source\n this.dash.initialize(this.video, this.source.src, false)\n }\n\n /**\n * Check if source can be played\n */\n canPlay(src: Src): boolean {\n return isDASHSource(src)\n }\n\n /**\n * Cleanup the provider\n */\n protected cleanup(): void {\n if (this.dash) {\n this.dash.reset()\n this.dash = null\n }\n\n if (this.video) {\n this.video.remove()\n this.video = null\n }\n\n this.source = null\n }\n\n // ==========================================================================\n // Playback\n // ==========================================================================\n\n async play(): Promise<void> {\n if (!this.video) return\n try {\n await this.video.play()\n } catch (error) {\n if ((error as Error).name === 'NotAllowedError') {\n this.emitError(1, 'Playback was blocked. User interaction required.', error)\n } else {\n throw error\n }\n }\n }\n\n pause(): void {\n if (this.video) {\n this.video.pause()\n }\n }\n\n // ==========================================================================\n // Seeking\n // ==========================================================================\n\n seekTo(time: number): void {\n if (this.video) {\n this.video.currentTime = time\n }\n }\n\n getCurrentTime(): number {\n return this.video?.currentTime || 0\n }\n\n getDuration(): number {\n return this.video?.duration || 0\n }\n\n // ==========================================================================\n // Volume\n // ==========================================================================\n\n setVolume(volume: number): void {\n this._volume = Math.max(0, Math.min(1, volume))\n if (this.video) {\n this.video.volume = this._volume\n }\n }\n\n getVolume(): number {\n return this.video?.volume ?? this._volume\n }\n\n setMuted(muted: boolean): void {\n this._muted = muted\n if (this.video) {\n this.video.muted = muted\n }\n }\n\n getMuted(): boolean {\n return this.video?.muted ?? this._muted\n }\n\n // ==========================================================================\n // Playback Rate\n // ==========================================================================\n\n setPlaybackRate(rate: number): void {\n this._playbackRate = rate\n if (this.video) {\n this.video.playbackRate = rate\n }\n }\n\n getPlaybackRate(): number {\n return this.video?.playbackRate ?? this._playbackRate\n }\n\n // ==========================================================================\n // Live\n // ==========================================================================\n\n get isLive(): boolean {\n return this.dash?.isDynamic() ?? false\n }\n\n get liveLatency(): number {\n return this.dash?.getCurrentLiveLatency() ?? 0\n }\n\n get canLiveSync(): boolean {\n return this.isLive\n }\n\n syncToLive(): void {\n if (this.dash && this.isLive) {\n this.dash.seekToOriginalLive()\n }\n }\n\n // ==========================================================================\n // Quality\n // ==========================================================================\n\n getQualities(): VideoQuality[] {\n if (!this.dash) return []\n\n const bitrateInfoList = this.dash.getBitrateInfoListFor('video')\n if (!bitrateInfoList) return []\n\n const currentIndex = this.dash.getQualityFor('video')\n\n return bitrateInfoList.map((info: any, index: number) => ({\n id: `${index}`,\n label: `${info.height}p`,\n width: info.width,\n height: info.height,\n bitrate: info.bitrate,\n selected: index === currentIndex,\n }))\n }\n\n setQuality(quality: VideoQuality | 'auto'): void {\n if (!this.dash) return\n\n if (quality === 'auto') {\n this.dash.updateSettings({\n streaming: { abr: { autoSwitchBitrate: { video: true } } },\n })\n }\n else {\n const index = Number.parseInt(quality.id, 10)\n if (!Number.isNaN(index)) {\n this.dash.updateSettings({\n streaming: { abr: { autoSwitchBitrate: { video: false } } },\n })\n this.dash.setQualityFor('video', index)\n }\n }\n }\n\n get isAutoQuality(): boolean {\n if (!this.dash) return true\n const settings = this.dash.getSettings()\n return settings?.streaming?.abr?.autoSwitchBitrate?.video ?? true\n }\n\n // ==========================================================================\n // Audio Tracks\n // ==========================================================================\n\n getAudioTracks(): AudioTrack[] {\n if (!this.dash) return []\n\n const tracks = this.dash.getTracksFor('audio')\n if (!tracks) return []\n\n const currentTrack = this.dash.getCurrentTrackFor('audio')\n\n return tracks.map((track: any, index: number) => ({\n id: track.id || `${index}`,\n label: track.labels?.[0]?.text || track.lang || `Track ${index + 1}`,\n language: track.lang || '',\n kind: 'main' as const,\n selected: track === currentTrack,\n }))\n }\n\n setAudioTrack(trackId: string): void {\n if (!this.dash) return\n\n const tracks = this.dash.getTracksFor('audio')\n const track = tracks.find((t: any, i: number) => (t.id || `${i}`) === trackId)\n\n if (track) {\n this.dash.setCurrentTrack(track)\n }\n }\n\n // ==========================================================================\n // Text Tracks\n // ==========================================================================\n\n getTextTracks(): TextTrack[] {\n if (!this.dash) return []\n\n const tracks = this.dash.getTracksFor('text')\n if (!tracks) return []\n\n const currentTrack = this.dash.getCurrentTrackFor('text')\n\n return tracks.map((track: any, index: number) => ({\n id: track.id || `${index}`,\n label: track.labels?.[0]?.text || track.lang || `Subtitle ${index + 1}`,\n language: track.lang || '',\n kind: (track.kind || 'subtitles') as TextTrack['kind'],\n mode: (track === currentTrack ? 'showing' : 'disabled') as TextTrack['mode'],\n cues: [],\n }))\n }\n\n setTextTrackMode(trackId: string, mode: 'disabled' | 'hidden' | 'showing'): void {\n if (!this.dash) return\n\n if (mode === 'disabled') {\n this.dash.enableText(false)\n return\n }\n\n const tracks = this.dash.getTracksFor('text')\n const track = tracks.find((t: any, i: number) => (t.id || `${i}`) === trackId)\n\n if (track) {\n this.dash.setCurrentTrack(track)\n this.dash.enableText(mode === 'showing')\n }\n }\n}\n\n// =============================================================================\n// Provider Loader\n// =============================================================================\n\nexport const dashLoader: ProviderLoader = {\n name: 'dash',\n type: 'dash',\n\n canPlay(src: Src): boolean {\n return isDASHSource(src)\n },\n\n mediaType(): 'video' {\n return 'video'\n },\n\n async load(container: HTMLElement, options: PlayerOptions): Promise<DASHProvider> {\n const provider = new DASHProvider()\n await provider.setup(container, options)\n return provider\n },\n\n preconnect(): string[] {\n return ['https://cdn.dashjs.org']\n },\n}\n",
|
|
16
|
+
"/**\n * ts-video-player Providers\n *\n * Export all media providers.\n *\n * @module providers\n */\n\n// Base\nexport { BaseProvider } from './base'\n\n// HTML5 Video/Audio\nexport { HTML5Provider, html5Loader, isHTML5Source } from './html5'\n\n// YouTube\nexport { YouTubeProvider, youtubeLoader, isYouTubeSource, extractYouTubeId } from './youtube'\n\n// Vimeo\nexport { VimeoProvider, vimeoLoader, isVimeoSource, extractVimeoId, extractVimeoHash } from './vimeo'\n\n// HLS\nexport { HLSProvider, hlsLoader, isHLSSource, isNativeHLSSupported } from './hls'\n\n// DASH\nexport { DASHProvider, dashLoader, isDASHSource } from './dash'\n\nexport type { DASHSource, DASHProviderConfig } from './dash'\nexport type { DRMConfig } from '../types'\n\n// Re-export types\nexport type { Provider, ProviderLoader, ProviderType } from '../types'\n\n// =============================================================================\n// Provider Registry\n// =============================================================================\n\nimport type { ProviderLoader, Src } from '../types'\nimport { html5Loader } from './html5'\nimport { youtubeLoader } from './youtube'\nimport { vimeoLoader } from './vimeo'\nimport { hlsLoader } from './hls'\nimport { dashLoader } from './dash'\n\n/**\n * Default provider loaders in priority order\n */\nexport const defaultLoaders: ProviderLoader[] = [\n hlsLoader, // Check HLS first\n dashLoader, // Then DASH\n youtubeLoader, // Then YouTube\n vimeoLoader, // Then Vimeo\n html5Loader, // HTML5 as fallback\n]\n\n/**\n * Find the appropriate provider loader for a source\n */\nexport function findLoader(src: Src, loaders: ProviderLoader[] = defaultLoaders): ProviderLoader | null {\n for (const loader of loaders) {\n if (loader.canPlay(src)) {\n return loader\n }\n }\n return null\n}\n\nexport interface SourceCandidate {\n src: Src\n index: number\n loader: ProviderLoader\n}\n\n/** Resolve every playable source without discarding ordered fallbacks. */\nexport function findSourceCandidates(sources: readonly Src[], loaders: ProviderLoader[] = defaultLoaders): SourceCandidate[] {\n const candidates: SourceCandidate[] = []\n sources.forEach((src, index) => {\n const loader = findLoader(src, loaders)\n if (loader) candidates.push({ src, index, loader })\n })\n return candidates\n}\n\n/**\n * Detect media type from source\n */\nexport function detectMediaType(src: Src, loaders: ProviderLoader[] = defaultLoaders): 'video' | 'audio' | 'unknown' {\n const loader = findLoader(src, loaders)\n return loader?.mediaType(src) || 'unknown'\n}\n\n/**\n * Get preconnect hints for a source\n */\nexport function getPreconnectHints(src: Src, loaders: ProviderLoader[] = defaultLoaders): string[] {\n const loader = findLoader(src, loaders)\n return loader?.preconnect?.() || []\n}\n",
|
|
17
|
+
"/**\n * ts-video-player Player\n *\n * Main player class that orchestrates providers, state, and UI.\n *\n * @module player\n */\n\nimport type {\n Player as IPlayer,\n PlayerOptions,\n PlayerState,\n PlayerEventMap,\n Provider,\n ProviderLoader,\n Src,\n VideoQuality,\n} from './types'\nimport { StateStore, createStorageSync, selectBufferedAmount } from './core/state'\nimport { EventEmitter, createKeyboardHandler, createActivityDetector, type KeyboardActions } from './core/events'\nimport { createMediaSession, type MediaSessionOptions } from './core/media-session'\nimport { lockOrientation, unlockOrientation } from './core/orientation'\nimport { defaultLoaders, findSourceCandidates } from './providers'\n\n// =============================================================================\n// Player Class\n// =============================================================================\n\n/**\n * ts-video-player main class\n */\nexport class Player implements IPlayer {\n private _el: HTMLElement\n private _options: PlayerOptions\n private _store: StateStore\n private _events = new EventEmitter<PlayerEventMap>()\n private _provider: Provider | null = null\n private _loaders: ProviderLoader[]\n private _cleanupFns: Array<() => void> = []\n private _destroyed = false\n private _sourceLoadId = 0\n private _announcer: HTMLElement | null = null\n\n /**\n * Create a new player instance\n */\n constructor(container: HTMLElement | string, options: PlayerOptions = {}) {\n // Resolve container\n if (typeof container === 'string') {\n const el = document.querySelector(container) as HTMLElement\n if (!el) throw new Error(`Container element not found: ${container}`)\n this._el = el\n // eslint-disable-next-line style/brace-style\n } else {\n this._el = container\n }\n\n this._options = { ...defaultOptions, ...options }\n this._loaders = defaultLoaders\n this._store = new StateStore()\n\n // Setup\n this.setupElement()\n this.setupState()\n this.setupKeyboard()\n this.setupActivity()\n this.setupStorage()\n this.setupMediaSession()\n this.setupEvents()\n\n // Load initial source\n if (this._options.src) {\n this.setSrc(this._options.src)\n }\n\n // Emit init event\n this._events.emit('init')\n }\n\n // === Public Properties ===\n\n get el(): HTMLElement {\n return this._el\n }\n\n get state(): PlayerState {\n return this._store.getState()\n }\n\n get provider(): Provider | null {\n return this._provider\n }\n\n get ready(): boolean {\n return this._provider?.ready || false\n }\n\n // === Setup ===\n\n private setupElement(): void {\n this._el.classList.add('ts-video-player')\n this._el.setAttribute('tabindex', '0')\n this._el.setAttribute('role', 'region')\n this._el.setAttribute('aria-roledescription', 'video player')\n this._el.setAttribute('aria-label', this._options.title || 'Video player')\n this._el.setAttribute('aria-keyshortcuts', 'Space K M F I C ArrowLeft ArrowRight J L ArrowUp ArrowDown Home End 0 1 2 3 4 5 6 7 8 9')\n\n // Create inner structure\n const mediaContainer = document.createElement('div')\n mediaContainer.className = 'ts-video-player__container'\n this._el.appendChild(mediaContainer)\n\n // Create ARIA live region for screen reader announcements\n if (this._options.announcements !== false) {\n const liveRegion = document.createElement('div')\n liveRegion.className = 'ts-video-player__announcer'\n liveRegion.setAttribute('role', 'status')\n liveRegion.setAttribute('aria-live', 'polite')\n liveRegion.setAttribute('aria-atomic', 'true')\n // Visually hidden but accessible to screen readers\n liveRegion.style.cssText = 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0'\n this._el.appendChild(liveRegion)\n this._announcer = liveRegion\n }\n\n // Store reference\n const _el = this._el as any\n _el.__videoPlayer = this\n }\n\n private setupState(): void {\n // Initial state from options\n this._store.batch({\n volume: this._options.volume ?? 1,\n muted: this._options.muted ?? false,\n loop: this._options.loop ?? false,\n autoplay: this._options.autoplay ?? false,\n playsinline: this._options.playsinline ?? true,\n playbackRate: this._options.playbackRate ?? 1,\n poster: this._options.poster || '',\n title: this._options.title || '',\n preload: this._options.preload || 'metadata',\n })\n\n // Subscribe to state changes\n const unsubscribe = this._store.subscribe((state) => {\n // Update DOM attributes\n this.updateAttributes(state)\n })\n\n this._cleanupFns.push(unsubscribe)\n }\n\n private setupKeyboard(): void {\n if (this._options.keyboard === false) return\n\n const actions: KeyboardActions = {\n togglePlay: () => this.togglePlay(),\n toggleMute: () => this.toggleMute(),\n toggleFullscreen: () => this.toggleFullscreen(),\n togglePiP: () => this.togglePiP(),\n toggleCaptions: () => this.toggleCaptions(),\n seekBackward: () => this.seekBy(-5),\n seekForward: () => this.seekBy(5),\n volumeUp: () => this.setVolume(Math.min(1, this.state.volume + 0.1)),\n volumeDown: () => this.setVolume(Math.max(0, this.state.volume - 0.1)),\n speedUp: () => this.setPlaybackRate(Math.min(4, this.state.playbackRate + 0.25)),\n speedDown: () => this.setPlaybackRate(Math.max(0.25, this.state.playbackRate - 0.25)),\n seekStart: () => this.seekTo(0),\n seekEnd: () => this.seekTo(this.state.duration),\n seekPercent: (percent) => this.seekTo((percent / 100) * this.state.duration),\n }\n\n const config = typeof this._options.keyboard === 'object' ? this._options.keyboard : {}\n const handler = createKeyboardHandler(actions, {\n seekStep: config.seekStep,\n volumeStep: config.volumeStep,\n })\n\n const target = config.global ? document : this._el\n target.addEventListener('keydown', handler as EventListener)\n\n this._cleanupFns.push(() => target.removeEventListener('keydown', handler as EventListener))\n }\n\n private setupActivity(): void {\n const cleanup = createActivityDetector(this._el, {\n timeout: this._options.controlsTimeout || 3000,\n onActive: () => {\n this._store.batch({ userActive: true, controlsVisible: true })\n this._events.emit('useractivitychange', true)\n this._events.emit('controlschange', true)\n },\n onInactive: () => {\n // Only hide controls if playing\n if (this.state.playing) {\n this._store.batch({ userActive: false, controlsVisible: false })\n this._events.emit('useractivitychange', false)\n this._events.emit('controlschange', false)\n }\n },\n })\n\n this._cleanupFns.push(cleanup)\n\n // Pointer over tracking\n const onMouseEnter = () => this._store.set('pointerOver', true)\n const onMouseLeave = () => this._store.set('pointerOver', false)\n this._el.addEventListener('mouseenter', onMouseEnter)\n this._el.addEventListener('mouseleave', onMouseLeave)\n this._cleanupFns.push(\n () => this._el.removeEventListener('mouseenter', onMouseEnter),\n () => this._el.removeEventListener('mouseleave', onMouseLeave),\n )\n }\n\n private setupStorage(): void {\n if (!this._options.storage) return\n\n const config = typeof this._options.storage === 'object' ? this._options.storage : {}\n const storage = config.type === 'session' ? sessionStorage : localStorage\n\n const cleanup = createStorageSync(this._store, config.persist || ['volume', 'muted', 'playbackRate'], storage)\n\n this._cleanupFns.push(cleanup)\n }\n\n private setupMediaSession(): void {\n if (this._options.mediaSession === false) return\n\n const sessionOpts: MediaSessionOptions | undefined =\n typeof this._options.mediaSession === 'object'\n ? this._options.mediaSession\n : this._options.title\n ? { title: this._options.title }\n : undefined\n\n const cleanup = createMediaSession(\n this._store,\n {\n play: () => this.play(),\n pause: () => this.pause(),\n seekTo: (time) => this.seekTo(time),\n seekBy: (offset) => this.seekBy(offset),\n stop: () => this.stop(),\n },\n sessionOpts,\n )\n\n this._cleanupFns.push(cleanup)\n }\n\n private setupEvents(): void {\n // Attach option event listeners\n if (this._options.on) {\n for (const [event, handler] of Object.entries(this._options.on)) {\n if (handler) {\n this.on(event as keyof PlayerEventMap, handler as PlayerEventMap[keyof PlayerEventMap])\n }\n }\n }\n }\n\n private updateAttributes(state: PlayerState): void {\n const el = this._el\n\n // Playback state\n el.dataset.paused = String(state.paused)\n el.dataset.playing = String(state.playing)\n el.dataset.started = String(state.started)\n el.dataset.ended = String(state.ended)\n el.dataset.seeking = String(state.seeking)\n el.dataset.waiting = String(state.waiting)\n el.dataset.loop = String(state.loop)\n el.dataset.autoplay = String(state.autoplay)\n\n // Loading state\n el.dataset.loading = String(state.waiting || state.loadingState === 'loading')\n el.dataset.canPlay = String(state.canPlay)\n el.dataset.loadingState = state.loadingState\n el.setAttribute('aria-busy', String(state.waiting || state.loadingState === 'loading'))\n\n // Volume\n el.dataset.muted = String(state.muted)\n el.dataset.volumeLevel = state.muted || state.volume === 0\n ? 'silent'\n : state.volume < 0.5\n ? 'low'\n : 'high'\n\n // Fullscreen / PiP\n el.dataset.fullscreen = String(state.fullscreen)\n el.dataset.pip = String(state.pictureInPicture)\n\n // UI\n el.dataset.controlsVisible = String(state.controlsVisible)\n el.dataset.userActive = String(state.userActive)\n\n // Media type\n el.dataset.mediaType = state.mediaType\n el.dataset.streamType = state.streamType\n\n // Captions\n const hasActiveCaptions = state.textTracks.some((t) => t.mode === 'showing')\n el.dataset.captions = String(hasActiveCaptions)\n\n // Error\n el.dataset.error = String(!!state.error)\n }\n\n /**\n * Announce a message to screen readers via the ARIA live region\n */\n private announce(message: string): void {\n if (!this._announcer) return\n // Clear and set to force re-announcement of same message\n this._announcer.textContent = ''\n requestAnimationFrame(() => {\n if (this._announcer) this._announcer.textContent = message\n })\n }\n\n // === Lifecycle ===\n\n destroy(): void {\n if (this._destroyed) return\n this._destroyed = true\n this._sourceLoadId++\n\n // Emit destroy event\n this._events.emit('destroy')\n\n // Cleanup\n this._cleanupFns.forEach((fn) => fn())\n this._cleanupFns = []\n\n // Destroy provider\n this._provider?.destroy()\n this._provider = null\n\n // Remove events\n this._events.removeAllListeners()\n\n // Reset store\n this._store.reset()\n\n // Clear element\n this._el.classList.remove('ts-video-player')\n this._el.innerHTML = ''\n delete (this._el as any).__videoPlayer\n }\n\n // === Source ===\n\n async setSrc(src: Src | Src[]): Promise<void> {\n if (this._destroyed) return\n\n // Invalidate any older asynchronous provider load.\n const loadId = ++this._sourceLoadId\n const sources = Array.isArray(src) ? src : [src]\n if (sources.length === 0) {\n return\n }\n\n // Update state\n this._store.batch({\n sources,\n src: sources[0],\n loadingState: 'loading',\n error: null,\n })\n\n this._events.emit('sourceschange', sources)\n\n const candidates = findSourceCandidates(sources, this._loaders)\n if (candidates.length === 0) {\n this._store.batch({\n loadingState: 'error',\n error: { code: 4, message: 'No compatible provider found for source' },\n })\n return\n }\n\n let lastError: unknown\n for (const candidate of candidates) {\n if (this._destroyed || loadId !== this._sourceLoadId) return\n const { loader } = candidate\n\n if (this._provider && this._provider.type !== loader.type) {\n this._provider.destroy()\n this._provider = null\n this._events.emit('providerchange', null)\n }\n\n if (!this._provider) {\n const mediaContainer = this._el.querySelector('.ts-video-player__container') as HTMLElement\n try {\n this._provider = await loader.load(mediaContainer, {\n ...this._options,\n mediaType: loader.mediaType(candidate.src),\n })\n if (this._destroyed || loadId !== this._sourceLoadId) {\n this._provider.destroy()\n this._provider = null\n return\n }\n this._events.emit('providerchange', this._provider)\n this.attachProviderEvents(this._provider)\n }\n catch (error) {\n lastError = error\n this._provider = null\n continue\n }\n }\n\n try {\n // Native media elements should receive all same-provider sources so\n // the browser can choose by MIME and codec support.\n const compatibleSources = loader.type === 'video'\n ? candidates.filter(item => item.loader.type === loader.type).map(item => item.src)\n : [candidate.src]\n await this._provider.load(compatibleSources.length > 1 ? compatibleSources : candidate.src)\n if (this._destroyed || loadId !== this._sourceLoadId) return\n this._store.batch({\n src: candidate.src,\n currentSourceIndex: candidate.index,\n mediaType: loader.mediaType(candidate.src),\n providerType: loader.type,\n })\n return\n }\n catch (error) {\n lastError = error\n if (this._provider) {\n this._provider.destroy()\n this._provider = null\n }\n this._events.emit('providerchange', null)\n }\n }\n\n if (this._destroyed || loadId !== this._sourceLoadId) return\n this._store.batch({\n loadingState: 'error',\n error: { code: 4, message: 'Failed to load all compatible sources', details: lastError },\n })\n }\n\n getSrc(): Src | null {\n return this.state.src\n }\n\n private attachProviderEvents(provider: Provider): void {\n // Forward provider events and update state\n provider.on('loadstart', () => {\n this._store.set('loadingState', 'loading')\n this._events.emit('loadstart')\n })\n\n provider.on('loadedmetadata', () => {\n this._events.emit('loadedmetadata')\n })\n\n provider.on('loadeddata', () => {\n this._events.emit('loadeddata')\n })\n\n provider.on('canplay', () => {\n this._store.batch({ canPlay: true, loadingState: 'loaded' })\n this._events.emit('canplay')\n })\n\n provider.on('canplaythrough', () => {\n this._store.set('canPlayThrough', true)\n this._events.emit('canplaythrough')\n })\n\n provider.on('play', () => {\n this._store.batch({ paused: false, ended: false })\n this._events.emit('play')\n this.announce('Playing')\n })\n\n provider.on('pause', () => {\n this._store.set('paused', true)\n this._events.emit('pause')\n this.announce('Paused')\n })\n\n provider.on('playing', () => {\n this._store.batch({ playing: true, started: true, waiting: false, playbackState: 'playing' })\n this._events.emit('playing')\n })\n\n provider.on('waiting', () => {\n this._store.batch({ waiting: true, playbackState: 'buffering' })\n this._events.emit('waiting')\n })\n\n provider.on('seeking', () => {\n this._store.set('seeking', true)\n this._events.emit('seeking')\n })\n\n provider.on('seeked', () => {\n this._store.set('seeking', false)\n this._events.emit('seeked')\n })\n\n provider.on('timeupdate', (time) => {\n this._store.set('currentTime', time)\n this._events.emit('timeupdate', time)\n })\n\n provider.on('durationchange', (duration) => {\n this._store.set('duration', duration)\n this._events.emit('durationchange', duration)\n })\n\n provider.on('volumechange', (volume, muted) => {\n this._store.batch({ volume, muted })\n this._events.emit('volumechange', volume, muted)\n if (muted) {\n this.announce('Muted')\n // eslint-disable-next-line style/brace-style\n } else {\n this.announce(`Volume ${Math.round(volume * 100)}%`)\n }\n })\n\n provider.on('ratechange', (rate) => {\n this._store.set('playbackRate', rate)\n this._events.emit('ratechange', rate)\n })\n\n provider.on('progress', (buffered) => {\n this._store.batch({\n buffered,\n bufferedAmount: selectBufferedAmount({ ...this.state, buffered }),\n })\n this._events.emit('progress', buffered)\n })\n\n provider.on('ended', () => {\n this._store.batch({ ended: true, playing: false, playbackState: 'ended' })\n this._events.emit('ended')\n this.announce('Playback ended')\n\n // Handle loop\n if (this.state.loop) {\n this.seekTo(0)\n this.play()\n }\n })\n\n provider.on('error', (error) => {\n this._store.batch({ loadingState: 'error', error })\n this._events.emit('error', error)\n this.announce(`Error: ${error.message}`)\n })\n\n provider.on('statechange', (state) => {\n this._store.batch(state)\n this._events.emit('statechange', state)\n })\n\n provider.on('qualitychange', (quality) => {\n this._store.set('qualities', provider.getQualities())\n this._events.emit('qualitychange', quality)\n })\n\n provider.on('fullscreenchange', (fullscreen) => {\n this._store.set('fullscreen', fullscreen)\n this._events.emit('fullscreenchange', fullscreen)\n this.announce(fullscreen ? 'Entered fullscreen' : 'Exited fullscreen')\n })\n\n provider.on('pipchange', (pip) => {\n this._store.set('pictureInPicture', pip)\n this._events.emit('pipchange', pip)\n })\n\n provider.on('availabilitychange', (feature, availability) => {\n if (feature === 'volume') this._store.set('volumeAvailability', availability)\n else if (feature === 'fullscreen') this._store.set('fullscreenAvailability', availability)\n else if (feature === 'pip') this._store.set('pipAvailability', availability)\n this._events.emit('availabilitychange', feature, availability)\n })\n }\n\n // === Playback ===\n\n async play(): Promise<void> {\n if (!this._provider) return\n await this._provider.play()\n }\n\n pause(): void {\n this._provider?.pause()\n }\n\n togglePlay(): void {\n if (this.state.paused) {\n this.play().catch(() => {\n // Play rejection is already handled and emitted as error by provider\n })\n // eslint-disable-next-line style/brace-style\n } else {\n this.pause()\n }\n }\n\n stop(): void {\n this._provider?.stop()\n this._store.batch({ playing: false, paused: true, currentTime: 0, playbackState: 'idle' })\n }\n\n // === Seeking ===\n\n seekTo(time: number): void {\n this._provider?.seekTo(time)\n }\n\n seekBy(offset: number): void {\n const newTime = Math.max(0, Math.min(this.state.currentTime + offset, this.state.duration))\n this.seekTo(newTime)\n }\n\n // === Volume ===\n\n setVolume(volume: number): void {\n if (this.state.volumeAvailability === 'unsupported') return\n const clamped = Math.max(0, Math.min(1, volume))\n this._provider?.setVolume(clamped)\n this._store.set('volume', clamped)\n }\n\n setMuted(muted: boolean): void {\n this._provider?.setMuted(muted)\n this._store.set('muted', muted)\n }\n\n toggleMute(): void {\n this.setMuted(!this.state.muted)\n }\n\n // === Playback Rate ===\n\n setPlaybackRate(rate: number): void {\n const clamped = Math.max(0.25, Math.min(4, rate))\n this._provider?.setPlaybackRate(clamped)\n this._store.set('playbackRate', clamped)\n }\n\n // === Fullscreen ===\n\n async enterFullscreen(): Promise<void> {\n if (this.state.fullscreenAvailability === 'unsupported') return\n // Exit PiP before entering fullscreen (mutual exclusion)\n if (this.state.pictureInPicture) {\n await this.exitPiP()\n }\n await this._provider?.enterFullscreen()\n if (this._options.fullscreenOrientationLock !== false) {\n lockOrientation('landscape')\n }\n }\n\n async exitFullscreen(): Promise<void> {\n await this._provider?.exitFullscreen()\n if (this._options.fullscreenOrientationLock !== false) {\n unlockOrientation()\n }\n }\n\n async toggleFullscreen(): Promise<void> {\n if (this.state.fullscreen) {\n await this.exitFullscreen()\n // eslint-disable-next-line style/brace-style\n } else {\n await this.enterFullscreen()\n }\n }\n\n // === Picture-in-Picture ===\n\n async enterPiP(): Promise<void> {\n if (this.state.pipAvailability === 'unsupported') return\n // Exit fullscreen before entering PiP (mutual exclusion)\n if (this.state.fullscreen) {\n await this.exitFullscreen()\n }\n await this._provider?.enterPiP()\n }\n\n async exitPiP(): Promise<void> {\n await this._provider?.exitPiP()\n }\n\n async togglePiP(): Promise<void> {\n if (this.state.pictureInPicture) {\n await this.exitPiP()\n // eslint-disable-next-line style/brace-style\n } else {\n await this.enterPiP()\n }\n }\n\n // === Tracks ===\n\n setQuality(quality: VideoQuality | 'auto'): void {\n this._provider?.setQuality(quality)\n if (quality === 'auto') {\n this._store.set('autoQuality', true)\n // eslint-disable-next-line style/brace-style\n } else {\n this._store.set('autoQuality', false)\n }\n }\n\n setTextTrack(trackId: string, mode: 'disabled' | 'hidden' | 'showing'): void {\n this._provider?.setTextTrackMode(trackId, mode)\n this._store.set('textTracks', this._provider?.getTextTracks() || [])\n }\n\n setAudioTrack(trackId: string): void {\n this._provider?.setAudioTrack(trackId)\n this._store.set('audioTracks', this._provider?.getAudioTracks() || [])\n }\n\n private toggleCaptions(): void {\n const textTracks = this.state.textTracks\n const showing = textTracks.find((t) => t.mode === 'showing')\n\n if (showing) {\n this.setTextTrack(showing.id, 'disabled')\n // eslint-disable-next-line style/brace-style\n } else {\n const first = textTracks.find((t) => t.kind === 'subtitles' || t.kind === 'captions')\n if (first) {\n this.setTextTrack(first.id, 'showing')\n }\n }\n }\n\n // === Events ===\n\n on<K extends keyof PlayerEventMap>(event: K, handler: PlayerEventMap[K]): void {\n this._events.on(event, handler)\n }\n\n off<K extends keyof PlayerEventMap>(event: K, handler: PlayerEventMap[K]): void {\n this._events.off(event, handler)\n }\n\n once<K extends keyof PlayerEventMap>(event: K, handler: PlayerEventMap[K]): void {\n this._events.once(event, handler)\n }\n\n // === State Subscription ===\n\n subscribe(\n // eslint-disable-next-line pickier/no-unused-vars\n keyOrListener: keyof PlayerState | '*' | ((state: PlayerState, key?: keyof PlayerState) => void),\n listener?: (state: PlayerState, key?: keyof PlayerState) => void,\n ): () => void {\n return this._store.subscribe(keyOrListener, listener)\n }\n}\n\n// =============================================================================\n// Default Options\n// =============================================================================\n\n// eslint-disable-next-line pickier/no-unused-vars\nconst defaultOptions: PlayerOptions = {\n autoplay: false,\n loop: false,\n muted: false,\n volume: 1,\n playbackRate: 1,\n playsinline: true,\n preload: 'metadata',\n controls: true,\n keyboard: true,\n controlsTimeout: 3000,\n storage: true,\n}\n\n// =============================================================================\n// Factory Function\n// =============================================================================\n\n/**\n * Create a new player instance\n *\n * @example\n * ```typescript\n * const player = createPlayer('#video-container', {\n * src: 'https://example.com/video.mp4',\n * autoplay: true,\n * })\n *\n * player.on('playing', () => console.log('Video is playing!'))\n * ```\n */\nexport function createPlayer(container: HTMLElement | string, options?: PlayerOptions): Player {\n return new Player(container, options)\n}\n\n// =============================================================================\n// Get Player Instance\n// =============================================================================\n\n/**\n * Get player instance from element\n */\nexport function getPlayer(element: HTMLElement): Player | null {\n return (element as any).__videoPlayer || null\n}\n",
|
|
18
|
+
"/**\n * CSS Variables / Theming System\n *\n * Provides a comprehensive theming system using CSS custom properties.\n *\n * @module core/themes\n */\n\n// =============================================================================\n// Theme Types\n// =============================================================================\n\nexport interface ThemeColors {\n /** Primary brand color (default: #00a8ff) */\n primary?: string\n /** Secondary accent color */\n secondary?: string\n /** Background color */\n background?: string\n /** Text color */\n text?: string\n /** Text color on primary background */\n textOnPrimary?: string\n /** Border color */\n border?: string\n /** Focus ring color */\n focus?: string\n /** Error/danger color */\n error?: string\n /** Success color */\n success?: string\n /** Warning color */\n warning?: string\n}\n\nexport interface ThemeTypography {\n /** Font family for UI elements */\n fontFamily?: string\n /** Base font size */\n fontSize?: string\n /** Small font size */\n fontSizeSmall?: string\n /** Large font size */\n fontSizeLarge?: string\n /** Font weight */\n fontWeight?: string\n /** Bold font weight */\n fontWeightBold?: string\n /** Line height */\n lineHeight?: string\n}\n\nexport interface ThemeSpacing {\n /** Extra small spacing (4px) */\n xs?: string\n /** Small spacing (8px) */\n sm?: string\n /** Medium spacing (12px) */\n md?: string\n /** Large spacing (16px) */\n lg?: string\n /** Extra large spacing (24px) */\n xl?: string\n}\n\nexport interface ThemeBorderRadius {\n /** Small border radius */\n sm?: string\n /** Medium border radius */\n md?: string\n /** Large border radius */\n lg?: string\n /** Full/round border radius */\n full?: string\n}\n\nexport interface ThemeShadows {\n /** Small shadow */\n sm?: string\n /** Medium shadow */\n md?: string\n /** Large shadow */\n lg?: string\n}\n\nexport interface ThemeTransitions {\n /** Fast transition duration */\n fast?: string\n /** Normal transition duration */\n normal?: string\n /** Slow transition duration */\n slow?: string\n /** Default easing function */\n easing?: string\n}\n\nexport interface ThemeSlider {\n /** Slider track height */\n trackHeight?: string\n /** Slider thumb size */\n thumbSize?: string\n /** Slider track background */\n trackBg?: string\n /** Slider track fill background */\n trackFillBg?: string\n /** Slider buffered background */\n bufferedBg?: string\n /** Slider thumb background */\n thumbBg?: string\n /** Slider thumb border */\n thumbBorder?: string\n}\n\nexport interface ThemeControls {\n /** Controls bar height */\n height?: string\n /** Controls background */\n background?: string\n /** Controls padding */\n padding?: string\n /** Button size */\n buttonSize?: string\n /** Icon size */\n iconSize?: string\n /** Tooltip background */\n tooltipBg?: string\n /** Tooltip text color */\n tooltipText?: string\n}\n\nexport interface ThemeMenu {\n /** Menu background */\n background?: string\n /** Menu border radius */\n borderRadius?: string\n /** Menu shadow */\n shadow?: string\n /** Menu item height */\n itemHeight?: string\n /** Menu item hover background */\n itemHoverBg?: string\n /** Menu item active background */\n itemActiveBg?: string\n}\n\nexport interface ThemeCaptions {\n /** Caption font family */\n fontFamily?: string\n /** Caption font size */\n fontSize?: string\n /** Caption text color */\n textColor?: string\n /** Caption background */\n background?: string\n /** Caption text shadow */\n textShadow?: string\n /** Caption border radius */\n borderRadius?: string\n /** Caption padding */\n padding?: string\n}\n\nexport interface ThemeThumbnails {\n /** Thumbnail width */\n width?: string\n /** Thumbnail height */\n height?: string\n /** Thumbnail border radius */\n borderRadius?: string\n /** Thumbnail border */\n border?: string\n /** Thumbnail shadow */\n shadow?: string\n}\n\nexport interface ThemeLive {\n /** Live indicator color */\n indicatorColor?: string\n /** Live indicator size */\n indicatorSize?: string\n /** Live badge background */\n badgeBg?: string\n /** Live badge text color */\n badgeText?: string\n}\n\nexport interface Theme {\n colors?: ThemeColors\n typography?: ThemeTypography\n spacing?: ThemeSpacing\n borderRadius?: ThemeBorderRadius\n shadows?: ThemeShadows\n transitions?: ThemeTransitions\n slider?: ThemeSlider\n controls?: ThemeControls\n menu?: ThemeMenu\n captions?: ThemeCaptions\n thumbnails?: ThemeThumbnails\n live?: ThemeLive\n}\n\n// =============================================================================\n// Default Theme\n// =============================================================================\n\nexport const defaultTheme: Required<Theme> = {\n colors: {\n primary: '#00a8ff',\n secondary: '#6c5ce7',\n background: 'rgba(0, 0, 0, 0.85)',\n text: '#ffffff',\n textOnPrimary: '#ffffff',\n border: 'rgba(255, 255, 255, 0.1)',\n focus: '#00a8ff',\n error: '#ff4757',\n success: '#2ed573',\n warning: '#ffa502',\n },\n typography: {\n fontFamily: '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif',\n fontSize: '14px',\n fontSizeSmall: '12px',\n fontSizeLarge: '16px',\n fontWeight: '400',\n fontWeightBold: '600',\n lineHeight: '1.5',\n },\n spacing: {\n xs: '4px',\n sm: '8px',\n md: '12px',\n lg: '16px',\n xl: '24px',\n },\n borderRadius: {\n sm: '4px',\n md: '8px',\n lg: '12px',\n full: '9999px',\n },\n shadows: {\n sm: '0 1px 2px rgba(0, 0, 0, 0.2)',\n md: '0 4px 6px rgba(0, 0, 0, 0.3)',\n lg: '0 10px 15px rgba(0, 0, 0, 0.4)',\n },\n transitions: {\n fast: '100ms',\n normal: '200ms',\n slow: '300ms',\n easing: 'cubic-bezier(0.4, 0, 0.2, 1)',\n },\n slider: {\n trackHeight: '4px',\n thumbSize: '14px',\n trackBg: 'rgba(255, 255, 255, 0.2)',\n trackFillBg: 'var(--tsvp-color-primary)',\n bufferedBg: 'rgba(255, 255, 255, 0.4)',\n thumbBg: '#ffffff',\n thumbBorder: 'none',\n },\n controls: {\n height: '48px',\n background: 'linear-gradient(transparent, rgba(0, 0, 0, 0.7))',\n padding: '0 12px',\n buttonSize: '40px',\n iconSize: '24px',\n tooltipBg: 'rgba(0, 0, 0, 0.9)',\n tooltipText: '#ffffff',\n },\n menu: {\n background: 'rgba(20, 20, 20, 0.95)',\n borderRadius: '8px',\n shadow: '0 4px 20px rgba(0, 0, 0, 0.5)',\n itemHeight: '40px',\n itemHoverBg: 'rgba(255, 255, 255, 0.1)',\n itemActiveBg: 'rgba(0, 168, 255, 0.2)',\n },\n captions: {\n fontFamily: 'inherit',\n fontSize: '1.5em',\n textColor: '#ffffff',\n background: 'rgba(0, 0, 0, 0.75)',\n textShadow: '0 1px 2px rgba(0, 0, 0, 0.5)',\n borderRadius: '4px',\n padding: '4px 8px',\n },\n thumbnails: {\n width: '160px',\n height: '90px',\n borderRadius: '4px',\n border: '2px solid rgba(255, 255, 255, 0.2)',\n shadow: '0 4px 12px rgba(0, 0, 0, 0.4)',\n },\n live: {\n indicatorColor: '#ff0000',\n indicatorSize: '8px',\n badgeBg: '#ff0000',\n badgeText: '#ffffff',\n },\n}\n\n// =============================================================================\n// Preset Themes\n// =============================================================================\n\nexport const darkTheme: Theme = {\n colors: {\n primary: '#00a8ff',\n background: 'rgba(0, 0, 0, 0.9)',\n text: '#ffffff',\n },\n}\n\nexport const lightTheme: Theme = {\n colors: {\n primary: '#0070f3',\n background: 'rgba(255, 255, 255, 0.95)',\n text: '#1a1a1a',\n textOnPrimary: '#ffffff',\n border: 'rgba(0, 0, 0, 0.1)',\n },\n controls: {\n background: 'linear-gradient(transparent, rgba(255, 255, 255, 0.9))',\n tooltipBg: 'rgba(0, 0, 0, 0.8)',\n },\n menu: {\n background: 'rgba(255, 255, 255, 0.98)',\n shadow: '0 4px 20px rgba(0, 0, 0, 0.15)',\n itemHoverBg: 'rgba(0, 0, 0, 0.05)',\n itemActiveBg: 'rgba(0, 112, 243, 0.1)',\n },\n captions: {\n background: 'rgba(255, 255, 255, 0.9)',\n textColor: '#1a1a1a',\n },\n}\n\nexport const minimalTheme: Theme = {\n colors: {\n primary: '#ffffff',\n background: 'transparent',\n },\n controls: {\n height: '40px',\n background: 'transparent',\n },\n slider: {\n trackHeight: '2px',\n thumbSize: '10px',\n },\n}\n\nexport const netflixTheme: Theme = {\n colors: {\n primary: '#e50914',\n background: 'rgba(0, 0, 0, 0.85)',\n },\n slider: {\n trackFillBg: '#e50914',\n },\n live: {\n indicatorColor: '#e50914',\n badgeBg: '#e50914',\n },\n}\n\nexport const youtubeTheme: Theme = {\n colors: {\n primary: '#ff0000',\n background: 'rgba(0, 0, 0, 0.85)',\n },\n slider: {\n trackHeight: '3px',\n thumbSize: '13px',\n trackFillBg: '#ff0000',\n },\n controls: {\n height: '48px',\n },\n live: {\n indicatorColor: '#ff0000',\n badgeBg: '#ff0000',\n },\n}\n\nexport const vimeoTheme: Theme = {\n colors: {\n primary: '#00adef',\n background: 'rgba(0, 0, 0, 0.85)',\n },\n slider: {\n trackFillBg: '#00adef',\n },\n}\n\nexport const presetThemes: Record<string, Theme> = {\n dark: darkTheme,\n light: lightTheme,\n minimal: minimalTheme,\n netflix: netflixTheme,\n youtube: youtubeTheme,\n vimeo: vimeoTheme,\n}\n\nexport type PresetThemeName = keyof typeof presetThemes\n\n// =============================================================================\n// Theme Utilities\n// =============================================================================\n\n/**\n * Deep merge two theme objects\n */\nexport function mergeThemes(base: Theme, override: Theme): Theme {\n const result: Theme = { ...base }\n\n for (const key of Object.keys(override) as (keyof Theme)[]) {\n const baseValue = base[key]\n const overrideValue = override[key]\n\n if (overrideValue && typeof overrideValue === 'object') {\n result[key] = {\n ...(baseValue || {}),\n ...overrideValue,\n } as any\n }\n else if (overrideValue !== undefined) {\n result[key] = overrideValue as any\n }\n }\n\n return result\n}\n\n/**\n * Generate CSS custom properties from a theme\n */\nexport function generateThemeCSS(theme: Theme, prefix = 'tsvp'): string {\n const merged = mergeThemes(defaultTheme, theme)\n const vars: string[] = []\n\n // Colors\n if (merged.colors) {\n vars.push(`--${prefix}-color-primary: ${merged.colors.primary};`)\n vars.push(`--${prefix}-color-secondary: ${merged.colors.secondary};`)\n vars.push(`--${prefix}-color-background: ${merged.colors.background};`)\n vars.push(`--${prefix}-color-text: ${merged.colors.text};`)\n vars.push(`--${prefix}-color-text-on-primary: ${merged.colors.textOnPrimary};`)\n vars.push(`--${prefix}-color-border: ${merged.colors.border};`)\n vars.push(`--${prefix}-color-focus: ${merged.colors.focus};`)\n vars.push(`--${prefix}-color-error: ${merged.colors.error};`)\n vars.push(`--${prefix}-color-success: ${merged.colors.success};`)\n vars.push(`--${prefix}-color-warning: ${merged.colors.warning};`)\n }\n\n // Typography\n if (merged.typography) {\n vars.push(`--${prefix}-font-family: ${merged.typography.fontFamily};`)\n vars.push(`--${prefix}-font-size: ${merged.typography.fontSize};`)\n vars.push(`--${prefix}-font-size-sm: ${merged.typography.fontSizeSmall};`)\n vars.push(`--${prefix}-font-size-lg: ${merged.typography.fontSizeLarge};`)\n vars.push(`--${prefix}-font-weight: ${merged.typography.fontWeight};`)\n vars.push(`--${prefix}-font-weight-bold: ${merged.typography.fontWeightBold};`)\n vars.push(`--${prefix}-line-height: ${merged.typography.lineHeight};`)\n }\n\n // Spacing\n if (merged.spacing) {\n vars.push(`--${prefix}-spacing-xs: ${merged.spacing.xs};`)\n vars.push(`--${prefix}-spacing-sm: ${merged.spacing.sm};`)\n vars.push(`--${prefix}-spacing-md: ${merged.spacing.md};`)\n vars.push(`--${prefix}-spacing-lg: ${merged.spacing.lg};`)\n vars.push(`--${prefix}-spacing-xl: ${merged.spacing.xl};`)\n }\n\n // Border Radius\n if (merged.borderRadius) {\n vars.push(`--${prefix}-radius-sm: ${merged.borderRadius.sm};`)\n vars.push(`--${prefix}-radius-md: ${merged.borderRadius.md};`)\n vars.push(`--${prefix}-radius-lg: ${merged.borderRadius.lg};`)\n vars.push(`--${prefix}-radius-full: ${merged.borderRadius.full};`)\n }\n\n // Shadows\n if (merged.shadows) {\n vars.push(`--${prefix}-shadow-sm: ${merged.shadows.sm};`)\n vars.push(`--${prefix}-shadow-md: ${merged.shadows.md};`)\n vars.push(`--${prefix}-shadow-lg: ${merged.shadows.lg};`)\n }\n\n // Transitions\n if (merged.transitions) {\n vars.push(`--${prefix}-transition-fast: ${merged.transitions.fast};`)\n vars.push(`--${prefix}-transition-normal: ${merged.transitions.normal};`)\n vars.push(`--${prefix}-transition-slow: ${merged.transitions.slow};`)\n vars.push(`--${prefix}-transition-easing: ${merged.transitions.easing};`)\n }\n\n // Slider\n if (merged.slider) {\n vars.push(`--${prefix}-slider-track-height: ${merged.slider.trackHeight};`)\n vars.push(`--${prefix}-slider-thumb-size: ${merged.slider.thumbSize};`)\n vars.push(`--${prefix}-slider-track-bg: ${merged.slider.trackBg};`)\n vars.push(`--${prefix}-slider-track-fill-bg: ${merged.slider.trackFillBg};`)\n vars.push(`--${prefix}-slider-buffered-bg: ${merged.slider.bufferedBg};`)\n vars.push(`--${prefix}-slider-thumb-bg: ${merged.slider.thumbBg};`)\n vars.push(`--${prefix}-slider-thumb-border: ${merged.slider.thumbBorder};`)\n }\n\n // Controls\n if (merged.controls) {\n vars.push(`--${prefix}-controls-height: ${merged.controls.height};`)\n vars.push(`--${prefix}-controls-bg: ${merged.controls.background};`)\n vars.push(`--${prefix}-controls-padding: ${merged.controls.padding};`)\n vars.push(`--${prefix}-controls-button-size: ${merged.controls.buttonSize};`)\n vars.push(`--${prefix}-controls-icon-size: ${merged.controls.iconSize};`)\n vars.push(`--${prefix}-controls-tooltip-bg: ${merged.controls.tooltipBg};`)\n vars.push(`--${prefix}-controls-tooltip-text: ${merged.controls.tooltipText};`)\n }\n\n // Menu\n if (merged.menu) {\n vars.push(`--${prefix}-menu-bg: ${merged.menu.background};`)\n vars.push(`--${prefix}-menu-radius: ${merged.menu.borderRadius};`)\n vars.push(`--${prefix}-menu-shadow: ${merged.menu.shadow};`)\n vars.push(`--${prefix}-menu-item-height: ${merged.menu.itemHeight};`)\n vars.push(`--${prefix}-menu-item-hover-bg: ${merged.menu.itemHoverBg};`)\n vars.push(`--${prefix}-menu-item-active-bg: ${merged.menu.itemActiveBg};`)\n }\n\n // Captions\n if (merged.captions) {\n vars.push(`--${prefix}-captions-font-family: ${merged.captions.fontFamily};`)\n vars.push(`--${prefix}-captions-font-size: ${merged.captions.fontSize};`)\n vars.push(`--${prefix}-captions-text-color: ${merged.captions.textColor};`)\n vars.push(`--${prefix}-captions-bg: ${merged.captions.background};`)\n vars.push(`--${prefix}-captions-text-shadow: ${merged.captions.textShadow};`)\n vars.push(`--${prefix}-captions-radius: ${merged.captions.borderRadius};`)\n vars.push(`--${prefix}-captions-padding: ${merged.captions.padding};`)\n }\n\n // Thumbnails\n if (merged.thumbnails) {\n vars.push(`--${prefix}-thumbnail-width: ${merged.thumbnails.width};`)\n vars.push(`--${prefix}-thumbnail-height: ${merged.thumbnails.height};`)\n vars.push(`--${prefix}-thumbnail-radius: ${merged.thumbnails.borderRadius};`)\n vars.push(`--${prefix}-thumbnail-border: ${merged.thumbnails.border};`)\n vars.push(`--${prefix}-thumbnail-shadow: ${merged.thumbnails.shadow};`)\n }\n\n // Live\n if (merged.live) {\n vars.push(`--${prefix}-live-indicator-color: ${merged.live.indicatorColor};`)\n vars.push(`--${prefix}-live-indicator-size: ${merged.live.indicatorSize};`)\n vars.push(`--${prefix}-live-badge-bg: ${merged.live.badgeBg};`)\n vars.push(`--${prefix}-live-badge-text: ${merged.live.badgeText};`)\n }\n\n return vars.join('\\n ')\n}\n\n/**\n * Create a style element with theme CSS\n */\nexport function createThemeStyleElement(theme: Theme, selector = '.tsvp-player'): HTMLStyleElement {\n const style = document.createElement('style')\n style.setAttribute('data-tsvp-theme', 'true')\n style.textContent = `\n${selector} {\n ${generateThemeCSS(theme)}\n}\n`\n return style\n}\n\n/**\n * Apply theme to a player element\n */\nexport function applyTheme(element: HTMLElement, theme: Theme | PresetThemeName): void {\n const themeObj = typeof theme === 'string' ? presetThemes[theme] : theme\n\n // Remove existing inline theme\n element.querySelectorAll('style[data-tsvp-theme]').forEach(el => el.remove())\n\n // Apply new theme\n const css = generateThemeCSS(themeObj)\n element.style.cssText += css.split('\\n').map(line => line.trim().replace(';', '')).join('; ')\n}\n\n/**\n * Inject global theme styles\n */\nexport function injectThemeStyles(theme: Theme | PresetThemeName = {}, selector = '.tsvp-player'): void {\n const themeObj = typeof theme === 'string' ? presetThemes[theme] : theme\n\n // Remove existing global theme\n document.querySelectorAll('style[data-tsvp-theme-global]').forEach(el => el.remove())\n\n const style = document.createElement('style')\n style.setAttribute('data-tsvp-theme-global', 'true')\n style.textContent = `\n${selector} {\n ${generateThemeCSS(themeObj)}\n}\n`\n document.head.appendChild(style)\n}\n",
|
|
19
|
+
"/**\n * Internationalization (i18n) System\n *\n * Provides translation support for all player UI strings.\n *\n * @module core/i18n\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface TranslationStrings {\n // Playback\n play: string\n pause: string\n replay: string\n // Volume\n mute: string\n unmute: string\n volume: string\n // Progress\n seekBackward: string\n seekForward: string\n seekTo: string\n duration: string\n currentTime: string\n played: string\n buffered: string\n // Fullscreen\n enterFullscreen: string\n exitFullscreen: string\n // PiP\n enterPiP: string\n exitPiP: string\n // Captions\n enableCaptions: string\n disableCaptions: string\n captions: string\n captionsOff: string\n // Settings\n settings: string\n quality: string\n auto: string\n speed: string\n normal: string\n loop: string\n loopEnabled: string\n loopDisabled: string\n // Audio\n audioBoost: string\n audioTrack: string\n // Live\n live: string\n liveEdge: string\n seekToLive: string\n behindLive: string\n // Errors\n networkError: string\n mediaError: string\n unknownError: string\n // Loading\n loading: string\n buffering: string\n // Chapters\n chapters: string\n // AirPlay / Cast\n airPlay: string\n googleCast: string\n casting: string\n // Misc\n download: string\n share: string\n close: string\n menu: string\n back: string\n}\n\nexport type TranslationKey = keyof TranslationStrings\n\nexport interface I18nConfig {\n /** Current language code */\n language?: string\n /** Fallback language code */\n fallbackLanguage?: string\n /** Custom translations */\n translations?: Partial<Record<string, Partial<TranslationStrings>>>\n /** Interpolation pattern (default: {key}) */\n interpolationPattern?: RegExp\n}\n\n// =============================================================================\n// Default Translations\n// =============================================================================\n\nexport const englishStrings: TranslationStrings = {\n // Playback\n play: 'Play',\n pause: 'Pause',\n replay: 'Replay',\n // Volume\n mute: 'Mute',\n unmute: 'Unmute',\n volume: 'Volume',\n // Progress\n seekBackward: 'Seek backward {seconds} seconds',\n seekForward: 'Seek forward {seconds} seconds',\n seekTo: 'Seek to {time}',\n duration: 'Duration',\n currentTime: 'Current time',\n played: 'Played',\n buffered: 'Buffered',\n // Fullscreen\n enterFullscreen: 'Enter fullscreen',\n exitFullscreen: 'Exit fullscreen',\n // PiP\n enterPiP: 'Enter picture-in-picture',\n exitPiP: 'Exit picture-in-picture',\n // Captions\n enableCaptions: 'Enable captions',\n disableCaptions: 'Disable captions',\n captions: 'Captions',\n captionsOff: 'Off',\n // Settings\n settings: 'Settings',\n quality: 'Quality',\n auto: 'Auto',\n speed: 'Speed',\n normal: 'Normal',\n loop: 'Loop',\n loopEnabled: 'Loop enabled',\n loopDisabled: 'Loop disabled',\n // Audio\n audioBoost: 'Audio boost',\n audioTrack: 'Audio track',\n // Live\n live: 'LIVE',\n liveEdge: 'At live edge',\n seekToLive: 'Seek to live edge',\n behindLive: '{seconds}s behind',\n // Errors\n networkError: 'A network error occurred',\n mediaError: 'The media could not be loaded',\n unknownError: 'An unknown error occurred',\n // Loading\n loading: 'Loading',\n buffering: 'Buffering',\n // Chapters\n chapters: 'Chapters',\n // AirPlay / Cast\n airPlay: 'AirPlay',\n googleCast: 'Cast',\n casting: 'Casting to {device}',\n // Misc\n download: 'Download',\n share: 'Share',\n close: 'Close',\n menu: 'Menu',\n back: 'Back',\n}\n\nexport const spanishStrings: TranslationStrings = {\n play: 'Reproducir',\n pause: 'Pausar',\n replay: 'Repetir',\n mute: 'Silenciar',\n unmute: 'Activar sonido',\n volume: 'Volumen',\n seekBackward: 'Retroceder {seconds} segundos',\n seekForward: 'Avanzar {seconds} segundos',\n seekTo: 'Ir a {time}',\n duration: 'Duración',\n currentTime: 'Tiempo actual',\n played: 'Reproducido',\n buffered: 'Almacenado',\n enterFullscreen: 'Pantalla completa',\n exitFullscreen: 'Salir de pantalla completa',\n enterPiP: 'Imagen en imagen',\n exitPiP: 'Salir de imagen en imagen',\n enableCaptions: 'Activar subtítulos',\n disableCaptions: 'Desactivar subtítulos',\n captions: 'Subtítulos',\n captionsOff: 'Desactivados',\n settings: 'Ajustes',\n quality: 'Calidad',\n auto: 'Automático',\n speed: 'Velocidad',\n normal: 'Normal',\n loop: 'Bucle',\n loopEnabled: 'Bucle activado',\n loopDisabled: 'Bucle desactivado',\n audioBoost: 'Aumento de audio',\n audioTrack: 'Pista de audio',\n live: 'EN VIVO',\n liveEdge: 'En vivo',\n seekToLive: 'Ir al directo',\n behindLive: '{seconds}s de retraso',\n networkError: 'Error de red',\n mediaError: 'No se pudo cargar el medio',\n unknownError: 'Error desconocido',\n loading: 'Cargando',\n buffering: 'Almacenando',\n chapters: 'Capítulos',\n airPlay: 'AirPlay',\n googleCast: 'Enviar',\n casting: 'Enviando a {device}',\n download: 'Descargar',\n share: 'Compartir',\n close: 'Cerrar',\n menu: 'Menú',\n back: 'Volver',\n}\n\nexport const frenchStrings: TranslationStrings = {\n play: 'Lecture',\n pause: 'Pause',\n replay: 'Relire',\n mute: 'Muet',\n unmute: 'Son activé',\n volume: 'Volume',\n seekBackward: 'Reculer de {seconds} secondes',\n seekForward: 'Avancer de {seconds} secondes',\n seekTo: 'Aller à {time}',\n duration: 'Durée',\n currentTime: 'Temps actuel',\n played: 'Joué',\n buffered: 'Mis en mémoire tampon',\n enterFullscreen: 'Plein écran',\n exitFullscreen: 'Quitter le plein écran',\n enterPiP: 'Image dans l\\'image',\n exitPiP: 'Quitter l\\'image dans l\\'image',\n enableCaptions: 'Activer les sous-titres',\n disableCaptions: 'Désactiver les sous-titres',\n captions: 'Sous-titres',\n captionsOff: 'Désactivés',\n settings: 'Paramètres',\n quality: 'Qualité',\n auto: 'Auto',\n speed: 'Vitesse',\n normal: 'Normal',\n loop: 'Boucle',\n loopEnabled: 'Boucle activée',\n loopDisabled: 'Boucle désactivée',\n audioBoost: 'Amplification audio',\n audioTrack: 'Piste audio',\n live: 'EN DIRECT',\n liveEdge: 'En direct',\n seekToLive: 'Aller au direct',\n behindLive: '{seconds}s de retard',\n networkError: 'Erreur réseau',\n mediaError: 'Le média n\\'a pas pu être chargé',\n unknownError: 'Erreur inconnue',\n loading: 'Chargement',\n buffering: 'Mise en mémoire tampon',\n chapters: 'Chapitres',\n airPlay: 'AirPlay',\n googleCast: 'Caster',\n casting: 'Diffusion sur {device}',\n download: 'Télécharger',\n share: 'Partager',\n close: 'Fermer',\n menu: 'Menu',\n back: 'Retour',\n}\n\nexport const germanStrings: TranslationStrings = {\n play: 'Abspielen',\n pause: 'Pause',\n replay: 'Wiederholen',\n mute: 'Stumm',\n unmute: 'Ton an',\n volume: 'Lautstärke',\n seekBackward: '{seconds} Sekunden zurück',\n seekForward: '{seconds} Sekunden vor',\n seekTo: 'Zu {time} springen',\n duration: 'Dauer',\n currentTime: 'Aktuelle Zeit',\n played: 'Abgespielt',\n buffered: 'Gepuffert',\n enterFullscreen: 'Vollbild',\n exitFullscreen: 'Vollbild beenden',\n enterPiP: 'Bild-in-Bild',\n exitPiP: 'Bild-in-Bild beenden',\n enableCaptions: 'Untertitel aktivieren',\n disableCaptions: 'Untertitel deaktivieren',\n captions: 'Untertitel',\n captionsOff: 'Aus',\n settings: 'Einstellungen',\n quality: 'Qualität',\n auto: 'Auto',\n speed: 'Geschwindigkeit',\n normal: 'Normal',\n loop: 'Schleife',\n loopEnabled: 'Schleife aktiviert',\n loopDisabled: 'Schleife deaktiviert',\n audioBoost: 'Audio-Verstärkung',\n audioTrack: 'Audiospur',\n live: 'LIVE',\n liveEdge: 'Am Live-Punkt',\n seekToLive: 'Zum Live-Punkt',\n behindLive: '{seconds}s hinter Live',\n networkError: 'Netzwerkfehler',\n mediaError: 'Medium konnte nicht geladen werden',\n unknownError: 'Unbekannter Fehler',\n loading: 'Wird geladen',\n buffering: 'Puffern',\n chapters: 'Kapitel',\n airPlay: 'AirPlay',\n googleCast: 'Streamen',\n casting: 'Streame zu {device}',\n download: 'Herunterladen',\n share: 'Teilen',\n close: 'Schließen',\n menu: 'Menü',\n back: 'Zurück',\n}\n\nexport const japaneseStrings: TranslationStrings = {\n play: '再生',\n pause: '一時停止',\n replay: 'リプレイ',\n mute: 'ミュート',\n unmute: 'ミュート解除',\n volume: '音量',\n seekBackward: '{seconds}秒戻る',\n seekForward: '{seconds}秒進む',\n seekTo: '{time}にシーク',\n duration: '長さ',\n currentTime: '現在の時間',\n played: '再生済み',\n buffered: 'バッファ済み',\n enterFullscreen: 'フルスクリーン',\n exitFullscreen: 'フルスクリーン終了',\n enterPiP: 'ピクチャーインピクチャー',\n exitPiP: 'ピクチャーインピクチャー終了',\n enableCaptions: '字幕を有効化',\n disableCaptions: '字幕を無効化',\n captions: '字幕',\n captionsOff: 'オフ',\n settings: '設定',\n quality: '画質',\n auto: '自動',\n speed: '再生速度',\n normal: '通常',\n loop: 'ループ',\n loopEnabled: 'ループ有効',\n loopDisabled: 'ループ無効',\n audioBoost: 'オーディオブースト',\n audioTrack: 'オーディオトラック',\n live: 'ライブ',\n liveEdge: 'ライブ中',\n seekToLive: 'ライブに移動',\n behindLive: '{seconds}秒遅れ',\n networkError: 'ネットワークエラー',\n mediaError: 'メディアを読み込めません',\n unknownError: '不明なエラー',\n loading: '読み込み中',\n buffering: 'バッファリング',\n chapters: 'チャプター',\n airPlay: 'AirPlay',\n googleCast: 'キャスト',\n casting: '{device}にキャスト中',\n download: 'ダウンロード',\n share: '共有',\n close: '閉じる',\n menu: 'メニュー',\n back: '戻る',\n}\n\nexport const chineseStrings: TranslationStrings = {\n play: '播放',\n pause: '暂停',\n replay: '重播',\n mute: '静音',\n unmute: '取消静音',\n volume: '音量',\n seekBackward: '后退{seconds}秒',\n seekForward: '前进{seconds}秒',\n seekTo: '跳转到{time}',\n duration: '时长',\n currentTime: '当前时间',\n played: '已播放',\n buffered: '已缓冲',\n enterFullscreen: '全屏',\n exitFullscreen: '退出全屏',\n enterPiP: '画中画',\n exitPiP: '退出画中画',\n enableCaptions: '开启字幕',\n disableCaptions: '关闭字幕',\n captions: '字幕',\n captionsOff: '关闭',\n settings: '设置',\n quality: '画质',\n auto: '自动',\n speed: '播放速度',\n normal: '正常',\n loop: '循环',\n loopEnabled: '循环已开启',\n loopDisabled: '循环已关闭',\n audioBoost: '音频增强',\n audioTrack: '音轨',\n live: '直播',\n liveEdge: '直播中',\n seekToLive: '跳转到直播',\n behindLive: '落后{seconds}秒',\n networkError: '网络错误',\n mediaError: '无法加载媒体',\n unknownError: '未知错误',\n loading: '加载中',\n buffering: '缓冲中',\n chapters: '章节',\n airPlay: 'AirPlay',\n googleCast: '投射',\n casting: '正在投射到{device}',\n download: '下载',\n share: '分享',\n close: '关闭',\n menu: '菜单',\n back: '返回',\n}\n\nexport const defaultTranslations: Record<string, TranslationStrings> = {\n en: englishStrings,\n es: spanishStrings,\n fr: frenchStrings,\n de: germanStrings,\n ja: japaneseStrings,\n zh: chineseStrings,\n}\n\n// =============================================================================\n// I18n Class\n// =============================================================================\n\nexport class I18n {\n private language: string\n private fallbackLanguage: string\n private translations: Record<string, Partial<TranslationStrings>>\n private interpolationPattern: RegExp\n\n constructor(config: I18nConfig = {}) {\n this.language = config.language || this.detectLanguage() || 'en'\n this.fallbackLanguage = config.fallbackLanguage || 'en'\n this.translations = { ...defaultTranslations }\n\n // Merge custom translations\n if (config.translations) {\n for (const [lang, strings] of Object.entries(config.translations)) {\n if (strings) {\n this.translations[lang] = { ...this.translations[lang], ...strings }\n }\n }\n }\n\n this.interpolationPattern = config.interpolationPattern || /\\{(\\w+)\\}/g\n }\n\n /**\n * Detect browser language\n */\n private detectLanguage(): string | null {\n if (typeof navigator === 'undefined') return null\n\n const lang = navigator.language || (navigator as any).userLanguage\n if (!lang) return null\n\n // Try exact match first\n if (this.translations[lang]) {\n return lang\n }\n\n // Try base language (e.g., 'en' from 'en-US')\n const baseLang = lang.split('-')[0]\n if (this.translations[baseLang]) {\n return baseLang\n }\n\n return null\n }\n\n /**\n * Get current language\n */\n getLanguage(): string {\n return this.language\n }\n\n /**\n * Set current language\n */\n setLanguage(language: string): void {\n this.language = language\n }\n\n /**\n * Get translation for a key\n */\n t(key: TranslationKey, params?: Record<string, string | number>): string {\n // Try current language\n let str = this.translations[this.language]?.[key]\n\n // Fall back to fallback language\n if (!str) {\n str = this.translations[this.fallbackLanguage]?.[key]\n }\n\n // Return key if no translation found\n if (!str) {\n return key\n }\n\n // Interpolate params\n if (params) {\n str = str.replace(this.interpolationPattern, (match, paramKey) => {\n const value = params[paramKey]\n return value !== undefined ? String(value) : match\n })\n }\n\n return str\n }\n\n /**\n * Add or update translations for a language\n */\n addTranslations(language: string, strings: Partial<TranslationStrings>): void {\n this.translations[language] = {\n ...this.translations[language],\n ...strings,\n }\n }\n\n /**\n * Get all translations for a language\n */\n getTranslations(language: string): Partial<TranslationStrings> | undefined {\n return this.translations[language]\n }\n\n /**\n * Get all available languages\n */\n getAvailableLanguages(): string[] {\n return Object.keys(this.translations)\n }\n\n /**\n * Check if a language is available\n */\n hasLanguage(language: string): boolean {\n return language in this.translations\n }\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\nlet globalI18n: I18n | null = null\n\n/**\n * Create an I18n instance\n */\nexport function createI18n(config?: I18nConfig): I18n {\n return new I18n(config)\n}\n\n/**\n * Get or create global I18n instance\n */\nexport function getI18n(config?: I18nConfig): I18n {\n if (!globalI18n) {\n globalI18n = createI18n(config)\n }\n return globalI18n\n}\n\n/**\n * Set global I18n instance\n */\nexport function setI18n(i18n: I18n): void {\n globalI18n = i18n\n}\n\n/**\n * Quick translation helper using global instance\n */\nexport function t(key: TranslationKey, params?: Record<string, string | number>): string {\n return getI18n().t(key, params)\n}\n",
|
|
20
|
+
"/**\n * Player Layouts\n *\n * Pre-built player layouts for quick setup.\n *\n * @module layouts\n */\n\nimport type { PlayerOptions } from '../types'\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface LayoutConfig {\n /** Layout name */\n name: string\n /** Layout description */\n description: string\n /** CSS styles to inject */\n styles: string\n /** HTML structure template */\n template: string\n /** Default player options for this layout */\n defaultOptions?: Partial<PlayerOptions>\n /** Initialize layout-specific behavior */\n init?: (container: HTMLElement) => void\n /** Cleanup layout */\n destroy?: (container: HTMLElement) => void\n}\n\nexport type LayoutName = 'default' | 'minimal' | 'cinema' | 'youtube' | 'vimeo' | 'audio'\n\n// =============================================================================\n// Default Layout\n// =============================================================================\n\nexport const defaultLayout: LayoutConfig = {\n name: 'default',\n description: 'Full-featured default layout with all controls',\n styles: `\n .tsvp-layout-default {\n --tsvp-controls-height: 48px;\n }\n\n .tsvp-layout-default .tsvp-controls {\n display: flex;\n flex-direction: column;\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n background: var(--tsvp-controls-bg);\n padding: var(--tsvp-controls-padding);\n opacity: 0;\n transition: opacity var(--tsvp-transition-normal) var(--tsvp-transition-easing);\n }\n\n .tsvp-layout-default:hover .tsvp-controls,\n .tsvp-layout-default.tsvp-controls-visible .tsvp-controls {\n opacity: 1;\n }\n\n .tsvp-layout-default .tsvp-controls-row {\n display: flex;\n align-items: center;\n gap: var(--tsvp-spacing-sm);\n }\n\n .tsvp-layout-default .tsvp-controls-row-progress {\n padding: 0 var(--tsvp-spacing-sm);\n }\n\n .tsvp-layout-default .tsvp-controls-row-buttons {\n height: var(--tsvp-controls-height);\n }\n\n .tsvp-layout-default .tsvp-controls-left,\n .tsvp-layout-default .tsvp-controls-right {\n display: flex;\n align-items: center;\n gap: var(--tsvp-spacing-xs);\n }\n\n .tsvp-layout-default .tsvp-controls-center {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .tsvp-layout-default .tsvp-controls-spacer {\n flex: 1;\n }\n `,\n template: `\n <div class=\"tsvp-video-container\">\n <slot name=\"video\"></slot>\n </div>\n <div class=\"tsvp-poster\"></div>\n <div class=\"tsvp-captions\"></div>\n <div class=\"tsvp-gestures\"></div>\n <div class=\"tsvp-buffering-indicator\"></div>\n <div class=\"tsvp-controls\">\n <div class=\"tsvp-controls-row tsvp-controls-row-progress\">\n <slot name=\"progress\"></slot>\n </div>\n <div class=\"tsvp-controls-row tsvp-controls-row-buttons\">\n <div class=\"tsvp-controls-left\">\n <slot name=\"play-button\"></slot>\n <slot name=\"volume\"></slot>\n <slot name=\"time\"></slot>\n <slot name=\"live-indicator\"></slot>\n </div>\n <div class=\"tsvp-controls-spacer\"></div>\n <div class=\"tsvp-controls-right\">\n <slot name=\"captions-button\"></slot>\n <slot name=\"settings-button\"></slot>\n <slot name=\"pip-button\"></slot>\n <slot name=\"airplay-button\"></slot>\n <slot name=\"cast-button\"></slot>\n <slot name=\"fullscreen-button\"></slot>\n </div>\n </div>\n </div>\n `,\n}\n\n// =============================================================================\n// Minimal Layout\n// =============================================================================\n\nexport const minimalLayout: LayoutConfig = {\n name: 'minimal',\n description: 'Clean, minimal layout with essential controls only',\n styles: `\n .tsvp-layout-minimal {\n --tsvp-controls-height: 36px;\n --tsvp-slider-track-height: 2px;\n --tsvp-slider-thumb-size: 10px;\n }\n\n .tsvp-layout-minimal .tsvp-controls {\n display: flex;\n align-items: center;\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: var(--tsvp-controls-height);\n background: linear-gradient(transparent, rgba(0,0,0,0.5));\n padding: 0 var(--tsvp-spacing-sm);\n opacity: 0;\n transition: opacity var(--tsvp-transition-normal);\n }\n\n .tsvp-layout-minimal:hover .tsvp-controls {\n opacity: 1;\n }\n\n .tsvp-layout-minimal .tsvp-progress {\n flex: 1;\n margin: 0 var(--tsvp-spacing-sm);\n }\n `,\n template: `\n <div class=\"tsvp-video-container\">\n <slot name=\"video\"></slot>\n </div>\n <div class=\"tsvp-controls\">\n <slot name=\"play-button\"></slot>\n <slot name=\"progress\"></slot>\n <slot name=\"time\"></slot>\n <slot name=\"fullscreen-button\"></slot>\n </div>\n `,\n}\n\n// =============================================================================\n// Cinema Layout\n// =============================================================================\n\nexport const cinemaLayout: LayoutConfig = {\n name: 'cinema',\n description: 'Immersive cinema-style layout with center play button',\n styles: `\n .tsvp-layout-cinema {\n --tsvp-controls-height: 60px;\n }\n\n .tsvp-layout-cinema .tsvp-center-play {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n opacity: 0;\n transition: opacity var(--tsvp-transition-normal), transform var(--tsvp-transition-normal);\n }\n\n .tsvp-layout-cinema:hover .tsvp-center-play,\n .tsvp-layout-cinema.tsvp-paused .tsvp-center-play {\n opacity: 1;\n }\n\n .tsvp-layout-cinema .tsvp-center-play:hover {\n transform: translate(-50%, -50%) scale(1.1);\n background: rgba(0, 0, 0, 0.8);\n }\n\n .tsvp-layout-cinema .tsvp-center-play svg {\n width: 40px;\n height: 40px;\n fill: white;\n }\n\n .tsvp-layout-cinema .tsvp-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n background: linear-gradient(transparent, rgba(0,0,0,0.8));\n padding: var(--tsvp-spacing-lg);\n opacity: 0;\n transform: translateY(10px);\n transition: all var(--tsvp-transition-slow);\n }\n\n .tsvp-layout-cinema:hover .tsvp-controls {\n opacity: 1;\n transform: translateY(0);\n }\n\n .tsvp-layout-cinema .tsvp-title {\n color: white;\n font-size: var(--tsvp-font-size-lg);\n font-weight: var(--tsvp-font-weight-bold);\n margin-bottom: var(--tsvp-spacing-md);\n }\n `,\n template: `\n <div class=\"tsvp-video-container\">\n <slot name=\"video\"></slot>\n </div>\n <div class=\"tsvp-poster\"></div>\n <div class=\"tsvp-captions\"></div>\n <div class=\"tsvp-center-play\">\n <svg viewBox=\"0 0 24 24\"><path d=\"M8 5v14l11-7z\"/></svg>\n </div>\n <div class=\"tsvp-controls\">\n <div class=\"tsvp-title\"></div>\n <div class=\"tsvp-controls-row\">\n <slot name=\"progress\"></slot>\n </div>\n <div class=\"tsvp-controls-row\">\n <slot name=\"play-button\"></slot>\n <slot name=\"volume\"></slot>\n <slot name=\"time\"></slot>\n <div style=\"flex:1\"></div>\n <slot name=\"captions-button\"></slot>\n <slot name=\"settings-button\"></slot>\n <slot name=\"fullscreen-button\"></slot>\n </div>\n </div>\n `,\n init: (container) => {\n const centerPlay = container.querySelector('.tsvp-center-play')\n if (centerPlay) {\n centerPlay.addEventListener('click', () => {\n container.dispatchEvent(new CustomEvent('tsvp:toggleplay'))\n })\n }\n },\n}\n\n// =============================================================================\n// YouTube-style Layout\n// =============================================================================\n\nexport const youtubeLayout: LayoutConfig = {\n name: 'youtube',\n description: 'YouTube-inspired layout with familiar controls',\n styles: `\n .tsvp-layout-youtube {\n --tsvp-color-primary: #ff0000;\n --tsvp-slider-track-height: 3px;\n --tsvp-slider-thumb-size: 13px;\n --tsvp-controls-height: 48px;\n }\n\n .tsvp-layout-youtube .tsvp-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n background: linear-gradient(transparent, rgba(0,0,0,0.7));\n opacity: 0;\n transition: opacity var(--tsvp-transition-normal);\n }\n\n .tsvp-layout-youtube:hover .tsvp-controls,\n .tsvp-layout-youtube.tsvp-paused .tsvp-controls {\n opacity: 1;\n }\n\n .tsvp-layout-youtube .tsvp-progress-container {\n position: relative;\n padding: 0 12px;\n margin-bottom: -4px;\n }\n\n .tsvp-layout-youtube .tsvp-controls-row {\n display: flex;\n align-items: center;\n height: var(--tsvp-controls-height);\n padding: 0 4px;\n }\n\n .tsvp-layout-youtube .tsvp-chapter-markers {\n position: absolute;\n left: 12px;\n right: 12px;\n top: 0;\n height: 100%;\n pointer-events: none;\n }\n\n .tsvp-layout-youtube .tsvp-end-screen {\n position: absolute;\n inset: 0;\n background: rgba(0, 0, 0, 0.85);\n display: none;\n align-items: center;\n justify-content: center;\n }\n\n .tsvp-layout-youtube.tsvp-ended .tsvp-end-screen {\n display: flex;\n }\n `,\n template: `\n <div class=\"tsvp-video-container\">\n <slot name=\"video\"></slot>\n </div>\n <div class=\"tsvp-poster\"></div>\n <div class=\"tsvp-captions\"></div>\n <div class=\"tsvp-controls\">\n <div class=\"tsvp-progress-container\">\n <slot name=\"progress\"></slot>\n <div class=\"tsvp-chapter-markers\"></div>\n <slot name=\"thumbnail-preview\"></slot>\n </div>\n <div class=\"tsvp-controls-row\">\n <slot name=\"play-button\"></slot>\n <slot name=\"next-button\"></slot>\n <slot name=\"volume\"></slot>\n <slot name=\"time\"></slot>\n <div style=\"flex:1\"></div>\n <slot name=\"autoplay-toggle\"></slot>\n <slot name=\"captions-button\"></slot>\n <slot name=\"settings-button\"></slot>\n <slot name=\"miniplayer-button\"></slot>\n <slot name=\"theater-button\"></slot>\n <slot name=\"fullscreen-button\"></slot>\n </div>\n </div>\n <div class=\"tsvp-end-screen\">\n <slot name=\"end-screen\"></slot>\n </div>\n `,\n}\n\n// =============================================================================\n// Vimeo-style Layout\n// =============================================================================\n\nexport const vimeoLayout: LayoutConfig = {\n name: 'vimeo',\n description: 'Vimeo-inspired clean layout',\n styles: `\n .tsvp-layout-vimeo {\n --tsvp-color-primary: #00adef;\n --tsvp-controls-height: 44px;\n --tsvp-slider-track-height: 4px;\n }\n\n .tsvp-layout-vimeo .tsvp-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n display: flex;\n flex-direction: column;\n background: linear-gradient(transparent, rgba(0,0,0,0.6));\n padding: var(--tsvp-spacing-sm);\n opacity: 0;\n transition: opacity var(--tsvp-transition-normal);\n }\n\n .tsvp-layout-vimeo:hover .tsvp-controls {\n opacity: 1;\n }\n\n .tsvp-layout-vimeo .tsvp-controls-row {\n display: flex;\n align-items: center;\n gap: var(--tsvp-spacing-sm);\n }\n\n .tsvp-layout-vimeo .tsvp-title-bar {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n padding: var(--tsvp-spacing-md);\n background: linear-gradient(rgba(0,0,0,0.6), transparent);\n color: white;\n opacity: 0;\n transition: opacity var(--tsvp-transition-normal);\n }\n\n .tsvp-layout-vimeo:hover .tsvp-title-bar {\n opacity: 1;\n }\n\n .tsvp-layout-vimeo .tsvp-like-button,\n .tsvp-layout-vimeo .tsvp-share-button {\n position: absolute;\n right: var(--tsvp-spacing-md);\n background: rgba(0,0,0,0.5);\n border-radius: var(--tsvp-radius-full);\n padding: var(--tsvp-spacing-sm);\n }\n\n .tsvp-layout-vimeo .tsvp-like-button { top: var(--tsvp-spacing-md); }\n .tsvp-layout-vimeo .tsvp-share-button { top: calc(var(--tsvp-spacing-md) + 48px); }\n `,\n template: `\n <div class=\"tsvp-video-container\">\n <slot name=\"video\"></slot>\n </div>\n <div class=\"tsvp-poster\"></div>\n <div class=\"tsvp-captions\"></div>\n <div class=\"tsvp-title-bar\">\n <slot name=\"title\"></slot>\n </div>\n <div class=\"tsvp-controls\">\n <div class=\"tsvp-controls-row\">\n <slot name=\"progress\"></slot>\n </div>\n <div class=\"tsvp-controls-row\">\n <slot name=\"play-button\"></slot>\n <slot name=\"volume\"></slot>\n <slot name=\"time\"></slot>\n <div style=\"flex:1\"></div>\n <slot name=\"captions-button\"></slot>\n <slot name=\"settings-button\"></slot>\n <slot name=\"pip-button\"></slot>\n <slot name=\"fullscreen-button\"></slot>\n </div>\n </div>\n `,\n}\n\n// =============================================================================\n// Audio Layout\n// =============================================================================\n\nexport const audioLayout: LayoutConfig = {\n name: 'audio',\n description: 'Compact layout for audio-only playback',\n styles: `\n .tsvp-layout-audio {\n --tsvp-controls-height: 64px;\n height: var(--tsvp-controls-height);\n background: var(--tsvp-color-background);\n border-radius: var(--tsvp-radius-lg);\n }\n\n .tsvp-layout-audio .tsvp-controls {\n display: flex;\n align-items: center;\n height: 100%;\n padding: 0 var(--tsvp-spacing-md);\n gap: var(--tsvp-spacing-md);\n }\n\n .tsvp-layout-audio .tsvp-artwork {\n width: 48px;\n height: 48px;\n border-radius: var(--tsvp-radius-sm);\n background: rgba(255,255,255,0.1);\n overflow: hidden;\n }\n\n .tsvp-layout-audio .tsvp-artwork img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n .tsvp-layout-audio .tsvp-info {\n flex: 0 0 auto;\n max-width: 200px;\n }\n\n .tsvp-layout-audio .tsvp-track-title {\n color: var(--tsvp-color-text);\n font-weight: var(--tsvp-font-weight-bold);\n font-size: var(--tsvp-font-size);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .tsvp-layout-audio .tsvp-track-artist {\n color: var(--tsvp-color-text);\n opacity: 0.7;\n font-size: var(--tsvp-font-size-sm);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .tsvp-layout-audio .tsvp-progress-section {\n flex: 1;\n display: flex;\n align-items: center;\n gap: var(--tsvp-spacing-sm);\n }\n\n .tsvp-layout-audio .tsvp-progress-bar {\n flex: 1;\n }\n `,\n template: `\n <div class=\"tsvp-controls\">\n <div class=\"tsvp-artwork\">\n <slot name=\"artwork\"></slot>\n </div>\n <slot name=\"play-button\"></slot>\n <div class=\"tsvp-info\">\n <div class=\"tsvp-track-title\"><slot name=\"title\"></slot></div>\n <div class=\"tsvp-track-artist\"><slot name=\"artist\"></slot></div>\n </div>\n <div class=\"tsvp-progress-section\">\n <slot name=\"current-time\"></slot>\n <div class=\"tsvp-progress-bar\">\n <slot name=\"progress\"></slot>\n </div>\n <slot name=\"duration\"></slot>\n </div>\n <slot name=\"volume\"></slot>\n </div>\n `,\n}\n\n// =============================================================================\n// Layout Registry\n// =============================================================================\n\nexport const layouts: Record<LayoutName, LayoutConfig> = {\n default: defaultLayout,\n minimal: minimalLayout,\n cinema: cinemaLayout,\n youtube: youtubeLayout,\n vimeo: vimeoLayout,\n audio: audioLayout,\n}\n\n// =============================================================================\n// Layout Manager\n// =============================================================================\n\nexport class LayoutManager {\n private container: HTMLElement\n private currentLayout: LayoutConfig | null = null\n private styleElement: HTMLStyleElement | null = null\n\n constructor(container: HTMLElement) {\n this.container = container\n }\n\n /**\n * Apply a layout to the container\n */\n apply(layout: LayoutName | LayoutConfig): void {\n // Get layout config\n const config = typeof layout === 'string' ? layouts[layout] : layout\n\n // Cleanup previous layout\n this.cleanup()\n\n // Apply new layout class\n this.container.classList.add(`tsvp-layout-${config.name}`)\n\n // Inject styles\n this.styleElement = document.createElement('style')\n this.styleElement.textContent = config.styles\n document.head.appendChild(this.styleElement)\n\n // Set template\n this.container.innerHTML = config.template\n\n // Initialize layout\n if (config.init) {\n config.init(this.container)\n }\n\n this.currentLayout = config\n }\n\n /**\n * Get current layout\n */\n getLayout(): LayoutConfig | null {\n return this.currentLayout\n }\n\n /**\n * Cleanup current layout\n */\n cleanup(): void {\n if (this.currentLayout) {\n // Remove layout class\n this.container.classList.remove(`tsvp-layout-${this.currentLayout.name}`)\n\n // Call destroy\n if (this.currentLayout.destroy) {\n this.currentLayout.destroy(this.container)\n }\n\n // Remove styles\n if (this.styleElement) {\n this.styleElement.remove()\n this.styleElement = null\n }\n\n this.currentLayout = null\n }\n }\n\n /**\n * Destroy layout manager\n */\n destroy(): void {\n this.cleanup()\n }\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\nexport function createLayoutManager(container: HTMLElement): LayoutManager {\n return new LayoutManager(container)\n}\n\nexport function getLayout(name: LayoutName): LayoutConfig {\n return layouts[name]\n}\n\nexport function registerLayout(config: LayoutConfig): void {\n (layouts as any)[config.name] = config\n}\n",
|
|
21
|
+
"/**\n * Video Analytics Plugin\n *\n * Deep integration with ts-analytics for comprehensive video tracking.\n *\n * @module plugins/analytics\n */\n\nimport type { Player } from '../player'\nimport type { VideoQuality, TextTrack, AudioTrack } from '../types'\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface VideoAnalyticsConfig {\n /** Site ID for analytics */\n siteId: string\n /** Analytics API endpoint */\n apiEndpoint: string\n /** Video ID (unique identifier) */\n videoId: string\n /** Video title */\n videoTitle?: string\n /** Video duration (auto-detected if not provided) */\n duration?: number\n /** Content type (vod, live, etc.) */\n contentType?: 'vod' | 'live' | 'dvr'\n /** Content category */\n category?: string\n /** Content tags */\n tags?: string[]\n /** Creator/channel ID */\n creatorId?: string\n /** Series/playlist ID */\n seriesId?: string\n /** Episode number */\n episode?: number\n /** Season number */\n season?: number\n /** Track playback milestones (percentages) */\n milestones?: number[]\n /** Track heartbeat interval (seconds) */\n heartbeatInterval?: number\n /** Track quality changes */\n trackQualityChanges?: boolean\n /** Track seeking behavior */\n trackSeeking?: boolean\n /** Track buffering */\n trackBuffering?: boolean\n /** Track errors */\n trackErrors?: boolean\n /** Track engagement (pauses, replays) */\n trackEngagement?: boolean\n /** Custom properties to include */\n customProperties?: Record<string, string | number | boolean>\n /** Session ID (auto-generated if not provided) */\n sessionId?: string\n /** Visitor ID (auto-generated if not provided) */\n visitorId?: string\n /** Disable analytics */\n disabled?: boolean\n}\n\nexport interface VideoEvent {\n /** Event name */\n name: VideoEventName\n /** Timestamp */\n timestamp: Date\n /** Current playback time */\n currentTime: number\n /** Video duration */\n duration: number\n /** Additional properties */\n properties?: Record<string, unknown>\n}\n\nexport type VideoEventName =\n | 'video_start'\n | 'video_play'\n | 'video_pause'\n | 'video_resume'\n | 'video_seek'\n | 'video_buffer_start'\n | 'video_buffer_end'\n | 'video_quality_change'\n | 'video_volume_change'\n | 'video_mute'\n | 'video_unmute'\n | 'video_fullscreen_enter'\n | 'video_fullscreen_exit'\n | 'video_pip_enter'\n | 'video_pip_exit'\n | 'video_caption_enable'\n | 'video_caption_disable'\n | 'video_caption_change'\n | 'video_audio_track_change'\n | 'video_speed_change'\n | 'video_error'\n | 'video_milestone'\n | 'video_quartile'\n | 'video_complete'\n | 'video_heartbeat'\n | 'video_engagement'\n | 'video_ad_start'\n | 'video_ad_complete'\n | 'video_ad_skip'\n | 'video_ad_click'\n\nexport interface VideoAnalyticsState {\n /** Has video started playing */\n hasStarted: boolean\n /** Has video completed */\n hasCompleted: boolean\n /** Total watch time in seconds */\n watchTime: number\n /** Number of pauses */\n pauseCount: number\n /** Number of seeks */\n seekCount: number\n /** Number of buffer events */\n bufferCount: number\n /** Total buffer time in seconds */\n bufferTime: number\n /** Number of quality changes */\n qualityChangeCount: number\n /** Reached milestones */\n reachedMilestones: number[]\n /** Reached quartiles */\n reachedQuartiles: number[]\n /** Playback start time */\n playbackStartTime: number | null\n /** Last heartbeat time */\n lastHeartbeat: number\n /** Is currently buffering */\n isBuffering: boolean\n /** Buffer start time */\n bufferStartTime: number | null\n}\n\n// =============================================================================\n// Default Configuration\n// =============================================================================\n\nconst DEFAULT_MILESTONES = [10, 25, 50, 75, 90, 100]\nconst DEFAULT_HEARTBEAT_INTERVAL = 30 // seconds\n\n// =============================================================================\n// Video Analytics Class\n// =============================================================================\n\nexport class VideoAnalytics {\n private player: Player | null = null\n private config: VideoAnalyticsConfig\n private state: VideoAnalyticsState\n private heartbeatTimer: ReturnType<typeof setInterval> | null = null\n private eventQueue: VideoEvent[] = []\n private flushTimer: ReturnType<typeof setTimeout> | null = null\n private sessionId: string\n private visitorId: string\n private unsubscribers: Array<() => void> = []\n\n constructor(config: VideoAnalyticsConfig) {\n this.config = {\n milestones: DEFAULT_MILESTONES,\n heartbeatInterval: DEFAULT_HEARTBEAT_INTERVAL,\n trackQualityChanges: true,\n trackSeeking: true,\n trackBuffering: true,\n trackErrors: true,\n trackEngagement: true,\n ...config,\n }\n\n this.sessionId = config.sessionId || this.generateId()\n this.visitorId = config.visitorId || this.getOrCreateVisitorId()\n\n this.state = this.createInitialState()\n }\n\n /**\n * Create initial analytics state\n */\n private createInitialState(): VideoAnalyticsState {\n return {\n hasStarted: false,\n hasCompleted: false,\n watchTime: 0,\n pauseCount: 0,\n seekCount: 0,\n bufferCount: 0,\n bufferTime: 0,\n qualityChangeCount: 0,\n reachedMilestones: [],\n reachedQuartiles: [],\n playbackStartTime: null,\n lastHeartbeat: 0,\n isBuffering: false,\n bufferStartTime: null,\n }\n }\n\n /**\n * Attach to a player instance\n */\n attach(player: Player): void {\n if (this.config.disabled) return\n\n this.player = player\n this.attachEventListeners()\n this.startHeartbeat()\n }\n\n /**\n * Detach from player\n */\n detach(): void {\n this.stopHeartbeat()\n this.unsubscribers.forEach(unsub => unsub())\n this.unsubscribers = []\n this.flush()\n this.player = null\n }\n\n /**\n * Attach event listeners to player\n */\n private attachEventListeners(): void {\n if (!this.player) return\n\n // Play events\n this.on('play', () => {\n if (!this.state.hasStarted) {\n this.state.hasStarted = true\n this.state.playbackStartTime = Date.now()\n this.trackEvent('video_start')\n }\n else {\n this.trackEvent('video_resume')\n }\n this.trackEvent('video_play')\n })\n\n // Pause\n this.on('pause', () => {\n this.state.pauseCount++\n this.updateWatchTime()\n this.trackEvent('video_pause')\n })\n\n // Seeking\n if (this.config.trackSeeking) {\n this.on('seeking', () => {\n this.state.seekCount++\n })\n\n this.on('seeked', () => {\n const playerState = this.player?.state\n this.trackEvent('video_seek', {\n seekTo: playerState?.currentTime || 0,\n })\n })\n }\n\n // Buffering\n if (this.config.trackBuffering) {\n this.on('waiting', () => {\n if (!this.state.isBuffering) {\n this.state.isBuffering = true\n this.state.bufferStartTime = Date.now()\n this.state.bufferCount++\n this.trackEvent('video_buffer_start')\n }\n })\n\n this.on('playing', () => {\n if (this.state.isBuffering) {\n this.state.isBuffering = false\n if (this.state.bufferStartTime) {\n this.state.bufferTime += (Date.now() - this.state.bufferStartTime) / 1000\n }\n this.trackEvent('video_buffer_end', {\n bufferDuration: this.state.bufferStartTime\n ? (Date.now() - this.state.bufferStartTime) / 1000\n : 0,\n })\n this.state.bufferStartTime = null\n }\n })\n }\n\n // Time update for milestones\n this.on('timeupdate', () => {\n this.checkMilestones()\n })\n\n // Quality changes\n if (this.config.trackQualityChanges) {\n this.on('qualitychange', (quality: VideoQuality | null) => {\n this.state.qualityChangeCount++\n this.trackEvent('video_quality_change', {\n quality: quality ? `${quality.height}p` : 'auto',\n bitrate: quality?.bitrate,\n })\n })\n }\n\n // Volume changes\n this.on('volumechange', () => {\n const playerState = this.player?.state\n if (playerState?.muted) {\n this.trackEvent('video_mute')\n }\n else {\n this.trackEvent('video_volume_change', {\n volume: playerState?.volume,\n })\n }\n })\n\n // Fullscreen\n this.on('fullscreenchange', (isFullscreen: boolean) => {\n this.trackEvent(isFullscreen ? 'video_fullscreen_enter' : 'video_fullscreen_exit')\n })\n\n // PiP\n this.on('pipchange', (isPiP: boolean) => {\n this.trackEvent(isPiP ? 'video_pip_enter' : 'video_pip_exit')\n })\n\n // Captions\n this.on('texttrackchange', (track: TextTrack | null) => {\n if (track) {\n this.trackEvent('video_caption_change', {\n language: track.language,\n label: track.label,\n })\n }\n else {\n this.trackEvent('video_caption_disable')\n }\n })\n\n // Audio track\n this.on('audiotrackchange', (track: AudioTrack | null) => {\n if (track) {\n this.trackEvent('video_audio_track_change', {\n language: track.language,\n label: track.label,\n })\n }\n })\n\n // Speed change\n this.on('ratechange', (rate: number) => {\n this.trackEvent('video_speed_change', { speed: rate })\n })\n\n // Ended\n this.on('ended', () => {\n if (!this.state.hasCompleted) {\n this.state.hasCompleted = true\n this.updateWatchTime()\n this.trackEvent('video_complete', {\n watchTime: this.state.watchTime,\n pauseCount: this.state.pauseCount,\n seekCount: this.state.seekCount,\n bufferCount: this.state.bufferCount,\n bufferTime: this.state.bufferTime,\n })\n }\n })\n\n // Errors\n if (this.config.trackErrors) {\n this.on('error', (error: any) => {\n this.trackEvent('video_error', {\n code: error?.code,\n message: error?.message,\n })\n })\n }\n }\n\n /**\n * Helper to subscribe to player events\n */\n private on(event: string, handler: (...args: any[]) => void): void {\n if (!this.player) return\n this.player.on(event as any, handler)\n this.unsubscribers.push(() => this.player?.off(event as any, handler))\n }\n\n /**\n * Check and track milestones\n */\n private checkMilestones(): void {\n const playerState = this.player?.state\n if (!playerState || !playerState.duration) return\n\n const progress = (playerState.currentTime / playerState.duration) * 100\n\n // Check custom milestones\n for (const milestone of this.config.milestones || []) {\n if (progress >= milestone && !this.state.reachedMilestones.includes(milestone)) {\n this.state.reachedMilestones.push(milestone)\n this.trackEvent('video_milestone', { milestone })\n }\n }\n\n // Check quartiles (25%, 50%, 75%, 100%)\n const quartiles = [25, 50, 75, 100]\n for (const quartile of quartiles) {\n if (progress >= quartile && !this.state.reachedQuartiles.includes(quartile)) {\n this.state.reachedQuartiles.push(quartile)\n this.trackEvent('video_quartile', { quartile })\n }\n }\n }\n\n /**\n * Update watch time\n */\n private updateWatchTime(): void {\n if (this.state.playbackStartTime) {\n this.state.watchTime += (Date.now() - this.state.playbackStartTime) / 1000\n this.state.playbackStartTime = Date.now()\n }\n }\n\n /**\n * Start heartbeat tracking\n */\n private startHeartbeat(): void {\n if (this.heartbeatTimer) return\n\n const interval = (this.config.heartbeatInterval || DEFAULT_HEARTBEAT_INTERVAL) * 1000\n\n this.heartbeatTimer = setInterval(() => {\n const playerState = this.player?.state\n if (playerState?.playbackState === 'playing') {\n this.updateWatchTime()\n this.trackEvent('video_heartbeat', {\n watchTime: this.state.watchTime,\n currentTime: playerState.currentTime,\n })\n }\n }, interval)\n }\n\n /**\n * Stop heartbeat tracking\n */\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * Track an event\n */\n trackEvent(name: VideoEventName, properties?: Record<string, unknown>): void {\n if (this.config.disabled) return\n\n const playerState = this.player?.state\n\n const event: VideoEvent = {\n name,\n timestamp: new Date(),\n currentTime: playerState?.currentTime || 0,\n duration: playerState?.duration || this.config.duration || 0,\n properties,\n }\n\n this.eventQueue.push(event)\n this.scheduleFlush()\n }\n\n /**\n * Schedule event flush\n */\n private scheduleFlush(): void {\n if (this.flushTimer) return\n\n this.flushTimer = setTimeout(() => {\n this.flush()\n this.flushTimer = null\n }, 1000) // Batch events for 1 second\n }\n\n /**\n * Flush events to analytics endpoint\n */\n async flush(): Promise<void> {\n if (this.eventQueue.length === 0) return\n\n const events = [...this.eventQueue]\n this.eventQueue = []\n\n try {\n await this.sendEvents(events)\n }\n catch (error) {\n // Re-queue on failure\n console.error('Failed to send analytics events:', error)\n this.eventQueue.unshift(...events)\n }\n }\n\n /**\n * Send events to analytics endpoint\n */\n private async sendEvents(events: VideoEvent[]): Promise<void> {\n const payload = events.map(event => this.buildEventPayload(event))\n\n // Send to ts-analytics /collect endpoint\n for (const p of payload) {\n await fetch(this.config.apiEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(p),\n keepalive: true,\n })\n }\n }\n\n /**\n * Build event payload for ts-analytics\n */\n private buildEventPayload(event: VideoEvent): Record<string, unknown> {\n return {\n s: this.config.siteId, // siteId\n sid: this.sessionId, // sessionId\n vid: this.visitorId, // visitorId\n e: 'event', // event type (custom event)\n u: typeof window !== 'undefined' ? window.location.href : '', // URL\n p: {\n // Event properties\n eventName: event.name,\n videoId: this.config.videoId,\n videoTitle: this.config.videoTitle,\n contentType: this.config.contentType,\n category: this.config.category,\n creatorId: this.config.creatorId,\n seriesId: this.config.seriesId,\n episode: this.config.episode,\n season: this.config.season,\n currentTime: event.currentTime,\n duration: event.duration,\n progress: event.duration > 0 ? (event.currentTime / event.duration) * 100 : 0,\n watchTime: this.state.watchTime,\n ...this.config.customProperties,\n ...event.properties,\n },\n }\n }\n\n /**\n * Generate unique ID\n */\n private generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n }\n\n /**\n * Get or create visitor ID\n */\n private getOrCreateVisitorId(): string {\n if (typeof localStorage === 'undefined') {\n return this.generateId()\n }\n\n const key = 'tsvp_visitor_id'\n let visitorId = localStorage.getItem(key)\n\n if (!visitorId) {\n visitorId = this.generateId()\n localStorage.setItem(key, visitorId)\n }\n\n return visitorId\n }\n\n /**\n * Get current analytics state\n */\n getState(): VideoAnalyticsState {\n return { ...this.state }\n }\n\n /**\n * Get session summary\n */\n getSummary(): Record<string, unknown> {\n return {\n videoId: this.config.videoId,\n sessionId: this.sessionId,\n hasStarted: this.state.hasStarted,\n hasCompleted: this.state.hasCompleted,\n watchTime: this.state.watchTime,\n pauseCount: this.state.pauseCount,\n seekCount: this.state.seekCount,\n bufferCount: this.state.bufferCount,\n bufferTime: this.state.bufferTime,\n qualityChangeCount: this.state.qualityChangeCount,\n reachedMilestones: this.state.reachedMilestones,\n completionRate: this.state.reachedQuartiles.length > 0\n ? Math.max(...this.state.reachedQuartiles)\n : 0,\n }\n }\n\n /**\n * Reset analytics state\n */\n reset(): void {\n this.flush()\n this.state = this.createInitialState()\n this.sessionId = this.generateId()\n }\n\n /**\n * Destroy analytics instance\n */\n destroy(): void {\n this.detach()\n if (this.flushTimer) {\n clearTimeout(this.flushTimer)\n }\n }\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\nexport function createVideoAnalytics(config: VideoAnalyticsConfig): VideoAnalytics {\n return new VideoAnalytics(config)\n}\n\n// =============================================================================\n// Player Plugin Integration\n// =============================================================================\n\n/**\n * Analytics plugin for Player\n */\nexport function analyticsPlugin(config: Omit<VideoAnalyticsConfig, 'videoId'>): { name: string, install: (player: Player, videoConfig: { videoId: string, videoTitle?: string }) => () => void } {\n return {\n name: 'analytics',\n\n install(player: Player, videoConfig: { videoId: string; videoTitle?: string }) {\n const analytics = createVideoAnalytics({\n ...config,\n ...videoConfig,\n })\n\n analytics.attach(player)\n\n // Expose on player\n ;(player as any).analytics = analytics\n\n return () => {\n analytics.destroy()\n delete (player as any).analytics\n }\n },\n }\n}\n",
|
|
22
|
+
"/**\n * Video Ads Plugin\n *\n * VAST/VPAID ad support for video playback.\n *\n * @module plugins/ads\n */\n\nimport type { Player } from '../player'\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport type AdType = 'linear' | 'nonlinear' | 'companion'\nexport type AdBreakType = 'preroll' | 'midroll' | 'postroll'\n\nexport interface AdConfig {\n /** VAST tag URL or inline VAST XML */\n vastUrl?: string\n /** VAST XML content */\n vastXml?: string\n /** VMAP URL for ad schedule */\n vmapUrl?: string\n /** Ad schedule */\n schedule?: AdBreak[]\n /** Skip delay in seconds (0 = no skip, -1 = use VAST value) */\n skipDelay?: number\n /** Maximum number of redirects to follow */\n maxRedirects?: number\n /** Timeout for ad requests in ms */\n timeout?: number\n /** Enable VPAID */\n vpaidEnabled?: boolean\n /** Companion ad container selectors */\n companionContainers?: Record<string, string>\n /** Preload ads */\n preloadAds?: boolean\n /** Debug mode */\n debug?: boolean\n}\n\nexport interface AdBreak {\n /** Break type */\n type: AdBreakType\n /** Time offset in seconds (for midrolls) */\n timeOffset?: number\n /** VAST URL */\n vastUrl?: string\n /** VAST XML */\n vastXml?: string\n /** Has played */\n played?: boolean\n}\n\nexport interface Ad {\n /** Ad ID */\n id: string\n /** Ad type */\n type: AdType\n /** Ad system */\n adSystem?: string\n /** Ad title */\n title?: string\n /** Ad description */\n description?: string\n /** Ad duration in seconds */\n duration: number\n /** Skip offset in seconds */\n skipOffset?: number\n /** Click through URL */\n clickThroughUrl?: string\n /** Media files */\n mediaFiles: AdMediaFile[]\n /** Companion ads */\n companions?: CompanionAd[]\n /** Tracking events */\n trackingEvents: AdTrackingEvent[]\n /** VPAID script */\n vpaidScript?: string\n /** Icons */\n icons?: AdIcon[]\n}\n\nexport interface AdMediaFile {\n /** Media URL */\n url: string\n /** MIME type */\n type: string\n /** Width */\n width: number\n /** Height */\n height: number\n /** Bitrate */\n bitrate?: number\n /** Codec */\n codec?: string\n /** Delivery type */\n delivery: 'progressive' | 'streaming'\n}\n\nexport interface CompanionAd {\n /** Width */\n width: number\n /** Height */\n height: number\n /** Resource URL (image, iframe, HTML) */\n resourceUrl: string\n /** Resource type */\n resourceType: 'static' | 'iframe' | 'html'\n /** Click through URL */\n clickThroughUrl?: string\n /** Alt text */\n altText?: string\n /** Tracking pixel */\n trackingUrl?: string\n}\n\nexport interface AdTrackingEvent {\n /** Event name */\n event: string\n /** Tracking URL */\n url: string\n /** Offset (for progress events) */\n offset?: number\n}\n\nexport interface AdIcon {\n /** Icon URL */\n url: string\n /** Width */\n width: number\n /** Height */\n height: number\n /** Position */\n xPosition: string\n /** Position */\n yPosition: string\n /** Click URL */\n clickUrl?: string\n}\n\nexport interface AdState {\n /** Is ad playing */\n isPlaying: boolean\n /** Current ad */\n currentAd: Ad | null\n /** Current ad break */\n currentBreak: AdBreak | null\n /** Time remaining */\n timeRemaining: number\n /** Can skip */\n canSkip: boolean\n /** Skip countdown */\n skipCountdown: number\n /** Is loading */\n isLoading: boolean\n /** Error */\n error: string | null\n}\n\n// =============================================================================\n// VAST Parser\n// =============================================================================\n\nexport class VASTParser {\n private maxRedirects: number\n private timeout: number\n private redirectCount = 0\n\n constructor(options: { maxRedirects?: number; timeout?: number } = {}) {\n this.maxRedirects = options.maxRedirects ?? 5\n this.timeout = options.timeout ?? 10000\n }\n\n /**\n * Parse VAST from URL\n */\n async parseFromUrl(url: string): Promise<Ad[]> {\n this.redirectCount = 0\n return this.fetchAndParse(url)\n }\n\n /**\n * Parse VAST from XML string\n */\n parseFromXml(xml: string): Ad[] {\n const parser = new DOMParser()\n const doc = parser.parseFromString(xml, 'text/xml')\n return this.parseDocument(doc)\n }\n\n /**\n * Fetch and parse VAST\n */\n private async fetchAndParse(url: string): Promise<Ad[]> {\n if (this.redirectCount >= this.maxRedirects) {\n throw new Error('Too many VAST redirects')\n }\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), this.timeout)\n\n try {\n const response = await fetch(url, { signal: controller.signal })\n const text = await response.text()\n const parser = new DOMParser()\n const doc = parser.parseFromString(text, 'text/xml')\n\n // Check for wrapper (redirect)\n const wrapper = doc.querySelector('Wrapper')\n if (wrapper) {\n const vastAdTagUri = wrapper.querySelector('VASTAdTagURI')?.textContent?.trim()\n if (vastAdTagUri) {\n this.redirectCount++\n return this.fetchAndParse(vastAdTagUri)\n }\n }\n\n return this.parseDocument(doc)\n }\n finally {\n clearTimeout(timeoutId)\n }\n }\n\n /**\n * Parse VAST document\n */\n private parseDocument(doc: Document): Ad[] {\n const ads: Ad[] = []\n const adElements = doc.querySelectorAll('Ad')\n\n adElements.forEach((adEl) => {\n const inLine = adEl.querySelector('InLine')\n if (!inLine) return\n\n const ad: Ad = {\n id: adEl.getAttribute('id') || this.generateId(),\n type: 'linear',\n adSystem: inLine.querySelector('AdSystem')?.textContent?.trim(),\n title: inLine.querySelector('AdTitle')?.textContent?.trim(),\n description: inLine.querySelector('Description')?.textContent?.trim(),\n duration: 0,\n mediaFiles: [],\n trackingEvents: [],\n }\n\n // Parse Linear\n const linear = inLine.querySelector('Linear')\n if (linear) {\n // Duration\n const durationStr = linear.querySelector('Duration')?.textContent?.trim()\n if (durationStr) {\n ad.duration = this.parseDuration(durationStr)\n }\n\n // Skip offset\n const skipOffset = linear.getAttribute('skipoffset')\n if (skipOffset) {\n ad.skipOffset = this.parseOffset(skipOffset, ad.duration)\n }\n\n // Media files\n linear.querySelectorAll('MediaFile').forEach((mf) => {\n const url = mf.textContent?.trim()\n if (url) {\n ad.mediaFiles.push({\n url,\n type: mf.getAttribute('type') || 'video/mp4',\n width: Number.parseInt(mf.getAttribute('width') || '0', 10),\n height: Number.parseInt(mf.getAttribute('height') || '0', 10),\n bitrate: mf.getAttribute('bitrate')\n ? Number.parseInt(mf.getAttribute('bitrate')!, 10)\n : undefined,\n delivery: (mf.getAttribute('delivery') as any) || 'progressive',\n })\n }\n })\n\n // Click through\n const clickThrough = linear.querySelector('VideoClicks > ClickThrough')\n if (clickThrough) {\n ad.clickThroughUrl = clickThrough.textContent?.trim()\n }\n\n // Tracking events\n linear.querySelectorAll('TrackingEvents > Tracking').forEach((tracking) => {\n const event = tracking.getAttribute('event')\n const url = tracking.textContent?.trim()\n if (event && url) {\n const trackingEvent: AdTrackingEvent = { event, url }\n\n // Handle progress offset\n if (event === 'progress') {\n const offset = tracking.getAttribute('offset')\n if (offset) {\n trackingEvent.offset = this.parseOffset(offset, ad.duration)\n }\n }\n\n ad.trackingEvents.push(trackingEvent)\n }\n })\n\n // Icons\n linear.querySelectorAll('Icons > Icon').forEach((icon) => {\n const staticResource = icon.querySelector('StaticResource')\n if (staticResource?.textContent) {\n ad.icons = ad.icons || []\n ad.icons.push({\n url: staticResource.textContent.trim(),\n width: Number.parseInt(icon.getAttribute('width') || '0', 10),\n height: Number.parseInt(icon.getAttribute('height') || '0', 10),\n xPosition: icon.getAttribute('xPosition') || 'right',\n yPosition: icon.getAttribute('yPosition') || 'top',\n clickUrl: icon.querySelector('IconClicks > IconClickThrough')?.textContent?.trim(),\n })\n }\n })\n }\n\n // Parse Companion ads\n inLine.querySelectorAll('CompanionAds > Companion').forEach((comp) => {\n ad.companions = ad.companions || []\n\n const staticResource = comp.querySelector('StaticResource')\n const iframeResource = comp.querySelector('IFrameResource')\n const htmlResource = comp.querySelector('HTMLResource')\n\n let resourceUrl = ''\n let resourceType: CompanionAd['resourceType'] = 'static'\n\n if (staticResource?.textContent) {\n resourceUrl = staticResource.textContent.trim()\n resourceType = 'static'\n }\n else if (iframeResource?.textContent) {\n resourceUrl = iframeResource.textContent.trim()\n resourceType = 'iframe'\n }\n else if (htmlResource?.textContent) {\n resourceUrl = htmlResource.textContent.trim()\n resourceType = 'html'\n }\n\n if (resourceUrl) {\n ad.companions.push({\n width: Number.parseInt(comp.getAttribute('width') || '0', 10),\n height: Number.parseInt(comp.getAttribute('height') || '0', 10),\n resourceUrl,\n resourceType,\n clickThroughUrl: comp.querySelector('CompanionClickThrough')?.textContent?.trim(),\n altText: comp.getAttribute('altText') || undefined,\n trackingUrl: comp.querySelector('TrackingEvents > Tracking')?.textContent?.trim(),\n })\n }\n })\n\n if (ad.mediaFiles.length > 0) {\n ads.push(ad)\n }\n })\n\n return ads\n }\n\n /**\n * Parse duration string (HH:MM:SS.mmm)\n */\n private parseDuration(duration: string): number {\n const parts = duration.split(':')\n if (parts.length !== 3) return 0\n\n const hours = Number.parseFloat(parts[0])\n const minutes = Number.parseFloat(parts[1])\n const seconds = Number.parseFloat(parts[2])\n\n return hours * 3600 + minutes * 60 + seconds\n }\n\n /**\n * Parse offset (time or percentage)\n */\n private parseOffset(offset: string, duration: number): number {\n if (offset.endsWith('%')) {\n const percent = Number.parseFloat(offset) / 100\n return duration * percent\n }\n return this.parseDuration(offset)\n }\n\n /**\n * Generate unique ID\n */\n private generateId(): string {\n return `ad-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`\n }\n}\n\n// =============================================================================\n// Ads Manager\n// =============================================================================\n\nexport class AdsManager {\n private player: Player | null = null\n private config: AdConfig\n private parser: VASTParser\n private state: AdState\n private container: HTMLElement | null = null\n private adVideo: HTMLVideoElement | null = null\n private adContainer: HTMLElement | null = null\n private skipButton: HTMLButtonElement | null = null\n private adBreaks: AdBreak[] = []\n private currentAds: Ad[] = []\n private currentAdIndex = 0\n private skipTimer: ReturnType<typeof setInterval> | null = null\n private contentPaused = false\n\n // Event handlers\n // eslint-disable-next-line pickier/no-unused-vars\n private onAdStart: ((_ad: Ad) => void) | null = null\n // eslint-disable-next-line pickier/no-unused-vars\n private onAdComplete: ((_ad: Ad) => void) | null = null\n // eslint-disable-next-line pickier/no-unused-vars\n private onAdSkip: ((_ad: Ad) => void) | null = null\n // eslint-disable-next-line pickier/no-unused-vars\n private onAdClick: ((_ad: Ad) => void) | null = null\n // eslint-disable-next-line pickier/no-unused-vars\n private onAdErrorCallback: ((_error: string) => void) | null = null\n private onAllAdsComplete: (() => void) | null = null\n\n constructor(config: AdConfig = {}) {\n this.config = {\n skipDelay: 5,\n maxRedirects: 5,\n timeout: 10000,\n vpaidEnabled: false,\n preloadAds: true,\n debug: false,\n ...config,\n }\n\n this.parser = new VASTParser({\n maxRedirects: this.config.maxRedirects,\n timeout: this.config.timeout,\n })\n\n this.state = {\n isPlaying: false,\n currentAd: null,\n currentBreak: null,\n timeRemaining: 0,\n canSkip: false,\n skipCountdown: 0,\n isLoading: false,\n error: null,\n }\n\n // Parse schedule\n if (this.config.schedule) {\n this.adBreaks = this.config.schedule\n }\n else if (this.config.vastUrl || this.config.vastXml) {\n // Single preroll\n this.adBreaks = [{\n type: 'preroll',\n vastUrl: this.config.vastUrl,\n vastXml: this.config.vastXml,\n }]\n }\n }\n\n /**\n * Attach to player\n */\n attach(player: Player, container: HTMLElement): void {\n this.player = player\n this.container = container\n\n this.createAdContainer()\n this.attachPlayerEvents()\n\n // Preload ads\n if (this.config.preloadAds) {\n this.preloadAds()\n }\n }\n\n /**\n * Create ad container\n */\n private createAdContainer(): void {\n if (!this.container) return\n\n this.adContainer = document.createElement('div')\n this.adContainer.className = 'tsvp-ad-container'\n this.adContainer.style.cssText = `\n position: absolute;\n inset: 0;\n z-index: 50;\n display: none;\n background: #000;\n `\n\n // Ad video\n this.adVideo = document.createElement('video')\n this.adVideo.style.cssText = `\n width: 100%;\n height: 100%;\n object-fit: contain;\n `\n this.adVideo.playsInline = true\n\n // Ad overlay\n const overlay = document.createElement('div')\n overlay.className = 'tsvp-ad-overlay'\n overlay.style.cssText = `\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n pointer-events: none;\n `\n\n // Ad info\n const adInfo = document.createElement('div')\n adInfo.className = 'tsvp-ad-info'\n adInfo.style.cssText = `\n position: absolute;\n top: 12px;\n left: 12px;\n padding: 6px 12px;\n background: rgba(0, 0, 0, 0.7);\n color: #fff;\n font-size: 12px;\n font-family: var(--tsvp-font-family, sans-serif);\n border-radius: 4px;\n `\n adInfo.textContent = 'Ad'\n\n // Skip button\n this.skipButton = document.createElement('button')\n this.skipButton.className = 'tsvp-ad-skip'\n this.skipButton.style.cssText = `\n position: absolute;\n bottom: 80px;\n right: 12px;\n padding: 10px 16px;\n background: rgba(0, 0, 0, 0.8);\n color: #fff;\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 4px;\n font-size: 14px;\n font-family: var(--tsvp-font-family, sans-serif);\n cursor: pointer;\n pointer-events: auto;\n display: none;\n `\n this.skipButton.addEventListener('click', () => this.skipAd())\n\n // Learn more button\n const learnMore = document.createElement('button')\n learnMore.className = 'tsvp-ad-learn-more'\n learnMore.style.cssText = `\n position: absolute;\n bottom: 80px;\n left: 12px;\n padding: 10px 16px;\n background: var(--tsvp-color-primary, #00a8ff);\n color: #fff;\n border: none;\n border-radius: 4px;\n font-size: 14px;\n font-family: var(--tsvp-font-family, sans-serif);\n cursor: pointer;\n pointer-events: auto;\n display: none;\n `\n learnMore.textContent = 'Learn More'\n learnMore.addEventListener('click', () => this.handleAdClick())\n\n overlay.appendChild(adInfo)\n overlay.appendChild(this.skipButton)\n overlay.appendChild(learnMore)\n\n this.adContainer.appendChild(this.adVideo)\n this.adContainer.appendChild(overlay)\n this.container.appendChild(this.adContainer)\n\n // Ad video events\n this.adVideo.addEventListener('timeupdate', () => this.onAdTimeUpdate())\n this.adVideo.addEventListener('ended', () => this.onAdEnded())\n this.adVideo.addEventListener('error', () => this.handleAdError())\n }\n\n /**\n * Attach player events\n */\n private attachPlayerEvents(): void {\n if (!this.player) return\n\n // Check for preroll on first play\n let firstPlay = true\n this.player.on('play', async () => {\n if (firstPlay) {\n firstPlay = false\n const preroll = this.adBreaks.find(b => b.type === 'preroll' && !b.played)\n if (preroll) {\n await this.playAdBreak(preroll)\n }\n }\n })\n\n // Check for midrolls\n this.player.on('timeupdate', () => {\n const state = this.player?.state\n if (!state) return\n\n for (const adBreak of this.adBreaks) {\n if (\n adBreak.type === 'midroll'\n && !adBreak.played\n && adBreak.timeOffset\n && state.currentTime >= adBreak.timeOffset\n ) {\n this.playAdBreak(adBreak)\n break\n }\n }\n })\n\n // Check for postroll\n this.player.on('ended', async () => {\n const postroll = this.adBreaks.find(b => b.type === 'postroll' && !b.played)\n if (postroll) {\n await this.playAdBreak(postroll)\n }\n })\n }\n\n /**\n * Preload ads\n */\n private async preloadAds(): Promise<void> {\n for (const adBreak of this.adBreaks) {\n if (adBreak.vastUrl) {\n try {\n // Pre-parse VAST\n await this.parser.parseFromUrl(adBreak.vastUrl)\n }\n catch (error) {\n this.log('Failed to preload ad:', error)\n }\n }\n }\n }\n\n /**\n * Play ad break\n */\n async playAdBreak(adBreak: AdBreak): Promise<void> {\n if (this.state.isPlaying) return\n\n this.state.currentBreak = adBreak\n this.state.isLoading = true\n\n // Pause content\n this.pauseContent()\n\n try {\n // Parse VAST\n if (adBreak.vastUrl) {\n this.currentAds = await this.parser.parseFromUrl(adBreak.vastUrl)\n }\n else if (adBreak.vastXml) {\n this.currentAds = this.parser.parseFromXml(adBreak.vastXml)\n }\n\n if (this.currentAds.length === 0) {\n throw new Error('No ads found in VAST response')\n }\n\n this.currentAdIndex = 0\n adBreak.played = true\n\n await this.playAd(this.currentAds[0])\n }\n catch (error) {\n this.state.error = (error as Error).message\n this.state.isLoading = false\n this.onAdErrorCallback?.((error as Error).message)\n this.resumeContent()\n }\n }\n\n /**\n * Play single ad\n */\n private async playAd(ad: Ad): Promise<void> {\n if (!this.adVideo || !this.adContainer) return\n\n this.state.currentAd = ad\n this.state.isPlaying = true\n this.state.isLoading = false\n this.state.timeRemaining = ad.duration\n this.state.canSkip = false\n this.state.skipCountdown = this.config.skipDelay || 0\n\n // Select best media file\n const mediaFile = this.selectMediaFile(ad.mediaFiles)\n if (!mediaFile) {\n throw new Error('No suitable media file found')\n }\n\n // Show ad container\n this.adContainer.style.display = 'block'\n\n // Set source and play\n this.adVideo.src = mediaFile.url\n await this.adVideo.play()\n\n // Track impression\n this.trackEvent(ad, 'impression')\n this.trackEvent(ad, 'start')\n\n // Start skip timer\n this.startSkipTimer(ad)\n\n this.onAdStart?.(ad)\n }\n\n /**\n * Select best media file\n */\n private selectMediaFile(mediaFiles: AdMediaFile[]): AdMediaFile | null {\n // Sort by bitrate (highest first) and prefer mp4\n const sorted = [...mediaFiles].sort((a, b) => {\n if (a.type.includes('mp4') && !b.type.includes('mp4')) return -1\n if (!a.type.includes('mp4') && b.type.includes('mp4')) return 1\n return (b.bitrate || 0) - (a.bitrate || 0)\n })\n\n return sorted[0] || null\n }\n\n /**\n * Start skip timer\n */\n private startSkipTimer(ad: Ad): void {\n const skipDelay = this.config.skipDelay === -1\n ? (ad.skipOffset || 5)\n : (this.config.skipDelay || 0)\n\n if (skipDelay <= 0) {\n this.state.canSkip = true\n this.updateSkipButton()\n return\n }\n\n this.state.skipCountdown = skipDelay\n\n this.skipTimer = setInterval(() => {\n this.state.skipCountdown--\n\n if (this.state.skipCountdown <= 0) {\n this.state.canSkip = true\n if (this.skipTimer) {\n clearInterval(this.skipTimer)\n this.skipTimer = null\n }\n }\n\n this.updateSkipButton()\n }, 1000)\n }\n\n /**\n * Update skip button\n */\n private updateSkipButton(): void {\n if (!this.skipButton) return\n\n if (this.state.canSkip) {\n this.skipButton.textContent = 'Skip Ad'\n this.skipButton.style.display = 'block'\n }\n else if (this.state.skipCountdown > 0) {\n this.skipButton.textContent = `Skip in ${this.state.skipCountdown}s`\n this.skipButton.style.display = 'block'\n this.skipButton.style.cursor = 'default'\n }\n else {\n this.skipButton.style.display = 'none'\n }\n }\n\n /**\n * Skip current ad\n */\n skipAd(): void {\n if (!this.state.canSkip || !this.state.currentAd) return\n\n this.trackEvent(this.state.currentAd, 'skip')\n this.onAdSkip?.(this.state.currentAd)\n this.onAdEnded()\n }\n\n /**\n * Handle ad click\n */\n private handleAdClick(): void {\n if (!this.state.currentAd?.clickThroughUrl) return\n\n // Pause ad\n this.adVideo?.pause()\n\n // Track click\n this.trackEvent(this.state.currentAd, 'clickThrough')\n this.onAdClick?.(this.state.currentAd)\n\n // Open URL\n window.open(this.state.currentAd.clickThroughUrl, '_blank')\n }\n\n /**\n * Handle ad time update\n */\n private onAdTimeUpdate(): void {\n if (!this.adVideo || !this.state.currentAd) return\n\n const currentTime = this.adVideo.currentTime\n const duration = this.state.currentAd.duration\n\n this.state.timeRemaining = Math.max(0, duration - currentTime)\n\n // Track quartiles\n const progress = currentTime / duration\n if (progress >= 0.25) this.trackEvent(this.state.currentAd, 'firstQuartile')\n if (progress >= 0.5) this.trackEvent(this.state.currentAd, 'midpoint')\n if (progress >= 0.75) this.trackEvent(this.state.currentAd, 'thirdQuartile')\n\n // Track progress events\n for (const event of this.state.currentAd.trackingEvents) {\n if (event.event === 'progress' && event.offset && currentTime >= event.offset) {\n this.trackUrl(event.url)\n }\n }\n }\n\n /**\n * Handle ad ended\n */\n private onAdEnded(): void {\n if (this.state.currentAd) {\n this.trackEvent(this.state.currentAd, 'complete')\n this.onAdComplete?.(this.state.currentAd)\n }\n\n // Clean up skip timer\n if (this.skipTimer) {\n clearInterval(this.skipTimer)\n this.skipTimer = null\n }\n\n // Play next ad or resume content\n this.currentAdIndex++\n if (this.currentAdIndex < this.currentAds.length) {\n this.playAd(this.currentAds[this.currentAdIndex])\n }\n else {\n this.finishAdBreak()\n }\n }\n\n /**\n * Handle ad error\n */\n private handleAdError(): void {\n const error = 'Ad playback failed'\n this.state.error = error\n this.onAdErrorCallback?.(error)\n this.trackEvent(this.state.currentAd!, 'error')\n this.finishAdBreak()\n }\n\n /**\n * Finish ad break\n */\n private finishAdBreak(): void {\n this.state.isPlaying = false\n this.state.currentAd = null\n this.state.currentBreak = null\n this.currentAds = []\n this.currentAdIndex = 0\n\n // Hide ad container\n if (this.adContainer) {\n this.adContainer.style.display = 'none'\n }\n\n this.onAllAdsComplete?.()\n this.resumeContent()\n }\n\n /**\n * Pause content\n */\n private pauseContent(): void {\n if (!this.player) return\n\n const state = this.player.state\n if (state.playbackState === 'playing') {\n this.contentPaused = true\n this.player.pause()\n }\n }\n\n /**\n * Resume content\n */\n private resumeContent(): void {\n if (!this.player) return\n\n if (this.contentPaused) {\n this.contentPaused = false\n this.player.play()\n }\n }\n\n /**\n * Track ad event\n */\n private trackEvent(ad: Ad, eventName: string): void {\n const events = ad.trackingEvents.filter(e => e.event === eventName)\n for (const event of events) {\n this.trackUrl(event.url)\n }\n }\n\n /**\n * Track URL (fire pixel)\n */\n private trackUrl(url: string): void {\n const img = new Image()\n img.src = url\n }\n\n /**\n * Log debug message\n */\n private log(...args: unknown[]): void {\n if (this.config.debug) {\n // eslint-disable-next-line no-console\n console.log('[VideoAds]', ...args)\n }\n }\n\n /**\n * Get current state\n */\n getState(): AdState {\n return { ...this.state }\n }\n\n /**\n * Set event handlers\n */\n on(event: 'adStart', handler: (ad: Ad) => void): void\n on(event: 'adComplete', handler: (ad: Ad) => void): void\n on(event: 'adSkip', handler: (ad: Ad) => void): void\n on(event: 'adClick', handler: (ad: Ad) => void): void\n on(event: 'adError', handler: (error: string) => void): void\n on(event: 'allAdsComplete', handler: () => void): void\n on(event: string, handler: (...args: any[]) => void): void {\n switch (event) {\n case 'adStart':\n this.onAdStart = handler\n break\n case 'adComplete':\n this.onAdComplete = handler\n break\n case 'adSkip':\n this.onAdSkip = handler\n break\n case 'adClick':\n this.onAdClick = handler\n break\n case 'adError':\n this.onAdErrorCallback = handler\n break\n case 'allAdsComplete':\n this.onAllAdsComplete = handler\n break\n }\n }\n\n /**\n * Destroy ads manager\n */\n destroy(): void {\n if (this.skipTimer) {\n clearInterval(this.skipTimer)\n }\n\n if (this.adContainer) {\n this.adContainer.remove()\n }\n\n this.player = null\n this.container = null\n this.adVideo = null\n this.adContainer = null\n }\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\nexport function createAdsManager(config?: AdConfig): AdsManager {\n return new AdsManager(config)\n}\n\n/**\n * Ads plugin for Player\n */\nexport function adsPlugin(config: AdConfig): { name: string, install: (player: Player, container: HTMLElement) => () => void } {\n return {\n name: 'ads',\n\n install(player: Player, container: HTMLElement) {\n const ads = createAdsManager(config)\n ads.attach(player, container)\n\n // Expose on player\n ;(player as any).ads = ads\n\n return () => {\n ads.destroy()\n delete (player as any).ads\n }\n },\n }\n}\n",
|
|
23
|
+
"/**\n * Skip Segments Plugin\n *\n * Skip intro, outro, recap, and other segments.\n *\n * @module plugins/skip-segments\n */\n\nimport type { Player } from '../player'\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport type SegmentType = 'intro' | 'outro' | 'recap' | 'preview' | 'credits' | 'sponsor' | 'custom'\n\nexport interface SkipSegment {\n /** Segment type */\n type: SegmentType\n /** Start time in seconds */\n startTime: number\n /** End time in seconds */\n endTime: number\n /** Skip to time (defaults to endTime) */\n skipTo?: number\n /** Button text */\n buttonText?: string\n /** Auto-skip after delay (0 = no auto-skip) */\n autoSkipDelay?: number\n /** Show countdown */\n showCountdown?: boolean\n /** Custom data */\n data?: Record<string, unknown>\n}\n\nexport interface SkipSegmentsConfig {\n /** Skip segments */\n segments: SkipSegment[]\n /** Default button visibility duration (ms) */\n buttonVisibleDuration?: number\n /** Default auto-skip delay (seconds) */\n defaultAutoSkipDelay?: number\n /** Position of skip button */\n buttonPosition?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'\n /** Show progress bar markers */\n showMarkers?: boolean\n /** Marker color */\n markerColor?: string\n /** Enable keyboard shortcut (Enter to skip) */\n keyboardShortcut?: boolean\n}\n\nexport interface SkipSegmentsState {\n /** Current active segment */\n activeSegment: SkipSegment | null\n /** Is button visible */\n buttonVisible: boolean\n /** Auto-skip countdown */\n countdown: number\n /** Is auto-skipping */\n isAutoSkipping: boolean\n}\n\n// =============================================================================\n// Default Text Labels\n// =============================================================================\n\nconst defaultButtonText: Record<SegmentType, string> = {\n intro: 'Skip Intro',\n outro: 'Skip Outro',\n recap: 'Skip Recap',\n preview: 'Skip Preview',\n credits: 'Skip Credits',\n sponsor: 'Skip',\n custom: 'Skip',\n}\n\n// =============================================================================\n// Skip Segments Manager\n// =============================================================================\n\nexport class SkipSegmentsManager {\n private player: Player | null = null\n private config: Required<SkipSegmentsConfig>\n private state: SkipSegmentsState\n private container: HTMLElement | null = null\n private button: HTMLButtonElement | null = null\n private markersContainer: HTMLElement | null = null\n private countdownTimer: ReturnType<typeof setInterval> | null = null\n private hideTimer: ReturnType<typeof setTimeout> | null = null\n private unsubscribers: Array<() => void> = []\n\n // Event handlers\n // eslint-disable-next-line pickier/no-unused-vars\n private onSkip: ((_segment: SkipSegment) => void) | null = null\n // eslint-disable-next-line pickier/no-unused-vars\n private onSegmentEnter: ((_segment: SkipSegment) => void) | null = null\n // eslint-disable-next-line pickier/no-unused-vars\n private onSegmentExit: ((_segment: SkipSegment) => void) | null = null\n\n constructor(config: SkipSegmentsConfig) {\n this.config = {\n segments: config.segments || [],\n buttonVisibleDuration: config.buttonVisibleDuration ?? 10000,\n defaultAutoSkipDelay: config.defaultAutoSkipDelay ?? 0,\n buttonPosition: config.buttonPosition || 'bottom-right',\n showMarkers: config.showMarkers ?? true,\n markerColor: config.markerColor || 'rgba(255, 255, 0, 0.8)',\n keyboardShortcut: config.keyboardShortcut ?? true,\n }\n\n this.state = {\n activeSegment: null,\n buttonVisible: false,\n countdown: 0,\n isAutoSkipping: false,\n }\n }\n\n /**\n * Attach to player\n */\n attach(player: Player, container: HTMLElement): void {\n this.player = player\n this.container = container\n\n this.createButton()\n this.attachEventListeners()\n\n if (this.config.showMarkers) {\n this.createMarkers()\n }\n }\n\n /**\n * Create skip button\n */\n private createButton(): void {\n if (!this.container) return\n\n this.button = document.createElement('button')\n this.button.className = 'tsvp-skip-button'\n this.button.type = 'button'\n\n const positionStyles = this.getPositionStyles()\n\n this.button.style.cssText = `\n position: absolute;\n ${positionStyles}\n padding: 12px 24px;\n background: rgba(0, 0, 0, 0.85);\n color: #fff;\n border: 2px solid rgba(255, 255, 255, 0.8);\n border-radius: 4px;\n font-size: 16px;\n font-weight: 600;\n font-family: var(--tsvp-font-family, sans-serif);\n cursor: pointer;\n z-index: 30;\n display: none;\n transition: transform 150ms ease, background 150ms ease;\n `\n\n this.button.addEventListener('mouseenter', () => {\n this.button!.style.background = 'rgba(255, 255, 255, 0.2)'\n this.button!.style.transform = 'scale(1.05)'\n })\n\n this.button.addEventListener('mouseleave', () => {\n this.button!.style.background = 'rgba(0, 0, 0, 0.85)'\n this.button!.style.transform = 'scale(1)'\n })\n\n this.button.addEventListener('click', () => this.skip())\n\n this.container.appendChild(this.button)\n }\n\n /**\n * Get position styles for button\n */\n private getPositionStyles(): string {\n switch (this.config.buttonPosition) {\n case 'top-left':\n return 'top: 20px; left: 20px;'\n case 'top-right':\n return 'top: 20px; right: 20px;'\n case 'bottom-left':\n return 'bottom: 80px; left: 20px;'\n case 'bottom-right':\n default:\n return 'bottom: 80px; right: 20px;'\n }\n }\n\n /**\n * Create progress bar markers\n */\n private createMarkers(): void {\n // Markers will be created when attached to progress bar\n }\n\n /**\n * Get markers element for progress bar\n */\n getMarkersElement(duration: number): HTMLElement {\n this.markersContainer = document.createElement('div')\n this.markersContainer.className = 'tsvp-skip-markers'\n this.markersContainer.style.cssText = `\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n pointer-events: none;\n `\n\n for (const segment of this.config.segments) {\n const marker = document.createElement('div')\n marker.className = 'tsvp-skip-marker'\n\n const startPercent = (segment.startTime / duration) * 100\n const widthPercent = ((segment.endTime - segment.startTime) / duration) * 100\n\n marker.style.cssText = `\n position: absolute;\n left: ${startPercent}%;\n width: ${widthPercent}%;\n top: 0;\n bottom: 0;\n background: ${this.config.markerColor};\n opacity: 0.6;\n `\n\n this.markersContainer.appendChild(marker)\n }\n\n return this.markersContainer\n }\n\n /**\n * Attach event listeners\n */\n private attachEventListeners(): void {\n if (!this.player) return\n\n // Time update to check for segments\n const handleTimeUpdate = () => {\n const state = this.player?.state\n if (!state) return\n\n this.checkSegments(state.currentTime)\n }\n\n this.player.on('timeupdate', handleTimeUpdate)\n this.unsubscribers.push(() => this.player?.off('timeupdate', handleTimeUpdate))\n\n // Keyboard shortcut\n if (this.config.keyboardShortcut) {\n const handleKeydown = (e: KeyboardEvent) => {\n if (e.key === 'Enter' && this.state.activeSegment && this.state.buttonVisible) {\n e.preventDefault()\n this.skip()\n }\n }\n\n document.addEventListener('keydown', handleKeydown)\n this.unsubscribers.push(() => document.removeEventListener('keydown', handleKeydown))\n }\n }\n\n /**\n * Check if current time is in any segment\n */\n private checkSegments(currentTime: number): void {\n const previousSegment = this.state.activeSegment\n\n // Find active segment\n let activeSegment: SkipSegment | null = null\n for (const segment of this.config.segments) {\n if (currentTime >= segment.startTime && currentTime < segment.endTime) {\n activeSegment = segment\n break\n }\n }\n\n // Segment changed\n if (activeSegment !== previousSegment) {\n if (previousSegment) {\n this.onSegmentExit?.(previousSegment)\n this.hideButton()\n }\n\n if (activeSegment) {\n this.state.activeSegment = activeSegment\n this.onSegmentEnter?.(activeSegment)\n this.showButton(activeSegment)\n }\n else {\n this.state.activeSegment = null\n }\n }\n }\n\n /**\n * Show skip button\n */\n private showButton(segment: SkipSegment): void {\n if (!this.button) return\n\n // Set button text\n const text = segment.buttonText || defaultButtonText[segment.type]\n this.button.textContent = text\n\n this.state.buttonVisible = true\n this.button.style.display = 'block'\n\n // Animate in\n this.button.style.opacity = '0'\n this.button.style.transform = 'translateX(20px)'\n requestAnimationFrame(() => {\n this.button!.style.transition = 'opacity 200ms ease, transform 200ms ease'\n this.button!.style.opacity = '1'\n this.button!.style.transform = 'translateX(0)'\n })\n\n // Start auto-skip countdown\n const autoSkipDelay = segment.autoSkipDelay ?? this.config.defaultAutoSkipDelay\n if (autoSkipDelay > 0) {\n this.startAutoSkip(autoSkipDelay, segment)\n }\n\n // Auto-hide after duration\n if (this.config.buttonVisibleDuration > 0 && !autoSkipDelay) {\n this.hideTimer = setTimeout(() => {\n this.hideButton()\n }, this.config.buttonVisibleDuration)\n }\n }\n\n /**\n * Hide skip button\n */\n private hideButton(): void {\n if (!this.button) return\n\n this.state.buttonVisible = false\n this.state.isAutoSkipping = false\n this.state.countdown = 0\n\n // Clear timers\n if (this.countdownTimer) {\n clearInterval(this.countdownTimer)\n this.countdownTimer = null\n }\n if (this.hideTimer) {\n clearTimeout(this.hideTimer)\n this.hideTimer = null\n }\n\n // Animate out\n this.button.style.opacity = '0'\n this.button.style.transform = 'translateX(20px)'\n\n setTimeout(() => {\n if (this.button && !this.state.buttonVisible) {\n this.button.style.display = 'none'\n }\n }, 200)\n }\n\n /**\n * Start auto-skip countdown\n */\n private startAutoSkip(delay: number, segment: SkipSegment): void {\n this.state.isAutoSkipping = true\n this.state.countdown = delay\n\n this.updateButtonText(segment)\n\n this.countdownTimer = setInterval(() => {\n this.state.countdown--\n this.updateButtonText(segment)\n\n if (this.state.countdown <= 0) {\n if (this.countdownTimer) {\n clearInterval(this.countdownTimer)\n this.countdownTimer = null\n }\n this.skip()\n }\n }, 1000)\n }\n\n /**\n * Update button text with countdown\n */\n private updateButtonText(segment: SkipSegment): void {\n if (!this.button) return\n\n const baseText = segment.buttonText || defaultButtonText[segment.type]\n\n if (this.state.isAutoSkipping && this.state.countdown > 0 && segment.showCountdown !== false) {\n this.button.textContent = `${baseText} (${this.state.countdown})`\n }\n else {\n this.button.textContent = baseText\n }\n }\n\n /**\n * Skip current segment\n */\n skip(): void {\n if (!this.state.activeSegment || !this.player) return\n\n const segment = this.state.activeSegment\n const skipTo = segment.skipTo ?? segment.endTime\n\n this.player.seekTo(skipTo)\n this.onSkip?.(segment)\n this.hideButton()\n }\n\n /**\n * Add segment\n */\n addSegment(segment: SkipSegment): void {\n this.config.segments.push(segment)\n }\n\n /**\n * Remove segment\n */\n removeSegment(type: SegmentType): void {\n this.config.segments = this.config.segments.filter(s => s.type !== type)\n }\n\n /**\n * Clear all segments\n */\n clearSegments(): void {\n this.config.segments = []\n this.hideButton()\n }\n\n /**\n * Set segments\n */\n setSegments(segments: SkipSegment[]): void {\n this.config.segments = segments\n }\n\n /**\n * Get segments\n */\n getSegments(): SkipSegment[] {\n return [...this.config.segments]\n }\n\n /**\n * Get current state\n */\n getState(): SkipSegmentsState {\n return { ...this.state }\n }\n\n /**\n * Set event handlers\n */\n on(event: 'skip', handler: (segment: SkipSegment) => void): void\n on(event: 'segmentEnter', handler: (segment: SkipSegment) => void): void\n on(event: 'segmentExit', handler: (segment: SkipSegment) => void): void\n on(event: string, handler: (...args: any[]) => void): void {\n switch (event) {\n case 'skip':\n this.onSkip = handler\n break\n case 'segmentEnter':\n this.onSegmentEnter = handler\n break\n case 'segmentExit':\n this.onSegmentExit = handler\n break\n }\n }\n\n /**\n * Destroy\n */\n destroy(): void {\n if (this.countdownTimer) {\n clearInterval(this.countdownTimer)\n }\n if (this.hideTimer) {\n clearTimeout(this.hideTimer)\n }\n\n this.unsubscribers.forEach(unsub => unsub())\n\n this.button?.remove()\n this.markersContainer?.remove()\n\n this.player = null\n this.container = null\n }\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\nexport function createSkipSegments(config: SkipSegmentsConfig): SkipSegmentsManager {\n return new SkipSegmentsManager(config)\n}\n\n/**\n * Skip segments plugin for Player\n */\nexport function skipSegmentsPlugin(config: SkipSegmentsConfig): { name: string, install: (player: Player, container: HTMLElement) => () => void } {\n return {\n name: 'skipSegments',\n\n install(player: Player, container: HTMLElement): () => void {\n const skipSegments = createSkipSegments(config)\n skipSegments.attach(player, container)\n\n // Expose on player\n ;(player as any).skipSegments = skipSegments\n\n return () => {\n skipSegments.destroy()\n delete (player as any).skipSegments\n }\n },\n }\n}\n",
|
|
24
|
+
"/**\n * End Screen Plugin\n *\n * Display recommendations and actions at video end.\n *\n * @module plugins/end-screen\n */\n\nimport type { Player } from '../player'\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface EndScreenConfig {\n /** Show end screen on video end */\n enabled?: boolean\n /** Auto-show delay after video ends (ms) */\n showDelay?: number\n /** Recommendations */\n recommendations?: EndScreenRecommendation[]\n /** Custom actions */\n actions?: EndScreenAction[]\n /** Replay button */\n showReplay?: boolean\n /** Share button */\n showShare?: boolean\n /** Countdown to next video (0 = disabled) */\n autoplayCountdown?: number\n /** Title text */\n title?: string\n /** Custom template */\n template?: string\n /** Background style */\n backgroundStyle?: 'blur' | 'dark' | 'gradient' | 'none'\n}\n\nexport interface EndScreenRecommendation {\n /** Unique ID */\n id: string\n /** Title */\n title: string\n /** Description */\n description?: string\n /** Thumbnail URL */\n thumbnail: string\n /** Duration string (e.g., \"12:34\") */\n duration?: string\n /** Video URL or callback */\n url?: string\n /** Click handler */\n onClick?: () => void\n /** Channel/creator name */\n channel?: string\n /** View count */\n views?: string\n /** Upload date */\n date?: string\n}\n\nexport interface EndScreenAction {\n /** Button text */\n label: string\n /** Icon (SVG or emoji) */\n icon?: string\n /** Click handler */\n onClick: () => void\n /** Button style */\n style?: 'primary' | 'secondary' | 'outline'\n}\n\nexport interface EndScreenState {\n /** Is visible */\n visible: boolean\n /** Autoplay countdown */\n countdown: number\n /** Is countdown active */\n isCountingDown: boolean\n /** Selected recommendation index */\n selectedIndex: number\n}\n\n// =============================================================================\n// End Screen Manager\n// =============================================================================\n\nexport class EndScreenManager {\n private player: Player | null = null\n private config: Required<EndScreenConfig>\n private state: EndScreenState\n private container: HTMLElement | null = null\n private element: HTMLElement | null = null\n private countdownTimer: ReturnType<typeof setInterval> | null = null\n private showTimer: ReturnType<typeof setTimeout> | null = null\n private unsubscribers: Array<() => void> = []\n\n // Event handlers\n // eslint-disable-next-line pickier/no-unused-vars\n private onRecommendationClick: ((_rec: EndScreenRecommendation) => void) | null = null\n private onReplay: (() => void) | null = null\n // eslint-disable-next-line pickier/no-unused-vars\n private onAutoplay: ((_rec: EndScreenRecommendation) => void) | null = null\n private onShow: (() => void) | null = null\n private onHide: (() => void) | null = null\n\n constructor(config: EndScreenConfig = {}) {\n this.config = {\n enabled: config.enabled ?? true,\n showDelay: config.showDelay ?? 0,\n recommendations: config.recommendations || [],\n actions: config.actions || [],\n showReplay: config.showReplay ?? true,\n showShare: config.showShare ?? false,\n autoplayCountdown: config.autoplayCountdown ?? 0,\n title: config.title || '',\n template: config.template || '',\n backgroundStyle: config.backgroundStyle || 'blur',\n }\n\n this.state = {\n visible: false,\n countdown: 0,\n isCountingDown: false,\n selectedIndex: 0,\n }\n }\n\n /**\n * Attach to player\n */\n attach(player: Player, container: HTMLElement): void {\n this.player = player\n this.container = container\n\n this.createElement()\n this.attachEventListeners()\n }\n\n /**\n * Create end screen element\n */\n private createElement(): void {\n if (!this.container) return\n\n this.element = document.createElement('div')\n this.element.className = 'tsvp-end-screen'\n\n this.element.style.cssText = `\n position: absolute;\n inset: 0;\n display: none;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n z-index: 40;\n font-family: var(--tsvp-font-family, sans-serif);\n color: var(--tsvp-color-text, #ffffff);\n padding: 24px;\n overflow: auto;\n `\n\n this.applyBackgroundStyle()\n this.container.appendChild(this.element)\n }\n\n /**\n * Apply background style\n */\n private applyBackgroundStyle(): void {\n if (!this.element) return\n\n switch (this.config.backgroundStyle) {\n case 'blur':\n this.element.style.background = 'rgba(0, 0, 0, 0.85)'\n this.element.style.backdropFilter = 'blur(10px)'\n break\n case 'dark':\n this.element.style.background = 'rgba(0, 0, 0, 0.95)'\n break\n case 'gradient':\n this.element.style.background = 'linear-gradient(135deg, rgba(0,0,0,0.9) 0%, rgba(20,20,40,0.95) 100%)'\n break\n case 'none':\n this.element.style.background = 'transparent'\n break\n }\n }\n\n /**\n * Render end screen content\n */\n private render(): void {\n if (!this.element) return\n\n // Use custom template if provided\n if (this.config.template) {\n this.element.innerHTML = this.config.template\n return\n }\n\n this.element.innerHTML = ''\n\n // Title\n if (this.config.title) {\n const title = document.createElement('h2')\n title.className = 'tsvp-end-screen-title'\n title.textContent = this.config.title\n title.style.cssText = `\n margin: 0 0 24px;\n font-size: 24px;\n font-weight: 600;\n text-align: center;\n `\n this.element.appendChild(title)\n }\n\n // Recommendations grid\n if (this.config.recommendations.length > 0) {\n const grid = document.createElement('div')\n grid.className = 'tsvp-end-screen-grid'\n grid.style.cssText = `\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n max-width: 900px;\n width: 100%;\n margin-bottom: 24px;\n `\n\n this.config.recommendations.forEach((rec, index) => {\n const card = this.createRecommendationCard(rec, index)\n grid.appendChild(card)\n })\n\n this.element.appendChild(grid)\n }\n\n // Actions row\n const actionsRow = document.createElement('div')\n actionsRow.className = 'tsvp-end-screen-actions'\n actionsRow.style.cssText = `\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n justify-content: center;\n `\n\n // Replay button\n if (this.config.showReplay) {\n const replayBtn = this.createActionButton({\n label: 'Replay',\n icon: `<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"currentColor\">\n <path d=\"M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z\"/>\n </svg>`,\n style: 'secondary',\n onClick: () => this.replay(),\n })\n actionsRow.appendChild(replayBtn)\n }\n\n // Share button\n if (this.config.showShare) {\n const shareBtn = this.createActionButton({\n label: 'Share',\n icon: `<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"currentColor\">\n <path d=\"M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z\"/>\n </svg>`,\n style: 'outline',\n onClick: () => this.share(),\n })\n actionsRow.appendChild(shareBtn)\n }\n\n // Custom actions\n for (const action of this.config.actions) {\n const btn = this.createActionButton(action)\n actionsRow.appendChild(btn)\n }\n\n this.element.appendChild(actionsRow)\n\n // Autoplay countdown\n if (this.state.isCountingDown && this.config.recommendations.length > 0) {\n const countdown = document.createElement('div')\n countdown.className = 'tsvp-end-screen-countdown'\n countdown.style.cssText = `\n margin-top: 24px;\n text-align: center;\n font-size: 14px;\n opacity: 0.8;\n `\n countdown.innerHTML = `\n Next video in <strong>${this.state.countdown}</strong> seconds\n <button class=\"tsvp-end-screen-cancel\" style=\"\n margin-left: 12px;\n padding: 4px 12px;\n border: 1px solid rgba(255,255,255,0.3);\n border-radius: 4px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n \">Cancel</button>\n `\n\n const cancelBtn = countdown.querySelector('.tsvp-end-screen-cancel')\n cancelBtn?.addEventListener('click', () => this.cancelAutoplay())\n\n this.element.appendChild(countdown)\n }\n }\n\n /**\n * Create recommendation card\n */\n private createRecommendationCard(rec: EndScreenRecommendation, index: number): HTMLElement {\n const card = document.createElement('div')\n card.className = 'tsvp-end-screen-card'\n card.tabIndex = 0\n\n const isSelected = index === this.state.selectedIndex && this.state.isCountingDown\n\n card.style.cssText = `\n background: rgba(255, 255, 255, 0.1);\n border-radius: 8px;\n overflow: hidden;\n cursor: pointer;\n transition: transform 150ms ease, background 150ms ease;\n ${isSelected ? 'outline: 2px solid var(--tsvp-color-primary, #00a8ff); outline-offset: 2px;' : ''}\n `\n\n // Thumbnail\n const thumbnail = document.createElement('div')\n thumbnail.className = 'tsvp-end-screen-thumbnail'\n thumbnail.style.cssText = `\n position: relative;\n aspect-ratio: 16 / 9;\n background: url(${rec.thumbnail}) center/cover;\n `\n\n // Duration badge\n if (rec.duration) {\n const duration = document.createElement('span')\n duration.className = 'tsvp-end-screen-duration'\n duration.textContent = rec.duration\n duration.style.cssText = `\n position: absolute;\n bottom: 4px;\n right: 4px;\n padding: 2px 6px;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n `\n thumbnail.appendChild(duration)\n }\n\n card.appendChild(thumbnail)\n\n // Info\n const info = document.createElement('div')\n info.className = 'tsvp-end-screen-info'\n info.style.cssText = `\n padding: 12px;\n `\n\n const title = document.createElement('h3')\n title.className = 'tsvp-end-screen-card-title'\n title.textContent = rec.title\n title.style.cssText = `\n margin: 0 0 4px;\n font-size: 14px;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n `\n info.appendChild(title)\n\n if (rec.channel) {\n const channel = document.createElement('p')\n channel.className = 'tsvp-end-screen-channel'\n channel.textContent = rec.channel\n channel.style.cssText = `\n margin: 0;\n font-size: 12px;\n opacity: 0.7;\n `\n info.appendChild(channel)\n }\n\n if (rec.views || rec.date) {\n const meta = document.createElement('p')\n meta.className = 'tsvp-end-screen-meta'\n meta.textContent = [rec.views, rec.date].filter(Boolean).join(' • ')\n meta.style.cssText = `\n margin: 4px 0 0;\n font-size: 12px;\n opacity: 0.6;\n `\n info.appendChild(meta)\n }\n\n card.appendChild(info)\n\n // Hover effects\n card.addEventListener('mouseenter', () => {\n card.style.transform = 'scale(1.03)'\n card.style.background = 'rgba(255, 255, 255, 0.15)'\n })\n\n card.addEventListener('mouseleave', () => {\n card.style.transform = 'scale(1)'\n card.style.background = 'rgba(255, 255, 255, 0.1)'\n })\n\n // Click handler\n card.addEventListener('click', () => {\n this.onRecommendationClick?.(rec)\n if (rec.onClick) {\n rec.onClick()\n }\n else if (rec.url) {\n window.location.href = rec.url\n }\n })\n\n return card\n }\n\n /**\n * Create action button\n */\n private createActionButton(action: EndScreenAction): HTMLButtonElement {\n const btn = document.createElement('button')\n btn.className = `tsvp-end-screen-btn tsvp-end-screen-btn--${action.style || 'secondary'}`\n btn.type = 'button'\n\n let bgColor = 'rgba(255, 255, 255, 0.2)'\n let borderColor = 'transparent'\n\n switch (action.style) {\n case 'primary':\n bgColor = 'var(--tsvp-color-primary, #00a8ff)'\n break\n case 'outline':\n bgColor = 'transparent'\n borderColor = 'rgba(255, 255, 255, 0.5)'\n break\n }\n\n btn.style.cssText = `\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n background: ${bgColor};\n border: 1px solid ${borderColor};\n border-radius: 6px;\n color: #fff;\n font-size: 14px;\n font-weight: 500;\n font-family: inherit;\n cursor: pointer;\n transition: transform 150ms ease, opacity 150ms ease;\n `\n\n if (action.icon) {\n const icon = document.createElement('span')\n icon.innerHTML = action.icon\n icon.style.display = 'flex'\n btn.appendChild(icon)\n }\n\n const label = document.createElement('span')\n label.textContent = action.label\n btn.appendChild(label)\n\n btn.addEventListener('mouseenter', () => {\n btn.style.transform = 'scale(1.05)'\n })\n\n btn.addEventListener('mouseleave', () => {\n btn.style.transform = 'scale(1)'\n })\n\n btn.addEventListener('click', action.onClick)\n\n return btn\n }\n\n /**\n * Attach event listeners\n */\n private attachEventListeners(): void {\n if (!this.player) return\n\n const handleEnded = () => {\n if (this.config.enabled) {\n this.showTimer = setTimeout(() => {\n this.show()\n }, this.config.showDelay)\n }\n }\n\n this.player.on('ended', handleEnded)\n this.unsubscribers.push(() => this.player?.off('ended', handleEnded))\n\n // Hide on play\n const handlePlay = () => {\n if (this.state.visible) {\n this.hide()\n }\n }\n\n this.player.on('play', handlePlay)\n this.unsubscribers.push(() => this.player?.off('play', handlePlay))\n\n // Keyboard navigation\n const handleKeydown = (e: KeyboardEvent) => {\n if (!this.state.visible) return\n\n switch (e.key) {\n case 'Escape':\n this.hide()\n break\n case 'ArrowLeft':\n this.selectPrevious()\n break\n case 'ArrowRight':\n this.selectNext()\n break\n case 'Enter':\n this.selectCurrent()\n break\n }\n }\n\n document.addEventListener('keydown', handleKeydown)\n this.unsubscribers.push(() => document.removeEventListener('keydown', handleKeydown))\n }\n\n /**\n * Show end screen\n */\n show(): void {\n if (!this.element || this.state.visible) return\n\n this.state.visible = true\n this.state.selectedIndex = 0\n\n // Start autoplay countdown\n if (this.config.autoplayCountdown > 0 && this.config.recommendations.length > 0) {\n this.startAutoplayCountdown()\n }\n\n this.render()\n\n this.element.style.display = 'flex'\n this.element.style.opacity = '0'\n requestAnimationFrame(() => {\n this.element!.style.transition = 'opacity 300ms ease'\n this.element!.style.opacity = '1'\n })\n\n this.onShow?.()\n }\n\n /**\n * Hide end screen\n */\n hide(): void {\n if (!this.element || !this.state.visible) return\n\n this.state.visible = false\n this.cancelAutoplay()\n\n this.element.style.opacity = '0'\n setTimeout(() => {\n if (this.element && !this.state.visible) {\n this.element.style.display = 'none'\n }\n }, 300)\n\n this.onHide?.()\n }\n\n /**\n * Start autoplay countdown\n */\n private startAutoplayCountdown(): void {\n this.state.isCountingDown = true\n this.state.countdown = this.config.autoplayCountdown\n\n this.countdownTimer = setInterval(() => {\n this.state.countdown--\n this.render()\n\n if (this.state.countdown <= 0) {\n this.playNextVideo()\n }\n }, 1000)\n }\n\n /**\n * Cancel autoplay\n */\n cancelAutoplay(): void {\n this.state.isCountingDown = false\n this.state.countdown = 0\n\n if (this.countdownTimer) {\n clearInterval(this.countdownTimer)\n this.countdownTimer = null\n }\n\n if (this.state.visible) {\n this.render()\n }\n }\n\n /**\n * Play next video\n */\n private playNextVideo(): void {\n const rec = this.config.recommendations[this.state.selectedIndex]\n if (rec) {\n this.onAutoplay?.(rec)\n if (rec.onClick) {\n rec.onClick()\n }\n else if (rec.url) {\n window.location.href = rec.url\n }\n }\n }\n\n /**\n * Select previous recommendation\n */\n private selectPrevious(): void {\n if (this.config.recommendations.length === 0) return\n this.state.selectedIndex = (this.state.selectedIndex - 1 + this.config.recommendations.length)\n % this.config.recommendations.length\n this.render()\n }\n\n /**\n * Select next recommendation\n */\n private selectNext(): void {\n if (this.config.recommendations.length === 0) return\n this.state.selectedIndex = (this.state.selectedIndex + 1) % this.config.recommendations.length\n this.render()\n }\n\n /**\n * Select current recommendation\n */\n private selectCurrent(): void {\n const rec = this.config.recommendations[this.state.selectedIndex]\n if (rec) {\n this.onRecommendationClick?.(rec)\n if (rec.onClick) {\n rec.onClick()\n }\n else if (rec.url) {\n window.location.href = rec.url\n }\n }\n }\n\n /**\n * Replay video\n */\n private replay(): void {\n this.hide()\n this.player?.seekTo(0)\n this.player?.play()\n this.onReplay?.()\n }\n\n /**\n * Share video\n */\n private share(): void {\n if (navigator.share) {\n navigator.share({\n title: document.title,\n url: window.location.href,\n })\n }\n else {\n // Fallback: copy to clipboard\n navigator.clipboard?.writeText(window.location.href)\n }\n }\n\n /**\n * Update recommendations\n */\n setRecommendations(recommendations: EndScreenRecommendation[]): void {\n this.config.recommendations = recommendations\n if (this.state.visible) {\n this.render()\n }\n }\n\n /**\n * Get state\n */\n getState(): EndScreenState {\n return { ...this.state }\n }\n\n /**\n * Set event handlers\n */\n on(event: 'recommendationClick', handler: (rec: EndScreenRecommendation) => void): void\n on(event: 'replay', handler: () => void): void\n on(event: 'autoplay', handler: (rec: EndScreenRecommendation) => void): void\n on(event: 'show' | 'hide', handler: () => void): void\n on(event: string, handler: (...args: any[]) => void): void {\n switch (event) {\n case 'recommendationClick':\n this.onRecommendationClick = handler\n break\n case 'replay':\n this.onReplay = handler\n break\n case 'autoplay':\n this.onAutoplay = handler\n break\n case 'show':\n this.onShow = handler\n break\n case 'hide':\n this.onHide = handler\n break\n }\n }\n\n /**\n * Destroy\n */\n destroy(): void {\n if (this.countdownTimer) {\n clearInterval(this.countdownTimer)\n }\n if (this.showTimer) {\n clearTimeout(this.showTimer)\n }\n\n this.unsubscribers.forEach(unsub => unsub())\n\n this.element?.remove()\n\n this.player = null\n this.container = null\n }\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\nexport function createEndScreen(config?: EndScreenConfig): EndScreenManager {\n return new EndScreenManager(config)\n}\n\n/**\n * End screen plugin for Player\n */\nexport function endScreenPlugin(config: EndScreenConfig): { name: string, install: (player: Player, container: HTMLElement) => () => void } {\n return {\n name: 'endScreen',\n\n install(player: Player, container: HTMLElement) {\n const endScreen = createEndScreen(config)\n endScreen.attach(player, container)\n\n // Expose on player\n ;(player as any).endScreen = endScreen\n\n return () => {\n endScreen.destroy()\n delete (player as any).endScreen\n }\n },\n }\n}\n",
|
|
25
|
+
"/**\n * Watermark Plugin\n *\n * Display watermarks on video playback.\n *\n * @module plugins/watermark\n */\n\nimport type { Player } from '../player'\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport type WatermarkPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'center-left'\n | 'center'\n | 'center-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right'\n\nexport interface WatermarkConfig {\n /** Image URL */\n image?: string\n /** Text content */\n text?: string\n /** HTML content */\n html?: string\n /** Position */\n position?: WatermarkPosition\n /** Opacity (0-1) */\n opacity?: number\n /** Size (CSS value) */\n size?: string\n /** Offset from edges */\n offset?: {\n x?: number | string\n y?: number | string\n }\n /** Link URL */\n url?: string\n /** Open link in new tab */\n newTab?: boolean\n /** Show only during playback */\n showOnlyDuringPlayback?: boolean\n /** Fade in duration (ms) */\n fadeIn?: number\n /** Fade out duration (ms) */\n fadeOut?: number\n /** Animation */\n animation?: WatermarkAnimation\n /** Custom styles */\n styles?: Partial<CSSStyleDeclaration>\n /** Rotate degrees */\n rotate?: number\n}\n\nexport interface WatermarkAnimation {\n /** Animation type */\n type: 'fade' | 'slide' | 'bounce' | 'pulse' | 'none'\n /** Animation duration (ms) */\n duration?: number\n /** Animation delay (ms) */\n delay?: number\n /** Repeat animation */\n repeat?: boolean\n}\n\nexport interface DynamicWatermark {\n /** User ID or session info */\n userId?: string\n /** Timestamp */\n showTimestamp?: boolean\n /** IP address (for forensic marking) */\n showIp?: boolean\n /** Custom text */\n customText?: string\n /** Update interval (ms) */\n updateInterval?: number\n /** Position randomization */\n randomizePosition?: boolean\n /** Move interval (ms) */\n moveInterval?: number\n}\n\n// =============================================================================\n// Watermark Manager\n// =============================================================================\n\nexport class WatermarkManager {\n private player: Player | null = null\n private container: HTMLElement | null = null\n private elements: Map<string, HTMLElement> = new Map()\n private dynamicTimers: Map<string, ReturnType<typeof setInterval>> = new Map()\n private unsubscribers: Array<() => void> = []\n\n constructor() {}\n\n /**\n * Attach to player\n */\n attach(player: Player, container: HTMLElement): void {\n this.player = player\n this.container = container\n }\n\n /**\n * Add a watermark\n */\n add(id: string, config: WatermarkConfig): HTMLElement {\n if (!this.container) {\n throw new Error('Watermark manager not attached to container')\n }\n\n // Remove existing watermark with same ID\n this.remove(id)\n\n const element = this.createElement(config)\n element.dataset.watermarkId = id\n\n this.elements.set(id, element)\n this.container.appendChild(element)\n\n // Handle playback visibility\n if (config.showOnlyDuringPlayback && this.player) {\n this.setupPlaybackVisibility(id, element)\n }\n\n // Apply animation\n if (config.animation && config.animation.type !== 'none') {\n this.applyAnimation(element, config.animation)\n }\n\n return element\n }\n\n /**\n * Create watermark element\n */\n private createElement(config: WatermarkConfig): HTMLElement {\n const wrapper = document.createElement('div')\n wrapper.className = 'tsvp-watermark'\n\n const positionStyles = this.getPositionStyles(config.position || 'bottom-right', config.offset)\n\n wrapper.style.cssText = `\n position: absolute;\n ${positionStyles}\n z-index: 25;\n pointer-events: ${config.url ? 'auto' : 'none'};\n opacity: ${config.opacity ?? 0.7};\n transition: opacity ${config.fadeIn || 300}ms ease;\n ${config.rotate ? `transform: rotate(${config.rotate}deg);` : ''}\n `\n\n // Apply custom styles\n if (config.styles) {\n Object.assign(wrapper.style, config.styles)\n }\n\n // Create content\n let content: HTMLElement\n\n if (config.html) {\n content = document.createElement('div')\n content.innerHTML = config.html\n }\n else if (config.image) {\n const img = document.createElement('img')\n img.src = config.image\n img.alt = 'Watermark'\n img.style.cssText = `\n display: block;\n max-width: 100%;\n height: auto;\n ${config.size ? `width: ${config.size};` : ''}\n `\n content = img\n }\n else if (config.text) {\n content = document.createElement('span')\n content.textContent = config.text\n content.style.cssText = `\n font-family: var(--tsvp-font-family, sans-serif);\n font-size: ${config.size || '14px'};\n color: rgba(255, 255, 255, 0.8);\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);\n `\n }\n else {\n content = document.createElement('span')\n }\n\n // Wrap in link if URL provided\n if (config.url) {\n const link = document.createElement('a')\n link.href = config.url\n link.target = config.newTab ? '_blank' : '_self'\n link.rel = config.newTab ? 'noopener noreferrer' : ''\n link.style.textDecoration = 'none'\n link.appendChild(content)\n wrapper.appendChild(link)\n }\n else {\n wrapper.appendChild(content)\n }\n\n return wrapper\n }\n\n /**\n * Get position styles\n */\n private getPositionStyles(\n position: WatermarkPosition,\n offset?: { x?: number | string; y?: number | string }\n ): string {\n const offsetX = typeof offset?.x === 'number' ? `${offset.x}px` : (offset?.x || '16px')\n const offsetY = typeof offset?.y === 'number' ? `${offset.y}px` : (offset?.y || '16px')\n\n const positions: Record<WatermarkPosition, string> = {\n 'top-left': `top: ${offsetY}; left: ${offsetX};`,\n 'top-center': `top: ${offsetY}; left: 50%; transform: translateX(-50%);`,\n 'top-right': `top: ${offsetY}; right: ${offsetX};`,\n 'center-left': `top: 50%; left: ${offsetX}; transform: translateY(-50%);`,\n 'center': `top: 50%; left: 50%; transform: translate(-50%, -50%);`,\n 'center-right': `top: 50%; right: ${offsetX}; transform: translateY(-50%);`,\n 'bottom-left': `bottom: ${offsetY}; left: ${offsetX};`,\n 'bottom-center': `bottom: ${offsetY}; left: 50%; transform: translateX(-50%);`,\n 'bottom-right': `bottom: ${offsetY}; right: ${offsetX};`,\n }\n\n return positions[position] || positions['bottom-right']\n }\n\n /**\n * Setup playback visibility\n */\n private setupPlaybackVisibility(_id: string, element: HTMLElement): void {\n if (!this.player) return\n\n element.style.opacity = '0'\n\n const handlePlay = () => {\n element.style.opacity = ''\n }\n\n const handlePause = () => {\n element.style.opacity = '0'\n }\n\n this.player.on('play', handlePlay)\n this.player.on('pause', handlePause)\n this.player.on('ended', handlePause)\n\n this.unsubscribers.push(() => {\n this.player?.off('play', handlePlay)\n this.player?.off('pause', handlePause)\n this.player?.off('ended', handlePause)\n })\n }\n\n /**\n * Apply animation to watermark\n */\n private applyAnimation(element: HTMLElement, animation: WatermarkAnimation): void {\n const duration = animation.duration || 1000\n const delay = animation.delay || 0\n const repeat = animation.repeat ? 'infinite' : '1'\n\n // Add keyframes if not exists\n if (!document.querySelector('#tsvp-watermark-animations')) {\n const style = document.createElement('style')\n style.id = 'tsvp-watermark-animations'\n style.textContent = `\n @keyframes tsvp-watermark-fade {\n 0%, 100% { opacity: 0.3; }\n 50% { opacity: 1; }\n }\n @keyframes tsvp-watermark-slide {\n 0% { transform: translateX(-10px); }\n 50% { transform: translateX(10px); }\n 100% { transform: translateX(-10px); }\n }\n @keyframes tsvp-watermark-bounce {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-10px); }\n }\n @keyframes tsvp-watermark-pulse {\n 0%, 100% { transform: scale(1); }\n 50% { transform: scale(1.1); }\n }\n `\n document.head.appendChild(style)\n }\n\n const animationName = `tsvp-watermark-${animation.type}`\n element.style.animation = `${animationName} ${duration}ms ease ${delay}ms ${repeat}`\n }\n\n /**\n * Add dynamic watermark (forensic marking)\n */\n addDynamic(id: string, config: DynamicWatermark, baseConfig: WatermarkConfig = {}): HTMLElement {\n const getText = (): string => {\n const parts: string[] = []\n\n if (config.userId) {\n parts.push(config.userId)\n }\n\n if (config.showTimestamp) {\n parts.push(new Date().toISOString())\n }\n\n if (config.customText) {\n parts.push(config.customText)\n }\n\n return parts.join(' | ')\n }\n\n const positions: WatermarkPosition[] = [\n 'top-left', 'top-right', 'bottom-left', 'bottom-right',\n 'center-left', 'center-right',\n ]\n\n let currentPosition = baseConfig.position || 'bottom-right'\n\n const element = this.add(id, {\n ...baseConfig,\n text: getText(),\n position: currentPosition,\n opacity: baseConfig.opacity ?? 0.3,\n })\n\n // Update text periodically\n if (config.updateInterval && config.updateInterval > 0) {\n const textTimer = setInterval(() => {\n const textEl = element.querySelector('span')\n if (textEl) {\n textEl.textContent = getText()\n }\n }, config.updateInterval)\n\n this.dynamicTimers.set(`${id}-text`, textTimer)\n }\n\n // Move position periodically\n if (config.randomizePosition && config.moveInterval && config.moveInterval > 0) {\n const moveTimer = setInterval(() => {\n const randomIndex = Math.floor(Math.random() * positions.length)\n currentPosition = positions[randomIndex]\n\n const positionStyles = this.getPositionStyles(currentPosition, baseConfig.offset)\n element.style.cssText = element.style.cssText.replace(\n /top:.*?;|bottom:.*?;|left:.*?;|right:.*?;|transform:.*?;/g,\n ''\n )\n element.style.cssText += positionStyles\n }, config.moveInterval)\n\n this.dynamicTimers.set(`${id}-move`, moveTimer)\n }\n\n return element\n }\n\n /**\n * Remove watermark\n */\n remove(id: string): void {\n const element = this.elements.get(id)\n if (element) {\n element.remove()\n this.elements.delete(id)\n }\n\n // Clear dynamic timers\n const textTimer = this.dynamicTimers.get(`${id}-text`)\n if (textTimer) {\n clearInterval(textTimer)\n this.dynamicTimers.delete(`${id}-text`)\n }\n\n const moveTimer = this.dynamicTimers.get(`${id}-move`)\n if (moveTimer) {\n clearInterval(moveTimer)\n this.dynamicTimers.delete(`${id}-move`)\n }\n }\n\n /**\n * Remove all watermarks\n */\n removeAll(): void {\n for (const id of this.elements.keys()) {\n this.remove(id)\n }\n }\n\n /**\n * Update watermark\n */\n update(id: string, config: Partial<WatermarkConfig>): void {\n const element = this.elements.get(id)\n if (!element) return\n\n if (config.opacity !== undefined) {\n element.style.opacity = String(config.opacity)\n }\n\n if (config.position) {\n const positionStyles = this.getPositionStyles(config.position, config.offset)\n element.style.cssText = element.style.cssText.replace(\n /top:.*?;|bottom:.*?;|left:.*?;|right:.*?;|transform:.*?;/g,\n ''\n )\n element.style.cssText += positionStyles\n }\n\n if (config.text) {\n const textEl = element.querySelector('span')\n if (textEl) {\n textEl.textContent = config.text\n }\n }\n\n if (config.image) {\n const imgEl = element.querySelector('img')\n if (imgEl) {\n imgEl.src = config.image\n }\n }\n }\n\n /**\n * Show watermark\n */\n show(id: string): void {\n const element = this.elements.get(id)\n if (element) {\n element.style.display = ''\n }\n }\n\n /**\n * Hide watermark\n */\n hide(id: string): void {\n const element = this.elements.get(id)\n if (element) {\n element.style.display = 'none'\n }\n }\n\n /**\n * Show all watermarks\n */\n showAll(): void {\n for (const id of this.elements.keys()) {\n this.show(id)\n }\n }\n\n /**\n * Hide all watermarks\n */\n hideAll(): void {\n for (const id of this.elements.keys()) {\n this.hide(id)\n }\n }\n\n /**\n * Get watermark element\n */\n get(id: string): HTMLElement | undefined {\n return this.elements.get(id)\n }\n\n /**\n * Get all watermark IDs\n */\n getAll(): string[] {\n return Array.from(this.elements.keys())\n }\n\n /**\n * Destroy manager\n */\n destroy(): void {\n this.removeAll()\n this.unsubscribers.forEach(unsub => unsub())\n this.player = null\n this.container = null\n }\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\nexport function createWatermarkManager(): WatermarkManager {\n return new WatermarkManager()\n}\n\n/**\n * Watermark plugin for Player\n */\nexport function watermarkPlugin(watermarks: Array<{ id: string } & WatermarkConfig>): { name: string, install: (player: Player, container: HTMLElement) => () => void } {\n return {\n name: 'watermark',\n\n install(player: Player, container: HTMLElement): () => void {\n const manager = createWatermarkManager()\n manager.attach(player, container)\n\n // Add initial watermarks\n for (const { id, ...config } of watermarks) {\n manager.add(id, config)\n }\n\n // Expose on player\n ;(player as any).watermark = manager\n\n return () => {\n manager.destroy()\n delete (player as any).watermark\n }\n },\n }\n}\n",
|
|
26
|
+
"/** Browser HTMLElement base with an inert server-side constructor for import-safe SSR. */\nexport const HTMLElementBase: typeof HTMLElement = globalThis.HTMLElement ?? class {} as typeof HTMLElement\n",
|
|
27
|
+
"/**\n * ts-video-player <video-player> Custom Element\n *\n * The root element that wraps the Player class.\n *\n * @module elements/video-player\n */\n\nimport { Player } from '../player'\nimport type { DRMConfig, PlayerOptions, PlayerEventMap, Src, TextTrackSource } from '../types'\n\nconst OBSERVED_ATTRS = ['src', 'poster', 'autoplay', 'loop', 'muted', 'controls', 'volume', 'playback-rate', 'preload', 'playsinline', 'crossorigin', 'controlslist', 'disable-picture-in-picture'] as const\n\nimport { HTMLElementBase } from './base'\n\nexport class VideoPlayerElement extends HTMLElementBase {\n private _player: Player | null = null\n private _container: HTMLElement | null = null\n private _disconnectId = 0\n\n static get observedAttributes(): string[] {\n return [...OBSERVED_ATTRS]\n }\n\n get player(): Player | null {\n return this._player\n }\n\n connectedCallback(): void {\n this._disconnectId++\n if (this._player) return\n\n // Create shadow DOM\n const shadow = this.shadowRoot ?? this.attachShadow({ mode: 'open' })\n\n // Container\n this._container = document.createElement('div')\n this._container.setAttribute('part', 'container')\n this._container.style.cssText = 'position:relative;width:100%;height:100%;'\n\n // Slot for child media elements (buttons, etc.)\n const slot = document.createElement('slot')\n this._container.appendChild(slot)\n\n shadow.appendChild(this._container)\n\n // Build options from attributes\n const options = this.buildOptions()\n\n // Create player\n this._player = new Player(this._container, options)\n\n // Forward player events as DOM events\n this.forwardEvents()\n }\n\n disconnectedCallback(): void {\n if (this.hasAttribute('keep-alive')) return\n const disconnectId = ++this._disconnectId\n queueMicrotask(() => {\n if (this.isConnected || disconnectId !== this._disconnectId) return\n this._player?.destroy()\n this._player = null\n this._container = null\n this.shadowRoot?.replaceChildren()\n })\n }\n\n attributeChangedCallback(name: string, _old: string | null, value: string | null): void {\n if (!this._player) return\n\n switch (name) {\n case 'src':\n if (value) this._player.setSrc(value)\n break\n case 'muted':\n this._player.setMuted(value !== null)\n break\n case 'volume':\n if (value !== null) this._player.setVolume(parseFloat(value))\n break\n case 'playback-rate':\n if (value !== null) this._player.setPlaybackRate(parseFloat(value))\n break\n }\n }\n\n private buildOptions(): PlayerOptions {\n const options: PlayerOptions = {}\n\n const config = this.readSourceConfig()\n if (config.sources?.length) options.src = config.sources\n if (config.tracks?.length) options.tracks = config.tracks\n\n const src = this.getAttribute('src')\n if (src && !options.src) options.src = config.drm ? { src, type: 'application/dash+xml', drm: config.drm } : src\n\n const poster = this.getAttribute('poster')\n if (poster) options.poster = poster\n\n const title = this.getAttribute('title')\n if (title) options.title = title\n\n if (this.hasAttribute('autoplay')) options.autoplay = true\n if (this.hasAttribute('loop')) options.loop = true\n if (this.hasAttribute('muted')) options.muted = true\n\n const volume = this.getAttribute('volume')\n if (volume) options.volume = parseFloat(volume)\n\n const rate = this.getAttribute('playback-rate')\n if (rate) options.playbackRate = parseFloat(rate)\n\n const preload = this.getAttribute('preload')\n if (preload) options.preload = preload as PlayerOptions['preload']\n\n if (this.hasAttribute('controls')) options.controls = true\n options.playsinline = this.getAttribute('playsinline') !== 'false'\n\n const crossorigin = this.getAttribute('crossorigin')\n if (crossorigin !== null) options.crossorigin = crossorigin as PlayerOptions['crossorigin']\n\n const controlsList = this.getAttribute('controlslist')\n if (controlsList) options.controlsList = controlsList.split(/\\s+/) as PlayerOptions['controlsList']\n options.disablePictureInPicture = this.hasAttribute('disable-picture-in-picture')\n\n return options\n }\n\n private readSourceConfig(): { sources?: Src[], drm?: DRMConfig, tracks?: TextTrackSource[] } {\n const script = this.querySelector('script[type=\"application/json\"][data-media-config]')\n const value = this.getAttribute('data-media-config')?.trim() || script?.textContent?.trim()\n if (!value) return {}\n if (value.length > 65_536) throw new TypeError('Media player configuration is too large')\n try {\n const parsed = JSON.parse(value) as { sources?: unknown, drm?: unknown, tracks?: unknown }\n if (parsed.sources !== undefined && (!Array.isArray(parsed.sources) || parsed.sources.some(source => !source || typeof source !== 'object' || typeof (source as { src?: unknown }).src !== 'string'))) {\n throw new TypeError('Media player sources are invalid')\n }\n const trackKinds = new Set(['subtitles', 'captions', 'descriptions', 'chapters', 'metadata'])\n if (parsed.tracks !== undefined && (!Array.isArray(parsed.tracks) || parsed.tracks.some(track => !track || typeof track !== 'object' || typeof (track as { src?: unknown }).src !== 'string' || !trackKinds.has(String((track as { kind?: unknown }).kind))))) {\n throw new TypeError('Media player tracks are invalid')\n }\n return { sources: parsed.sources as Src[] | undefined, drm: parsed.drm as DRMConfig | undefined, tracks: parsed.tracks as TextTrackSource[] | undefined }\n }\n catch (error) {\n if (error instanceof TypeError) throw error\n throw new TypeError('Media player configuration is invalid JSON')\n }\n }\n\n private forwardEvents(): void {\n if (!this._player) return\n\n const events: (keyof PlayerEventMap)[] = [\n 'play', 'pause', 'playing', 'waiting', 'ended',\n 'timeupdate', 'durationchange', 'volumechange',\n 'fullscreenchange', 'pipchange', 'error',\n 'loadstart', 'loadeddata', 'loadedmetadata',\n 'canplay', 'canplaythrough', 'seeking', 'seeked',\n 'ratechange', 'qualitychange', 'statechange',\n 'availabilitychange',\n ]\n\n for (const event of events) {\n this._player.on(event, ((...args: unknown[]) => {\n this.dispatchEvent(new CustomEvent(event, {\n detail: args.length === 1 ? args[0] : args,\n bubbles: true,\n composed: true,\n }))\n }) as any)\n }\n }\n}\n",
|
|
28
|
+
"/**\n * ts-video-player Element Utilities\n *\n * Shared helpers for custom elements.\n *\n * @module elements/utils\n */\n\n/**\n * Format time in seconds to MM:SS or HH:MM:SS\n */\nexport function formatTime(seconds: number, guide = 0): string {\n if (!isFinite(seconds) || isNaN(seconds)) return '0:00'\n\n const s = Math.floor(seconds)\n const h = Math.floor(s / 3600)\n const m = Math.floor((s % 3600) / 60)\n const sec = s % 60\n\n const showHours = h > 0 || guide >= 3600\n\n if (showHours) {\n return `${h}:${m.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`\n }\n\n return `${m}:${sec.toString().padStart(2, '0')}`\n}\n\n/**\n * Format time as a human-readable phrase for screen readers.\n * e.g., \"1 minute, 30 seconds\"\n */\nexport function formatTimePhrase(seconds: number): string {\n if (!isFinite(seconds) || isNaN(seconds)) return '0 seconds'\n\n const s = Math.floor(seconds)\n const h = Math.floor(s / 3600)\n const m = Math.floor((s % 3600) / 60)\n const sec = s % 60\n\n const parts: string[] = []\n if (h > 0) parts.push(`${h} ${h === 1 ? 'hour' : 'hours'}`)\n if (m > 0) parts.push(`${m} ${m === 1 ? 'minute' : 'minutes'}`)\n if (sec > 0 || parts.length === 0) parts.push(`${sec} ${sec === 1 ? 'second' : 'seconds'}`)\n\n return parts.join(', ')\n}\n\n/**\n * Convert seconds to ISO 8601 duration string.\n * e.g., 90 -> \"PT1M30S\"\n */\nexport function toISODuration(seconds: number): string {\n if (!isFinite(seconds) || isNaN(seconds)) return 'PT0S'\n\n const s = Math.floor(seconds)\n const h = Math.floor(s / 3600)\n const m = Math.floor((s % 3600) / 60)\n const sec = s % 60\n\n let result = 'PT'\n if (h > 0) result += `${h}H`\n if (m > 0) result += `${m}M`\n if (sec > 0 || result === 'PT') result += `${sec}S`\n\n return result\n}\n\n/**\n * Resolve the player instance from an element.\n * Uses `for` attribute to find by ID, or walks up the DOM to find `<video-player>`.\n */\nexport function resolvePlayer(el: HTMLElement): any | null {\n const forId = el.getAttribute('for')\n if (forId) {\n const target = document.getElementById(forId)\n return (target as any)?.player ?? null\n }\n\n const host = el.closest('video-player')\n return (host as any)?.player ?? null\n}\n",
|
|
29
|
+
"/**\n * ts-video-player <video-skin> Custom Element\n *\n * Adaptive skin wrapper that provides a default layout for player controls.\n * Adjusts visibility of controls based on available features.\n *\n * Follows the v10 layered model:\n * <video-player> ← Behavior\n * <video-skin> ← Appearance (this element)\n * <media-*> ← UI controls\n * </video-skin>\n * </video-player>\n *\n * @module elements/video-skin\n */\n\nimport { resolvePlayer } from './utils'\n\nimport { HTMLElementBase } from './base'\n\nexport class VideoSkin extends HTMLElementBase {\n private _cleanups: Array<() => void> = []\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host {\n display: flex;\n flex-direction: column;\n position: absolute;\n inset: 0;\n z-index: 1;\n pointer-events: none;\n color: white;\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n }\n\n /* Click-to-play overlay */\n .overlay {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: auto;\n }\n\n /* Bottom controls bar */\n .controls {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 8px 12px;\n background: linear-gradient(transparent, rgba(0,0,0,0.7));\n pointer-events: auto;\n transition: opacity 0.3s, visibility 0.3s;\n }\n\n :host([controls-hidden]) .controls {\n opacity: 0;\n visibility: hidden;\n }\n\n /* Spacer pushes right-aligned controls */\n .spacer {\n flex: 1;\n }\n\n /* Slots for left/center/right control groups */\n ::slotted(*) {\n pointer-events: auto;\n }\n </style>\n <div class=\"overlay\" part=\"overlay\">\n <slot name=\"overlay\"></slot>\n </div>\n <div class=\"controls\" part=\"controls\">\n <slot name=\"left\"></slot>\n <slot name=\"center\"></slot>\n <div class=\"spacer\"></div>\n <slot name=\"right\"></slot>\n <slot></slot>\n </div>\n `\n }\n\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this._cleanups.forEach((fn) => fn())\n this._cleanups = []\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n if (!player) return\n\n // Sync controls visibility\n const unsub = player.subscribe((state: any) => {\n if (state.controlsVisible) {\n this.removeAttribute('controls-hidden')\n } else {\n this.setAttribute('controls-hidden', '')\n }\n\n // Propagate feature availability as data attributes for CSS adaptation\n this.dataset.fullscreen = state.fullscreenAvailability\n this.dataset.pip = state.pipAvailability\n this.dataset.volume = state.volumeAvailability\n this.dataset.captions = String(state.textTracks.length > 0)\n this.dataset.qualities = String(state.qualities.length > 0)\n })\n\n // Click overlay to toggle play\n const overlay = this.shadowRoot!.querySelector('.overlay')!\n const onOverlayClick = () => player.togglePlay()\n overlay.addEventListener('click', onOverlayClick)\n\n this._cleanups.push(\n unsub,\n () => overlay.removeEventListener('click', onOverlayClick),\n )\n }\n}\n",
|
|
30
|
+
"/**\n * ts-video-player <media-play-button> Custom Element\n *\n * @module elements/media-play-button\n */\n\nimport { resolvePlayer } from './utils'\n\nconst PLAY_ICON = 'M8 5v14l11-7z'\nconst PAUSE_ICON = 'M6 19h4V5H6v14zm8-14v14h4V5h-4z'\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaPlayButton extends HTMLElementBase {\n private _cleanup: (() => void) | null = null\n private _onClick: (() => void) | null = null\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: inline-flex; }\n button {\n display: inline-flex; align-items: center; justify-content: center;\n width: 40px; height: 40px; padding: 8px;\n background: transparent; border: none; color: inherit; cursor: pointer;\n }\n button:hover { opacity: 0.8; }\n </style>\n <button part=\"button\" type=\"button\" aria-label=\"Play\">\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"currentColor\" part=\"icon\">\n <path d=\"${PLAY_ICON}\"/>\n </svg>\n </button>\n `\n }\n\n // Defer player resolution to next microtask (player may not be ready yet)\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n const button = this.shadowRoot?.querySelector('button')\n if (button && this._onClick) button.removeEventListener('click', this._onClick)\n this._cleanup?.()\n this._cleanup = null\n this._onClick = null\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n if (!player) return\n\n const btn = this.shadowRoot!.querySelector('button')!\n this._onClick = () => player.togglePlay()\n btn.addEventListener('click', this._onClick)\n\n const unsub = player.subscribe('paused', (state: any) => {\n const paused = state.paused\n btn.setAttribute('aria-label', paused ? 'Play' : 'Pause')\n btn.setAttribute('aria-pressed', String(!paused))\n const path = this.shadowRoot!.querySelector('path')!\n path.setAttribute('d', paused ? PLAY_ICON : PAUSE_ICON)\n })\n\n this._cleanup = unsub\n }\n}\n",
|
|
31
|
+
"/**\n * ts-video-player <media-mute-button> Custom Element\n *\n * @module elements/media-mute-button\n */\n\nimport { resolvePlayer } from './utils'\n\nconst ICON_MUTED = 'M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51A8.796 8.796 0 0 0 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3 3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06a8.99 8.99 0 0 0 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4 9.91 6.09 12 8.18V4z'\nconst ICON_LOW = 'M18.5 12A4.5 4.5 0 0 0 16 7.97v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z'\nconst ICON_HIGH = 'M3 9v6h4l5 5V4L7 9H3zm13.5 3A4.5 4.5 0 0 0 14 7.97v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z'\n\nfunction getVolumeIcon(muted: boolean, volume: number): string {\n if (muted || volume === 0) return ICON_MUTED\n if (volume < 0.5) return ICON_LOW\n return ICON_HIGH\n}\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaMuteButton extends HTMLElementBase {\n private _cleanup: (() => void) | null = null\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: inline-flex; }\n button {\n display: inline-flex; align-items: center; justify-content: center;\n width: 40px; height: 40px; padding: 8px;\n background: transparent; border: none; color: inherit; cursor: pointer;\n }\n button:hover { opacity: 0.8; }\n </style>\n <button part=\"button\" type=\"button\" aria-label=\"Mute\">\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"currentColor\" part=\"icon\">\n <path d=\"${ICON_HIGH}\"/>\n </svg>\n </button>\n `\n }\n\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this._cleanup?.()\n this._cleanup = null\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n if (!player) return\n\n const btn = this.shadowRoot!.querySelector('button')!\n btn.addEventListener('click', () => player.toggleMute())\n\n const unsub = player.subscribe((state: any) => {\n const icon = getVolumeIcon(state.muted, state.volume)\n btn.setAttribute('aria-label', state.muted ? 'Unmute' : 'Mute')\n const path = this.shadowRoot!.querySelector('path')!\n path.setAttribute('d', icon)\n })\n\n this._cleanup = unsub\n }\n}\n",
|
|
32
|
+
"/**\n * ts-video-player <media-fullscreen-button> Custom Element\n *\n * @module elements/media-fullscreen-button\n */\n\nimport { resolvePlayer } from './utils'\n\nconst ENTER_FS = 'M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z'\nconst EXIT_FS = 'M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z'\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaFullscreenButton extends HTMLElementBase {\n private _cleanup: (() => void) | null = null\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: inline-flex; }\n :host([hidden]) { display: none; }\n button {\n display: inline-flex; align-items: center; justify-content: center;\n width: 40px; height: 40px; padding: 8px;\n background: transparent; border: none; color: inherit; cursor: pointer;\n }\n button:hover { opacity: 0.8; }\n button:disabled { opacity: 0.4; cursor: not-allowed; }\n </style>\n <button part=\"button\" type=\"button\" aria-label=\"Enter fullscreen\">\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"currentColor\" part=\"icon\">\n <path d=\"${ENTER_FS}\"/>\n </svg>\n </button>\n `\n }\n\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this._cleanup?.()\n this._cleanup = null\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n if (!player) return\n\n const btn = this.shadowRoot!.querySelector('button')!\n btn.addEventListener('click', () => player.toggleFullscreen())\n\n const unsub = player.subscribe((state: any) => {\n const avail = state.fullscreenAvailability\n if (avail === 'unsupported') {\n this.setAttribute('hidden', '')\n } else {\n this.removeAttribute('hidden')\n }\n btn.disabled = avail !== 'available'\n\n const fs = state.fullscreen\n btn.setAttribute('aria-label', fs ? 'Exit fullscreen' : 'Enter fullscreen')\n const path = this.shadowRoot!.querySelector('path')!\n path.setAttribute('d', fs ? EXIT_FS : ENTER_FS)\n })\n\n this._cleanup = unsub\n }\n}\n",
|
|
33
|
+
"/**\n * ts-video-player <media-pip-button> Custom Element\n *\n * @module elements/media-pip-button\n */\n\nimport { resolvePlayer } from './utils'\n\nconst PIP_ICON = 'M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z'\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaPipButton extends HTMLElementBase {\n private _cleanup: (() => void) | null = null\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: inline-flex; }\n :host([hidden]) { display: none; }\n button {\n display: inline-flex; align-items: center; justify-content: center;\n width: 40px; height: 40px; padding: 8px;\n background: transparent; border: none; color: inherit; cursor: pointer;\n }\n button:hover { opacity: 0.8; }\n button:disabled { opacity: 0.4; cursor: not-allowed; }\n </style>\n <button part=\"button\" type=\"button\" aria-label=\"Picture in picture\">\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"currentColor\" part=\"icon\">\n <path d=\"${PIP_ICON}\"/>\n </svg>\n </button>\n `\n }\n\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this._cleanup?.()\n this._cleanup = null\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n if (!player) return\n\n const btn = this.shadowRoot!.querySelector('button')!\n btn.addEventListener('click', () => player.togglePiP())\n\n const unsub = player.subscribe((state: any) => {\n const avail = state.pipAvailability\n if (avail === 'unsupported') {\n this.setAttribute('hidden', '')\n } else {\n this.removeAttribute('hidden')\n }\n btn.disabled = avail !== 'available'\n })\n\n this._cleanup = unsub\n }\n}\n",
|
|
34
|
+
"/**\n * ts-video-player <media-time-display> Custom Element\n *\n * Displays current time, duration, or remaining time with semantic markup.\n * Attributes: type=\"current|duration|remaining\"\n *\n * @module elements/media-time-display\n */\n\nimport { resolvePlayer, formatTime, formatTimePhrase, toISODuration } from './utils'\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaTimeDisplay extends HTMLElementBase {\n private _cleanup: (() => void) | null = null\n private _showRemaining = false\n\n static get observedAttributes(): string[] {\n return ['type']\n }\n\n get type(): string {\n return this.getAttribute('type') || 'current'\n }\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host {\n display: inline-flex;\n font-size: 14px;\n font-variant-numeric: tabular-nums;\n user-select: none;\n cursor: pointer;\n color: inherit;\n }\n time { color: inherit; }\n .separator { margin: 0 2px; }\n </style>\n <time part=\"time\" datetime=\"PT0S\" aria-valuetext=\"0 seconds\">0:00</time>\n `\n }\n\n this.addEventListener('click', this.handleClick)\n\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this._cleanup?.()\n this._cleanup = null\n this.removeEventListener('click', this.handleClick)\n }\n\n private handleClick = (): void => {\n if (this.type === 'duration') {\n this._showRemaining = !this._showRemaining\n // Re-render with current state\n const player = resolvePlayer(this)\n if (player) this.render(player.state)\n }\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n if (!player) return\n\n const unsub = player.subscribe((state: any) => {\n this.render(state)\n })\n\n this._cleanup = unsub\n }\n\n private render(state: any): void {\n const timeEl = this.shadowRoot?.querySelector('time')\n if (!timeEl) return\n\n const type = this.type\n let seconds: number\n\n if (type === 'remaining' || (type === 'duration' && this._showRemaining)) {\n seconds = Math.max(state.duration - state.currentTime, 0)\n const formatted = `-${formatTime(seconds, state.duration)}`\n timeEl.textContent = formatted\n } else if (type === 'duration') {\n seconds = state.duration\n timeEl.textContent = formatTime(seconds)\n } else {\n seconds = state.currentTime\n timeEl.textContent = formatTime(seconds, state.duration)\n }\n\n timeEl.setAttribute('datetime', toISODuration(seconds))\n timeEl.setAttribute('aria-valuetext', formatTimePhrase(seconds))\n }\n}\n",
|
|
35
|
+
"/**\n * ts-video-player <media-time-group> Custom Element\n *\n * Container for composing time display elements (current / separator / duration).\n * Provides layout and semantic grouping.\n *\n * @example\n * ```html\n * <media-time-group>\n * <media-time-display type=\"current\"></media-time-display>\n * <media-time-separator></media-time-separator>\n * <media-time-display type=\"duration\"></media-time-display>\n * </media-time-group>\n * ```\n *\n * @module elements/media-time-group\n */\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaTimeGroup extends HTMLElementBase {\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n font-variant-numeric: tabular-nums;\n font-family: inherit;\n font-size: inherit;\n color: inherit;\n }\n </style>\n <slot></slot>\n `\n }\n }\n}\n",
|
|
36
|
+
"/**\n * ts-video-player <media-time-separator> Custom Element\n *\n * Visual separator between time displays (typically \"/\" or \":\").\n * Hidden from screen readers via aria-hidden.\n *\n * @module elements/media-time-separator\n */\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaTimeSeparator extends HTMLElementBase {\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host {\n display: inline-flex;\n align-items: center;\n padding: 0 2px;\n font-family: inherit;\n font-size: inherit;\n color: inherit;\n opacity: 0.7;\n }\n </style>\n <span aria-hidden=\"true\" part=\"separator\"><slot>/</slot></span>\n `\n }\n\n this.setAttribute('role', 'separator')\n this.setAttribute('aria-hidden', 'true')\n }\n}\n",
|
|
37
|
+
"/**\n * ts-video-player <media-preview-time> Custom Element\n *\n * Displays the time at the current pointer position on the seek bar.\n * Intended to be placed inside or near a <media-progress-bar>.\n * Listens for custom 'previewtime' events or pointer events on the progress bar.\n *\n * @module elements/media-preview-time\n */\n\nimport { resolvePlayer, formatTime } from './utils'\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaPreviewTime extends HTMLElementBase {\n private _cleanups: Array<() => void> = []\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host {\n display: inline-block;\n font-variant-numeric: tabular-nums;\n font-family: monospace;\n font-size: 12px;\n color: inherit;\n pointer-events: none;\n }\n </style>\n <span part=\"value\">0:00</span>\n `\n }\n\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this._cleanups.forEach((fn) => fn())\n this._cleanups = []\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n if (!player) return\n\n // Find sibling or parent progress bar to listen for pointer events\n const progressBar =\n this.closest('media-progress-bar') ||\n this.parentElement?.querySelector('media-progress-bar')\n\n if (progressBar) {\n const onPointerMove = (e: Event) => {\n const pe = e as PointerEvent\n const track = (progressBar.shadowRoot?.querySelector('.track') ||\n progressBar) as HTMLElement\n const rect = track.getBoundingClientRect()\n const percent = Math.max(0, Math.min(1, (pe.clientX - rect.left) / rect.width))\n const time = percent * (player.state.duration || 0)\n this.updateTime(time, player.state.duration)\n }\n\n progressBar.addEventListener('pointermove', onPointerMove)\n this._cleanups.push(() => progressBar.removeEventListener('pointermove', onPointerMove))\n }\n }\n\n private updateTime(time: number, duration: number): void {\n const span = this.shadowRoot?.querySelector('span')\n if (span) {\n span.textContent = formatTime(time, duration)\n }\n }\n}\n",
|
|
38
|
+
"/**\n * ts-video-player <media-progress-bar> Custom Element\n *\n * Seek bar with buffered overlay, time tooltip, pointer/keyboard interaction.\n *\n * @module elements/media-progress-bar\n */\n\nimport { resolvePlayer, formatTime, formatTimePhrase } from './utils'\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaProgressBar extends HTMLElementBase {\n private _cleanups: Array<() => void> = []\n private _isDragging = false\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: flex; flex: 1; align-items: center; }\n .container {\n position: relative; width: 100%; height: 20px;\n cursor: pointer; touch-action: none;\n }\n .track {\n position: absolute; top: 50%; left: 0; right: 0;\n height: 4px; background: rgba(255,255,255,0.3);\n border-radius: 2px; transform: translateY(-50%);\n }\n :host([waveform]) .track { height: 20px; overflow: hidden; background: rgba(255,255,255,0.16); }\n .waveform { position: absolute; inset: 0; background-position: center; background-repeat: no-repeat; background-size: 100% 100%; opacity: 0.65; }\n .buffered {\n position: absolute; top: 0; left: 0;\n height: 100%; background: rgba(255,255,255,0.4);\n border-radius: 2px; width: 0%;\n }\n .fill {\n position: absolute; top: 0; left: 0;\n height: 100%; background: #fff;\n border-radius: 2px; width: 0%;\n }\n .thumb {\n position: absolute; top: 50%; left: 0%;\n width: 12px; height: 12px; background: #fff;\n border-radius: 50%; transform: translate(-50%, -50%);\n transition: transform 0.1s;\n }\n .container:hover .thumb { transform: translate(-50%, -50%) scale(1.2); }\n .tooltip {\n position: absolute; bottom: 100%; left: 0;\n transform: translateX(-50%);\n background: rgba(0,0,0,0.85); color: #fff;\n padding: 4px 8px; border-radius: 3px;\n font-size: 12px; font-family: monospace;\n white-space: nowrap; pointer-events: none;\n opacity: 0; transition: opacity 0.15s;\n margin-bottom: 8px;\n }\n .tooltip::after {\n content: ''; position: absolute;\n top: 100%; left: 50%; transform: translateX(-50%);\n border: 4px solid transparent;\n border-top-color: rgba(0,0,0,0.85);\n }\n .container:hover .tooltip { opacity: 1; }\n </style>\n <div class=\"container\" part=\"container\" role=\"slider\"\n aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"0\"\n aria-label=\"Seek\" tabindex=\"0\">\n <div class=\"tooltip\" part=\"tooltip\">0:00</div>\n <div class=\"track\" part=\"track\">\n <div class=\"waveform\" part=\"waveform\" aria-hidden=\"true\"></div>\n <div class=\"buffered\" part=\"buffered\"></div>\n <div class=\"fill\" part=\"fill\"></div>\n <div class=\"thumb\" part=\"thumb\"></div>\n </div>\n </div>\n `\n }\n\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this._cleanups.forEach((fn) => fn())\n this._cleanups = []\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n if (!player) return\n\n const container = this.shadowRoot!.querySelector('.container') as HTMLElement\n const track = this.shadowRoot!.querySelector('.track') as HTMLElement\n const fill = this.shadowRoot!.querySelector('.fill') as HTMLElement\n const thumb = this.shadowRoot!.querySelector('.thumb') as HTMLElement\n const buffered = this.shadowRoot!.querySelector('.buffered') as HTMLElement\n const tooltip = this.shadowRoot!.querySelector('.tooltip') as HTMLElement\n const waveform = this.getAttribute('waveform')\n if (waveform) {\n const resolved = new URL(waveform, document.baseURI)\n if (['http:', 'https:', 'blob:'].includes(resolved.protocol) || resolved.href.startsWith('data:image/')) {\n const layer = this.shadowRoot!.querySelector('.waveform') as HTMLElement\n layer.style.backgroundImage = `url(${JSON.stringify(resolved.href)})`\n }\n }\n\n const updateTooltip = (e: PointerEvent) => {\n const rect = track.getBoundingClientRect()\n const percent = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))\n const time = percent * (player.state.duration || 0)\n\n tooltip.textContent = formatTime(time, player.state.duration)\n\n // Position tooltip, clamping to container edges\n const containerRect = container.getBoundingClientRect()\n const tooltipWidth = tooltip.offsetWidth\n const rawLeft = e.clientX - containerRect.left\n const minLeft = tooltipWidth / 2\n const maxLeft = containerRect.width - tooltipWidth / 2\n const clampedLeft = Math.max(minLeft, Math.min(maxLeft, rawLeft))\n\n tooltip.style.left = `${clampedLeft}px`\n }\n\n const seekFromEvent = (e: PointerEvent) => {\n const rect = track.getBoundingClientRect()\n const percent = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))\n const time = percent * (player.state.duration || 0)\n player.seekTo(time)\n }\n\n const onPointerDown = (e: PointerEvent) => {\n this._isDragging = true\n container.setPointerCapture(e.pointerId)\n seekFromEvent(e)\n }\n\n const onPointerMove = (e: PointerEvent) => {\n updateTooltip(e)\n if (!this._isDragging) return\n seekFromEvent(e)\n }\n\n const onPointerUp = (e: PointerEvent) => {\n if (!this._isDragging) return\n this._isDragging = false\n container.releasePointerCapture(e.pointerId)\n }\n\n const onKeyDown = (e: KeyboardEvent) => {\n const step = 5\n let newTime = player.state.currentTime\n\n switch (e.key) {\n case 'ArrowRight': newTime = Math.min(player.state.duration, newTime + step); break\n case 'ArrowLeft': newTime = Math.max(0, newTime - step); break\n case 'Home': newTime = 0; break\n case 'End': newTime = player.state.duration; break\n default: return\n }\n\n e.preventDefault()\n player.seekTo(newTime)\n }\n\n container.addEventListener('pointerdown', onPointerDown)\n container.addEventListener('pointermove', onPointerMove)\n container.addEventListener('pointerup', onPointerUp)\n container.addEventListener('keydown', onKeyDown)\n\n this._cleanups.push(\n () => container.removeEventListener('pointerdown', onPointerDown),\n () => container.removeEventListener('pointermove', onPointerMove),\n () => container.removeEventListener('pointerup', onPointerUp),\n () => container.removeEventListener('keydown', onKeyDown),\n )\n\n const unsub = player.subscribe((state: any) => {\n if (this._isDragging) return\n\n const duration = state.duration || 0\n const percent = duration > 0 ? (state.currentTime / duration) * 100 : 0\n\n fill.style.width = `${percent}%`\n thumb.style.left = `${percent}%`\n\n container.setAttribute('aria-valuenow', String(Math.round(percent)))\n container.setAttribute('aria-valuetext', `${formatTimePhrase(state.currentTime)} of ${formatTimePhrase(duration)}`)\n\n // Buffered\n const bufferedPct = (state.bufferedAmount || 0) * 100\n buffered.style.width = `${bufferedPct}%`\n })\n\n this._cleanups.push(unsub)\n }\n}\n",
|
|
39
|
+
"/**\n * ts-video-player <media-volume-slider> Custom Element\n *\n * Volume control slider. Hidden when volume is unsupported (iOS Safari).\n *\n * @module elements/media-volume-slider\n */\n\nimport { resolvePlayer } from './utils'\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaVolumeSlider extends HTMLElementBase {\n private _cleanups: Array<() => void> = []\n private _isDragging = false\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: inline-flex; align-items: center; width: 80px; }\n :host([hidden]) { display: none; }\n .container {\n position: relative; width: 100%; height: 20px;\n cursor: pointer; touch-action: none;\n }\n .track {\n position: absolute; top: 50%; left: 0; right: 0;\n height: 4px; background: rgba(255,255,255,0.3);\n border-radius: 2px; transform: translateY(-50%);\n }\n .fill {\n position: absolute; top: 0; left: 0;\n height: 100%; background: #fff;\n border-radius: 2px; width: 100%;\n }\n .thumb {\n position: absolute; top: 50%; left: 100%;\n width: 12px; height: 12px; background: #fff;\n border-radius: 50%; transform: translate(-50%, -50%);\n transition: transform 0.1s;\n }\n .container:hover .thumb { transform: translate(-50%, -50%) scale(1.2); }\n </style>\n <div class=\"container\" part=\"container\" role=\"slider\"\n aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=\"100\"\n aria-label=\"Volume\" tabindex=\"0\">\n <div class=\"track\" part=\"track\">\n <div class=\"fill\" part=\"fill\"></div>\n <div class=\"thumb\" part=\"thumb\"></div>\n </div>\n </div>\n `\n }\n\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this._cleanups.forEach((fn) => fn())\n this._cleanups = []\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n if (!player) return\n\n const container = this.shadowRoot!.querySelector('.container') as HTMLElement\n const track = this.shadowRoot!.querySelector('.track') as HTMLElement\n const fill = this.shadowRoot!.querySelector('.fill') as HTMLElement\n const thumb = this.shadowRoot!.querySelector('.thumb') as HTMLElement\n\n const setFromEvent = (e: PointerEvent) => {\n const rect = track.getBoundingClientRect()\n const volume = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))\n player.setVolume(volume)\n if (volume > 0 && player.state.muted) player.setMuted(false)\n }\n\n const onPointerDown = (e: PointerEvent) => {\n this._isDragging = true\n container.setPointerCapture(e.pointerId)\n setFromEvent(e)\n }\n const onPointerMove = (e: PointerEvent) => {\n if (!this._isDragging) return\n setFromEvent(e)\n }\n const onPointerUp = (e: PointerEvent) => {\n if (!this._isDragging) return\n this._isDragging = false\n container.releasePointerCapture(e.pointerId)\n }\n const onKeyDown = (e: KeyboardEvent) => {\n let volume = player.state.volume\n const step = 0.05\n\n switch (e.key) {\n case 'ArrowRight':\n case 'ArrowUp': volume = Math.min(1, volume + step); break\n case 'ArrowLeft':\n case 'ArrowDown': volume = Math.max(0, volume - step); break\n default: return\n }\n\n e.preventDefault()\n player.setVolume(volume)\n }\n\n container.addEventListener('pointerdown', onPointerDown)\n container.addEventListener('pointermove', onPointerMove)\n container.addEventListener('pointerup', onPointerUp)\n container.addEventListener('keydown', onKeyDown)\n\n this._cleanups.push(\n () => container.removeEventListener('pointerdown', onPointerDown),\n () => container.removeEventListener('pointermove', onPointerMove),\n () => container.removeEventListener('pointerup', onPointerUp),\n () => container.removeEventListener('keydown', onKeyDown),\n )\n\n const unsub = player.subscribe((state: any) => {\n if (state.volumeAvailability === 'unsupported') {\n this.setAttribute('hidden', '')\n return\n }\n this.removeAttribute('hidden')\n\n const vol = state.muted ? 0 : state.volume\n const percent = vol * 100\n\n fill.style.width = `${percent}%`\n thumb.style.left = `${percent}%`\n container.setAttribute('aria-valuenow', String(Math.round(percent)))\n })\n\n this._cleanups.push(unsub)\n }\n}\n",
|
|
40
|
+
"/**\n * ts-video-player Popover Positioning\n *\n * Collision-aware positioning for menus and tooltips.\n *\n * @module core/popover\n */\n\nexport interface Rect {\n top: number\n left: number\n bottom: number\n right: number\n width: number\n height: number\n}\n\nexport type Placement = 'top' | 'bottom' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end'\n\nexport interface PositionOptions {\n /** Preferred placement */\n placement?: Placement\n /** Gap between anchor and popover in px */\n offset?: number\n /** Boundary element (defaults to viewport) */\n boundary?: HTMLElement | null\n /** Minimum distance from boundary edges in px */\n padding?: number\n}\n\nexport interface PositionResult {\n top: number\n left: number\n placement: Placement\n}\n\n/**\n * Calculate the position of a popover relative to an anchor element,\n * flipping when it would overflow the boundary.\n */\nexport function computePosition(\n anchor: HTMLElement,\n popover: HTMLElement,\n options: PositionOptions = {},\n): PositionResult {\n const {\n placement = 'top',\n offset = 4,\n boundary = null,\n padding = 8,\n } = options\n\n const anchorRect = anchor.getBoundingClientRect()\n const popoverRect = popover.getBoundingClientRect()\n\n const boundaryRect = boundary\n ? boundary.getBoundingClientRect()\n : { top: 0, left: 0, bottom: window.innerHeight, right: window.innerWidth, width: window.innerWidth, height: window.innerHeight }\n\n const availableTop = anchorRect.top - boundaryRect.top - padding\n const availableBottom = boundaryRect.bottom - anchorRect.bottom - padding\n\n // Determine vertical placement (flip if needed)\n let vertical: 'top' | 'bottom'\n if (placement.startsWith('top')) {\n vertical = popoverRect.height + offset <= availableTop ? 'top' : 'bottom'\n } else {\n vertical = popoverRect.height + offset <= availableBottom ? 'bottom' : 'top'\n }\n\n // Calculate top\n let top: number\n if (vertical === 'top') {\n top = anchorRect.top - popoverRect.height - offset\n } else {\n top = anchorRect.bottom + offset\n }\n\n // Determine horizontal alignment\n let align: '' | '-start' | '-end' = ''\n if (placement.endsWith('-start')) align = '-start'\n else if (placement.endsWith('-end')) align = '-end'\n\n // Calculate left\n let left: number\n switch (align) {\n case '-start':\n left = anchorRect.left\n break\n case '-end':\n left = anchorRect.right - popoverRect.width\n break\n default:\n left = anchorRect.left + (anchorRect.width - popoverRect.width) / 2\n break\n }\n\n // Clamp horizontal to boundary\n const minLeft = boundaryRect.left + padding\n const maxLeft = boundaryRect.right - popoverRect.width - padding\n left = Math.max(minLeft, Math.min(maxLeft, left))\n\n // Clamp vertical to boundary\n const minTop = boundaryRect.top + padding\n const maxTop = boundaryRect.bottom - popoverRect.height - padding\n top = Math.max(minTop, Math.min(maxTop, top))\n\n const finalPlacement: Placement = `${vertical}${align}` as Placement\n\n return { top, left, placement: finalPlacement }\n}\n\n/**\n * Apply computed position to a popover element using absolute positioning\n * relative to an offset parent.\n */\nexport function applyPosition(\n anchor: HTMLElement,\n popover: HTMLElement,\n options: PositionOptions = {},\n): Placement {\n // Temporarily make visible for measurement\n const prevVisibility = popover.style.visibility\n popover.style.visibility = 'hidden'\n popover.style.display = 'block'\n\n const result = computePosition(anchor, popover, options)\n\n // Convert to relative coordinates if popover has an offset parent\n const offsetParent = popover.offsetParent as HTMLElement | null\n if (offsetParent) {\n const parentRect = offsetParent.getBoundingClientRect()\n popover.style.top = `${result.top - parentRect.top}px`\n popover.style.left = `${result.left - parentRect.left}px`\n } else {\n popover.style.top = `${result.top}px`\n popover.style.left = `${result.left}px`\n }\n\n popover.style.visibility = prevVisibility\n\n return result.placement\n}\n",
|
|
41
|
+
"/**\n * ts-video-player <media-settings-menu> Custom Element\n *\n * Settings dropdown with quality, speed, captions, and audio track options.\n * Uses collision-aware popover positioning.\n *\n * @module elements/media-settings-menu\n */\n\nimport { resolvePlayer } from './utils'\nimport { computePosition } from '../core/popover'\n\nconst GEAR_ICON = 'M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.488.488 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z'\n\nconst SPEEDS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaSettingsMenu extends HTMLElementBase {\n private _cleanup: (() => void) | null = null\n private _isOpen = false\n private _currentPanel: 'main' | 'speed' | 'quality' | 'captions' | 'audio' = 'main'\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: inline-flex; position: relative; }\n button.trigger {\n display: inline-flex; align-items: center; justify-content: center;\n width: 40px; height: 40px; padding: 8px;\n background: transparent; border: none; color: inherit; cursor: pointer;\n }\n button.trigger:hover { opacity: 0.8; }\n .menu {\n position: fixed; z-index: 9999;\n min-width: 160px; max-height: 250px; overflow-y: auto;\n background: rgba(28,28,28,0.95); border-radius: 4px;\n padding: 4px 0; opacity: 0; visibility: hidden;\n transform: translateY(4px);\n transition: opacity 0.2s, transform 0.2s, visibility 0.2s;\n }\n :host([open]) .menu {\n opacity: 1; visibility: visible; transform: translateY(0);\n }\n .item {\n display: flex; align-items: center; gap: 8px;\n width: 100%; padding: 8px 16px;\n background: none; border: none; color: white;\n text-align: left; cursor: pointer; font-size: 14px;\n }\n .item:hover { background: rgba(255,255,255,0.1); }\n .item[aria-checked=\"true\"]::before { content: '\\\\2713'; }\n .item[aria-checked=\"false\"]::before { content: ''; min-width: 12px; display: inline-block; }\n .back { font-weight: bold; border-bottom: 1px solid rgba(255,255,255,0.2); }\n </style>\n <button class=\"trigger\" part=\"trigger\" type=\"button\"\n aria-label=\"Settings\" aria-haspopup=\"true\" aria-expanded=\"false\">\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"currentColor\" part=\"icon\">\n <path d=\"${GEAR_ICON}\"/>\n </svg>\n </button>\n <div class=\"menu\" part=\"menu\" role=\"menu\" aria-hidden=\"true\"></div>\n `\n }\n\n const trigger = this.shadowRoot!.querySelector('.trigger')!\n this._onTriggerClick = () => this.toggle()\n trigger.addEventListener('click', this._onTriggerClick)\n\n // Close on outside click\n this._onDocClick = (e: MouseEvent) => {\n if (this._isOpen && !this.contains(e.target as Node)) this.close()\n }\n document.addEventListener('click', this._onDocClick)\n\n // Close on escape\n this._onDocKeydown = (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this._isOpen) this.close()\n }\n document.addEventListener('keydown', this._onDocKeydown)\n\n queueMicrotask(() => this.attach())\n }\n\n private _onTriggerClick: (() => void) | null = null\n // eslint-disable-next-line pickier/no-unused-vars\n private _onDocClick: ((e: MouseEvent) => void) | null = null\n // eslint-disable-next-line pickier/no-unused-vars\n private _onDocKeydown: ((e: KeyboardEvent) => void) | null = null\n\n disconnectedCallback(): void {\n this._cleanup?.()\n this._cleanup = null\n if (this._onTriggerClick) {\n this.shadowRoot?.querySelector('.trigger')?.removeEventListener('click', this._onTriggerClick)\n }\n if (this._onDocClick) document.removeEventListener('click', this._onDocClick)\n if (this._onDocKeydown) document.removeEventListener('keydown', this._onDocKeydown)\n }\n\n toggle(): void {\n if (this._isOpen) this.close()\n else this.open()\n }\n\n open(): void {\n this._isOpen = true\n this._currentPanel = 'main'\n this.setAttribute('open', '')\n this.shadowRoot!.querySelector('.trigger')!.setAttribute('aria-expanded', 'true')\n this.shadowRoot!.querySelector('.menu')!.setAttribute('aria-hidden', 'false')\n this.renderPanel()\n this.positionMenu()\n }\n\n close(): void {\n this._isOpen = false\n this.removeAttribute('open')\n this.shadowRoot!.querySelector('.trigger')!.setAttribute('aria-expanded', 'false')\n this.shadowRoot!.querySelector('.menu')!.setAttribute('aria-hidden', 'true')\n }\n\n private positionMenu(): void {\n const trigger = this.shadowRoot!.querySelector('.trigger') as HTMLElement\n const menu = this.shadowRoot!.querySelector('.menu') as HTMLElement\n if (!trigger || !menu) return\n\n // Find the player container as boundary (for fullscreen)\n const playerEl = this.closest('video-player, .ts-video-player') as HTMLElement | null\n\n const result = computePosition(trigger, menu, {\n placement: 'top-end',\n offset: 4,\n boundary: playerEl,\n padding: 8,\n })\n\n menu.style.top = `${result.top}px`\n menu.style.left = `${result.left}px`\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n if (!player) return\n\n const unsub = player.subscribe(() => {\n if (this._isOpen) this.renderPanel()\n })\n\n this._cleanup = unsub\n }\n\n private renderPanel(): void {\n const player = resolvePlayer(this)\n if (!player) return\n\n const menu = this.shadowRoot!.querySelector('.menu')!\n menu.innerHTML = ''\n\n switch (this._currentPanel) {\n case 'main': this.renderMain(menu, player); break\n case 'speed': this.renderSpeed(menu, player); break\n case 'quality': this.renderQuality(menu, player); break\n case 'captions': this.renderCaptions(menu, player); break\n case 'audio': this.renderAudio(menu, player); break\n }\n\n // Re-position after content changes\n if (this._isOpen) {\n queueMicrotask(() => this.positionMenu())\n }\n }\n\n private renderMain(menu: Element, player: any): void {\n const state = player.state\n\n // Speed\n this.addItem(menu, `Speed: ${state.playbackRate}x`, () => {\n this._currentPanel = 'speed'\n this.renderPanel()\n })\n\n // Quality\n if (state.qualities.length > 0) {\n const current = state.qualities.find((q: any) => q.selected)\n const label = state.autoQuality ? 'Auto' : (current ? `${current.height}p` : 'Auto')\n this.addItem(menu, `Quality: ${label}`, () => {\n this._currentPanel = 'quality'\n this.renderPanel()\n })\n }\n\n // Captions\n if (state.textTracks.length > 0) {\n const showing = state.textTracks.find((t: any) => t.mode === 'showing')\n this.addItem(menu, `Captions: ${showing?.label || 'Off'}`, () => {\n this._currentPanel = 'captions'\n this.renderPanel()\n })\n }\n\n if (state.audioTracks.length > 1) {\n const selected = state.audioTracks.find((track: { selected: boolean }) => track.selected)\n this.addItem(menu, `Audio: ${selected?.label || 'Default'}`, () => {\n this._currentPanel = 'audio'\n this.renderPanel()\n })\n }\n }\n\n private renderSpeed(menu: Element, player: any): void {\n this.addItem(menu, 'Back', () => {\n this._currentPanel = 'main'\n this.renderPanel()\n }, false, true)\n\n for (const speed of SPEEDS) {\n const checked = player.state.playbackRate === speed\n this.addItem(menu, `${speed}x`, () => {\n player.setPlaybackRate(speed)\n this.close()\n }, checked)\n }\n }\n\n private renderQuality(menu: Element, player: any): void {\n this.addItem(menu, 'Back', () => {\n this._currentPanel = 'main'\n this.renderPanel()\n }, false, true)\n\n // Auto option\n this.addItem(menu, 'Auto', () => {\n player.setQuality('auto')\n this.close()\n }, player.state.autoQuality)\n\n for (const q of player.state.qualities) {\n this.addItem(menu, `${q.height}p`, () => {\n player.setQuality(q)\n this.close()\n }, q.selected && !player.state.autoQuality)\n }\n }\n\n private renderCaptions(menu: Element, player: any): void {\n this.addItem(menu, 'Back', () => {\n this._currentPanel = 'main'\n this.renderPanel()\n }, false, true)\n\n // Off option\n const anyShowing = player.state.textTracks.some((t: any) => t.mode === 'showing')\n this.addItem(menu, 'Off', () => {\n for (const t of player.state.textTracks) {\n if (t.mode === 'showing') player.setTextTrack(t.id, 'disabled')\n }\n this.close()\n }, !anyShowing)\n\n for (const track of player.state.textTracks) {\n if (track.kind === 'subtitles' || track.kind === 'captions') {\n this.addItem(menu, track.label, () => {\n player.setTextTrack(track.id, 'showing')\n this.close()\n }, track.mode === 'showing')\n }\n }\n }\n\n private renderAudio(menu: Element, player: any): void {\n this.addItem(menu, 'Back', () => {\n this._currentPanel = 'main'\n this.renderPanel()\n }, false, true)\n\n for (const track of player.state.audioTracks) {\n this.addItem(menu, track.label || track.language || 'Audio track', () => {\n player.setAudioTrack(track.id)\n this.close()\n }, track.selected)\n }\n }\n\n private addItem(\n menu: Element,\n label: string,\n onClick: () => void,\n checked = false,\n isBack = false,\n ): void {\n const btn = document.createElement('button')\n btn.className = `item${isBack ? ' back' : ''}`\n btn.setAttribute('role', 'menuitem')\n if (!isBack) btn.setAttribute('aria-checked', String(checked))\n btn.textContent = label\n btn.addEventListener('click', onClick)\n menu.appendChild(btn)\n }\n}\n",
|
|
42
|
+
"import { resolvePlayer } from './utils'\nimport type { PlayerState } from '../types'\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaSeekButton extends HTMLElementBase {\n private cleanup: (() => void) | null = null\n private onClick: (() => void) | null = null\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: inline-flex; }\n button { min-width: 40px; height: 40px; padding: 0 8px; border: 0; background: transparent; color: inherit; cursor: pointer; font: inherit; }\n button:hover { opacity: .8; }\n button:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }\n button:disabled { cursor: default; opacity: .45; }\n </style>\n <button part=\"button\" type=\"button\"></button>\n `\n }\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n const button = this.shadowRoot?.querySelector('button')\n if (button && this.onClick) button.removeEventListener('click', this.onClick)\n this.cleanup?.()\n this.cleanup = null\n this.onClick = null\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n const button = this.shadowRoot?.querySelector('button')\n if (!player || !button) return\n const seconds = Number.parseFloat(this.getAttribute('seconds') ?? '10')\n const offset = Number.isFinite(seconds) && seconds !== 0 ? seconds : 10\n const label = offset > 0 ? `Forward ${offset} seconds` : `Back ${Math.abs(offset)} seconds`\n button.textContent = offset > 0 ? `+${offset}` : String(offset)\n button.setAttribute('aria-label', label)\n this.onClick = () => player.seekBy(offset)\n button.addEventListener('click', this.onClick)\n this.cleanup = player.subscribe((state: PlayerState) => {\n button.disabled = offset < 0 ? state.currentTime <= 0 : state.duration > 0 && state.currentTime >= state.duration\n })\n }\n}\n",
|
|
43
|
+
"import { resolvePlayer } from './utils'\nimport type { PlayerState } from '../types'\n\nconst defaultRates = [0.5, 0.75, 1, 1.25, 1.5, 2]\n\nimport { HTMLElementBase } from './base'\n\nexport class MediaPlaybackRateButton extends HTMLElementBase {\n private cleanup: (() => void) | null = null\n private onClick: (() => void) | null = null\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: inline-flex; }\n button { min-width: 42px; height: 40px; padding: 0 8px; border: 0; background: transparent; color: inherit; cursor: pointer; font: inherit; font-variant-numeric: tabular-nums; }\n button:hover { opacity: .8; }\n button:focus-visible { outline: 2px solid currentColor; outline-offset: -2px; }\n </style>\n <button part=\"button\" type=\"button\" aria-label=\"Playback speed\"></button>\n `\n }\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n const button = this.shadowRoot?.querySelector('button')\n if (button && this.onClick) button.removeEventListener('click', this.onClick)\n this.cleanup?.()\n this.cleanup = null\n this.onClick = null\n }\n\n private getRates(): number[] {\n const rates = (this.getAttribute('rates') ?? '')\n .split(',')\n .map(value => Number.parseFloat(value.trim()))\n .filter(value => Number.isFinite(value) && value >= 0.25 && value <= 4)\n return [...new Set(rates.length > 0 ? rates : defaultRates)].sort((a, b) => a - b)\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n const button = this.shadowRoot?.querySelector('button')\n if (!player || !button) return\n const rates = this.getRates()\n this.onClick = () => {\n const currentIndex = rates.findIndex(rate => rate > player.state.playbackRate + 0.001)\n player.setPlaybackRate(rates[currentIndex < 0 ? 0 : currentIndex])\n }\n button.addEventListener('click', this.onClick)\n this.cleanup = player.subscribe('playbackRate', (state: PlayerState) => {\n button.textContent = `${state.playbackRate}x`\n button.setAttribute('aria-label', `Playback speed ${state.playbackRate}x`)\n })\n }\n}\n",
|
|
44
|
+
"/**\n * ts-video-player <media-airplay-button> Custom Element\n *\n * @module elements/media-airplay-button\n */\n\nimport { HTMLElementBase } from './base'\nimport { resolvePlayer } from './utils'\n\nconst AIRPLAY_ICON = 'M6.2 19h11.6L12 13.2 6.2 19zM19 3H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h2v-2H5V5h14v10h-2v2h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'\n\ninterface AirPlayVideo extends HTMLVideoElement {\n webkitShowPlaybackTargetPicker?: () => void\n webkitCurrentPlaybackTargetIsWireless?: boolean\n}\n\nexport class MediaAirplayButton extends HTMLElementBase {\n private cleanup: (() => void) | null = null\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: none; }\n :host([available]) { display: inline-flex; }\n button { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; }\n button:hover { opacity: 0.8; }\n :host([connected]) button { color: var(--media-airplay-active-color, #38bdf8); }\n </style>\n <button part=\"button\" type=\"button\" aria-label=\"Play on AirPlay\">\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"currentColor\" part=\"icon\"><path d=\"${AIRPLAY_ICON}\"/></svg>\n </button>\n `\n }\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this.cleanup?.()\n this.cleanup = null\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n const button = this.shadowRoot?.querySelector('button')\n if (!player || !button) return\n\n let video: AirPlayVideo | null = null\n const availability = (): void => update()\n const update = (): void => {\n const media = player.provider?.mediaElement\n const nextVideo = media instanceof HTMLVideoElement ? media as AirPlayVideo : null\n if (nextVideo !== video) {\n video?.removeEventListener('webkitplaybacktargetavailabilitychanged', availability)\n video?.removeEventListener('webkitcurrentplaybacktargetiswirelesschanged', availability)\n nextVideo?.addEventListener('webkitplaybacktargetavailabilitychanged', availability)\n nextVideo?.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', availability)\n video = nextVideo\n }\n const available = typeof video?.webkitShowPlaybackTargetPicker === 'function'\n this.toggleAttribute('available', available)\n this.toggleAttribute('connected', video?.webkitCurrentPlaybackTargetIsWireless === true)\n button.setAttribute('aria-pressed', String(video?.webkitCurrentPlaybackTargetIsWireless === true))\n }\n const click = (): void => video?.webkitShowPlaybackTargetPicker?.()\n button.addEventListener('click', click)\n const unsubscribe = player.subscribe(update)\n update()\n\n this.cleanup = () => {\n unsubscribe()\n button.removeEventListener('click', click)\n video?.removeEventListener('webkitplaybacktargetavailabilitychanged', availability)\n video?.removeEventListener('webkitcurrentplaybacktargetiswirelesschanged', availability)\n }\n }\n}\n",
|
|
45
|
+
"/**\n * ts-video-player <media-live-button> Custom Element\n *\n * @module elements/media-live-button\n */\n\nimport { HTMLElementBase } from './base'\nimport { resolvePlayer } from './utils'\n\nexport class MediaLiveButton extends HTMLElementBase {\n private cleanup: (() => void) | null = null\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: none; }\n :host([available]) { display: inline-flex; }\n button { display: inline-flex; align-items: center; gap: 6px; min-height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; }\n i { width: 8px; height: 8px; border-radius: 9999px; background: #ef4444; }\n :host(:not([at-live-edge])) button { opacity: 0.72; }\n </style>\n <button part=\"button\" type=\"button\" aria-label=\"Go to live edge\"><i aria-hidden=\"true\"></i><span>LIVE</span></button>\n `\n }\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this.cleanup?.()\n this.cleanup = null\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n const button = this.shadowRoot?.querySelector('button')\n if (!player || !button) return\n const threshold = Math.max(0, Number(this.getAttribute('threshold') || 10))\n const update = (): void => {\n const state = player.state\n const end = state.seekable.at(-1)?.end\n const providerLive = (player.provider as { isLive?: boolean } | null)?.isLive === true\n const available = end !== undefined && (providerLive || !Number.isFinite(state.duration))\n const atLiveEdge = available && end - state.currentTime <= threshold\n this.toggleAttribute('available', available)\n this.toggleAttribute('at-live-edge', atLiveEdge)\n button.setAttribute('aria-pressed', String(atLiveEdge))\n }\n const click = (): void => {\n const end = player.state.seekable.at(-1)?.end\n if (end !== undefined) {\n player.seekTo(end)\n void player.play()\n }\n }\n button.addEventListener('click', click)\n const unsubscribe = player.subscribe(update)\n update()\n this.cleanup = () => {\n unsubscribe()\n button.removeEventListener('click', click)\n }\n }\n}\n",
|
|
46
|
+
"/**\n * ts-video-player <media-error-display> Custom Element\n *\n * @module elements/media-error-display\n */\n\nimport { HTMLElementBase } from './base'\nimport { resolvePlayer } from './utils'\n\nexport class MediaErrorDisplay extends HTMLElementBase {\n private cleanup: (() => void) | null = null\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: none; position: absolute; inset: 0; z-index: 5; place-items: center; padding: 24px; background: rgba(0, 0, 0, 0.76); color: white; text-align: center; }\n :host([open]) { display: grid; }\n p { max-width: 42rem; margin: 0; font: 500 14px/1.5 system-ui, sans-serif; }\n </style>\n <p part=\"message\" role=\"alert\" aria-live=\"assertive\"></p>\n `\n }\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this.cleanup?.()\n this.cleanup = null\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n const message = this.shadowRoot?.querySelector('p')\n if (!player || !message) return\n const update = (): void => {\n const error = player.state.error\n this.toggleAttribute('open', !!error)\n message.textContent = error?.message || ''\n if (error?.code !== undefined) this.setAttribute('data-code', String(error.code))\n else this.removeAttribute('data-code')\n }\n this.cleanup = player.subscribe(update)\n update()\n }\n}\n",
|
|
47
|
+
"/**\n * ts-video-player <media-remote-playback-button> Custom Element\n *\n * Uses the browser Remote Playback API for native device pickers such as\n * Google Cast without loading a vendor SDK.\n *\n * @module elements/media-remote-playback-button\n */\n\nimport { HTMLElementBase } from './base'\nimport { resolvePlayer } from './utils'\n\nconst REMOTE_ICON = 'M1 18v3h3a3 3 0 0 0-3-3zm0-4v2a5 5 0 0 1 5 5h2a7 7 0 0 0-7-7zm0-4v2c5 0 9 4 9 9h2c0-6.1-4.9-11-11-11zm18-7H5a2 2 0 0 0-2 2v3h2V5h14v10h-5v2h5a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z'\n\ninterface RemotePlaybackHandle extends EventTarget {\n state: 'connecting' | 'connected' | 'disconnected'\n prompt: () => Promise<void>\n watchAvailability: (_callback: (_available: boolean) => void) => Promise<number>\n cancelWatchAvailability: (_id: number) => Promise<void>\n}\n\nexport class MediaRemotePlaybackButton extends HTMLElementBase {\n private cleanup: (() => void) | null = null\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const shadow = this.attachShadow({ mode: 'open' })\n shadow.innerHTML = `\n <style>\n :host { display: none; }\n :host([available]) { display: inline-flex; }\n button { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 8px; background: transparent; border: 0; color: inherit; cursor: pointer; }\n button:hover { opacity: 0.8; }\n :host([connected]) button { color: var(--media-remote-active-color, #38bdf8); }\n </style>\n <button part=\"button\" type=\"button\" aria-label=\"Play on another device\">\n <svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"currentColor\" part=\"icon\"><path d=\"${REMOTE_ICON}\"/></svg>\n </button>\n `\n }\n queueMicrotask(() => this.attach())\n }\n\n disconnectedCallback(): void {\n this.cleanup?.()\n this.cleanup = null\n }\n\n private attach(): void {\n const player = resolvePlayer(this)\n const button = this.shadowRoot?.querySelector('button')\n if (!player || !button) return\n let remote: RemotePlaybackHandle | null = null\n let watchId: number | null = null\n let generation = 0\n\n const stateChanged = (): void => {\n const connected = remote?.state === 'connected'\n this.toggleAttribute('connected', connected)\n button.setAttribute('aria-pressed', String(connected))\n }\n const detachRemote = (): void => {\n generation++\n remote?.removeEventListener('connecting', stateChanged)\n remote?.removeEventListener('connect', stateChanged)\n remote?.removeEventListener('disconnect', stateChanged)\n if (remote && watchId !== null) void remote.cancelWatchAvailability(watchId).catch(() => {})\n remote = null\n watchId = null\n this.removeAttribute('available')\n this.removeAttribute('connected')\n }\n const update = (): void => {\n const media = player.provider?.mediaElement\n const nextRemote = media instanceof HTMLMediaElement\n ? (media as HTMLMediaElement & { remote?: RemotePlaybackHandle }).remote ?? null\n : null\n if (nextRemote === remote) return\n detachRemote()\n if (!nextRemote) return\n remote = nextRemote\n const currentGeneration = generation\n remote.addEventListener('connecting', stateChanged)\n remote.addEventListener('connect', stateChanged)\n remote.addEventListener('disconnect', stateChanged)\n stateChanged()\n void remote.watchAvailability((available) => {\n if (generation === currentGeneration && remote === nextRemote) this.toggleAttribute('available', available)\n })\n .then((id) => {\n if (generation === currentGeneration && remote === nextRemote) watchId = id\n else void nextRemote.cancelWatchAvailability(id).catch(() => {})\n })\n .catch(() => this.removeAttribute('available'))\n }\n const click = (): void => {\n void remote?.prompt().catch(() => {})\n }\n button.addEventListener('click', click)\n const unsubscribe = player.subscribe(update)\n update()\n this.cleanup = () => {\n unsubscribe()\n button.removeEventListener('click', click)\n detachRemote()\n }\n }\n}\n",
|
|
48
|
+
"/**\n * ts-video-player Custom Elements\n *\n * Web Components for building video player interfaces.\n *\n * @module elements\n */\n\nexport { VideoPlayerElement } from './video-player'\nexport { VideoSkin } from './video-skin'\nexport { MediaPlayButton } from './media-play-button'\nexport { MediaMuteButton } from './media-mute-button'\nexport { MediaFullscreenButton } from './media-fullscreen-button'\nexport { MediaPipButton } from './media-pip-button'\nexport { MediaTimeDisplay } from './media-time-display'\nexport { MediaTimeGroup } from './media-time-group'\nexport { MediaTimeSeparator } from './media-time-separator'\nexport { MediaPreviewTime } from './media-preview-time'\nexport { MediaProgressBar } from './media-progress-bar'\nexport { MediaVolumeSlider } from './media-volume-slider'\nexport { MediaSettingsMenu } from './media-settings-menu'\nexport { MediaSeekButton } from './media-seek-button'\nexport { MediaPlaybackRateButton } from './media-playback-rate-button'\nexport { MediaAirplayButton } from './media-airplay-button'\nexport { MediaLiveButton } from './media-live-button'\nexport { MediaErrorDisplay } from './media-error-display'\nexport { MediaRemotePlaybackButton } from './media-remote-playback-button'\nexport { formatTime, formatTimePhrase, toISODuration, resolvePlayer } from './utils'\n\nimport { VideoPlayerElement } from './video-player'\nimport { VideoSkin } from './video-skin'\nimport { MediaPlayButton } from './media-play-button'\nimport { MediaMuteButton } from './media-mute-button'\nimport { MediaFullscreenButton } from './media-fullscreen-button'\nimport { MediaPipButton } from './media-pip-button'\nimport { MediaTimeDisplay } from './media-time-display'\nimport { MediaTimeGroup } from './media-time-group'\nimport { MediaTimeSeparator } from './media-time-separator'\nimport { MediaPreviewTime } from './media-preview-time'\nimport { MediaProgressBar } from './media-progress-bar'\nimport { MediaVolumeSlider } from './media-volume-slider'\nimport { MediaSettingsMenu } from './media-settings-menu'\nimport { MediaSeekButton } from './media-seek-button'\nimport { MediaPlaybackRateButton } from './media-playback-rate-button'\nimport { MediaAirplayButton } from './media-airplay-button'\nimport { MediaLiveButton } from './media-live-button'\nimport { MediaErrorDisplay } from './media-error-display'\nimport { MediaRemotePlaybackButton } from './media-remote-playback-button'\n\nconst elements = [\n ['video-player', VideoPlayerElement],\n ['video-skin', VideoSkin],\n ['media-play-button', MediaPlayButton],\n ['media-mute-button', MediaMuteButton],\n ['media-fullscreen-button', MediaFullscreenButton],\n ['media-pip-button', MediaPipButton],\n ['media-time-display', MediaTimeDisplay],\n ['media-time-group', MediaTimeGroup],\n ['media-time-separator', MediaTimeSeparator],\n ['media-preview-time', MediaPreviewTime],\n ['media-progress-bar', MediaProgressBar],\n ['media-volume-slider', MediaVolumeSlider],\n ['media-settings-menu', MediaSettingsMenu],\n ['media-seek-button', MediaSeekButton],\n ['media-playback-rate-button', MediaPlaybackRateButton],\n ['media-airplay-button', MediaAirplayButton],\n ['media-live-button', MediaLiveButton],\n ['media-error-display', MediaErrorDisplay],\n ['media-remote-playback-button', MediaRemotePlaybackButton],\n] as const\n\n/**\n * Register all custom elements.\n * Safe to call multiple times — skips already-defined elements.\n */\nexport function registerElements(): void {\n for (const [name, ctor] of elements) {\n if (!customElements.get(name)) {\n customElements.define(name, ctor)\n }\n }\n}\n",
|
|
49
|
+
"/**\n * ts-video-player CDN Entry Point\n *\n * Registers the library on `window.VideoPlayer` for script-tag usage.\n * This file has side effects and should be listed in package.json sideEffects.\n *\n * @module cdn\n */\n\nimport { createPlayer, Player } from './player'\nimport { EventEmitter } from './core/events'\nimport { StateStore } from './core/state'\nimport { HTML5Provider } from './providers/html5'\nimport { YouTubeProvider } from './providers/youtube'\nimport { HLSProvider } from './providers/hls'\nimport { DASHProvider } from './providers/dash'\nimport { applyTheme, presetThemes } from './core/themes'\nimport { I18n, t } from './core/i18n'\nimport { layouts, createLayoutManager } from './layouts'\nimport {\n VideoAnalytics, analyticsPlugin,\n AdsManager, adsPlugin,\n SkipSegmentsManager, skipSegmentsPlugin,\n EndScreenManager, endScreenPlugin,\n WatermarkManager, watermarkPlugin,\n} from './plugins'\nimport { registerElements } from './elements'\n\nif (typeof window !== 'undefined') {\n registerElements();\n (window as any).VideoPlayer = {\n createPlayer,\n Player,\n EventEmitter,\n StateStore,\n HTML5Provider,\n YouTubeProvider,\n HLSProvider,\n DASHProvider,\n applyTheme,\n presetThemes,\n I18n,\n t,\n layouts,\n createLayoutManager,\n registerElements,\n plugins: {\n VideoAnalytics,\n analyticsPlugin,\n AdsManager,\n adsPlugin,\n SkipSegmentsManager,\n skipSegmentsPlugin,\n EndScreenManager,\n endScreenPlugin,\n WatermarkManager,\n watermarkPlugin,\n },\n }\n}\n"
|
|
50
|
+
],
|
|
51
|
+
"mappings": "AAsGO,SAAS,EAAkB,EAAgB,CAChD,MAAO,CAEL,IAAK,KACL,QAAS,CAAC,EACV,mBAAoB,EACpB,UAAW,UACX,WAAY,UACZ,aAAc,UAGd,aAAc,OACd,QAAS,WACT,QAAS,GACT,eAAgB,GAChB,MAAO,KAGP,cAAe,OACf,OAAQ,GACR,QAAS,GACT,QAAS,GACT,MAAO,GACP,QAAS,GACT,QAAS,GACT,YAAa,EACb,SAAU,EACV,aAAc,EACd,KAAM,GACN,SAAU,GACV,YAAa,GAGb,MAAO,GACP,OAAQ,EAGR,SAAU,CAAC,EACX,eAAgB,EAChB,SAAU,CAAC,EAGX,WAAY,EACZ,YAAa,EACb,YAAa,mBAGb,UAAW,CAAC,EACZ,YAAa,GACb,YAAa,CAAC,EACd,WAAY,CAAC,EAGb,WAAY,GACZ,iBAAkB,GAClB,cAAe,GACf,oBAAqB,GAGrB,mBAAoB,cACpB,uBAAwB,cACxB,gBAAiB,cAGjB,gBAAiB,GACjB,WAAY,GACZ,YAAa,GAGb,MAAO,GACP,OAAQ,EACV,EAWK,MAAM,CAAW,CACd,MACA,UAAY,IAAI,IAChB,aAA8C,KAC9C,gBAAkB,GAE1B,WAAW,CAAC,EAAgC,CAC1C,KAAK,MAAQ,IAAK,GAAmB,KAAM,CAAQ,EAMrD,QAAQ,EAA0B,CAChC,OAAO,KAAK,MAMd,GAAgC,CAAC,EAAwB,CACvD,OAAO,KAAK,MAAM,GAMpB,GAAgC,CAAC,EAAQ,EAA6B,CACpE,GAAI,KAAK,MAAM,KAAS,EAAO,OAE/B,KAAK,MAAQ,IAAK,KAAK,OAAQ,GAAM,CAAM,EAC3C,KAAK,eAAe,CAAG,EAMzB,KAAK,CAAC,EAAqC,CACzC,QAAY,EAAK,KAAU,OAAO,QAAQ,CAAO,EAC/C,GAAI,KAAK,MAAM,KAA8B,EACzC,KAAK,MAAc,GAAO,EAC5B,KAAK,eAAe,CAAwB,EAQlD,SAAS,CAEP,EAEA,EACa,CACb,GAAI,OAAO,IAAkB,WAAY,CAGvC,GAAI,CAAC,KAAK,UAAU,IADR,GACe,EACzB,KAAK,UAAU,IAFL,IAEc,IAAI,GAAK,EAGnC,OADA,KAAK,UAAU,IAJH,GAIU,EAAG,IAAI,CAAa,EACnC,IAAM,KAAK,UAAU,IALhB,GAKuB,GAAG,OAAO,CAAa,EAI5D,IAAM,EAAM,EACZ,GAAI,CAAC,EAAU,MAAO,IAAM,GAE5B,GAAI,CAAC,KAAK,UAAU,IAAI,CAAG,EACzB,KAAK,UAAU,IAAI,EAAK,IAAI,GAAK,EAGnC,OADA,KAAK,UAAU,IAAI,CAAG,EAAG,IAAI,CAAQ,EAC9B,IAAM,KAAK,UAAU,IAAI,CAAG,GAAG,OAAO,CAAQ,EAMvD,KAAK,EAAS,CACZ,KAAK,MAAQ,GAAmB,EAChC,KAAK,aAAe,KACpB,KAAK,gBAAkB,GACvB,KAAK,UAAU,EAOjB,KAAK,EAAS,CACZ,GAAI,KAAK,aAAc,CACrB,IAAM,EAAO,KAAK,aAClB,KAAK,aAAe,KACpB,KAAK,gBAAkB,GACvB,QAAW,KAAO,EAChB,KAAK,UAAU,CAAG,EAEpB,KAAK,UAAU,GAIX,cAAc,CAAC,EAA8B,CACnD,GAAI,CAAC,KAAK,aACR,KAAK,aAAe,IAAI,IAI1B,GAFA,KAAK,aAAa,IAAI,CAAG,EAErB,CAAC,KAAK,gBACR,KAAK,gBAAkB,GACvB,eAAe,IAAM,KAAK,MAAM,CAAC,EAI7B,SAAS,CAAC,EAA8B,CAC9C,KAAK,UAAU,IAAI,CAAG,GAAG,QAAQ,CAAC,IAAa,EAAS,KAAK,MAAO,CAAG,CAAC,EAGlE,SAAS,EAAS,CACxB,KAAK,UAAU,IAAI,GAAG,GAAG,QAAQ,CAAC,IAAa,EAAS,KAAK,KAAK,CAAC,EAEvE,CASO,SAAS,EAAoB,CAAC,EAA4B,CAC/D,GAAI,EAAM,WAAa,GAAK,EAAM,SAAS,SAAW,EAAG,MAAO,GAEhE,IAAI,EAAW,EACf,QAAW,KAAS,EAAM,SACxB,GAAY,EAAM,IAAM,EAAM,MAEhC,OAAO,KAAK,IAAI,EAAW,EAAM,SAAU,CAAC,EA8F9C,IAAM,GAAc,kBAab,SAAS,EAAkB,CAAC,EAAmB,aAAoC,CACxF,GAAI,CACF,IAAM,EAAO,EAAQ,QAAQ,EAAW,EACxC,GAAI,EACF,OAAO,KAAK,MAAM,CAAI,EAExB,KAAM,EAGR,MAAO,CAAC,EAMH,SAAS,EAAkB,CAAC,EAA6B,EAAmB,aAAoB,CACrG,GAAI,CACF,IAAM,EAAW,GAAmB,CAAO,EAC3C,EAAQ,QAAQ,GAAa,KAAK,UAAU,IAAK,KAAa,CAAM,CAAC,CAAC,EACtE,KAAM,GAQH,SAAS,EAAiB,CAC/B,EACA,EAA8B,CAAC,SAAU,QAAS,cAAc,EAChE,EAAmB,aACN,CAEb,IAAM,EAAY,GAAmB,CAAO,EAC5C,GAAI,EAAU,SAAW,OAAW,EAAM,IAAI,SAAU,EAAU,MAAM,EACxE,GAAI,EAAU,QAAU,OAAW,EAAM,IAAI,QAAS,EAAU,KAAK,EACrE,GAAI,EAAU,eAAiB,OAAW,EAAM,IAAI,eAAgB,EAAU,YAAY,EAG1F,IAAM,EAAe,EAAK,IAAI,CAAC,IAC7B,EAAM,UAAU,EAA0B,CAAC,IAAU,CACnD,IAAM,EAAQ,EAAM,GACpB,GAAmB,EAAG,GAAM,CAAM,EAA2B,CAAO,EACrE,CACH,EAEA,MAAO,IAAM,EAAa,QAAQ,CAAC,IAAU,EAAM,CAAC,EC7c/C,MAAM,CAAqE,CACxE,UAAY,IAAI,IAChB,cAAgB,IAAI,IAK5B,EAAqB,CAAC,EAAU,EAA2B,CACzD,GAAI,CAAC,KAAK,UAAU,IAAI,CAAK,EAC3B,KAAK,UAAU,IAAI,EAAO,IAAI,GAAK,EAIrC,OAFA,KAAK,UAAU,IAAI,CAAK,EAAG,IAAI,CAAO,EAE/B,IAAM,KAAK,IAAI,EAAO,CAAO,EAMtC,IAAuB,CAAC,EAAU,EAA2B,CAC3D,GAAI,CAAC,KAAK,cAAc,IAAI,CAAK,EAC/B,KAAK,cAAc,IAAI,EAAO,IAAI,GAAK,EAIzC,OAFA,KAAK,cAAc,IAAI,CAAK,EAAG,IAAI,CAAO,EAEnC,IAAM,KAAK,cAAc,IAAI,CAAK,GAAG,OAAO,CAAO,EAM5D,GAAsB,CAAC,EAAU,EAAqB,CACpD,KAAK,UAAU,IAAI,CAAK,GAAG,OAAO,CAAO,EACzC,KAAK,cAAc,IAAI,CAAK,GAAG,OAAO,CAAO,EAM/C,IAAuB,CAAC,KAAa,EAA8B,CAEjE,KAAK,UAAU,IAAI,CAAK,GAAG,QAAQ,CAAC,IAAY,CAC9C,GAAI,CACA,EAAqC,GAAG,CAAI,EAC9C,MAAO,EAAO,CACd,QAAQ,MAAM,iDAAiD,OAAO,CAAK,MAAO,CAAK,GAE1F,EAGD,IAAM,EAAe,KAAK,cAAc,IAAI,CAAK,EACjD,GAAI,EACF,EAAa,QAAQ,CAAC,IAAY,CAChC,GAAI,CACA,EAAqC,GAAG,CAAI,EAC9C,MAAO,EAAO,CACd,QAAQ,MAAM,gDAAgD,OAAO,CAAK,MAAO,CAAK,GAEzF,EACD,EAAa,MAAM,EAOvB,kBAAkB,CAAC,EAAuB,CACxC,GAAI,EACF,KAAK,UAAU,OAAO,CAAK,EAC3B,KAAK,cAAc,OAAO,CAAK,EAE/B,UAAK,UAAU,MAAM,EACrB,KAAK,cAAc,MAAM,EAO7B,aAAa,CAAC,EAAwB,CACpC,OAAQ,KAAK,UAAU,IAAI,CAAK,GAAG,MAAQ,IAAM,KAAK,cAAc,IAAI,CAAK,GAAG,MAAQ,GAE5F,CASO,MAAM,CAAsB,CACzB,MACA,QACA,UAA+B,CAAC,EAExC,WAAW,CAAC,EAAyB,EAAyC,CAC5E,KAAK,MAAQ,EACb,KAAK,QAAU,EACf,KAAK,gBAAgB,EAGf,eAAe,EAAS,CAC9B,IAAM,EAAK,CAAC,EAAe,IAAgC,CACzD,KAAK,MAAM,iBAAiB,EAAO,CAAO,EAC1C,KAAK,UAAU,KAAK,IAAM,KAAK,MAAM,oBAAoB,EAAO,CAAO,CAAC,GAI1E,EAAG,YAAa,IAAM,KAAK,QAAQ,KAAK,WAAW,CAAC,EACpD,EAAG,iBAAkB,IAAM,KAAK,QAAQ,KAAK,gBAAgB,CAAC,EAC9D,EAAG,aAAc,IAAM,KAAK,QAAQ,KAAK,YAAY,CAAC,EACtD,EAAG,UAAW,IAAM,KAAK,QAAQ,KAAK,SAAS,CAAC,EAChD,EAAG,iBAAkB,IAAM,KAAK,QAAQ,KAAK,gBAAgB,CAAC,EAG9D,EAAG,OAAQ,IAAM,KAAK,QAAQ,KAAK,MAAM,CAAC,EAC1C,EAAG,QAAS,IAAM,KAAK,QAAQ,KAAK,OAAO,CAAC,EAC5C,EAAG,UAAW,IAAM,KAAK,QAAQ,KAAK,SAAS,CAAC,EAChD,EAAG,UAAW,IAAM,KAAK,QAAQ,KAAK,SAAS,CAAC,EAChD,EAAG,QAAS,IAAM,KAAK,QAAQ,KAAK,OAAO,CAAC,EAG5C,EAAG,UAAW,IAAM,KAAK,QAAQ,KAAK,SAAS,CAAC,EAChD,EAAG,SAAU,IAAM,KAAK,QAAQ,KAAK,QAAQ,CAAC,EAG9C,EAAG,aAAc,IAAM,KAAK,QAAQ,KAAK,aAAc,KAAK,MAAM,WAAW,CAAC,EAC9E,EAAG,iBAAkB,IAAM,KAAK,QAAQ,KAAK,iBAAkB,KAAK,MAAM,QAAQ,CAAC,EAGnF,EAAG,eAAgB,IAAM,KAAK,QAAQ,KAAK,eAAgB,KAAK,MAAM,OAAQ,KAAK,MAAM,KAAK,CAAC,EAG/F,EAAG,aAAc,IAAM,KAAK,QAAQ,KAAK,aAAc,KAAK,MAAM,YAAY,CAAC,EAG/E,EAAG,WAAY,IAAM,CACnB,IAAM,EAAW,KAAK,cAAc,KAAK,MAAM,QAAQ,EACvD,KAAK,QAAQ,KAAK,WAAY,CAAQ,EACvC,EAGD,EAAG,QAAS,IAAM,CAChB,IAAM,EAAQ,KAAK,eAAe,KAAK,MAAM,KAAK,EAClD,KAAK,QAAQ,KAAK,QAAS,CAAK,EACjC,EAGK,aAAa,CAAC,EAAiC,CACrD,IAAM,EAAsB,CAAC,EAC7B,QAAS,EAAI,EAAG,EAAI,EAAO,OAAQ,IACjC,EAAO,KAAK,CACV,MAAO,EAAO,MAAM,CAAC,EACrB,IAAK,EAAO,IAAI,CAAC,CACnB,CAAC,EAEH,OAAO,EAGD,cAAc,CAAC,EAAiD,CACtE,GAAI,CAAC,EACH,MAAO,CAAE,KAAM,EAAG,QAAS,eAAgB,EAG7C,IAAM,EAAmC,CACvC,EAAG,oEACH,EAAG,8DACH,EAAG,uDACH,EAAG,sCACL,EAEA,MAAO,CACL,KAAM,EAAM,KACZ,QAAS,EAAS,EAAM,OAAS,EAAM,SAAW,eACpD,EAMF,OAAO,EAAS,CACd,KAAK,UAAU,QAAQ,CAAC,IAAW,EAAO,CAAC,EAC3C,KAAK,UAAY,CAAC,EAEtB,CASO,SAAS,CAAkB,CAAC,EAAoD,CACrF,IAAM,EAAS,CAAC,mBAAoB,yBAA0B,sBAAuB,oBAAoB,EAEnG,EAAW,IAAM,CACrB,IAAM,EAAa,CAAC,EAClB,SAAS,mBACR,SAAiB,yBACjB,SAAiB,sBACjB,SAAiB,qBAEpB,EAAQ,CAAU,GAKpB,OAFA,EAAO,QAAQ,CAAC,IAAU,SAAS,iBAAiB,EAAO,CAAQ,CAAC,EAE7D,IAAM,CACX,EAAO,QAAQ,CAAC,IAAU,SAAS,oBAAoB,EAAO,CAAQ,CAAC,GAWpE,SAAS,CAAW,CAAC,EAAyB,EAA6C,CAChG,IAAM,EAAe,IAAM,EAAQ,EAAI,EACjC,EAAe,IAAM,EAAQ,EAAK,EAKxC,OAHA,EAAM,iBAAiB,wBAAyB,CAAY,EAC5D,EAAM,iBAAiB,wBAAyB,CAAY,EAErD,IAAM,CACX,EAAM,oBAAoB,wBAAyB,CAAY,EAC/D,EAAM,oBAAoB,wBAAyB,CAAY,GAW5D,IAAM,GAAqD,CAChE,IAAK,aACL,EAAG,aACH,EAAG,aACH,EAAG,mBACH,EAAG,YACH,EAAG,iBACH,UAAW,eACX,WAAY,cACZ,EAAG,eACH,EAAG,cACH,QAAS,WACT,UAAW,aACX,IAAK,UACL,IAAK,YACL,KAAM,YACN,IAAK,UACL,IAAK,QACL,IAAK,SACL,IAAK,SACL,IAAK,SACL,IAAK,SACL,IAAK,SACL,IAAK,SACL,IAAK,SACL,IAAK,SACL,IAAK,QACP,EAyBO,SAAS,EAAqB,CACnC,EACA,EAA0E,CAAC,EAC3C,CAChC,IAAQ,SAAU,EAAY,EAAG,WAAY,EAAc,IAAK,UAAW,EAAa,MAAS,EAEjG,MAAO,CAAC,IAAyB,CAE/B,GACE,EAAM,kBAAkB,kBACxB,EAAM,kBAAkB,qBACxB,EAAM,kBAAkB,mBACvB,EAAM,QAAwB,kBAE/B,OAGF,IAAM,EAAM,EAAM,IACZ,EAAS,GAA2B,GAE1C,GAAI,CAAC,EAAQ,OAKb,OAFA,EAAM,eAAe,EAEb,OACD,aACH,EAAQ,WAAW,EACnB,UACG,aACH,EAAQ,WAAW,EACnB,UACG,mBACH,EAAQ,iBAAiB,EACzB,UACG,YACH,EAAQ,UAAU,EAClB,UACG,iBACH,EAAQ,eAAe,EACvB,UACG,eACH,EAAQ,aAAa,EACrB,UACG,cACH,EAAQ,YAAY,EACpB,UACG,WACH,EAAQ,SAAS,EACjB,UACG,aACH,EAAQ,WAAW,EACnB,UACG,UACH,EAAQ,QAAQ,EAChB,UACG,YACH,EAAQ,UAAU,EAClB,UACG,YACH,EAAQ,UAAU,EAClB,UACG,UACH,EAAQ,QAAQ,EAChB,cAGA,GAAI,EAAO,WAAW,MAAM,EAAG,CAC7B,IAAM,EAAU,SAAS,EAAO,QAAQ,OAAQ,EAAE,EAAG,EAAE,EACvD,GAAI,CAAC,MAAM,CAAO,EAChB,EAAQ,YAAY,CAAO,KAchC,SAAS,EAAsB,CACpC,EACA,EAAgF,CAAC,EACrE,CACZ,IAAQ,UAAU,KAAM,WAAU,cAAe,EAE7C,EAA8C,KAC9C,EAAS,GAEP,EAAY,IAAM,CACtB,GAAI,CAAC,EACH,EAAS,GACT,IAAW,EAEb,EAAW,GAGP,EAAc,IAAM,CACxB,GAAI,EACF,EAAS,GACT,IAAa,GAIX,EAAa,IAAM,CACvB,GAAI,EAAO,aAAa,CAAK,EAC7B,EAAQ,WAAW,EAAa,CAAO,GAInC,EAAS,CAAC,YAAa,aAAc,aAAc,YAAa,UAAW,aAAc,WAAW,EAQ1G,OANA,EAAO,QAAQ,CAAC,IAAU,EAAQ,iBAAiB,EAAO,CAAS,CAAC,EAGpE,EAAW,EAGJ,IAAM,CACX,GAAI,EAAO,aAAa,CAAK,EAC7B,EAAO,QAAQ,CAAC,IAAU,EAAQ,oBAAoB,EAAO,CAAS,CAAC,GCtZpE,SAAS,EAAuB,EAAY,CACjD,OAAO,OAAO,UAAc,KAAe,iBAAkB,UAOxD,SAAS,EAAkB,CAChC,EACA,EAOA,EACY,CACZ,GAAI,CAAC,GAAwB,EAAG,MAAO,IAAM,GAE7C,IAAM,EAAU,UAAU,aAG1B,GAAI,EACF,GAAe,CAAO,EAIxB,IAAM,EAAmE,CACvE,CAAC,OAAQ,IAAM,EAAQ,KAAK,CAAC,EAC7B,CAAC,QAAS,IAAM,EAAQ,MAAM,CAAC,EAC/B,CAAC,OAAQ,IAAM,EAAQ,KAAK,CAAC,EAC7B,CAAC,eAAgB,CAAC,IAAY,CAC5B,IAAM,EAAU,EAAgE,YAAc,IAC9F,EAAQ,OAAO,CAAM,EACtB,EACD,CAAC,cAAe,CAAC,IAAY,CAC3B,IAAM,EAAU,EAAgE,YAAc,GAC9F,EAAQ,OAAO,CAAM,EACtB,EACD,CAAC,SAAU,CAAC,IAAY,CACtB,IAAM,EAAY,EAA8D,SAChF,GAAI,IAAa,OAAW,EAAQ,OAAO,CAAQ,EACpD,CACH,EAEA,QAAY,EAAQ,KAAY,EAC9B,GAAI,CACF,EAAQ,iBAAiB,EAAQ,CAAO,EACxC,KAAM,EAMV,IAAM,EAAc,EAAM,UAAU,SAAU,CAAC,IAAU,CACvD,EAAQ,cAAgB,EAAM,OAAS,SAAW,UACnD,EAGG,EAAuD,KAErD,EAAiB,IAAM,CAC3B,IAAM,EAAQ,EAAM,SAAS,EAC7B,GAAI,CAAC,SAAS,EAAM,QAAQ,GAAK,EAAM,UAAY,EAAG,OACtD,GAAI,CACF,EAAQ,iBAAiB,CACvB,SAAU,EAAM,SAChB,aAAc,EAAM,aACpB,SAAU,KAAK,IAAI,EAAM,YAAa,EAAM,QAAQ,CACtD,CAAC,EACD,KAAM,IAKJ,EAAe,EAAM,UAAU,UAAW,CAAC,IAAU,CACzD,GAAI,EAAM,SAER,GADA,EAAe,EACX,CAAC,EACH,EAAgB,YAAY,EAAgB,IAAI,EAE7C,KACL,GAAI,EAAiB,cAAc,CAAa,EAAG,EAAgB,KACnE,EAAe,GAElB,EAED,MAAO,IAAM,CACX,GAAI,EAAe,cAAc,CAAa,EAC9C,EAAY,EACZ,EAAa,EAGb,QAAY,KAAW,EACrB,GAAI,CAAE,EAAQ,iBAAiB,EAAQ,IAAI,EAAI,KAAM,IAQpD,SAAS,EAAc,CAAC,EAAoC,CACjE,GAAI,CAAC,GAAwB,EAAG,OAChC,UAAU,aAAa,SAAW,IAAI,cAAc,CAClD,MAAO,EAAQ,OAAS,GACxB,OAAQ,EAAQ,QAAU,GAC1B,MAAO,EAAQ,OAAS,GACxB,QAAS,EAAQ,SAAW,CAAC,CAC/B,CAAC,ECrHI,SAAS,EAA0B,EAAY,CACpD,OACE,OAAO,OAAW,KAClB,gBAAiB,QACjB,OAAQ,OAAO,YAAgD,OAAS,WAOrE,SAAS,EAAe,CAAC,EAA6B,CAC3D,GAAI,CAAC,GAA2B,EAAG,OAEnC,IAAM,EAAkD,CACtD,UAAW,YACX,SAAU,WACV,IAAK,KACP,EAEE,OAAO,YACN,KAAK,EAAe,EAAK,EACzB,MAAM,IAAM,EAEZ,EAME,SAAS,EAAiB,EAAS,CACxC,GAAI,CAAC,GAA2B,EAAG,OAEnC,GAAI,CACA,OAAO,YAAgD,OAAO,EAChE,KAAM,GCpCH,SAAS,EAAW,EAAY,CACrC,GAAI,OAAO,UAAc,IAAa,MAAO,GAC7C,IAAM,EAAK,UAAU,UAErB,GAAI,mBAAmB,KAAK,CAAE,GAAK,CAAE,OAAe,SAAU,MAAO,GAErE,GAAI,UAAU,WAAa,YAAc,UAAU,eAAiB,EAAG,MAAO,GAC9E,MAAO,GAMF,SAAS,EAAQ,EAAY,CAClC,GAAI,OAAO,UAAc,IAAa,MAAO,GAC7C,MAAO,mCAAmC,KAAK,UAAU,SAAS,EAM7D,SAAS,EAAe,EAAY,CACzC,GAAI,OAAO,OAAW,IAAa,MAAO,GAC1C,OAAQ,UAAkB,aAAe,IACpC,OAAO,WAAW,4BAA4B,EAAE,QAWhD,SAAS,EAAwB,CACtC,EACqB,CACrB,GAAI,GAAY,EAAG,MAAO,cAC1B,GAAI,CAAC,EAAO,MAAO,cACnB,MAAO,YAOT,eAAsB,EAAuB,CAC3C,EAC8B,CAC9B,IAAM,EAAW,EAAM,OACvB,GAAI,CACF,IAAM,EAAY,IAAa,EAAI,IAAM,EACzC,EAAM,OAAS,EAEf,MAAM,IAAI,QAAQ,CAAC,IAAM,WAAW,EAAG,CAAC,CAAC,EACzC,IAAM,EAAU,EAAM,SAAW,EAEjC,OADA,EAAM,OAAS,EACR,EAAU,YAAc,cAC/B,KAAM,CACN,GAAI,CAAE,EAAM,OAAS,EAAW,KAAM,EACtC,MAAO,eAmCJ,SAAS,EAA4B,CAC1C,EACA,EACqB,CACrB,IAAM,EAAM,OAAO,SAAa,IAAc,SAA6B,KAC3E,GAAI,CAAC,EAAK,MAAO,cAGjB,GAAI,EAAI,mBAAqB,EAAI,wBAAyB,MAAO,YAEjE,GAAI,EAAI,sBAAwB,EAAI,oBAAqB,MAAO,YAGhE,GAAI,EAAc,CAChB,IAAM,EAAQ,EACd,GAAI,EAAM,2BAA6B,GAAM,MAAO,YACpD,GAAI,OAAO,EAAM,wBAA0B,WAAY,MAAO,YAGhE,GAAI,CAAC,GAAa,CAAC,EAAc,MAAO,cACxC,MAAO,cAkBT,eAAsB,EAAe,CACnC,EACA,EACe,CACf,IAAM,EAAQ,EAGd,GAAI,EAAW,CACb,IAAM,EAAK,EACX,GAAI,OAAO,EAAG,oBAAsB,WAAY,OAAO,EAAG,kBAAkB,EAC5E,GAAI,OAAO,EAAG,0BAA4B,WAAY,OAAO,EAAG,wBAAwB,EACxF,GAAI,OAAO,EAAG,0BAA4B,WAAY,OAAO,EAAG,wBAAwB,EAI1F,GAAI,GAAS,OAAO,EAAM,wBAA0B,WAAY,CAC9D,EAAM,sBAAsB,EAC5B,OAIF,GAAI,GAAS,OAAO,EAAM,oBAAsB,WAC9C,OAAO,EAAM,kBAAkB,EAOnC,eAAsB,EAAc,CAAC,EAAgD,CACnF,IAAM,EAAM,SACN,EAAQ,EAEd,GAAI,OAAO,EAAI,iBAAmB,WAAY,OAAO,EAAI,eAAe,EACxE,GAAI,OAAO,EAAI,uBAAyB,WAAY,OAAO,EAAI,qBAAqB,EACpF,GAAI,OAAO,EAAI,yBAA2B,WAAY,OAAO,EAAI,uBAAuB,EAGxF,GAAI,GAAS,OAAO,EAAM,uBAAyB,WACjD,EAAM,qBAAqB,EAWxB,SAAS,EAAqB,CACnC,EACqB,CACrB,GAAI,CAAC,EAAc,MAAO,cAG1B,GAAI,4BAA6B,EAAc,CAC7C,GAAI,OAAO,SAAa,KAAe,SAAS,0BAA4B,GAC1E,MAAO,cAGT,GAAI,GAAS,GAAK,GAAgB,EAAG,MAAO,cAC5C,MAAO,YAIT,IAAM,EAAQ,EACd,GAAI,OAAO,EAAM,4BAA8B,WAAY,CACzD,GAAI,GAAgB,EAAG,MAAO,cAC9B,GAAI,OAAO,EAAM,iCAAmC,WAClD,OAAO,EAAM,+BAA+B,oBAAoB,EAC5D,YACA,cAEN,MAAO,YAGT,MAAO,cAMT,eAAsB,EAAQ,CAAC,EAAwC,CACrE,IAAM,EAAQ,EAGd,GAAI,OAAO,EAAM,0BAA4B,WAAY,CACvD,MAAM,EAAM,wBAAwB,EACpC,OAIF,GAAI,OAAO,EAAM,4BAA8B,WAC7C,EAAM,0BAA0B,oBAAoB,EAOxD,eAAsB,EAAO,CAAC,EAAgD,CAE5E,GAAI,SAAS,yBAA2B,OAAO,SAAS,uBAAyB,WAAY,CAC3F,MAAM,SAAS,qBAAqB,EACpC,OAIF,GAAI,EAAO,CACT,IAAM,EAAQ,EACd,GAAI,EAAM,yBAA2B,sBAChC,OAAO,EAAM,4BAA8B,WAC9C,EAAM,0BAA0B,QAAQ,GCtOvC,MAAe,CAAiC,CAI3C,UAAgC,KAChC,QAAyB,CAAC,EAC1B,OAAyC,IAAI,EAC7C,OAAS,MAEf,aAAY,EAAmE,CACjF,OAAO,QAGL,MAAK,EAAY,CACnB,OAAO,KAAK,YAKR,MAAK,CAAC,EAAwB,EAAuC,CACzE,KAAK,UAAY,EACjB,KAAK,QAAU,EACf,MAAM,KAAK,WAAW,EACtB,KAAK,OAAS,GACd,KAAK,OAAO,KAAK,OAAO,EAK1B,OAAO,EAAS,CACd,KAAK,OAAS,GACd,KAAK,QAAQ,EACb,KAAK,OAAO,mBAAmB,EAejC,IAAI,EAAS,CACX,KAAK,MAAM,EACX,KAAK,OAAO,CAAC,EAuBf,YAAY,EAAmB,CAC7B,MAAO,CAAC,EAGV,UAAU,CAAC,EAAuC,EAMlD,aAAa,EAAgB,CAC3B,MAAO,CAAC,EAGV,gBAAgB,CAAC,EAAkB,EAAgD,EAInF,cAAc,EAAiB,CAC7B,MAAO,CAAC,EAGV,aAAa,CAAC,EAAwB,EAMtC,sBAAsB,CAAC,EAA+D,CAEpF,IAAM,EAAK,KAAK,aACV,EAAQ,aAAc,iBAAmB,EAAK,KAC9C,EAAQ,aAAc,iBAAmB,EAAK,KAEpD,OAAQ,OACD,SAEH,OAAO,GAAM,EAAE,aAAc,kBAAoB,YAAc,GAAyB,CAAK,MAC1F,aACH,OAAO,GAA6B,KAAK,UAAW,CAAK,MACtD,MAEH,OAAO,GAAM,EAAE,aAAc,kBAAoB,cAAgB,GAAsB,CAAK,UAE5F,MAAO,oBAMP,gBAAe,EAAkB,CACrC,MAAM,GAAQ,KAAK,UAAW,KAAK,YAAuC,OAGtE,eAAc,EAAkB,CACpC,MAAM,GAAO,KAAK,YAAuC,EAGjD,oBAAoB,EAAuB,CACnD,OAAO,KAAK,eAKR,SAAQ,EAAkB,CAC9B,IAAM,EAAQ,KAAK,aACnB,GAAI,CAAC,EAAO,OACZ,MAAM,GAAS,CAAK,OAGhB,QAAO,EAAkB,CAC7B,MAAM,GAAQ,KAAK,YAAuC,EAK5D,EAAoC,CAAC,EAAU,EAAoC,CACjF,KAAK,OAAO,GAAG,EAAO,CAAO,EAG/B,GAAqC,CAAC,EAAU,EAAoC,CAClF,KAAK,OAAO,IAAI,EAAO,CAAO,EAKtB,SAAS,CAAC,EAAc,EAAiB,EAAyB,CAC1E,IAAM,EAAoB,CAAE,OAAM,UAAS,SAAQ,EACnD,KAAK,OAAO,KAAK,QAAS,CAAK,EAEnC,CC7KO,SAAS,EAAa,CAAC,EAAmB,CAC/C,GAAI,OAAO,IAAQ,SAAU,CAC3B,IAAM,EAAY,EAAa,CAAG,EAElC,GACE,IAAc,OACd,IAAc,QACd,IAAc,OACd,IAAc,OACd,IAAc,OACd,IAAc,OACd,IAAc,OACd,IAAc,QACd,IAAc,OACd,IAAc,OACd,IAAc,OACd,IAAc,OAEd,MAAO,GAGT,MAAO,GAGT,GAAI,OAAO,IAAQ,UAAY,SAAU,EAAK,CAC5C,IAAM,EAAQ,EAAoB,MAAM,YAAY,GAAK,GACzD,OACE,EAAK,WAAW,QAAQ,GACxB,EAAK,WAAW,QAAQ,GACxB,IAAS,kBAIb,MAAO,GAGT,SAAS,CAAY,CAAC,EAAqB,CACzC,IAAM,EAAO,EAAI,MAAM,OAAQ,CAAC,EAAE,GAClC,OAAO,EAAK,MAAM,EAAK,YAAY,GAAG,EAAI,CAAC,EAAE,YAAY,EAM3D,SAAS,EAAW,CAAC,EAAqB,CACxC,IAAM,EAAM,EAAa,CAAG,EAe5B,MAd0C,CACxC,IAAK,YACL,KAAM,aACN,IAAK,YACL,IAAK,YACL,IAAK,kBACL,IAAK,aACL,IAAK,YACL,KAAM,aACN,IAAK,YACL,IAAK,YACL,IAAK,YACL,KAAM,wBACR,EACiB,GAAO,KAAO,YAM1B,MAAM,UAAsB,CAAa,CACrC,KAAO,QACP,KAAqB,QAEtB,MAAoD,KACpD,iBAAiD,KACjD,kBAAyC,KACzC,WAAkC,KAClC,QAAU,MAEd,aAAY,EAA+C,CAC7D,OAAO,KAAK,WAKE,WAAU,EAAkB,CAC1C,GAAI,CAAC,KAAK,UACR,MAAU,MAAM,+BAA+B,EAWjD,GAPA,KAAK,QAAU,KAAK,QAAQ,YAAc,QAG1C,KAAK,MAAQ,SAAS,cAAc,KAAK,QAAU,QAAU,OAAO,EACpE,KAAK,MAAM,UAAY,yBAGnB,KAAK,QAAQ,SAAU,KAAK,MAAM,SAAW,GACjD,GAAI,KAAK,QAAQ,KAAM,KAAK,MAAM,KAAO,GACzC,GAAI,KAAK,QAAQ,MAAO,KAAK,MAAM,MAAQ,GAC3C,GAAI,KAAK,QAAQ,aAAe,CAAC,KAAK,QACnC,KAAK,MAA2B,YAAc,GAEjD,GAAI,KAAK,QAAQ,QAAS,KAAK,MAAM,QAAU,KAAK,QAAQ,QAC5D,GAAI,KAAK,QAAQ,YAAa,KAAK,MAAM,YAAc,KAAK,QAAQ,YACpE,GAAI,KAAK,QAAQ,cAAc,OAAQ,KAAK,MAAM,aAAa,eAAgB,KAAK,QAAQ,aAAa,KAAK,GAAG,CAAC,EAClH,GAAI,CAAC,KAAK,SAAW,KAAK,QAAQ,wBAC/B,KAAK,MAA2B,wBAA0B,GAE7D,GAAI,KAAK,QAAQ,QAAU,CAAC,KAAK,QAC9B,KAAK,MAA2B,OAAS,KAAK,QAAQ,OAIzD,GAAI,KAAK,QAAQ,SAAW,OAC1B,KAAK,MAAM,OAAS,KAAK,QAAQ,OAInC,KAAK,UAAU,YAAY,KAAK,KAAK,EACrC,QAAW,KAAS,KAAK,QAAQ,QAAU,CAAC,EAAG,KAAK,aAAa,CAAK,EAWtE,GARA,KAAK,iBAAmB,IAAI,EAAsB,KAAK,MAAO,KAAK,MAAM,EAGzE,KAAK,kBAAoB,EAAmB,CAAC,IAAe,CAC1D,KAAK,OAAO,KAAK,mBAAoB,CAAU,EAChD,EAGG,CAAC,KAAK,SAAW,KAAK,iBAAiB,iBACzC,KAAK,WAAa,EAAY,KAAK,MAAO,CAAC,IAAQ,CACjD,KAAK,OAAO,KAAK,YAAa,CAAG,EAClC,EAIH,GAAI,CAAC,KAAK,QACR,KAAK,MAAM,iBAAiB,iBAAkB,IAAM,CAClD,IAAM,EAAQ,KAAK,MACb,EAAyB,KAAK,uBAAuB,YAAY,EACjE,EAAkB,KAAK,uBAAuB,KAAK,EACnD,EAAqB,KAAK,uBAAuB,QAAQ,EAC/D,KAAK,OAAO,KAAK,cAAe,CAC9B,WAAY,EAAM,WAClB,YAAa,EAAM,YACnB,aAAc,EAAM,YAAc,EAAI,EAAM,WAAa,EAAM,YAAc,IAAM,mBACnF,cAAe,IAA2B,YAC1C,oBAAqB,IAAoB,YACzC,yBACA,kBACA,oBACF,CAAC,EACD,KAAK,OAAO,KAAK,qBAAsB,aAAc,CAAsB,EAC3E,KAAK,OAAO,KAAK,qBAAsB,MAAO,CAAe,EAC7D,KAAK,OAAO,KAAK,qBAAsB,SAAU,CAAkB,EACpE,EAiBH,GAbA,KAAK,MAAM,iBAAiB,aAAc,IAAM,CAC9C,GAAI,CAAC,KAAK,MAAO,OACjB,GAAwB,KAAK,KAAK,EAAE,KAAK,CAAC,IAAiB,CACzD,KAAK,OAAO,KAAK,cAAe,CAC9B,mBAAoB,CACtB,CAAC,EACD,KAAK,OAAO,KAAK,qBAAsB,SAAU,CAAY,EAC9D,EAAE,MAAM,IAAM,EAEd,EACF,EAGG,CAAC,KAAK,SAAW,KAAK,iBAAiB,iBACzC,KAAK,MAAM,iBAAiB,gCAAiC,IAAM,CACjE,IAAM,EAAQ,KAAK,MACnB,GAAI,GAAO,uBAAwB,CACjC,IAAM,EAAQ,EAAM,yBAA2B,qBAC/C,KAAK,OAAO,KAAK,YAAa,CAAK,GAEtC,EAIK,OAAO,EAAS,CAKxB,GAJA,KAAK,kBAAkB,QAAQ,EAC/B,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAEd,KAAK,MACP,KAAK,MAAM,MAAM,EACjB,KAAK,MAAM,gBAAgB,KAAK,EAChC,KAAK,MAAM,KAAK,EAChB,KAAK,MAAM,OAAO,EAClB,KAAK,MAAQ,KAMjB,OAAO,CAAC,EAAmB,CACzB,OAAO,GAAc,CAAG,OAGpB,KAAI,CAAC,EAAyB,CAClC,GAAI,CAAC,KAAK,MAAO,OAGjB,KAAK,MAAM,UAAY,GACvB,KAAK,MAAM,gBAAgB,KAAK,EAGhC,IAAM,EAAU,MAAM,QAAQ,CAAG,EAAI,EAAM,CAAC,CAAG,EAE/C,QAAW,KAAU,EACnB,GAAI,OAAO,IAAW,SAAU,CAE9B,IAAM,EAAW,SAAS,cAAc,QAAQ,EAChD,EAAS,IAAM,EACf,EAAS,KAAO,GAAY,CAAM,EAClC,KAAK,MAAM,YAAY,CAAQ,EAC1B,QAAI,QAAS,GAAU,OAAO,EAAO,MAAQ,SAAU,CAE5D,IAAM,EAAc,EACd,EAAW,SAAS,cAAc,QAAQ,EAChD,EAAS,IAAM,EAAY,IAC3B,EAAS,KAAO,EAAY,MAAQ,GAAY,EAAY,GAAG,EAC/D,KAAK,MAAM,YAAY,CAAQ,EAKnC,KAAK,MAAM,KAAK,OAKZ,KAAI,EAAkB,CAC1B,GAAI,CAAC,KAAK,MAAO,OACjB,GAAI,CACF,MAAM,KAAK,MAAM,KAAK,EACtB,MAAO,EAAO,CAEd,GAAK,EAAgB,OAAS,kBAC5B,KAAK,UAAU,EAAG,mDAAoD,CAAK,EAE3E,WAAM,GAKZ,KAAK,EAAS,CACZ,KAAK,OAAO,MAAM,EAKpB,MAAM,CAAC,EAAoB,CACzB,GAAI,KAAK,MACP,KAAK,MAAM,YAAc,KAAK,IAAI,EAAG,KAAK,IAAI,EAAM,KAAK,YAAY,CAAC,CAAC,EAI3E,cAAc,EAAW,CACvB,OAAO,KAAK,OAAO,aAAe,EAGpC,WAAW,EAAW,CACpB,OAAO,KAAK,OAAO,UAAY,EAKjC,SAAS,CAAC,EAAsB,CAC9B,GAAI,KAAK,MACP,KAAK,MAAM,OAAS,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,CAAM,CAAC,EAIvD,SAAS,EAAW,CAClB,OAAO,KAAK,OAAO,QAAU,EAG/B,QAAQ,CAAC,EAAsB,CAC7B,GAAI,KAAK,MACP,KAAK,MAAM,MAAQ,EAIvB,QAAQ,EAAY,CAClB,OAAO,KAAK,OAAO,OAAS,GAK9B,eAAe,CAAC,EAAoB,CAClC,GAAI,KAAK,MACP,KAAK,MAAM,aAAe,KAAK,IAAI,KAAM,KAAK,IAAI,EAAG,CAAI,CAAC,EAI9D,eAAe,EAAW,CACxB,OAAO,KAAK,OAAO,cAAgB,EAKrC,aAAa,EAAgB,CAC3B,GAAI,CAAC,KAAK,MAAO,MAAO,CAAC,EAEzB,IAAM,EAAsB,CAAC,EACvB,EAAa,KAAK,MAAM,WAE9B,QAAS,EAAI,EAAG,EAAI,EAAW,OAAQ,IAAK,CAC1C,IAAM,EAAQ,EAAW,GACzB,EAAO,KAAK,CACV,GAAI,OAAO,CAAC,EACZ,KAAM,EAAM,KACZ,MAAO,EAAM,OAAS,SAAS,EAAI,IACnC,SAAU,EAAM,UAAY,GAC5B,KAAM,EAAM,KACZ,KAAM,KAAK,aAAa,CAAK,CAC/B,CAAC,EAGH,OAAO,EAGD,YAAY,CAAC,EAAgD,CACnE,GAAI,CAAC,EAAM,KAAM,MAAO,CAAC,EAEzB,IAAM,EAA0B,CAAC,EACjC,QAAS,EAAI,EAAG,EAAI,EAAM,KAAK,OAAQ,IAAK,CAC1C,IAAM,EAAM,EAAM,KAAK,GACvB,EAAK,KAAK,CACR,GAAI,EAAI,IAAM,OAAO,CAAC,EACtB,UAAW,EAAI,UACf,QAAS,EAAI,QACb,KAAM,EAAI,IACZ,CAAC,EAEH,OAAO,EAGT,gBAAgB,CAAC,EAAiB,EAA+C,CAC/E,GAAI,CAAC,KAAK,MAAO,OAEjB,IAAM,EAAQ,SAAS,EAAS,EAAE,EAC5B,EAAQ,KAAK,MAAM,WAAW,GACpC,GAAI,EACF,EAAM,KAAO,EACb,KAAK,OAAO,KAAK,kBAAmB,IAAS,UAAY,KAAK,cAAc,EAAE,GAAS,IAAI,EAM/F,cAAc,EAAiB,CAC7B,GAAI,CAAC,KAAK,OAAS,KAAK,QAAS,MAAO,CAAC,EAEzC,IAAM,EAAQ,KAAK,MACnB,GAAI,EAAE,gBAAiB,GAAQ,MAAO,CAAC,EAEvC,IAAM,EAAe,EAAc,YAC7B,EAAuB,CAAC,EAE9B,QAAS,EAAI,EAAG,EAAI,EAAY,OAAQ,IAAK,CAC3C,IAAM,EAAQ,EAAY,GAC1B,EAAO,KAAK,CACV,GAAI,EAAM,IAAM,OAAO,CAAC,EACxB,MAAO,EAAM,OAAS,SAAS,EAAI,IACnC,SAAU,EAAM,UAAY,GAC5B,KAAM,EAAM,MAAQ,GACpB,SAAU,EAAM,OAClB,CAAC,EAGH,OAAO,EAGT,aAAa,CAAC,EAAuB,CACnC,GAAI,CAAC,KAAK,OAAS,KAAK,QAAS,OAEjC,IAAM,EAAQ,KAAK,MACnB,GAAI,EAAE,gBAAiB,GAAQ,OAE/B,IAAM,EAAe,EAAc,YACnC,QAAS,EAAI,EAAG,EAAI,EAAY,OAAQ,IAAK,CAC3C,IAAM,EAAW,EAAY,GAAG,KAAO,GAAW,OAAO,CAAC,IAAM,EAChE,EAAY,GAAG,QAAU,EAG3B,KAAK,OAAO,KAAK,mBAAoB,KAAK,eAAe,EAAE,KAAK,CAAC,IAAM,EAAE,QAAQ,GAAK,IAAI,EAK5F,YAAY,CAAC,EAMJ,CACP,GAAI,CAAC,KAAK,MAAO,OAEjB,IAAM,EAAQ,SAAS,cAAc,OAAO,EAC5C,GAAI,EAAQ,IAAK,EAAM,IAAM,EAAQ,IAIrC,GAHA,EAAM,KAAO,EAAQ,MAAQ,YAC7B,EAAM,MAAQ,EAAQ,OAAS,GAC/B,EAAM,QAAU,EAAQ,UAAY,GAChC,EAAQ,QAAS,EAAM,QAAU,GAErC,KAAK,MAAM,YAAY,CAAK,EAEhC,CAKO,IAAM,GAAc,CACzB,KAAM,QACN,KAAM,QAEN,OAAO,CAAC,EAAmB,CACzB,OAAO,GAAc,CAAG,GAG1B,SAAS,CAAC,EAAyC,CACjD,GAAI,OAAO,IAAQ,SAAU,CAC3B,IAAM,EAAY,EAAa,CAAG,EAClC,GAAI,CAAC,MAAO,MAAO,OAAQ,MAAO,MAAO,MAAO,MAAM,EAAE,SAAS,CAAS,EACxE,MAAO,QAET,MAAO,QAGT,GAAI,OAAO,IAAQ,UAAY,SAAU,GAEvC,IADc,EAAoB,MAAM,YAAY,GAAK,IAChD,WAAW,QAAQ,EAAG,MAAO,QAGxC,MAAO,cAGH,KAAI,CAAC,EAAwB,EAAgD,CACjF,IAAM,EAAW,IAAI,EAErB,OADA,MAAM,EAAS,MAAM,EAAW,CAAO,EAChC,EAEX,ECtcA,IAAM,GAAmB,CACvB,mFACA,uCACA,2CACF,EAKO,SAAS,CAAgB,CAAC,EAA4B,CAC3D,QAAW,KAAW,GAAkB,CACtC,IAAM,EAAQ,EAAI,MAAM,CAAO,EAC/B,GAAI,EAAO,OAAO,EAAM,GAE1B,OAAO,KAMF,SAAS,EAAe,CAAC,EAAmB,CACjD,GAAI,OAAO,IAAQ,SACjB,OAAO,EAAiB,CAAG,IAAM,KAGnC,GAAI,OAAO,IAAQ,UAAY,SAAU,EACvC,OAAQ,EAAsB,OAAS,UAGzC,MAAO,GAMT,SAAS,EAAkB,CAAC,EAA4E,CACtG,GAAI,OAAO,IAAQ,SAAU,CAC3B,IAAM,EAAU,EAAiB,CAAG,EACpC,GAAI,CAAC,EAAS,OAAO,KAGrB,IAAM,EAAM,IAAI,IAAI,EAAK,qBAAqB,EACxC,EAAQ,EAAI,aAAa,IAAI,GAAG,GAAK,EAAI,aAAa,IAAI,OAAO,EACjE,EAAM,EAAI,aAAa,IAAI,KAAK,EAEtC,MAAO,CACL,UACA,UAAW,EAAQ,GAAU,CAAK,EAAI,OACtC,QAAS,EAAM,GAAU,CAAG,EAAI,MAClC,EAGF,GAAI,OAAO,IAAQ,UAAY,SAAU,GAAQ,EAAsB,OAAS,UAAW,CACzF,IAAM,EAAQ,EACd,MAAO,CACL,QAAS,EAAM,SAAW,EAAiB,EAAM,GAAG,GAAK,GACzD,UAAW,EAAM,UACjB,QAAS,EAAM,OACjB,EAGF,OAAO,KAMT,SAAS,EAAS,CAAC,EAAsB,CAEvC,GAAI,QAAQ,KAAK,CAAI,EAAG,OAAO,SAAS,EAAM,EAAE,EAGhD,IAAM,EAAS,EAAK,MAAM,mCAAmC,EAC7D,GAAI,EAAQ,CACV,IAAM,EAAI,SAAS,EAAO,IAAM,IAAK,EAAE,EACjC,EAAI,SAAS,EAAO,IAAM,IAAK,EAAE,EACjC,EAAI,SAAS,EAAO,IAAM,IAAK,EAAE,EACvC,OAAO,EAAI,KAAO,EAAI,GAAK,EAI7B,IAAM,EAAQ,EAAK,MAAM,GAAG,EAAE,IAAI,CAAC,IAAM,SAAS,EAAG,EAAE,CAAC,EACxD,GAAI,EAAM,SAAW,EAAG,OAAO,EAAM,GAAK,GAAK,EAAM,GACrD,GAAI,EAAM,SAAW,EAAG,OAAO,EAAM,GAAK,KAAO,EAAM,GAAK,GAAK,EAAM,GAEvE,MAAO,GA4EF,MAAM,UAAwB,CAAa,CACvC,KAAO,UACP,KAAqB,UAEtB,OAA0B,KAC1B,OAAmC,KACnC,eAAgC,KAChC,UACA,QACA,mBAA4D,KAC5D,UAAY,MAEhB,aAAY,EAA6B,CAC3C,OAAO,KAAK,YAKE,WAAU,EAAkB,CAC1C,GAAI,CAAC,KAAK,UACR,MAAU,MAAM,+BAA+B,EAIjD,MAAM,KAAK,eAAe,EAG1B,IAAM,EAAY,SAAS,cAAc,KAAK,EAC9C,EAAU,GAAK,sBAAsB,KAAK,IAAI,IAC9C,EAAU,UAAY,2BACtB,KAAK,UAAU,YAAY,CAAS,EAGpC,IAAM,EAA8C,CAClD,SAAU,KAAK,QAAQ,SAAW,EAAI,EACtC,SAAU,EACV,UAAW,EACX,YAAa,EACb,eAAgB,EAChB,eAAgB,EAChB,YAAa,KAAK,QAAQ,YAAc,EAAI,EAC5C,IAAK,EACL,OAAQ,OAAO,SAAS,MAC1B,EAGA,GAAI,KAAK,QAAQ,cACf,OAAO,OAAO,EAAY,KAAK,QAAQ,aAAa,EAItD,EAAW,KAAO,mCAGlB,KAAK,OAAS,IAAI,OAAO,GAAI,OAAO,EAAU,GAAI,CAChD,MAAO,OACP,OAAQ,OACR,aACA,OAAQ,CACN,QAAS,IAAM,KAAK,QAAQ,EAC5B,cAAe,CAAC,IAAM,KAAK,cAAc,CAAC,EAC1C,QAAS,CAAC,IAAM,KAAK,QAAQ,CAAC,EAC9B,wBAAyB,IAAM,KAAK,gBAAgB,EACpD,qBAAsB,IAAM,KAAK,aAAa,CAChD,CACF,CAAC,EAGD,KAAK,OAAS,KAAK,OAAO,UAAU,EACpC,KAAK,OAAO,UAAY,8BAGZ,eAAc,EAAkB,CAC5C,GAAI,OAAO,IAAI,OAAQ,OAEvB,OAAO,IAAI,QAAQ,CAAC,IAAY,CAE9B,GAAI,SAAS,cAAc,uCAAuC,EAAG,CACnE,IAAM,EAAQ,IAAM,CAClB,GAAI,OAAO,IAAI,OACb,EAAQ,EAER,gBAAW,EAAO,GAAG,GAGzB,EAAM,EACN,OAIF,IAAM,EAAS,SAAS,cAAc,QAAQ,EAC9C,EAAO,IAAM,qCACb,EAAO,MAAQ,GAEf,OAAO,wBAA0B,IAAM,CACrC,EAAQ,GAGV,SAAS,KAAK,YAAY,CAAM,EACjC,EAGO,OAAO,EAAS,CACxB,GAAI,KAAK,mBACP,cAAc,KAAK,kBAAkB,EACrC,KAAK,mBAAqB,KAG5B,KAAK,QAAQ,QAAQ,EACrB,KAAK,OAAS,KACd,KAAK,OAAS,KAKR,OAAO,EAAS,CAatB,GAXA,KAAK,mBAAqB,YAAY,IAAM,CAC1C,GAAI,KAAK,QAAU,KAAK,YAAc,EACpC,KAAK,OAAO,KAAK,aAAc,KAAK,eAAe,CAAC,GAErD,GAAG,EAGN,KAAK,OAAO,KAAK,gBAAgB,EACjC,KAAK,OAAO,KAAK,SAAS,EAGtB,KAAK,eACP,KAAK,UAAU,KAAK,eAAgB,KAAK,UAAW,KAAK,OAAO,EAChE,KAAK,eAAiB,KAIlB,aAAa,CAAC,EAA4B,CAChD,IAAM,EAAQ,EAAM,KACpB,KAAK,UAAY,EAEjB,IAAM,EAAc,OAAO,GAAI,YAE/B,OAAQ,QACD,EAAY,UACf,KAAK,OAAO,KAAK,WAAW,EAC5B,WACG,EAAY,QACf,KAAK,OAAO,KAAK,MAAM,EACvB,KAAK,OAAO,KAAK,SAAS,EAC1B,KAAK,OAAO,KAAK,cAAe,CAAE,OAAQ,GAAO,QAAS,GAAM,QAAS,EAAM,CAAC,EAChF,WACG,EAAY,OACf,KAAK,OAAO,KAAK,OAAO,EACxB,KAAK,OAAO,KAAK,cAAe,CAAE,OAAQ,GAAM,QAAS,EAAM,CAAC,EAChE,WACG,EAAY,UACf,KAAK,OAAO,KAAK,SAAS,EAC1B,KAAK,OAAO,KAAK,cAAe,CAAE,QAAS,EAAK,CAAC,EACjD,WACG,EAAY,MACf,KAAK,OAAO,KAAK,OAAO,EACxB,KAAK,OAAO,KAAK,cAAe,CAAE,MAAO,GAAM,QAAS,EAAM,CAAC,EAC/D,WACG,EAAY,KACf,KAAK,OAAO,KAAK,YAAY,EAC7B,KAAK,OAAO,KAAK,iBAAkB,KAAK,YAAY,CAAC,EACrD,OAIE,OAAO,CAAC,EAA4B,CAC1C,IAAM,EAAwC,CAC5C,EAAG,mBACH,EAAG,qBACH,IAAK,6BACL,IAAK,2BACL,IAAK,0BACP,EAEA,KAAK,UAAU,EAAM,KAAM,EAAc,EAAM,OAAS,uBAAuB,EAGzE,eAAe,EAAS,CAC9B,IAAM,EAAU,KAAK,QAAQ,mBAAmB,GAAK,OAC/C,EAAY,KAAK,aAAa,EACpC,KAAK,OAAO,KAAK,gBAAiB,EAAU,KAAK,CAAC,IAAM,EAAE,KAAO,CAAO,GAAK,IAAI,EAG3E,YAAY,EAAS,CAC3B,KAAK,OAAO,KAAK,aAAc,KAAK,gBAAgB,CAAC,EAKvD,OAAO,CAAC,EAAmB,CACzB,OAAO,GAAgB,CAAG,OAGtB,KAAI,CAAC,EAAyB,CAClC,IAAM,EAAS,GAAmB,CAAG,EACrC,GAAI,CAAC,EAAQ,CACX,KAAK,UAAU,EAAG,wBAAwB,EAC1C,OAMF,GAHA,KAAK,UAAY,EAAO,UACxB,KAAK,QAAU,EAAO,QAElB,CAAC,KAAK,QAAU,CAAC,KAAK,OAAQ,CAChC,KAAK,eAAiB,EAAO,QAC7B,OAGF,KAAK,UAAU,EAAO,QAAS,EAAO,UAAW,EAAO,OAAO,EAGzD,SAAS,CAAC,EAAiB,EAAoB,EAAwB,CAC7E,GAAI,CAAC,KAAK,OAAQ,OAElB,GAAI,KAAK,QAAQ,SACf,KAAK,OAAO,cAAc,CACxB,UACA,aAAc,EACd,WAAY,CACd,CAAC,EAED,UAAK,OAAO,aAAa,CACvB,UACA,aAAc,EACd,WAAY,CACd,CAAC,OAMC,KAAI,EAAkB,CAC1B,KAAK,QAAQ,UAAU,EAGzB,KAAK,EAAS,CACZ,KAAK,QAAQ,WAAW,EAG1B,IAAI,EAAS,CACX,KAAK,QAAQ,UAAU,EAKzB,MAAM,CAAC,EAAoB,CACzB,KAAK,QAAQ,OAAO,EAAM,EAAI,EAC9B,KAAK,OAAO,KAAK,SAAS,EAE1B,WAAW,IAAM,KAAK,OAAO,KAAK,QAAQ,EAAG,GAAG,EAGlD,cAAc,EAAW,CACvB,OAAO,KAAK,QAAQ,eAAe,GAAK,EAG1C,WAAW,EAAW,CACpB,OAAO,KAAK,QAAQ,YAAY,GAAK,EAKvC,SAAS,CAAC,EAAsB,CAC9B,KAAK,QAAQ,UAAU,KAAK,MAAM,EAAS,GAAG,CAAC,EAC/C,KAAK,OAAO,KAAK,eAAgB,EAAQ,KAAK,SAAS,CAAC,EAG1D,SAAS,EAAW,CAClB,OAAQ,KAAK,QAAQ,UAAU,GAAK,KAAO,IAG7C,QAAQ,CAAC,EAAsB,CAC7B,GAAI,EACF,KAAK,QAAQ,KAAK,EAElB,UAAK,QAAQ,OAAO,EAEtB,KAAK,OAAO,KAAK,eAAgB,KAAK,UAAU,EAAG,CAAK,EAG1D,QAAQ,EAAY,CAClB,OAAO,KAAK,QAAQ,QAAQ,GAAK,GAKnC,eAAe,CAAC,EAAoB,CAClC,KAAK,QAAQ,gBAAgB,CAAI,EAGnC,eAAe,EAAW,CACxB,OAAO,KAAK,QAAQ,gBAAgB,GAAK,EAK3C,YAAY,EAAmB,CAC7B,IAAM,EAAS,KAAK,QAAQ,0BAA0B,GAAK,CAAC,EACtD,EAAkC,CACtC,QAAS,KACT,OAAQ,KACR,OAAQ,KACR,OAAQ,KACR,MAAO,IACP,MAAO,IACP,OAAQ,IACR,MAAO,IACP,KAAM,GACR,EAEM,EAAU,KAAK,QAAQ,mBAAmB,EAEhD,OAAO,EAAO,IAAI,CAAC,KAAW,CAC5B,GAAI,EACJ,MAAO,KAAK,OAAO,EAAQ,IAAU,KAAQ,kBAAO,EACpD,OAAQ,EAAQ,IAAU,IAC1B,QAAS,EACT,SAAU,IAAU,CACtB,EAAE,EAGJ,UAAU,CAAC,EAAsC,CAC/C,GAAI,IAAY,OACd,KAAK,QAAQ,mBAAmB,SAAS,EAEzC,UAAK,QAAQ,mBAAmB,EAAQ,EAAE,EAGhD,CAKO,IAAM,GAAgB,CAC3B,KAAM,UACN,KAAM,UAEN,OAAO,CAAC,EAAmB,CACzB,OAAO,GAAgB,CAAG,GAG5B,SAAS,EAAY,CACnB,MAAO,SAGT,UAAU,EAAa,CACrB,MAAO,CACL,mCACA,0BACA,sBACA,qBACF,QAGI,KAAI,CAAC,EAAwB,EAAkD,CACnF,IAAM,EAAW,IAAI,EAErB,OADA,MAAM,EAAS,MAAM,EAAW,CAAO,EAChC,EAEX,EC3gBA,IAAM,GAAiB,CACrB,oBACA,2BACA,mCACA,qCACA,0CACF,EAKO,SAAS,CAAc,CAAC,EAA4B,CACzD,QAAW,KAAW,GAAgB,CACpC,IAAM,EAAQ,EAAI,MAAM,CAAO,EAC/B,GAAI,EAAO,OAAO,EAAM,GAE1B,OAAO,KAMF,SAAS,EAAgB,CAAC,EAA4B,CAE3D,IAAM,EAAQ,EAAI,MAAM,8BAA8B,EACtD,OAAO,EAAQ,EAAM,GAAK,KAMrB,SAAS,EAAa,CAAC,EAAmB,CAC/C,GAAI,OAAO,IAAQ,SACjB,OAAO,EAAe,CAAG,IAAM,KAGjC,GAAI,OAAO,IAAQ,UAAY,SAAU,EACvC,OAAQ,EAAoB,OAAS,QAGvC,MAAO,GAMT,SAAS,EAAgB,CAAC,EAAqD,CAC7E,GAAI,OAAO,IAAQ,SAAU,CAC3B,IAAM,EAAU,EAAe,CAAG,EAClC,GAAI,CAAC,EAAS,OAAO,KACrB,MAAO,CAAE,UAAS,KAAM,GAAiB,CAAG,GAAK,MAAU,EAG7D,GAAI,OAAO,IAAQ,UAAY,SAAU,GAAQ,EAAoB,OAAS,QAAS,CACrF,IAAM,EAAO,EACb,MAAO,CACL,QAAS,EAAK,SAAW,EAAe,EAAK,GAAG,GAAK,GACrD,KAAM,EAAK,IACb,EAGF,OAAO,KAgFT,IAAM,GAAgB,yCAKf,MAAM,WAAsB,CAAa,CACrC,KAAO,QACP,KAAqB,QAEtB,aAAmC,KACnC,OAA6B,KAC7B,OAAmC,KACnC,mBAA4D,KAC5D,WAA6B,CAAC,EAC9B,UAAY,KAChB,aAAY,EAA6B,CAC3C,OAAO,KAAK,YAKE,WAAU,EAAkB,CAC1C,GAAI,CAAC,KAAK,UACR,MAAU,MAAM,+BAA+B,EAIjD,MAAM,KAAK,aAAa,EAGxB,KAAK,aAAe,SAAS,cAAc,KAAK,EAChD,KAAK,aAAa,UAAY,yBAC9B,KAAK,UAAU,YAAY,KAAK,YAAY,OAGhC,aAAY,EAAkB,CAC1C,GAAI,OAAO,MAAO,OAElB,OAAO,IAAI,QAAQ,CAAC,EAAS,IAAW,CACtC,GAAI,SAAS,cAAc,eAAe,MAAiB,EAAG,CAC5D,IAAM,EAAQ,IAAM,CAClB,GAAI,OAAO,MAAO,EAAQ,EACrB,gBAAW,EAAO,GAAG,GAE5B,EAAM,EACN,OAGF,IAAM,EAAS,SAAS,cAAc,QAAQ,EAC9C,EAAO,IAAM,GACb,EAAO,MAAQ,GACf,EAAO,OAAS,IAAM,EAAQ,EAC9B,EAAO,QAAU,IAAM,EAAW,MAAM,0BAA0B,CAAC,EACnE,SAAS,KAAK,YAAY,CAAM,EACjC,EAGO,OAAO,EAAS,CACxB,GAAI,KAAK,mBACP,cAAc,KAAK,kBAAkB,EACrC,KAAK,mBAAqB,KAG5B,KAAK,QAAQ,QAAQ,EACrB,KAAK,OAAS,KACd,KAAK,OAAS,KACd,KAAK,cAAc,OAAO,EAC1B,KAAK,aAAe,KAKtB,OAAO,CAAC,EAAmB,CACzB,OAAO,GAAc,CAAG,OAGpB,KAAI,CAAC,EAAyB,CAClC,IAAM,EAAS,GAAiB,CAAG,EACnC,GAAI,CAAC,GAAU,CAAC,KAAK,aAAc,CACjC,KAAK,UAAU,EAAG,sBAAsB,EACxC,OAIF,GAAI,KAAK,OACP,MAAM,KAAK,OAAO,QAAQ,EAC1B,KAAK,OAAS,KAIhB,IAAM,EAAoC,CACxC,GAAI,SAAS,EAAO,QAAS,EAAE,EAC/B,UAAW,GACX,SAAU,KAAK,QAAQ,UAAY,GACnC,SAAU,GACV,KAAM,KAAK,QAAQ,MAAQ,GAC3B,MAAO,KAAK,QAAQ,OAAS,GAC7B,YAAa,KAAK,QAAQ,cAAgB,GAC1C,WAAY,GACZ,IAAK,EACP,EAGA,GAAI,KAAK,QAAQ,YACf,OAAO,OAAO,EAAe,KAAK,QAAQ,WAAW,EAWvD,GAPA,KAAK,OAAS,IAAI,OAAO,MAAO,OAAO,KAAK,aAAc,CAAa,EAGvE,MAAM,KAAK,OAAO,MAAM,EAGxB,KAAK,OAAS,KAAK,aAAa,cAAc,QAAQ,EAClD,KAAK,OACP,KAAK,OAAO,UAAY,yBAI1B,KAAK,kBAAkB,EAGvB,KAAK,OAAO,KAAK,gBAAgB,EACjC,KAAK,OAAO,KAAK,SAAS,EAG1B,KAAK,UAAY,MAAM,KAAK,OAAO,YAAY,EAC/C,KAAK,OAAO,KAAK,iBAAkB,KAAK,SAAS,EAGjD,MAAM,KAAK,gBAAgB,EAG3B,IAAO,EAAO,GAAU,MAAM,QAAQ,IAAI,CAAC,KAAK,OAAO,cAAc,EAAG,KAAK,OAAO,eAAe,CAAC,CAAC,EACrG,KAAK,OAAO,KAAK,cAAe,CAC9B,WAAY,EACZ,YAAa,EACb,YAAa,EAAQ,GAAU,mBAC/B,cAAe,GACf,oBAAqB,EACvB,CAAC,EAGK,iBAAiB,EAAS,CAChC,GAAI,CAAC,KAAK,OAAQ,OAGlB,KAAK,OAAO,GAAG,OAAQ,IAAM,CAC3B,KAAK,OAAO,KAAK,MAAM,EACvB,KAAK,OAAO,KAAK,SAAS,EAC1B,KAAK,OAAO,KAAK,cAAe,CAAE,OAAQ,GAAO,QAAS,EAAK,CAAC,EACjE,EAGD,KAAK,OAAO,GAAG,QAAS,IAAM,CAC5B,KAAK,OAAO,KAAK,OAAO,EACxB,KAAK,OAAO,KAAK,cAAe,CAAE,OAAQ,GAAM,QAAS,EAAM,CAAC,EACjE,EAGD,KAAK,OAAO,GAAG,QAAS,IAAM,CAC5B,KAAK,OAAO,KAAK,OAAO,EACxB,KAAK,OAAO,KAAK,cAAe,CAAE,MAAO,GAAM,QAAS,EAAM,CAAC,EAChE,EAGD,KAAK,OAAO,GAAG,aAAc,CAAC,IAA8B,CAC1D,KAAK,OAAO,KAAK,aAAc,EAAK,OAAO,EAC5C,EAGD,KAAK,OAAO,GAAG,UAAW,IAAM,CAC9B,KAAK,OAAO,KAAK,SAAS,EAC3B,EAED,KAAK,OAAO,GAAG,SAAU,IAAM,CAC7B,KAAK,OAAO,KAAK,QAAQ,EAC1B,EAGD,KAAK,OAAO,GAAG,cAAe,IAAM,CAClC,KAAK,OAAO,KAAK,SAAS,EAC1B,KAAK,OAAO,KAAK,cAAe,CAAE,QAAS,EAAK,CAAC,EAClD,EAED,KAAK,OAAO,GAAG,YAAa,IAAM,CAChC,KAAK,OAAO,KAAK,cAAe,CAAE,QAAS,EAAM,CAAC,EACnD,EAGD,KAAK,OAAO,GAAG,WAAY,CAAC,IAA8B,CACxD,IAAM,EAAW,CAAC,CAAE,MAAO,EAAG,IAAK,EAAK,QAAU,KAAK,SAAU,CAAC,EAClE,KAAK,OAAO,KAAK,WAAY,CAAQ,EACtC,EAGD,KAAK,OAAO,GAAG,eAAgB,SAAY,CACzC,IAAO,EAAQ,GAAS,MAAM,QAAQ,IAAI,CAAC,KAAK,OAAQ,UAAU,EAAG,KAAK,OAAQ,SAAS,CAAC,CAAC,EAC7F,KAAK,OAAO,KAAK,eAAgB,EAAQ,CAAK,EAC/C,EAGD,KAAK,OAAO,GAAG,qBAAsB,CAAC,IAAmC,CACvE,KAAK,OAAO,KAAK,aAAc,EAAK,YAAY,EACjD,EAGD,KAAK,OAAO,GAAG,gBAAiB,SAAY,CAC1C,MAAM,KAAK,gBAAgB,EAC3B,IAAM,EAAU,KAAK,WAAW,KAAK,CAAC,IAAM,EAAE,QAAQ,EACtD,KAAK,OAAO,KAAK,gBAAiB,GAAW,IAAI,EAClD,EAGD,KAAK,OAAO,GAAG,QAAS,CAAC,IAA6C,CACpE,KAAK,UAAU,EAAG,EAAM,QAAS,CAAK,EACvC,OAGW,gBAAe,EAAkB,CAC7C,GAAI,CAAC,KAAK,OAAQ,OAElB,GAAI,CACF,IAAM,EAAY,MAAM,KAAK,OAAO,aAAa,EACjD,KAAK,WAAa,EAAU,IAAI,CAAC,IAAM,CAErC,IAAM,EAAQ,EAAE,MAAM,MAAM,QAAQ,EAC9B,EAAS,EAAQ,SAAS,EAAM,GAAI,EAAE,EAAI,EAEhD,MAAO,CACL,GAAI,EAAE,GACN,MAAO,KAAK,MAAM,EAAU,kBAAO,EACnC,SACA,QAAS,EACT,SAAU,EAAE,MACd,EACD,EAED,KAAK,OAAO,KAAK,cAAe,CAAE,UAAW,KAAK,UAAW,CAAC,EAC9D,KAAM,QAOJ,KAAI,EAAkB,CAC1B,GAAI,CACF,MAAM,KAAK,QAAQ,KAAK,EACxB,MAAO,EAAO,CACd,GAAK,EAAgB,OAAS,kBAC5B,KAAK,UAAU,EAAG,mDAAoD,CAAK,EAE3E,WAAM,GAKZ,KAAK,EAAS,CACZ,KAAK,QAAQ,MAAM,EAGrB,IAAI,EAAS,CACX,KAAK,MAAM,EACX,KAAK,OAAO,CAAC,EAKf,MAAM,CAAC,EAAoB,CACzB,KAAK,QAAQ,eAAe,CAAI,EAGlC,cAAc,EAAW,CAEvB,MAAO,GAGT,WAAW,EAAW,CACpB,OAAO,KAAK,UAKd,SAAS,CAAC,EAAsB,CAC9B,KAAK,QAAQ,UAAU,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,CAAM,CAAC,CAAC,EAGzD,SAAS,EAAW,CAClB,MAAO,GAGT,QAAQ,CAAC,EAAsB,CAC7B,KAAK,QAAQ,SAAS,CAAK,EAG7B,QAAQ,EAAY,CAClB,MAAO,GAKT,eAAe,CAAC,EAAoB,CAClC,KAAK,QAAQ,gBAAgB,CAAI,EAGnC,eAAe,EAAW,CACxB,MAAO,GAKT,YAAY,EAAmB,CAC7B,OAAO,KAAK,WAGd,UAAU,CAAC,EAAsC,CAC/C,GAAI,CAAC,KAAK,OAAQ,OAElB,GAAI,IAAY,OACd,KAAK,OAAO,WAAW,MAAM,EAE7B,UAAK,OAAO,WAAW,EAAQ,EAAE,OAM/B,gBAAe,CAAC,EAAiC,CACrD,MAAM,KAAK,QAAQ,gBAAgB,CAAQ,OAGvC,iBAAgB,EAAkB,CACtC,MAAM,KAAK,QAAQ,iBAAiB,OAKhC,YAAW,EAAwE,CACvF,GAAI,CAAC,KAAK,OAAQ,MAAO,CAAC,EAC1B,GAAI,CACF,OAAO,MAAM,KAAK,OAAO,YAAY,EACrC,KAAM,CACN,MAAO,CAAC,GAGd,CAKO,IAAM,GAAc,CACzB,KAAM,QACN,KAAM,QAEN,OAAO,CAAC,EAAmB,CACzB,OAAO,GAAc,CAAG,GAG1B,SAAS,EAAY,CACnB,MAAO,SAGT,UAAU,EAAa,CACrB,MAAO,CAAC,2BAA4B,yBAA0B,wBAAwB,QAGlF,KAAI,CAAC,EAAwB,EAAgD,CACjF,IAAM,EAAW,IAAI,GAErB,OADA,MAAM,EAAS,MAAM,EAAW,CAAO,EAChC,EAEX,ECxfO,SAAS,EAAW,CAAC,EAAmB,CAC7C,GAAI,OAAO,IAAQ,SAAU,CAC3B,IAAM,EAAM,EAAI,YAAY,EAC5B,OAAO,EAAI,SAAS,OAAO,GAAK,EAAI,SAAS,uBAAuB,GAAK,EAAI,SAAS,+BAA+B,EAGvH,GAAI,OAAO,IAAQ,UAAY,SAAU,EAAK,CAC5C,IAAM,EAAQ,EAAkB,MAAM,YAAY,GAAK,GACvD,OAAO,IAAS,yBAA2B,IAAS,gCAGtD,MAAO,GAMF,SAAS,EAAoB,EAAY,CAE9C,OADc,SAAS,cAAc,OAAO,EAC/B,YAAY,+BAA+B,IAAM,GAkHhE,IAAM,GAAkB,6DAKjB,MAAM,UAAoB,CAAa,CACnC,KAAO,MACP,KAAqB,MAEtB,MAAiC,KACjC,IAAkB,KAClB,iBAAiD,KACjD,kBAAyC,KACzC,WAAkC,KAClC,aAAe,GACf,WAA6B,CAAC,EAC9B,aAA6B,CAAC,EAC9B,YAA2B,CAAC,EAC5B,oBAAsB,MAE1B,aAAY,EAA4B,CAC1C,OAAO,KAAK,SAMV,YAAW,EAAY,CACzB,MAAO,CAAC,KAAK,cAAgB,KAAK,MAAQ,QAMxC,UAAS,EAAc,CACzB,OAAO,KAAK,KAAK,QAAU,CAAC,OAKd,WAAU,EAAkB,CAC1C,GAAI,CAAC,KAAK,UACR,MAAU,MAAM,+BAA+B,EAQjD,GAJA,KAAK,MAAQ,SAAS,cAAc,OAAO,EAC3C,KAAK,MAAM,UAAY,yBAGnB,KAAK,QAAQ,SAAU,KAAK,MAAM,SAAW,GACjD,GAAI,KAAK,QAAQ,KAAM,KAAK,MAAM,KAAO,GACzC,GAAI,KAAK,QAAQ,MAAO,KAAK,MAAM,MAAQ,GAC3C,GAAI,KAAK,QAAQ,YAAa,KAAK,MAAM,YAAc,GACvD,GAAI,KAAK,QAAQ,QAAS,KAAK,MAAM,QAAU,KAAK,QAAQ,QAC5D,GAAI,KAAK,QAAQ,YAAa,KAAK,MAAM,YAAc,KAAK,QAAQ,YACpE,GAAI,KAAK,QAAQ,OAAQ,KAAK,MAAM,OAAS,KAAK,QAAQ,OAC1D,GAAI,KAAK,QAAQ,SAAW,OAAW,KAAK,MAAM,OAAS,KAAK,QAAQ,OAoBxE,GAlBA,KAAK,UAAU,YAAY,KAAK,KAAK,EAGrC,KAAK,iBAAmB,IAAI,EAAsB,KAAK,MAAO,KAAK,MAAM,EAGzE,KAAK,kBAAoB,EAAmB,CAAC,IAAe,CAC1D,KAAK,OAAO,KAAK,mBAAoB,CAAU,EAChD,EAED,KAAK,WAAa,EAAY,KAAK,MAAO,CAAC,IAAQ,CACjD,KAAK,OAAO,KAAK,YAAa,CAAG,EAClC,EAGD,KAAK,aAAe,GAAqB,EAGrC,CAAC,KAAK,aACR,MAAM,KAAK,eAAe,EAI5B,KAAK,MAAM,iBAAiB,iBAAkB,IAAM,CAClD,IAAM,EAAyB,KAAK,uBAAuB,YAAY,EACjE,EAAkB,KAAK,uBAAuB,KAAK,EACnD,EAAqB,KAAK,uBAAuB,QAAQ,EAC/D,KAAK,OAAO,KAAK,cAAe,CAC9B,WAAY,KAAK,MAAO,WACxB,YAAa,KAAK,MAAO,YACzB,aAAc,KAAK,MAAO,YAAc,EAAI,KAAK,MAAO,WAAa,KAAK,MAAO,YAAc,IAAM,mBACrG,cAAe,IAA2B,YAC1C,oBAAqB,IAAoB,YACzC,yBACA,kBACA,oBACF,CAAC,EACD,KAAK,OAAO,KAAK,qBAAsB,aAAc,CAAsB,EAC3E,KAAK,OAAO,KAAK,qBAAsB,MAAO,CAAe,EAC7D,KAAK,OAAO,KAAK,qBAAsB,SAAU,CAAkB,EACpE,OAGW,eAAc,EAAkB,CAC5C,GAAI,OAAO,IAAK,OAEhB,OAAO,IAAI,QAAQ,CAAC,EAAS,IAAW,CACtC,IAAM,EAAS,SAAS,cAAc,QAAQ,EAC9C,EAAO,IAAM,KAAK,QAAQ,WAAa,GACvC,EAAO,MAAQ,GACf,EAAO,OAAS,IAAM,EAAQ,EAC9B,EAAO,QAAU,IAAM,EAAW,MAAM,uBAAuB,CAAC,EAChE,SAAS,KAAK,YAAY,CAAM,EACjC,EAGO,OAAO,EAAS,CAKxB,GAJA,KAAK,kBAAkB,QAAQ,EAC/B,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAEd,KAAK,IACP,KAAK,IAAI,QAAQ,EACjB,KAAK,IAAM,KAGb,GAAI,KAAK,MACP,KAAK,MAAM,MAAM,EACjB,KAAK,MAAM,gBAAgB,KAAK,EAChC,KAAK,MAAM,KAAK,EAChB,KAAK,MAAM,OAAO,EAClB,KAAK,MAAQ,KAGf,KAAK,WAAa,CAAC,EACnB,KAAK,aAAe,CAAC,EACrB,KAAK,YAAc,CAAC,EAKtB,OAAO,CAAC,EAAmB,CACzB,OAAO,GAAY,CAAG,OAGlB,KAAI,CAAC,EAAyB,CAClC,GAAI,CAAC,KAAK,MAAO,OAEjB,IAAM,EAAM,OAAO,IAAQ,SAAW,EAAO,EAAkB,IAE/D,GAAI,KAAK,aAAc,CAErB,KAAK,MAAM,IAAM,EACjB,KAAK,MAAM,KAAK,EAChB,OAIF,GAAI,CAAC,OAAO,IACV,MAAM,KAAK,eAAe,EAG5B,GAAI,CAAC,OAAO,KAAK,YAAY,EAAG,CAC9B,KAAK,UAAU,EAAG,sCAAsC,EACxD,OAIF,GAAI,KAAK,IACP,KAAK,IAAI,QAAQ,EAInB,IAAM,EAAoB,CACxB,aAAc,GACd,eAAgB,MACb,KAAK,QAAQ,SAClB,EAGA,KAAK,IAAM,IAAI,OAAO,IAAI,CAAM,EAChC,KAAK,gBAAgB,EAGrB,KAAK,IAAI,YAAY,KAAK,KAAK,EAC/B,KAAK,IAAI,WAAW,CAAG,EAGjB,eAAe,EAAS,CAC9B,GAAI,CAAC,KAAK,KAAO,CAAC,OAAO,IAAK,OAE9B,IAAM,EAAM,OAAO,IAGnB,KAAK,IAAI,GAAG,EAAI,OAAO,gBAAiB,CAAC,EAAgB,IAAiC,CAIxF,GAHA,KAAK,gBAAgB,EAAK,MAAM,EAG5B,KAAK,QAAQ,SACf,KAAK,OAAO,KAAK,EAAE,MAAM,IAAM,EAAE,EAEpC,EAGD,KAAK,IAAI,GAAG,EAAI,OAAO,eAAgB,CAAC,EAAgB,IAA4B,CAClF,KAAK,oBAAsB,EAAK,MAChC,KAAK,uBAAuB,EAC5B,KAAK,OAAO,KAAK,gBAAiB,KAAK,WAAW,EAAK,QAAU,IAAI,EACtE,EAGD,KAAK,IAAI,GAAG,EAAI,OAAO,mBAAoB,IAAM,CAC/C,KAAK,kBAAkB,EACxB,EAGD,KAAK,IAAI,GAAG,EAAI,OAAO,qBAAsB,IAAM,CACjD,KAAK,kBAAkB,EACvB,IAAM,EAAU,KAAK,aAAa,KAAK,CAAC,IAAM,EAAE,QAAQ,EACxD,KAAK,OAAO,KAAK,mBAAoB,GAAW,IAAI,EACrD,EAGD,KAAK,IAAI,GAAG,EAAI,OAAO,sBAAuB,IAAM,CAClD,KAAK,iBAAiB,EACvB,EAGD,KAAK,IAAI,GAAG,EAAI,OAAO,MAAO,CAAC,EAAgB,IAA4D,CACzG,GAAI,EAAK,MACP,OAAQ,EAAK,WACN,EAAI,WAAW,cAElB,KAAK,KAAK,UAAU,EACpB,WACG,EAAI,WAAW,YAElB,KAAK,KAAK,kBAAkB,EAC5B,cAGA,KAAK,UAAU,EAAG,oBAAoB,EAAK,UAAW,CAAI,EAC1D,KAAK,KAAK,QAAQ,EAClB,OAGP,EAGK,eAAe,CAAC,EAA0B,CAChD,KAAK,WAAa,EAAO,IAAI,CAAC,EAAO,KAAW,CAC9C,GAAI,OAAO,CAAK,EAChB,MAAO,EAAM,MACb,OAAQ,EAAM,OACd,QAAS,EAAM,QACf,MAAO,EAAM,UAAY,EAAM,YAAc,OAC7C,SAAU,IAAU,KAAK,mBAC3B,EAAE,EAEF,KAAK,OAAO,KAAK,cAAe,CAAE,UAAW,KAAK,UAAW,CAAC,EAGxD,sBAAsB,EAAS,CACrC,KAAK,WAAa,KAAK,WAAW,IAAI,CAAC,EAAG,KAAO,IAC5C,EACH,SAAU,IAAM,KAAK,mBACvB,EAAE,EAGI,iBAAiB,EAAS,CAChC,GAAI,CAAC,KAAK,IAAK,OAEf,KAAK,aAAe,KAAK,IAAI,YAAY,IAAI,CAAC,EAAO,KAAW,CAC9D,GAAI,OAAO,EAAM,EAAE,EACnB,MAAO,EAAM,MAAQ,SAAS,EAAQ,IACtC,SAAU,EAAM,MAAQ,GACxB,KAAM,EAAM,QAAU,OAAS,cAC/B,SAAU,IAAU,KAAK,IAAK,UAChC,EAAE,EAEF,KAAK,OAAO,KAAK,cAAe,CAAE,YAAa,KAAK,YAAa,CAAC,EAG5D,gBAAgB,EAAS,CAC/B,GAAI,CAAC,KAAK,IAAK,OAEf,KAAK,YAAc,KAAK,IAAI,eAAe,IAAI,CAAC,EAAO,KAAW,CAChE,GAAI,OAAO,EAAM,EAAE,EACnB,KAAM,YACN,MAAO,EAAM,MAAQ,YAAY,EAAQ,IACzC,SAAU,EAAM,MAAQ,GACxB,KAAO,IAAU,KAAK,IAAK,cAAgB,UAAY,WACvD,KAAM,CAAC,CACT,EAAE,EAEF,KAAK,OAAO,KAAK,cAAe,CAAE,WAAY,KAAK,WAAY,CAAC,OAK5D,KAAI,EAAkB,CAC1B,GAAI,CAAC,KAAK,MAAO,OACjB,GAAI,CACF,MAAM,KAAK,MAAM,KAAK,EACtB,MAAO,EAAO,CACd,GAAK,EAAgB,OAAS,kBAC5B,KAAK,UAAU,EAAG,mDAAoD,CAAK,EAE3E,WAAM,GAKZ,KAAK,EAAS,CACZ,KAAK,OAAO,MAAM,EAKpB,MAAM,CAAC,EAAoB,CACzB,GAAI,KAAK,MACP,KAAK,MAAM,YAAc,KAAK,IAAI,EAAG,KAAK,IAAI,EAAM,KAAK,YAAY,CAAC,CAAC,EAI3E,cAAc,EAAW,CACvB,OAAO,KAAK,OAAO,aAAe,EAGpC,WAAW,EAAW,CACpB,OAAO,KAAK,OAAO,UAAY,EAMjC,UAAU,EAAS,CACjB,GAAI,KAAK,KAAK,kBAAoB,KAChC,KAAK,OAAO,KAAK,IAAI,gBAAgB,EAMzC,SAAS,CAAC,EAAsB,CAC9B,GAAI,KAAK,MACP,KAAK,MAAM,OAAS,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,CAAM,CAAC,EAIvD,SAAS,EAAW,CAClB,OAAO,KAAK,OAAO,QAAU,EAG/B,QAAQ,CAAC,EAAsB,CAC7B,GAAI,KAAK,MACP,KAAK,MAAM,MAAQ,EAIvB,QAAQ,EAAY,CAClB,OAAO,KAAK,OAAO,OAAS,GAK9B,eAAe,CAAC,EAAoB,CAClC,GAAI,KAAK,MACP,KAAK,MAAM,aAAe,KAAK,IAAI,KAAM,KAAK,IAAI,EAAG,CAAI,CAAC,EAI9D,eAAe,EAAW,CACxB,OAAO,KAAK,OAAO,cAAgB,EAKrC,YAAY,EAAmB,CAC7B,OAAO,KAAK,WAGd,UAAU,CAAC,EAAsC,CAC/C,GAAI,CAAC,KAAK,IAAK,OAEf,GAAI,IAAY,OACd,KAAK,IAAI,aAAe,GACxB,KAAK,oBAAsB,GAC3B,KAAK,OAAO,KAAK,cAAe,CAAE,YAAa,EAAK,CAAC,EAChD,KACL,IAAM,EAAQ,SAAS,EAAQ,GAAI,EAAE,EACrC,GAAI,CAAC,MAAM,CAAK,GAAK,GAAS,GAAK,EAAQ,KAAK,WAAW,OACzD,KAAK,IAAI,aAAe,EACxB,KAAK,oBAAsB,EAC3B,KAAK,OAAO,KAAK,cAAe,CAAE,YAAa,EAAM,CAAC,GAO5D,cAAc,EAAiB,CAC7B,OAAO,KAAK,aAGd,aAAa,CAAC,EAAuB,CACnC,GAAI,CAAC,KAAK,IAAK,OAEf,IAAM,EAAQ,SAAS,EAAS,EAAE,EAClC,GAAI,CAAC,MAAM,CAAK,GAAK,GAAS,GAAK,EAAQ,KAAK,IAAI,YAAY,OAC9D,KAAK,IAAI,WAAa,EAM1B,aAAa,EAAgB,CAC3B,OAAO,KAAK,YAGd,gBAAgB,CAAC,EAAiB,EAA+C,CAC/E,GAAI,CAAC,KAAK,IAAK,OAEf,GAAI,IAAS,UAAW,CACtB,IAAM,EAAQ,SAAS,EAAS,EAAE,EAClC,GAAI,CAAC,MAAM,CAAK,EACd,KAAK,IAAI,cAAgB,EAG3B,UAAK,IAAI,cAAgB,GAG3B,KAAK,iBAAiB,EACtB,IAAM,EAAU,KAAK,YAAY,KAAK,CAAC,IAAM,EAAE,OAAS,SAAS,EACjE,KAAK,OAAO,KAAK,kBAAmB,GAAW,IAAI,EAGvD,CAKO,IAAM,GAAY,CACvB,KAAM,MACN,KAAM,MAEN,OAAO,CAAC,EAAmB,CACzB,OAAO,GAAY,CAAG,GAGxB,SAAS,EAAY,CACnB,MAAO,SAGT,UAAU,EAAa,CACrB,MAAO,CAAC,0BAA0B,QAG9B,KAAI,CAAC,EAAwB,EAA8C,CAC/E,IAAM,EAAW,IAAI,EAErB,OADA,MAAM,EAAS,MAAM,EAAW,CAAO,EAChC,EAEX,ECjkBA,IAAI,EAAc,KACd,EAAmC,KAEjC,GAAmB,gDAKzB,eAAe,EAAU,CAAC,EAA+B,CACvD,GAAI,EAAQ,OAAO,EAEnB,GAAI,EAAa,OAAO,EA0BxB,OAxBA,EAAc,IAAI,QAAQ,CAAC,EAAS,IAAW,CAE7C,GAAI,OAAO,OAAW,KAAgB,OAAe,OAAQ,CAC3D,EAAU,OAAe,OACzB,EAAQ,CAAM,EACd,OAGF,IAAM,EAAS,SAAS,cAAc,QAAQ,EAC9C,EAAO,IAAM,GAAU,GACvB,EAAO,MAAQ,GAEf,EAAO,OAAS,IAAM,CACpB,EAAU,OAAe,OACzB,EAAQ,CAAM,GAGhB,EAAO,QAAU,IAAM,CACrB,EAAW,MAAM,wBAAwB,CAAC,GAG5C,SAAS,KAAK,YAAY,CAAM,EACjC,EAEM,EAUF,SAAS,CAAY,CAAC,EAAsC,CACjE,GAAI,OAAO,IAAQ,UAAY,SAAU,GAAO,EAAI,OAAS,OAC3D,MAAO,GAGT,GAAI,OAAO,IAAQ,SACjB,OAAO,EAAI,SAAS,MAAM,GAAK,EAAI,SAAS,qBAAqB,EAGnE,MAAO,GAOF,MAAM,UAAqB,CAAa,CACpC,KAAO,OACP,KAAO,OAER,KAAY,KACZ,MAAiC,KACjC,OAA4B,KAC5B,OAA6B,CAAC,EAC9B,QAAU,EACV,OAAS,GACT,cAAgB,KAEpB,aAAY,EAA4B,CAC1C,OAAO,KAAK,WAME,WAAU,EAAkB,CAc1C,GAZA,KAAK,MAAQ,SAAS,cAAc,OAAO,EAC3C,KAAK,MAAM,MAAM,MAAQ,OACzB,KAAK,MAAM,MAAM,OAAS,OAC1B,KAAK,MAAM,MAAM,QAAU,QAG3B,MAAM,GAAW,KAAK,QAAQ,UAAU,EAGxC,KAAK,KAAO,EAAO,YAAY,EAAE,OAAO,EAGpC,KAAK,QAAQ,WACf,KAAK,OAAO,WAAa,KAAK,QAAQ,WAGxC,GAAI,KAAK,OAAO,WACd,OAAO,QAAQ,KAAK,OAAO,UAAU,EAAE,QAAQ,EAAE,EAAK,KAAW,CAC/D,KAAK,KAAK,eAAe,EAAG,GAAM,CAAM,CAAC,EAC1C,EAIH,GAAI,KAAK,OAAO,WACd,KAAK,KAAK,eAAe,CACvB,UAAW,CACT,kBAAmB,GACnB,UAAW,EACX,oBAAqB,KACrB,wBAAyB,GAC3B,CACF,CAAC,EAOH,GAHA,KAAK,iBAAiB,EAGlB,KAAK,UACP,KAAK,UAAU,YAAY,KAAK,KAAK,EAIvC,KAAK,kBAAkB,EAMjB,iBAAiB,EAAS,CAChC,GAAI,CAAC,KAAK,MAAO,OAEjB,KAAK,MAAM,iBAAiB,OAAQ,IAAM,KAAK,OAAO,KAAK,MAAM,CAAC,EAClE,KAAK,MAAM,iBAAiB,QAAS,IAAM,KAAK,OAAO,KAAK,OAAO,CAAC,EACpE,KAAK,MAAM,iBAAiB,QAAS,IAAM,KAAK,OAAO,KAAK,OAAO,CAAC,EACpE,KAAK,MAAM,iBAAiB,aAAc,IAAM,KAAK,OAAO,KAAK,aAAc,KAAK,eAAe,CAAC,CAAC,EACrG,KAAK,MAAM,iBAAiB,iBAAkB,IAAM,KAAK,OAAO,KAAK,iBAAkB,KAAK,YAAY,CAAC,CAAC,EAC1G,KAAK,MAAM,iBAAiB,eAAgB,IAAM,CAChD,KAAK,OAAO,KAAK,eAAgB,KAAK,UAAU,EAAG,KAAK,SAAS,CAAC,EACnE,EACD,KAAK,MAAM,iBAAiB,UAAW,IAAM,KAAK,OAAO,KAAK,SAAS,CAAC,EACxE,KAAK,MAAM,iBAAiB,UAAW,IAAM,KAAK,OAAO,KAAK,SAAS,CAAC,EACxE,KAAK,MAAM,iBAAiB,UAAW,IAAM,KAAK,OAAO,KAAK,SAAS,CAAC,EACxE,KAAK,MAAM,iBAAiB,SAAU,IAAM,KAAK,OAAO,KAAK,QAAQ,CAAC,EACtE,KAAK,MAAM,iBAAiB,WAAY,IAAM,CAC5C,IAAM,EAAgD,CAAC,EACvD,QAAS,EAAI,EAAG,EAAI,KAAK,MAAO,SAAS,OAAQ,IAC/C,EAAO,KAAK,CACV,MAAO,KAAK,MAAO,SAAS,MAAM,CAAC,EACnC,IAAK,KAAK,MAAO,SAAS,IAAI,CAAC,CACjC,CAAC,EAEH,KAAK,OAAO,KAAK,WAAY,CAAM,EACpC,EACD,KAAK,MAAM,iBAAiB,iBAAkB,IAAM,CAClD,KAAK,OAAO,KAAK,gBAAgB,EACjC,IAAM,EAAyB,KAAK,uBAAuB,YAAY,EACjE,EAAkB,KAAK,uBAAuB,KAAK,EACnD,EAAqB,KAAK,uBAAuB,QAAQ,EAC/D,KAAK,OAAO,KAAK,cAAe,CAC9B,WAAY,KAAK,MAAO,WACxB,YAAa,KAAK,MAAO,YACzB,aAAc,KAAK,MAAO,YAAc,EAAI,KAAK,MAAO,WAAa,KAAK,MAAO,YAAc,IAAM,mBACrG,cAAe,IAA2B,YAC1C,oBAAqB,IAAoB,YACzC,yBACA,kBACA,oBACF,CAAC,EACD,KAAK,OAAO,KAAK,qBAAsB,aAAc,CAAsB,EAC3E,KAAK,OAAO,KAAK,qBAAsB,MAAO,CAAe,EAC7D,KAAK,OAAO,KAAK,qBAAsB,SAAU,CAAkB,EACpE,EACD,KAAK,MAAM,iBAAiB,aAAc,IAAM,KAAK,OAAO,KAAK,YAAY,CAAC,EAC9E,KAAK,MAAM,iBAAiB,UAAW,IAAM,KAAK,OAAO,KAAK,SAAS,CAAC,EACxE,KAAK,MAAM,iBAAiB,iBAAkB,IAAM,KAAK,OAAO,KAAK,gBAAgB,CAAC,EACtF,KAAK,MAAM,iBAAiB,aAAc,IAAM,KAAK,OAAO,KAAK,aAAc,KAAK,gBAAgB,CAAC,CAAC,EAMhG,gBAAgB,EAAS,CAC/B,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAc,EAAO,YAG3B,KAAK,KAAK,GAAG,EAAY,OAAO,wBAAyB,IAAM,CAC7D,KAAK,OAAO,KAAK,gBAAiB,KAAK,aAAa,EAAE,KAAK,KAAK,EAAE,QAAQ,GAAK,IAAI,EACpF,EAGD,KAAK,KAAK,GAAG,EAAY,OAAO,gBAAiB,IAAM,EAEtD,EAGD,KAAK,KAAK,GAAG,EAAY,OAAO,MAAO,CAAC,IAAe,CACrD,KAAK,UACH,EAAM,OAAO,MAAQ,EACrB,EAAM,OAAO,SAAW,sBACxB,EAAM,KACR,EACD,EAMK,YAAY,CAAC,EAAsB,CACzC,IAAM,EAAsC,CAAC,EAE7C,GAAI,EAAI,SACN,EAAe,sBAAwB,CACrC,UAAW,EAAI,SAAS,IACxB,mBAAoB,EAAI,SAAS,OACnC,EAGF,GAAI,EAAI,UACN,EAAe,2BAA6B,CAC1C,UAAW,EAAI,UAAU,IACzB,mBAAoB,EAAI,UAAU,OACpC,EAGF,GAAI,EAAI,SACN,EAAe,qBAAuB,CACpC,UAAW,EAAI,SAAS,IACxB,qBAAsB,EAAI,SAAS,eACnC,mBAAoB,EAAI,SAAS,OACnC,EAGF,KAAK,KAAK,kBAAkB,CAAc,OAMtC,KAAI,CAAC,EAAyB,CAClC,GAAI,CAAC,KAAK,OAAS,CAAC,KAAK,KAAM,OAG/B,GAAI,OAAO,IAAQ,SACjB,KAAK,OAAS,CAAE,MAAK,KAAM,MAAO,EAE/B,QAAI,EAAa,CAAG,EACvB,KAAK,OAAS,EAEX,KACH,KAAK,UAAU,EAAG,qBAAqB,EACvC,OAIF,GAAI,KAAK,OAAO,IACd,KAAK,aAAa,KAAK,OAAO,GAAG,EAInC,KAAK,KAAK,WAAW,KAAK,MAAO,KAAK,OAAO,IAAK,EAAK,EAMzD,OAAO,CAAC,EAAmB,CACzB,OAAO,EAAa,CAAG,EAMf,OAAO,EAAS,CACxB,GAAI,KAAK,KACP,KAAK,KAAK,MAAM,EAChB,KAAK,KAAO,KAGd,GAAI,KAAK,MACP,KAAK,MAAM,OAAO,EAClB,KAAK,MAAQ,KAGf,KAAK,OAAS,UAOV,KAAI,EAAkB,CAC1B,GAAI,CAAC,KAAK,MAAO,OACjB,GAAI,CACF,MAAM,KAAK,MAAM,KAAK,EACtB,MAAO,EAAO,CACd,GAAK,EAAgB,OAAS,kBAC5B,KAAK,UAAU,EAAG,mDAAoD,CAAK,EAE3E,WAAM,GAKZ,KAAK,EAAS,CACZ,GAAI,KAAK,MACP,KAAK,MAAM,MAAM,EAQrB,MAAM,CAAC,EAAoB,CACzB,GAAI,KAAK,MACP,KAAK,MAAM,YAAc,EAI7B,cAAc,EAAW,CACvB,OAAO,KAAK,OAAO,aAAe,EAGpC,WAAW,EAAW,CACpB,OAAO,KAAK,OAAO,UAAY,EAOjC,SAAS,CAAC,EAAsB,CAE9B,GADA,KAAK,QAAU,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,CAAM,CAAC,EAC1C,KAAK,MACP,KAAK,MAAM,OAAS,KAAK,QAI7B,SAAS,EAAW,CAClB,OAAO,KAAK,OAAO,QAAU,KAAK,QAGpC,QAAQ,CAAC,EAAsB,CAE7B,GADA,KAAK,OAAS,EACV,KAAK,MACP,KAAK,MAAM,MAAQ,EAIvB,QAAQ,EAAY,CAClB,OAAO,KAAK,OAAO,OAAS,KAAK,OAOnC,eAAe,CAAC,EAAoB,CAElC,GADA,KAAK,cAAgB,EACjB,KAAK,MACP,KAAK,MAAM,aAAe,EAI9B,eAAe,EAAW,CACxB,OAAO,KAAK,OAAO,cAAgB,KAAK,iBAOtC,OAAM,EAAY,CACpB,OAAO,KAAK,MAAM,UAAU,GAAK,MAG/B,YAAW,EAAW,CACxB,OAAO,KAAK,MAAM,sBAAsB,GAAK,KAG3C,YAAW,EAAY,CACzB,OAAO,KAAK,OAGd,UAAU,EAAS,CACjB,GAAI,KAAK,MAAQ,KAAK,OACpB,KAAK,KAAK,mBAAmB,EAQjC,YAAY,EAAmB,CAC7B,GAAI,CAAC,KAAK,KAAM,MAAO,CAAC,EAExB,IAAM,EAAkB,KAAK,KAAK,sBAAsB,OAAO,EAC/D,GAAI,CAAC,EAAiB,MAAO,CAAC,EAE9B,IAAM,EAAe,KAAK,KAAK,cAAc,OAAO,EAEpD,OAAO,EAAgB,IAAI,CAAC,EAAW,KAAmB,CACxD,GAAI,GAAG,IACP,MAAO,GAAG,EAAK,UACf,MAAO,EAAK,MACZ,OAAQ,EAAK,OACb,QAAS,EAAK,QACd,SAAU,IAAU,CACtB,EAAE,EAGJ,UAAU,CAAC,EAAsC,CAC/C,GAAI,CAAC,KAAK,KAAM,OAEhB,GAAI,IAAY,OACd,KAAK,KAAK,eAAe,CACvB,UAAW,CAAE,IAAK,CAAE,kBAAmB,CAAE,MAAO,EAAK,CAAE,CAAE,CAC3D,CAAC,EAEE,KACH,IAAM,EAAQ,OAAO,SAAS,EAAQ,GAAI,EAAE,EAC5C,GAAI,CAAC,OAAO,MAAM,CAAK,EACrB,KAAK,KAAK,eAAe,CACvB,UAAW,CAAE,IAAK,CAAE,kBAAmB,CAAE,MAAO,EAAM,CAAE,CAAE,CAC5D,CAAC,EACD,KAAK,KAAK,cAAc,QAAS,CAAK,MAKxC,cAAa,EAAY,CAC3B,GAAI,CAAC,KAAK,KAAM,MAAO,GAEvB,OADiB,KAAK,KAAK,YAAY,GACtB,WAAW,KAAK,mBAAmB,OAAS,GAO/D,cAAc,EAAiB,CAC7B,GAAI,CAAC,KAAK,KAAM,MAAO,CAAC,EAExB,IAAM,EAAS,KAAK,KAAK,aAAa,OAAO,EAC7C,GAAI,CAAC,EAAQ,MAAO,CAAC,EAErB,IAAM,EAAe,KAAK,KAAK,mBAAmB,OAAO,EAEzD,OAAO,EAAO,IAAI,CAAC,EAAY,KAAmB,CAChD,GAAI,EAAM,IAAM,GAAG,IACnB,MAAO,EAAM,SAAS,IAAI,MAAQ,EAAM,MAAQ,SAAS,EAAQ,IACjE,SAAU,EAAM,MAAQ,GACxB,KAAM,OACN,SAAU,IAAU,CACtB,EAAE,EAGJ,aAAa,CAAC,EAAuB,CACnC,GAAI,CAAC,KAAK,KAAM,OAGhB,IAAM,EADS,KAAK,KAAK,aAAa,OAAO,EACxB,KAAK,CAAC,EAAQ,KAAe,EAAE,IAAM,GAAG,OAAS,CAAO,EAE7E,GAAI,EACF,KAAK,KAAK,gBAAgB,CAAK,EAQnC,aAAa,EAAgB,CAC3B,GAAI,CAAC,KAAK,KAAM,MAAO,CAAC,EAExB,IAAM,EAAS,KAAK,KAAK,aAAa,MAAM,EAC5C,GAAI,CAAC,EAAQ,MAAO,CAAC,EAErB,IAAM,EAAe,KAAK,KAAK,mBAAmB,MAAM,EAExD,OAAO,EAAO,IAAI,CAAC,EAAY,KAAmB,CAChD,GAAI,EAAM,IAAM,GAAG,IACnB,MAAO,EAAM,SAAS,IAAI,MAAQ,EAAM,MAAQ,YAAY,EAAQ,IACpE,SAAU,EAAM,MAAQ,GACxB,KAAO,EAAM,MAAQ,YACrB,KAAO,IAAU,EAAe,UAAY,WAC5C,KAAM,CAAC,CACT,EAAE,EAGJ,gBAAgB,CAAC,EAAiB,EAA+C,CAC/E,GAAI,CAAC,KAAK,KAAM,OAEhB,GAAI,IAAS,WAAY,CACvB,KAAK,KAAK,WAAW,EAAK,EAC1B,OAIF,IAAM,EADS,KAAK,KAAK,aAAa,MAAM,EACvB,KAAK,CAAC,EAAQ,KAAe,EAAE,IAAM,GAAG,OAAS,CAAO,EAE7E,GAAI,EACF,KAAK,KAAK,gBAAgB,CAAK,EAC/B,KAAK,KAAK,WAAW,IAAS,SAAS,EAG7C,CAMO,IAAM,GAA6B,CACxC,KAAM,OACN,KAAM,OAEN,OAAO,CAAC,EAAmB,CACzB,OAAO,EAAa,CAAG,GAGzB,SAAS,EAAY,CACnB,MAAO,cAGH,KAAI,CAAC,EAAwB,EAA+C,CAChF,IAAM,EAAW,IAAI,EAErB,OADA,MAAM,EAAS,MAAM,EAAW,CAAO,EAChC,GAGT,UAAU,EAAa,CACrB,MAAO,CAAC,wBAAwB,EAEpC,EC7hBO,IAAM,EAAmC,CAC9C,GACA,GACA,GACA,GACA,EACF,EAKO,SAAS,EAAU,CAAC,EAAU,EAA4B,EAAuC,CACtG,QAAW,KAAU,EACnB,GAAI,EAAO,QAAQ,CAAG,EACpB,OAAO,EAGX,OAAO,KAUF,SAAS,EAAoB,CAAC,EAAyB,EAA4B,EAAmC,CAC3H,IAAM,EAAgC,CAAC,EAKvC,OAJA,EAAQ,QAAQ,CAAC,EAAK,IAAU,CAC9B,IAAM,EAAS,GAAW,EAAK,CAAO,EACtC,GAAI,EAAQ,EAAW,KAAK,CAAE,MAAK,QAAO,QAAO,CAAC,EACnD,EACM,EChDF,MAAM,CAA0B,CAC7B,IACA,SACA,OACA,QAAU,IAAI,EACd,UAA6B,KAC7B,SACA,YAAiC,CAAC,EAClC,WAAa,GACb,cAAgB,EAChB,WAAiC,KAKzC,WAAW,CAAC,EAAiC,EAAyB,CAAC,EAAG,CAExE,GAAI,OAAO,IAAc,SAAU,CACjC,IAAM,EAAK,SAAS,cAAc,CAAS,EAC3C,GAAI,CAAC,EAAI,MAAU,MAAM,gCAAgC,GAAW,EACpE,KAAK,IAAM,EAGX,UAAK,IAAM,EAiBb,GAdA,KAAK,SAAW,IAAK,MAAmB,CAAQ,EAChD,KAAK,SAAW,EAChB,KAAK,OAAS,IAAI,EAGlB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EAGb,KAAK,SAAS,IAChB,KAAK,OAAO,KAAK,SAAS,GAAG,EAI/B,KAAK,QAAQ,KAAK,MAAM,KAKtB,GAAE,EAAgB,CACpB,OAAO,KAAK,OAGV,MAAK,EAAgB,CACvB,OAAO,KAAK,OAAO,SAAS,KAG1B,SAAQ,EAAoB,CAC9B,OAAO,KAAK,aAGV,MAAK,EAAY,CACnB,OAAO,KAAK,WAAW,OAAS,GAK1B,YAAY,EAAS,CAC3B,KAAK,IAAI,UAAU,IAAI,iBAAiB,EACxC,KAAK,IAAI,aAAa,WAAY,GAAG,EACrC,KAAK,IAAI,aAAa,OAAQ,QAAQ,EACtC,KAAK,IAAI,aAAa,uBAAwB,cAAc,EAC5D,KAAK,IAAI,aAAa,aAAc,KAAK,SAAS,OAAS,cAAc,EACzE,KAAK,IAAI,aAAa,oBAAqB,yFAAyF,EAGpI,IAAM,EAAiB,SAAS,cAAc,KAAK,EAKnD,GAJA,EAAe,UAAY,6BAC3B,KAAK,IAAI,YAAY,CAAc,EAG/B,KAAK,SAAS,gBAAkB,GAAO,CACzC,IAAM,EAAa,SAAS,cAAc,KAAK,EAC/C,EAAW,UAAY,6BACvB,EAAW,aAAa,OAAQ,QAAQ,EACxC,EAAW,aAAa,YAAa,QAAQ,EAC7C,EAAW,aAAa,cAAe,MAAM,EAE7C,EAAW,MAAM,QAAU,8HAC3B,KAAK,IAAI,YAAY,CAAU,EAC/B,KAAK,WAAa,EAIpB,IAAM,EAAM,KAAK,IACjB,EAAI,cAAgB,KAGd,UAAU,EAAS,CAEzB,KAAK,OAAO,MAAM,CAChB,OAAQ,KAAK,SAAS,QAAU,EAChC,MAAO,KAAK,SAAS,OAAS,GAC9B,KAAM,KAAK,SAAS,MAAQ,GAC5B,SAAU,KAAK,SAAS,UAAY,GACpC,YAAa,KAAK,SAAS,aAAe,GAC1C,aAAc,KAAK,SAAS,cAAgB,EAC5C,OAAQ,KAAK,SAAS,QAAU,GAChC,MAAO,KAAK,SAAS,OAAS,GAC9B,QAAS,KAAK,SAAS,SAAW,UACpC,CAAC,EAGD,IAAM,EAAc,KAAK,OAAO,UAAU,CAAC,IAAU,CAEnD,KAAK,iBAAiB,CAAK,EAC5B,EAED,KAAK,YAAY,KAAK,CAAW,EAG3B,aAAa,EAAS,CAC5B,GAAI,KAAK,SAAS,WAAa,GAAO,OAEtC,IAAM,EAA2B,CAC/B,WAAY,IAAM,KAAK,WAAW,EAClC,WAAY,IAAM,KAAK,WAAW,EAClC,iBAAkB,IAAM,KAAK,iBAAiB,EAC9C,UAAW,IAAM,KAAK,UAAU,EAChC,eAAgB,IAAM,KAAK,eAAe,EAC1C,aAAc,IAAM,KAAK,OAAO,EAAE,EAClC,YAAa,IAAM,KAAK,OAAO,CAAC,EAChC,SAAU,IAAM,KAAK,UAAU,KAAK,IAAI,EAAG,KAAK,MAAM,OAAS,GAAG,CAAC,EACnE,WAAY,IAAM,KAAK,UAAU,KAAK,IAAI,EAAG,KAAK,MAAM,OAAS,GAAG,CAAC,EACrE,QAAS,IAAM,KAAK,gBAAgB,KAAK,IAAI,EAAG,KAAK,MAAM,aAAe,IAAI,CAAC,EAC/E,UAAW,IAAM,KAAK,gBAAgB,KAAK,IAAI,KAAM,KAAK,MAAM,aAAe,IAAI,CAAC,EACpF,UAAW,IAAM,KAAK,OAAO,CAAC,EAC9B,QAAS,IAAM,KAAK,OAAO,KAAK,MAAM,QAAQ,EAC9C,YAAa,CAAC,IAAY,KAAK,OAAQ,EAAU,IAAO,KAAK,MAAM,QAAQ,CAC7E,EAEM,EAAS,OAAO,KAAK,SAAS,WAAa,SAAW,KAAK,SAAS,SAAW,CAAC,EAChF,EAAU,GAAsB,EAAS,CAC7C,SAAU,EAAO,SACjB,WAAY,EAAO,UACrB,CAAC,EAEK,EAAS,EAAO,OAAS,SAAW,KAAK,IAC/C,EAAO,iBAAiB,UAAW,CAAwB,EAE3D,KAAK,YAAY,KAAK,IAAM,EAAO,oBAAoB,UAAW,CAAwB,CAAC,EAGrF,aAAa,EAAS,CAC5B,IAAM,EAAU,GAAuB,KAAK,IAAK,CAC/C,QAAS,KAAK,SAAS,iBAAmB,KAC1C,SAAU,IAAM,CACd,KAAK,OAAO,MAAM,CAAE,WAAY,GAAM,gBAAiB,EAAK,CAAC,EAC7D,KAAK,QAAQ,KAAK,qBAAsB,EAAI,EAC5C,KAAK,QAAQ,KAAK,iBAAkB,EAAI,GAE1C,WAAY,IAAM,CAEhB,GAAI,KAAK,MAAM,QACb,KAAK,OAAO,MAAM,CAAE,WAAY,GAAO,gBAAiB,EAAM,CAAC,EAC/D,KAAK,QAAQ,KAAK,qBAAsB,EAAK,EAC7C,KAAK,QAAQ,KAAK,iBAAkB,EAAK,EAG/C,CAAC,EAED,KAAK,YAAY,KAAK,CAAO,EAG7B,IAAM,EAAe,IAAM,KAAK,OAAO,IAAI,cAAe,EAAI,EACxD,EAAe,IAAM,KAAK,OAAO,IAAI,cAAe,EAAK,EAC/D,KAAK,IAAI,iBAAiB,aAAc,CAAY,EACpD,KAAK,IAAI,iBAAiB,aAAc,CAAY,EACpD,KAAK,YAAY,KACf,IAAM,KAAK,IAAI,oBAAoB,aAAc,CAAY,EAC7D,IAAM,KAAK,IAAI,oBAAoB,aAAc,CAAY,CAC/D,EAGM,YAAY,EAAS,CAC3B,GAAI,CAAC,KAAK,SAAS,QAAS,OAE5B,IAAM,EAAS,OAAO,KAAK,SAAS,UAAY,SAAW,KAAK,SAAS,QAAU,CAAC,EAC9E,EAAU,EAAO,OAAS,UAAY,eAAiB,aAEvD,EAAU,GAAkB,KAAK,OAAQ,EAAO,SAAW,CAAC,SAAU,QAAS,cAAc,EAAG,CAAO,EAE7G,KAAK,YAAY,KAAK,CAAO,EAGvB,iBAAiB,EAAS,CAChC,GAAI,KAAK,SAAS,eAAiB,GAAO,OAE1C,IAAM,EACJ,OAAO,KAAK,SAAS,eAAiB,SAClC,KAAK,SAAS,aACd,KAAK,SAAS,MACZ,CAAE,MAAO,KAAK,SAAS,KAAM,EAC7B,OAEF,EAAU,GACd,KAAK,OACL,CACE,KAAM,IAAM,KAAK,KAAK,EACtB,MAAO,IAAM,KAAK,MAAM,EACxB,OAAQ,CAAC,IAAS,KAAK,OAAO,CAAI,EAClC,OAAQ,CAAC,IAAW,KAAK,OAAO,CAAM,EACtC,KAAM,IAAM,KAAK,KAAK,CACxB,EACA,CACF,EAEA,KAAK,YAAY,KAAK,CAAO,EAGvB,WAAW,EAAS,CAE1B,GAAI,KAAK,SAAS,IAChB,QAAY,EAAO,KAAY,OAAO,QAAQ,KAAK,SAAS,EAAE,EAC5D,GAAI,EACF,KAAK,GAAG,EAA+B,CAA+C,GAMtF,gBAAgB,CAAC,EAA0B,CACjD,IAAM,EAAK,KAAK,IAGhB,EAAG,QAAQ,OAAS,OAAO,EAAM,MAAM,EACvC,EAAG,QAAQ,QAAU,OAAO,EAAM,OAAO,EACzC,EAAG,QAAQ,QAAU,OAAO,EAAM,OAAO,EACzC,EAAG,QAAQ,MAAQ,OAAO,EAAM,KAAK,EACrC,EAAG,QAAQ,QAAU,OAAO,EAAM,OAAO,EACzC,EAAG,QAAQ,QAAU,OAAO,EAAM,OAAO,EACzC,EAAG,QAAQ,KAAO,OAAO,EAAM,IAAI,EACnC,EAAG,QAAQ,SAAW,OAAO,EAAM,QAAQ,EAG3C,EAAG,QAAQ,QAAU,OAAO,EAAM,SAAW,EAAM,eAAiB,SAAS,EAC7E,EAAG,QAAQ,QAAU,OAAO,EAAM,OAAO,EACzC,EAAG,QAAQ,aAAe,EAAM,aAChC,EAAG,aAAa,YAAa,OAAO,EAAM,SAAW,EAAM,eAAiB,SAAS,CAAC,EAGtF,EAAG,QAAQ,MAAQ,OAAO,EAAM,KAAK,EACrC,EAAG,QAAQ,YAAc,EAAM,OAAS,EAAM,SAAW,EACrD,SACA,EAAM,OAAS,IACb,MACA,OAGN,EAAG,QAAQ,WAAa,OAAO,EAAM,UAAU,EAC/C,EAAG,QAAQ,IAAM,OAAO,EAAM,gBAAgB,EAG9C,EAAG,QAAQ,gBAAkB,OAAO,EAAM,eAAe,EACzD,EAAG,QAAQ,WAAa,OAAO,EAAM,UAAU,EAG/C,EAAG,QAAQ,UAAY,EAAM,UAC7B,EAAG,QAAQ,WAAa,EAAM,WAG9B,IAAM,EAAoB,EAAM,WAAW,KAAK,CAAC,IAAM,EAAE,OAAS,SAAS,EAC3E,EAAG,QAAQ,SAAW,OAAO,CAAiB,EAG9C,EAAG,QAAQ,MAAQ,OAAO,CAAC,CAAC,EAAM,KAAK,EAMjC,QAAQ,CAAC,EAAuB,CACtC,GAAI,CAAC,KAAK,WAAY,OAEtB,KAAK,WAAW,YAAc,GAC9B,sBAAsB,IAAM,CAC1B,GAAI,KAAK,WAAY,KAAK,WAAW,YAAc,EACpD,EAKH,OAAO,EAAS,CACd,GAAI,KAAK,WAAY,OACrB,KAAK,WAAa,GAClB,KAAK,gBAGL,KAAK,QAAQ,KAAK,SAAS,EAG3B,KAAK,YAAY,QAAQ,CAAC,IAAO,EAAG,CAAC,EACrC,KAAK,YAAc,CAAC,EAGpB,KAAK,WAAW,QAAQ,EACxB,KAAK,UAAY,KAGjB,KAAK,QAAQ,mBAAmB,EAGhC,KAAK,OAAO,MAAM,EAGlB,KAAK,IAAI,UAAU,OAAO,iBAAiB,EAC3C,KAAK,IAAI,UAAY,GACrB,OAAQ,KAAK,IAAY,mBAKrB,OAAM,CAAC,EAAiC,CAC5C,GAAI,KAAK,WAAY,OAGrB,IAAM,EAAS,EAAE,KAAK,cAChB,EAAU,MAAM,QAAQ,CAAG,EAAI,EAAM,CAAC,CAAG,EAC/C,GAAI,EAAQ,SAAW,EACrB,OAIF,KAAK,OAAO,MAAM,CAChB,UACA,IAAK,EAAQ,GACb,aAAc,UACd,MAAO,IACT,CAAC,EAED,KAAK,QAAQ,KAAK,gBAAiB,CAAO,EAE1C,IAAM,EAAa,GAAqB,EAAS,KAAK,QAAQ,EAC9D,GAAI,EAAW,SAAW,EAAG,CAC3B,KAAK,OAAO,MAAM,CAChB,aAAc,QACd,MAAO,CAAE,KAAM,EAAG,QAAS,yCAA0C,CACvE,CAAC,EACD,OAGF,IAAI,EACJ,QAAW,KAAa,EAAY,CAClC,GAAI,KAAK,YAAc,IAAW,KAAK,cAAe,OACtD,IAAQ,UAAW,EAEnB,GAAI,KAAK,WAAa,KAAK,UAAU,OAAS,EAAO,KACnD,KAAK,UAAU,QAAQ,EACvB,KAAK,UAAY,KACjB,KAAK,QAAQ,KAAK,iBAAkB,IAAI,EAG1C,GAAI,CAAC,KAAK,UAAW,CACnB,IAAM,EAAiB,KAAK,IAAI,cAAc,6BAA6B,EAC3E,GAAI,CAKF,GAJA,KAAK,UAAY,MAAM,EAAO,KAAK,EAAgB,IAC9C,KAAK,SACR,UAAW,EAAO,UAAU,EAAU,GAAG,CAC3C,CAAC,EACG,KAAK,YAAc,IAAW,KAAK,cAAe,CACpD,KAAK,UAAU,QAAQ,EACvB,KAAK,UAAY,KACjB,OAEF,KAAK,QAAQ,KAAK,iBAAkB,KAAK,SAAS,EAClD,KAAK,qBAAqB,KAAK,SAAS,EAE1C,MAAO,EAAO,CACZ,EAAY,EACZ,KAAK,UAAY,KACjB,UAIJ,GAAI,CAGF,IAAM,EAAoB,EAAO,OAAS,QACtC,EAAW,OAAO,KAAQ,EAAK,OAAO,OAAS,EAAO,IAAI,EAAE,IAAI,KAAQ,EAAK,GAAG,EAChF,CAAC,EAAU,GAAG,EAElB,GADA,MAAM,KAAK,UAAU,KAAK,EAAkB,OAAS,EAAI,EAAoB,EAAU,GAAG,EACtF,KAAK,YAAc,IAAW,KAAK,cAAe,OACtD,KAAK,OAAO,MAAM,CAChB,IAAK,EAAU,IACf,mBAAoB,EAAU,MAC9B,UAAW,EAAO,UAAU,EAAU,GAAG,EACzC,aAAc,EAAO,IACvB,CAAC,EACD,OAEF,MAAO,EAAO,CAEZ,GADA,EAAY,EACR,KAAK,UACP,KAAK,UAAU,QAAQ,EACvB,KAAK,UAAY,KAEnB,KAAK,QAAQ,KAAK,iBAAkB,IAAI,GAI5C,GAAI,KAAK,YAAc,IAAW,KAAK,cAAe,OACtD,KAAK,OAAO,MAAM,CAChB,aAAc,QACd,MAAO,CAAE,KAAM,EAAG,QAAS,wCAAyC,QAAS,CAAU,CACzF,CAAC,EAGH,MAAM,EAAe,CACnB,OAAO,KAAK,MAAM,IAGZ,oBAAoB,CAAC,EAA0B,CAErD,EAAS,GAAG,YAAa,IAAM,CAC7B,KAAK,OAAO,IAAI,eAAgB,SAAS,EACzC,KAAK,QAAQ,KAAK,WAAW,EAC9B,EAED,EAAS,GAAG,iBAAkB,IAAM,CAClC,KAAK,QAAQ,KAAK,gBAAgB,EACnC,EAED,EAAS,GAAG,aAAc,IAAM,CAC9B,KAAK,QAAQ,KAAK,YAAY,EAC/B,EAED,EAAS,GAAG,UAAW,IAAM,CAC3B,KAAK,OAAO,MAAM,CAAE,QAAS,GAAM,aAAc,QAAS,CAAC,EAC3D,KAAK,QAAQ,KAAK,SAAS,EAC5B,EAED,EAAS,GAAG,iBAAkB,IAAM,CAClC,KAAK,OAAO,IAAI,iBAAkB,EAAI,EACtC,KAAK,QAAQ,KAAK,gBAAgB,EACnC,EAED,EAAS,GAAG,OAAQ,IAAM,CACxB,KAAK,OAAO,MAAM,CAAE,OAAQ,GAAO,MAAO,EAAM,CAAC,EACjD,KAAK,QAAQ,KAAK,MAAM,EACxB,KAAK,SAAS,SAAS,EACxB,EAED,EAAS,GAAG,QAAS,IAAM,CACzB,KAAK,OAAO,IAAI,SAAU,EAAI,EAC9B,KAAK,QAAQ,KAAK,OAAO,EACzB,KAAK,SAAS,QAAQ,EACvB,EAED,EAAS,GAAG,UAAW,IAAM,CAC3B,KAAK,OAAO,MAAM,CAAE,QAAS,GAAM,QAAS,GAAM,QAAS,GAAO,cAAe,SAAU,CAAC,EAC5F,KAAK,QAAQ,KAAK,SAAS,EAC5B,EAED,EAAS,GAAG,UAAW,IAAM,CAC3B,KAAK,OAAO,MAAM,CAAE,QAAS,GAAM,cAAe,WAAY,CAAC,EAC/D,KAAK,QAAQ,KAAK,SAAS,EAC5B,EAED,EAAS,GAAG,UAAW,IAAM,CAC3B,KAAK,OAAO,IAAI,UAAW,EAAI,EAC/B,KAAK,QAAQ,KAAK,SAAS,EAC5B,EAED,EAAS,GAAG,SAAU,IAAM,CAC1B,KAAK,OAAO,IAAI,UAAW,EAAK,EAChC,KAAK,QAAQ,KAAK,QAAQ,EAC3B,EAED,EAAS,GAAG,aAAc,CAAC,IAAS,CAClC,KAAK,OAAO,IAAI,cAAe,CAAI,EACnC,KAAK,QAAQ,KAAK,aAAc,CAAI,EACrC,EAED,EAAS,GAAG,iBAAkB,CAAC,IAAa,CAC1C,KAAK,OAAO,IAAI,WAAY,CAAQ,EACpC,KAAK,QAAQ,KAAK,iBAAkB,CAAQ,EAC7C,EAED,EAAS,GAAG,eAAgB,CAAC,EAAQ,IAAU,CAG7C,GAFA,KAAK,OAAO,MAAM,CAAE,SAAQ,OAAM,CAAC,EACnC,KAAK,QAAQ,KAAK,eAAgB,EAAQ,CAAK,EAC3C,EACF,KAAK,SAAS,OAAO,EAGrB,UAAK,SAAS,UAAU,KAAK,MAAM,EAAS,GAAG,IAAI,EAEtD,EAED,EAAS,GAAG,aAAc,CAAC,IAAS,CAClC,KAAK,OAAO,IAAI,eAAgB,CAAI,EACpC,KAAK,QAAQ,KAAK,aAAc,CAAI,EACrC,EAED,EAAS,GAAG,WAAY,CAAC,IAAa,CACpC,KAAK,OAAO,MAAM,CAChB,WACA,eAAgB,GAAqB,IAAK,KAAK,MAAO,UAAS,CAAC,CAClE,CAAC,EACD,KAAK,QAAQ,KAAK,WAAY,CAAQ,EACvC,EAED,EAAS,GAAG,QAAS,IAAM,CAMzB,GALA,KAAK,OAAO,MAAM,CAAE,MAAO,GAAM,QAAS,GAAO,cAAe,OAAQ,CAAC,EACzE,KAAK,QAAQ,KAAK,OAAO,EACzB,KAAK,SAAS,gBAAgB,EAG1B,KAAK,MAAM,KACb,KAAK,OAAO,CAAC,EACb,KAAK,KAAK,EAEb,EAED,EAAS,GAAG,QAAS,CAAC,IAAU,CAC9B,KAAK,OAAO,MAAM,CAAE,aAAc,QAAS,OAAM,CAAC,EAClD,KAAK,QAAQ,KAAK,QAAS,CAAK,EAChC,KAAK,SAAS,UAAU,EAAM,SAAS,EACxC,EAED,EAAS,GAAG,cAAe,CAAC,IAAU,CACpC,KAAK,OAAO,MAAM,CAAK,EACvB,KAAK,QAAQ,KAAK,cAAe,CAAK,EACvC,EAED,EAAS,GAAG,gBAAiB,CAAC,IAAY,CACxC,KAAK,OAAO,IAAI,YAAa,EAAS,aAAa,CAAC,EACpD,KAAK,QAAQ,KAAK,gBAAiB,CAAO,EAC3C,EAED,EAAS,GAAG,mBAAoB,CAAC,IAAe,CAC9C,KAAK,OAAO,IAAI,aAAc,CAAU,EACxC,KAAK,QAAQ,KAAK,mBAAoB,CAAU,EAChD,KAAK,SAAS,EAAa,qBAAuB,mBAAmB,EACtE,EAED,EAAS,GAAG,YAAa,CAAC,IAAQ,CAChC,KAAK,OAAO,IAAI,mBAAoB,CAAG,EACvC,KAAK,QAAQ,KAAK,YAAa,CAAG,EACnC,EAED,EAAS,GAAG,qBAAsB,CAAC,EAAS,IAAiB,CAC3D,GAAI,IAAY,SAAU,KAAK,OAAO,IAAI,qBAAsB,CAAY,EACvE,QAAI,IAAY,aAAc,KAAK,OAAO,IAAI,yBAA0B,CAAY,EACpF,QAAI,IAAY,MAAO,KAAK,OAAO,IAAI,kBAAmB,CAAY,EAC3E,KAAK,QAAQ,KAAK,qBAAsB,EAAS,CAAY,EAC9D,OAKG,KAAI,EAAkB,CAC1B,GAAI,CAAC,KAAK,UAAW,OACrB,MAAM,KAAK,UAAU,KAAK,EAG5B,KAAK,EAAS,CACZ,KAAK,WAAW,MAAM,EAGxB,UAAU,EAAS,CACjB,GAAI,KAAK,MAAM,OACb,KAAK,KAAK,EAAE,MAAM,IAAM,EAEvB,EAGD,UAAK,MAAM,EAIf,IAAI,EAAS,CACX,KAAK,WAAW,KAAK,EACrB,KAAK,OAAO,MAAM,CAAE,QAAS,GAAO,OAAQ,GAAM,YAAa,EAAG,cAAe,MAAO,CAAC,EAK3F,MAAM,CAAC,EAAoB,CACzB,KAAK,WAAW,OAAO,CAAI,EAG7B,MAAM,CAAC,EAAsB,CAC3B,IAAM,EAAU,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,MAAM,YAAc,EAAQ,KAAK,MAAM,QAAQ,CAAC,EAC1F,KAAK,OAAO,CAAO,EAKrB,SAAS,CAAC,EAAsB,CAC9B,GAAI,KAAK,MAAM,qBAAuB,cAAe,OACrD,IAAM,EAAU,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,CAAM,CAAC,EAC/C,KAAK,WAAW,UAAU,CAAO,EACjC,KAAK,OAAO,IAAI,SAAU,CAAO,EAGnC,QAAQ,CAAC,EAAsB,CAC7B,KAAK,WAAW,SAAS,CAAK,EAC9B,KAAK,OAAO,IAAI,QAAS,CAAK,EAGhC,UAAU,EAAS,CACjB,KAAK,SAAS,CAAC,KAAK,MAAM,KAAK,EAKjC,eAAe,CAAC,EAAoB,CAClC,IAAM,EAAU,KAAK,IAAI,KAAM,KAAK,IAAI,EAAG,CAAI,CAAC,EAChD,KAAK,WAAW,gBAAgB,CAAO,EACvC,KAAK,OAAO,IAAI,eAAgB,CAAO,OAKnC,gBAAe,EAAkB,CACrC,GAAI,KAAK,MAAM,yBAA2B,cAAe,OAEzD,GAAI,KAAK,MAAM,iBACb,MAAM,KAAK,QAAQ,EAGrB,GADA,MAAM,KAAK,WAAW,gBAAgB,EAClC,KAAK,SAAS,4BAA8B,GAC9C,GAAgB,WAAW,OAIzB,eAAc,EAAkB,CAEpC,GADA,MAAM,KAAK,WAAW,eAAe,EACjC,KAAK,SAAS,4BAA8B,GAC9C,GAAkB,OAIhB,iBAAgB,EAAkB,CACtC,GAAI,KAAK,MAAM,WACb,MAAM,KAAK,eAAe,EAG1B,WAAM,KAAK,gBAAgB,OAMzB,SAAQ,EAAkB,CAC9B,GAAI,KAAK,MAAM,kBAAoB,cAAe,OAElD,GAAI,KAAK,MAAM,WACb,MAAM,KAAK,eAAe,EAE5B,MAAM,KAAK,WAAW,SAAS,OAG3B,QAAO,EAAkB,CAC7B,MAAM,KAAK,WAAW,QAAQ,OAG1B,UAAS,EAAkB,CAC/B,GAAI,KAAK,MAAM,iBACb,MAAM,KAAK,QAAQ,EAGnB,WAAM,KAAK,SAAS,EAMxB,UAAU,CAAC,EAAsC,CAE/C,GADA,KAAK,WAAW,WAAW,CAAO,EAC9B,IAAY,OACd,KAAK,OAAO,IAAI,cAAe,EAAI,EAGnC,UAAK,OAAO,IAAI,cAAe,EAAK,EAIxC,YAAY,CAAC,EAAiB,EAA+C,CAC3E,KAAK,WAAW,iBAAiB,EAAS,CAAI,EAC9C,KAAK,OAAO,IAAI,aAAc,KAAK,WAAW,cAAc,GAAK,CAAC,CAAC,EAGrE,aAAa,CAAC,EAAuB,CACnC,KAAK,WAAW,cAAc,CAAO,EACrC,KAAK,OAAO,IAAI,cAAe,KAAK,WAAW,eAAe,GAAK,CAAC,CAAC,EAG/D,cAAc,EAAS,CAC7B,IAAM,EAAa,KAAK,MAAM,WACxB,EAAU,EAAW,KAAK,CAAC,IAAM,EAAE,OAAS,SAAS,EAE3D,GAAI,EACF,KAAK,aAAa,EAAQ,GAAI,UAAU,EAEnC,KACL,IAAM,EAAQ,EAAW,KAAK,CAAC,IAAM,EAAE,OAAS,aAAe,EAAE,OAAS,UAAU,EACpF,GAAI,EACF,KAAK,aAAa,EAAM,GAAI,SAAS,GAO3C,EAAkC,CAAC,EAAU,EAAkC,CAC7E,KAAK,QAAQ,GAAG,EAAO,CAAO,EAGhC,GAAmC,CAAC,EAAU,EAAkC,CAC9E,KAAK,QAAQ,IAAI,EAAO,CAAO,EAGjC,IAAoC,CAAC,EAAU,EAAkC,CAC/E,KAAK,QAAQ,KAAK,EAAO,CAAO,EAKlC,SAAS,CAEP,EACA,EACY,CACZ,OAAO,KAAK,OAAO,UAAU,EAAe,CAAQ,EAExD,CAOA,IAAM,GAAgC,CACpC,SAAU,GACV,KAAM,GACN,MAAO,GACP,OAAQ,EACR,aAAc,EACd,YAAa,GACb,QAAS,WACT,SAAU,GACV,SAAU,GACV,gBAAiB,KACjB,QAAS,EACX,EAmBO,SAAS,EAAY,CAAC,EAAiC,EAAiC,CAC7F,OAAO,IAAI,EAAO,EAAW,CAAO,EC3lB/B,IAAM,GAAgC,CAC3C,OAAQ,CACN,QAAS,UACT,UAAW,UACX,WAAY,sBACZ,KAAM,UACN,cAAe,UACf,OAAQ,2BACR,MAAO,UACP,MAAO,UACP,QAAS,UACT,QAAS,SACX,EACA,WAAY,CACV,WAAY,sFACZ,SAAU,OACV,cAAe,OACf,cAAe,OACf,WAAY,MACZ,eAAgB,MAChB,WAAY,KACd,EACA,QAAS,CACP,GAAI,MACJ,GAAI,MACJ,GAAI,OACJ,GAAI,OACJ,GAAI,MACN,EACA,aAAc,CACZ,GAAI,MACJ,GAAI,MACJ,GAAI,OACJ,KAAM,QACR,EACA,QAAS,CACP,GAAI,+BACJ,GAAI,+BACJ,GAAI,gCACN,EACA,YAAa,CACX,KAAM,QACN,OAAQ,QACR,KAAM,QACN,OAAQ,8BACV,EACA,OAAQ,CACN,YAAa,MACb,UAAW,OACX,QAAS,2BACT,YAAa,4BACb,WAAY,2BACZ,QAAS,UACT,YAAa,MACf,EACA,SAAU,CACR,OAAQ,OACR,WAAY,mDACZ,QAAS,SACT,WAAY,OACZ,SAAU,OACV,UAAW,qBACX,YAAa,SACf,EACA,KAAM,CACJ,WAAY,yBACZ,aAAc,MACd,OAAQ,gCACR,WAAY,OACZ,YAAa,2BACb,aAAc,wBAChB,EACA,SAAU,CACR,WAAY,UACZ,SAAU,QACV,UAAW,UACX,WAAY,sBACZ,WAAY,+BACZ,aAAc,MACd,QAAS,SACX,EACA,WAAY,CACV,MAAO,QACP,OAAQ,OACR,aAAc,MACd,OAAQ,qCACR,OAAQ,+BACV,EACA,KAAM,CACJ,eAAgB,UAChB,cAAe,MACf,QAAS,UACT,UAAW,SACb,CACF,EAMa,GAAmB,CAC9B,OAAQ,CACN,QAAS,UACT,WAAY,qBACZ,KAAM,SACR,CACF,EAEa,GAAoB,CAC/B,OAAQ,CACN,QAAS,UACT,WAAY,4BACZ,KAAM,UACN,cAAe,UACf,OAAQ,oBACV,EACA,SAAU,CACR,WAAY,yDACZ,UAAW,oBACb,EACA,KAAM,CACJ,WAAY,4BACZ,OAAQ,iCACR,YAAa,sBACb,aAAc,wBAChB,EACA,SAAU,CACR,WAAY,2BACZ,UAAW,SACb,CACF,EAEa,GAAsB,CACjC,OAAQ,CACN,QAAS,UACT,WAAY,aACd,EACA,SAAU,CACR,OAAQ,OACR,WAAY,aACd,EACA,OAAQ,CACN,YAAa,MACb,UAAW,MACb,CACF,EAEa,GAAsB,CACjC,OAAQ,CACN,QAAS,UACT,WAAY,qBACd,EACA,OAAQ,CACN,YAAa,SACf,EACA,KAAM,CACJ,eAAgB,UAChB,QAAS,SACX,CACF,EAEa,GAAsB,CACjC,OAAQ,CACN,QAAS,UACT,WAAY,qBACd,EACA,OAAQ,CACN,YAAa,MACb,UAAW,OACX,YAAa,SACf,EACA,SAAU,CACR,OAAQ,MACV,EACA,KAAM,CACJ,eAAgB,UAChB,QAAS,SACX,CACF,EAEa,GAAoB,CAC/B,OAAQ,CACN,QAAS,UACT,WAAY,qBACd,EACA,OAAQ,CACN,YAAa,SACf,CACF,EAEa,EAAsC,CACjD,KAAM,GACN,MAAO,GACP,QAAS,GACT,QAAS,GACT,QAAS,GACT,MAAO,EACT,EAWO,SAAS,EAAW,CAAC,EAAa,EAAwB,CAC/D,IAAM,EAAgB,IAAK,CAAK,EAEhC,QAAW,KAAO,OAAO,KAAK,CAAQ,EAAsB,CAC1D,IAAM,EAAY,EAAK,GACjB,EAAgB,EAAS,GAE/B,GAAI,GAAiB,OAAO,IAAkB,SAC5C,EAAO,GAAO,IACR,GAAa,CAAC,KACf,CACL,EAEG,QAAI,IAAkB,OACzB,EAAO,GAAO,EAIlB,OAAO,EAMF,SAAS,EAAgB,CAAC,EAAc,EAAS,OAAgB,CACtE,IAAM,EAAS,GAAY,GAAc,CAAK,EACxC,EAAiB,CAAC,EAGxB,GAAI,EAAO,OACT,EAAK,KAAK,KAAK,oBAAyB,EAAO,OAAO,UAAU,EAChE,EAAK,KAAK,KAAK,sBAA2B,EAAO,OAAO,YAAY,EACpE,EAAK,KAAK,KAAK,uBAA4B,EAAO,OAAO,aAAa,EACtE,EAAK,KAAK,KAAK,iBAAsB,EAAO,OAAO,OAAO,EAC1D,EAAK,KAAK,KAAK,4BAAiC,EAAO,OAAO,gBAAgB,EAC9E,EAAK,KAAK,KAAK,mBAAwB,EAAO,OAAO,SAAS,EAC9D,EAAK,KAAK,KAAK,kBAAuB,EAAO,OAAO,QAAQ,EAC5D,EAAK,KAAK,KAAK,kBAAuB,EAAO,OAAO,QAAQ,EAC5D,EAAK,KAAK,KAAK,oBAAyB,EAAO,OAAO,UAAU,EAChE,EAAK,KAAK,KAAK,oBAAyB,EAAO,OAAO,UAAU,EAIlE,GAAI,EAAO,WACT,EAAK,KAAK,KAAK,kBAAuB,EAAO,WAAW,aAAa,EACrE,EAAK,KAAK,KAAK,gBAAqB,EAAO,WAAW,WAAW,EACjE,EAAK,KAAK,KAAK,mBAAwB,EAAO,WAAW,gBAAgB,EACzE,EAAK,KAAK,KAAK,mBAAwB,EAAO,WAAW,gBAAgB,EACzE,EAAK,KAAK,KAAK,kBAAuB,EAAO,WAAW,aAAa,EACrE,EAAK,KAAK,KAAK,uBAA4B,EAAO,WAAW,iBAAiB,EAC9E,EAAK,KAAK,KAAK,kBAAuB,EAAO,WAAW,aAAa,EAIvE,GAAI,EAAO,QACT,EAAK,KAAK,KAAK,iBAAsB,EAAO,QAAQ,KAAK,EACzD,EAAK,KAAK,KAAK,iBAAsB,EAAO,QAAQ,KAAK,EACzD,EAAK,KAAK,KAAK,iBAAsB,EAAO,QAAQ,KAAK,EACzD,EAAK,KAAK,KAAK,iBAAsB,EAAO,QAAQ,KAAK,EACzD,EAAK,KAAK,KAAK,iBAAsB,EAAO,QAAQ,KAAK,EAI3D,GAAI,EAAO,aACT,EAAK,KAAK,KAAK,gBAAqB,EAAO,aAAa,KAAK,EAC7D,EAAK,KAAK,KAAK,gBAAqB,EAAO,aAAa,KAAK,EAC7D,EAAK,KAAK,KAAK,gBAAqB,EAAO,aAAa,KAAK,EAC7D,EAAK,KAAK,KAAK,kBAAuB,EAAO,aAAa,OAAO,EAInE,GAAI,EAAO,QACT,EAAK,KAAK,KAAK,gBAAqB,EAAO,QAAQ,KAAK,EACxD,EAAK,KAAK,KAAK,gBAAqB,EAAO,QAAQ,KAAK,EACxD,EAAK,KAAK,KAAK,gBAAqB,EAAO,QAAQ,KAAK,EAI1D,GAAI,EAAO,YACT,EAAK,KAAK,KAAK,sBAA2B,EAAO,YAAY,OAAO,EACpE,EAAK,KAAK,KAAK,wBAA6B,EAAO,YAAY,SAAS,EACxE,EAAK,KAAK,KAAK,sBAA2B,EAAO,YAAY,OAAO,EACpE,EAAK,KAAK,KAAK,wBAA6B,EAAO,YAAY,SAAS,EAI1E,GAAI,EAAO,OACT,EAAK,KAAK,KAAK,0BAA+B,EAAO,OAAO,cAAc,EAC1E,EAAK,KAAK,KAAK,wBAA6B,EAAO,OAAO,YAAY,EACtE,EAAK,KAAK,KAAK,sBAA2B,EAAO,OAAO,UAAU,EAClE,EAAK,KAAK,KAAK,2BAAgC,EAAO,OAAO,cAAc,EAC3E,EAAK,KAAK,KAAK,yBAA8B,EAAO,OAAO,aAAa,EACxE,EAAK,KAAK,KAAK,sBAA2B,EAAO,OAAO,UAAU,EAClE,EAAK,KAAK,KAAK,0BAA+B,EAAO,OAAO,cAAc,EAI5E,GAAI,EAAO,SACT,EAAK,KAAK,KAAK,sBAA2B,EAAO,SAAS,SAAS,EACnE,EAAK,KAAK,KAAK,kBAAuB,EAAO,SAAS,aAAa,EACnE,EAAK,KAAK,KAAK,uBAA4B,EAAO,SAAS,UAAU,EACrE,EAAK,KAAK,KAAK,2BAAgC,EAAO,SAAS,aAAa,EAC5E,EAAK,KAAK,KAAK,yBAA8B,EAAO,SAAS,WAAW,EACxE,EAAK,KAAK,KAAK,0BAA+B,EAAO,SAAS,YAAY,EAC1E,EAAK,KAAK,KAAK,4BAAiC,EAAO,SAAS,cAAc,EAIhF,GAAI,EAAO,KACT,EAAK,KAAK,KAAK,cAAmB,EAAO,KAAK,aAAa,EAC3D,EAAK,KAAK,KAAK,kBAAuB,EAAO,KAAK,eAAe,EACjE,EAAK,KAAK,KAAK,kBAAuB,EAAO,KAAK,SAAS,EAC3D,EAAK,KAAK,KAAK,uBAA4B,EAAO,KAAK,aAAa,EACpE,EAAK,KAAK,KAAK,yBAA8B,EAAO,KAAK,cAAc,EACvE,EAAK,KAAK,KAAK,0BAA+B,EAAO,KAAK,eAAe,EAI3E,GAAI,EAAO,SACT,EAAK,KAAK,KAAK,2BAAgC,EAAO,SAAS,aAAa,EAC5E,EAAK,KAAK,KAAK,yBAA8B,EAAO,SAAS,WAAW,EACxE,EAAK,KAAK,KAAK,0BAA+B,EAAO,SAAS,YAAY,EAC1E,EAAK,KAAK,KAAK,kBAAuB,EAAO,SAAS,aAAa,EACnE,EAAK,KAAK,KAAK,2BAAgC,EAAO,SAAS,aAAa,EAC5E,EAAK,KAAK,KAAK,sBAA2B,EAAO,SAAS,eAAe,EACzE,EAAK,KAAK,KAAK,uBAA4B,EAAO,SAAS,UAAU,EAIvE,GAAI,EAAO,WACT,EAAK,KAAK,KAAK,sBAA2B,EAAO,WAAW,QAAQ,EACpE,EAAK,KAAK,KAAK,uBAA4B,EAAO,WAAW,SAAS,EACtE,EAAK,KAAK,KAAK,uBAA4B,EAAO,WAAW,eAAe,EAC5E,EAAK,KAAK,KAAK,uBAA4B,EAAO,WAAW,SAAS,EACtE,EAAK,KAAK,KAAK,uBAA4B,EAAO,WAAW,SAAS,EAIxE,GAAI,EAAO,KACT,EAAK,KAAK,KAAK,2BAAgC,EAAO,KAAK,iBAAiB,EAC5E,EAAK,KAAK,KAAK,0BAA+B,EAAO,KAAK,gBAAgB,EAC1E,EAAK,KAAK,KAAK,oBAAyB,EAAO,KAAK,UAAU,EAC9D,EAAK,KAAK,KAAK,sBAA2B,EAAO,KAAK,YAAY,EAGpE,OAAO,EAAK,KAAK;AAAA,GAAM,EAoBlB,SAAS,EAAU,CAAC,EAAsB,EAAsC,CACrF,IAAM,EAAW,OAAO,IAAU,SAAW,EAAa,GAAS,EAGnE,EAAQ,iBAAiB,wBAAwB,EAAE,QAAQ,KAAM,EAAG,OAAO,CAAC,EAG5E,IAAM,EAAM,GAAiB,CAAQ,EACrC,EAAQ,MAAM,SAAW,EAAI,MAAM;AAAA,CAAI,EAAE,IAAI,KAAQ,EAAK,KAAK,EAAE,QAAQ,IAAK,EAAE,CAAC,EAAE,KAAK,IAAI,EC3evF,IAAM,GAAqC,CAEhD,KAAM,OACN,MAAO,QACP,OAAQ,SAER,KAAM,OACN,OAAQ,SACR,OAAQ,SAER,aAAc,kCACd,YAAa,iCACb,OAAQ,iBACR,SAAU,WACV,YAAa,eACb,OAAQ,SACR,SAAU,WAEV,gBAAiB,mBACjB,eAAgB,kBAEhB,SAAU,2BACV,QAAS,0BAET,eAAgB,kBAChB,gBAAiB,mBACjB,SAAU,WACV,YAAa,MAEb,SAAU,WACV,QAAS,UACT,KAAM,OACN,MAAO,QACP,OAAQ,SACR,KAAM,OACN,YAAa,eACb,aAAc,gBAEd,WAAY,cACZ,WAAY,cAEZ,KAAM,OACN,SAAU,eACV,WAAY,oBACZ,WAAY,oBAEZ,aAAc,2BACd,WAAY,gCACZ,aAAc,4BAEd,QAAS,UACT,UAAW,YAEX,SAAU,WAEV,QAAS,UACT,WAAY,OACZ,QAAS,sBAET,SAAU,WACV,MAAO,QACP,MAAO,QACP,KAAM,OACN,KAAM,MACR,EAEa,GAAqC,CAChD,KAAM,aACN,MAAO,SACP,OAAQ,UACR,KAAM,YACN,OAAQ,iBACR,OAAQ,UACR,aAAc,gCACd,YAAa,6BACb,OAAQ,cACR,SAAU,WACV,YAAa,gBACb,OAAQ,cACR,SAAU,aACV,gBAAiB,oBACjB,eAAgB,6BAChB,SAAU,mBACV,QAAS,4BACT,eAAgB,qBAChB,gBAAiB,wBACjB,SAAU,aACV,YAAa,eACb,SAAU,UACV,QAAS,UACT,KAAM,aACN,MAAO,YACP,OAAQ,SACR,KAAM,QACN,YAAa,iBACb,aAAc,oBACd,WAAY,mBACZ,WAAY,iBACZ,KAAM,UACN,SAAU,UACV,WAAY,gBACZ,WAAY,wBACZ,aAAc,eACd,WAAY,6BACZ,aAAc,oBACd,QAAS,WACT,UAAW,cACX,SAAU,YACV,QAAS,UACT,WAAY,SACZ,QAAS,sBACT,SAAU,YACV,MAAO,YACP,MAAO,SACP,KAAM,OACN,KAAM,QACR,EAEa,GAAoC,CAC/C,KAAM,UACN,MAAO,QACP,OAAQ,SACR,KAAM,OACN,OAAQ,aACR,OAAQ,SACR,aAAc,gCACd,YAAa,gCACb,OAAQ,iBACR,SAAU,QACV,YAAa,eACb,OAAQ,OACR,SAAU,wBACV,gBAAiB,cACjB,eAAgB,yBAChB,SAAU,qBACV,QAAS,+BACT,eAAgB,0BAChB,gBAAiB,6BACjB,SAAU,cACV,YAAa,aACb,SAAU,aACV,QAAS,UACT,KAAM,OACN,MAAO,UACP,OAAQ,SACR,KAAM,SACN,YAAa,iBACb,aAAc,oBACd,WAAY,sBACZ,WAAY,cACZ,KAAM,YACN,SAAU,YACV,WAAY,kBACZ,WAAY,uBACZ,aAAc,gBACd,WAAY,kCACZ,aAAc,kBACd,QAAS,aACT,UAAW,yBACX,SAAU,YACV,QAAS,UACT,WAAY,SACZ,QAAS,yBACT,SAAU,cACV,MAAO,WACP,MAAO,SACP,KAAM,OACN,KAAM,QACR,EAEa,GAAoC,CAC/C,KAAM,YACN,MAAO,QACP,OAAQ,cACR,KAAM,QACN,OAAQ,SACR,OAAQ,aACR,aAAc,4BACd,YAAa,yBACb,OAAQ,qBACR,SAAU,QACV,YAAa,gBACb,OAAQ,aACR,SAAU,YACV,gBAAiB,WACjB,eAAgB,mBAChB,SAAU,eACV,QAAS,uBACT,eAAgB,wBAChB,gBAAiB,0BACjB,SAAU,aACV,YAAa,MACb,SAAU,gBACV,QAAS,WACT,KAAM,OACN,MAAO,kBACP,OAAQ,SACR,KAAM,WACN,YAAa,qBACb,aAAc,uBACd,WAAY,oBACZ,WAAY,YACZ,KAAM,OACN,SAAU,gBACV,WAAY,iBACZ,WAAY,yBACZ,aAAc,iBACd,WAAY,qCACZ,aAAc,qBACd,QAAS,eACT,UAAW,UACX,SAAU,UACV,QAAS,UACT,WAAY,WACZ,QAAS,sBACT,SAAU,gBACV,MAAO,SACP,MAAO,YACP,KAAM,OACN,KAAM,QACR,EAEa,GAAsC,CACjD,KAAM,KACN,MAAO,OACP,OAAQ,OACR,KAAM,OACN,OAAQ,SACR,OAAQ,KACR,aAAc,eACd,YAAa,eACb,OAAQ,aACR,SAAU,KACV,YAAa,QACb,OAAQ,OACR,SAAU,SACV,gBAAiB,UACjB,eAAgB,YAChB,SAAU,eACV,QAAS,iBACT,eAAgB,SAChB,gBAAiB,SACjB,SAAU,KACV,YAAa,KACb,SAAU,KACV,QAAS,KACT,KAAM,KACN,MAAO,OACP,OAAQ,KACR,KAAM,MACN,YAAa,QACb,aAAc,QACd,WAAY,YACZ,WAAY,YACZ,KAAM,MACN,SAAU,OACV,WAAY,SACZ,WAAY,eACZ,aAAc,YACd,WAAY,eACZ,aAAc,SACd,QAAS,QACT,UAAW,UACX,SAAU,QACV,QAAS,UACT,WAAY,OACZ,QAAS,iBACT,SAAU,SACV,MAAO,KACP,MAAO,MACP,KAAM,OACN,KAAM,IACR,EAEa,GAAqC,CAChD,KAAM,KACN,MAAO,KACP,OAAQ,KACR,KAAM,KACN,OAAQ,OACR,OAAQ,KACR,aAAc,eACd,YAAa,eACb,OAAQ,YACR,SAAU,KACV,YAAa,OACb,OAAQ,MACR,SAAU,MACV,gBAAiB,KACjB,eAAgB,OAChB,SAAU,MACV,QAAS,QACT,eAAgB,OAChB,gBAAiB,OACjB,SAAU,KACV,YAAa,KACb,SAAU,KACV,QAAS,KACT,KAAM,KACN,MAAO,OACP,OAAQ,KACR,KAAM,KACN,YAAa,QACb,aAAc,QACd,WAAY,OACZ,WAAY,KACZ,KAAM,KACN,SAAU,MACV,WAAY,QACZ,WAAY,eACZ,aAAc,OACd,WAAY,SACZ,aAAc,OACd,QAAS,MACT,UAAW,MACX,SAAU,KACV,QAAS,UACT,WAAY,KACZ,QAAS,gBACT,SAAU,KACV,MAAO,KACP,MAAO,KACP,KAAM,KACN,KAAM,IACR,EAEa,GAA0D,CACrE,GAAI,GACJ,GAAI,GACJ,GAAI,GACJ,GAAI,GACJ,GAAI,GACJ,GAAI,EACN,EAMO,MAAM,CAAK,CACR,SACA,iBACA,aACA,qBAER,WAAW,CAAC,EAAqB,CAAC,EAAG,CAMnC,GALA,KAAK,SAAW,EAAO,UAAY,KAAK,eAAe,GAAK,KAC5D,KAAK,iBAAmB,EAAO,kBAAoB,KACnD,KAAK,aAAe,IAAK,EAAoB,EAGzC,EAAO,cACT,QAAY,EAAM,KAAY,OAAO,QAAQ,EAAO,YAAY,EAC9D,GAAI,EACF,KAAK,aAAa,GAAQ,IAAK,KAAK,aAAa,MAAU,CAAQ,EAKzE,KAAK,qBAAuB,EAAO,sBAAwB,aAMrD,cAAc,EAAkB,CACtC,GAAI,OAAO,UAAc,IAAa,OAAO,KAE7C,IAAM,EAAO,UAAU,UAAa,UAAkB,aACtD,GAAI,CAAC,EAAM,OAAO,KAGlB,GAAI,KAAK,aAAa,GACpB,OAAO,EAIT,IAAM,EAAW,EAAK,MAAM,GAAG,EAAE,GACjC,GAAI,KAAK,aAAa,GACpB,OAAO,EAGT,OAAO,KAMT,WAAW,EAAW,CACpB,OAAO,KAAK,SAMd,WAAW,CAAC,EAAwB,CAClC,KAAK,SAAW,EAMlB,CAAC,CAAC,EAAqB,EAAkD,CAEvE,IAAI,EAAM,KAAK,aAAa,KAAK,YAAY,GAG7C,GAAI,CAAC,EACH,EAAM,KAAK,aAAa,KAAK,oBAAoB,GAInD,GAAI,CAAC,EACH,OAAO,EAIT,GAAI,EACF,EAAM,EAAI,QAAQ,KAAK,qBAAsB,CAAC,EAAO,IAAa,CAChE,IAAM,EAAQ,EAAO,GACrB,OAAO,IAAU,OAAY,OAAO,CAAK,EAAI,EAC9C,EAGH,OAAO,EAMT,eAAe,CAAC,EAAkB,EAA4C,CAC5E,KAAK,aAAa,GAAY,IACzB,KAAK,aAAa,MAClB,CACL,EAMF,eAAe,CAAC,EAA2D,CACzE,OAAO,KAAK,aAAa,GAM3B,qBAAqB,EAAa,CAChC,OAAO,OAAO,KAAK,KAAK,YAAY,EAMtC,WAAW,CAAC,EAA2B,CACrC,OAAO,KAAY,KAAK,aAE5B,CAMA,IAAI,EAA0B,KAKvB,SAAS,EAAU,CAAC,EAA2B,CACpD,OAAO,IAAI,EAAK,CAAM,EAMjB,SAAS,EAAO,CAAC,EAA2B,CACjD,GAAI,CAAC,EACH,EAAa,GAAW,CAAM,EAEhC,OAAO,EAaF,SAAS,EAAC,CAAC,EAAqB,EAAkD,CACvF,OAAO,GAAQ,EAAE,EAAE,EAAK,CAAM,ECviBzB,IAAM,GAA8B,CACzC,KAAM,UACN,YAAa,iDACb,OAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAuDR,SAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GA+BZ,EAMa,GAA8B,CACzC,KAAM,UACN,YAAa,qDACb,OAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA8BR,SAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAWZ,EAMa,GAA6B,CACxC,KAAM,SACN,YAAa,wDACb,OAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA8DR,SAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAyBV,KAAM,CAAC,IAAc,CACnB,IAAM,EAAa,EAAU,cAAc,mBAAmB,EAC9D,GAAI,EACF,EAAW,iBAAiB,QAAS,IAAM,CACzC,EAAU,cAAc,IAAI,YAAY,iBAAiB,CAAC,EAC3D,EAGP,EAMa,GAA8B,CACzC,KAAM,UACN,YAAa,iDACb,OAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA0DR,SAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GA8BZ,EAMa,GAA4B,CACvC,KAAM,QACN,YAAa,8BACb,OAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA0DR,SAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAyBZ,EAMa,GAA4B,CACvC,KAAM,QACN,YAAa,yCACb,OAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgER,SAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAoBZ,EAMa,EAA4C,CACvD,QAAS,GACT,QAAS,GACT,OAAQ,GACR,QAAS,GACT,MAAO,GACP,MAAO,EACT,EAMO,MAAM,EAAc,CACjB,UACA,cAAqC,KACrC,aAAwC,KAEhD,WAAW,CAAC,EAAwB,CAClC,KAAK,UAAY,EAMnB,KAAK,CAAC,EAAyC,CAE7C,IAAM,EAAS,OAAO,IAAW,SAAW,EAAQ,GAAU,EAiB9D,GAdA,KAAK,QAAQ,EAGb,KAAK,UAAU,UAAU,IAAI,eAAe,EAAO,MAAM,EAGzD,KAAK,aAAe,SAAS,cAAc,OAAO,EAClD,KAAK,aAAa,YAAc,EAAO,OACvC,SAAS,KAAK,YAAY,KAAK,YAAY,EAG3C,KAAK,UAAU,UAAY,EAAO,SAG9B,EAAO,KACT,EAAO,KAAK,KAAK,SAAS,EAG5B,KAAK,cAAgB,EAMvB,SAAS,EAAwB,CAC/B,OAAO,KAAK,cAMd,OAAO,EAAS,CACd,GAAI,KAAK,cAAe,CAKtB,GAHA,KAAK,UAAU,UAAU,OAAO,eAAe,KAAK,cAAc,MAAM,EAGpE,KAAK,cAAc,QACrB,KAAK,cAAc,QAAQ,KAAK,SAAS,EAI3C,GAAI,KAAK,aACP,KAAK,aAAa,OAAO,EACzB,KAAK,aAAe,KAGtB,KAAK,cAAgB,MAOzB,OAAO,EAAS,CACd,KAAK,QAAQ,EAEjB,CAMO,SAAS,EAAmB,CAAC,EAAuC,CACzE,OAAO,IAAI,GAAc,CAAS,ECtgBpC,IAAM,GAAqB,CAAC,GAAI,GAAI,GAAI,GAAI,GAAI,GAAG,EAO5C,MAAM,CAAe,CAClB,OAAwB,KACxB,OACA,MACA,eAAwD,KACxD,WAA2B,CAAC,EAC5B,WAAmD,KACnD,UACA,UACA,cAAmC,CAAC,EAE5C,WAAW,CAAC,EAA8B,CACxC,KAAK,OAAS,CACZ,WAAY,GACZ,kBApB6B,GAqB7B,oBAAqB,GACrB,aAAc,GACd,eAAgB,GAChB,YAAa,GACb,gBAAiB,MACd,CACL,EAEA,KAAK,UAAY,EAAO,WAAa,KAAK,WAAW,EACrD,KAAK,UAAY,EAAO,WAAa,KAAK,qBAAqB,EAE/D,KAAK,MAAQ,KAAK,mBAAmB,EAM/B,kBAAkB,EAAwB,CAChD,MAAO,CACL,WAAY,GACZ,aAAc,GACd,UAAW,EACX,WAAY,EACZ,UAAW,EACX,YAAa,EACb,WAAY,EACZ,mBAAoB,EACpB,kBAAmB,CAAC,EACpB,iBAAkB,CAAC,EACnB,kBAAmB,KACnB,cAAe,EACf,YAAa,GACb,gBAAiB,IACnB,EAMF,MAAM,CAAC,EAAsB,CAC3B,GAAI,KAAK,OAAO,SAAU,OAE1B,KAAK,OAAS,EACd,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EAMtB,MAAM,EAAS,CACb,KAAK,cAAc,EACnB,KAAK,cAAc,QAAQ,KAAS,EAAM,CAAC,EAC3C,KAAK,cAAgB,CAAC,EACtB,KAAK,MAAM,EACX,KAAK,OAAS,KAMR,oBAAoB,EAAS,CACnC,GAAI,CAAC,KAAK,OAAQ,OAuBlB,GApBA,KAAK,GAAG,OAAQ,IAAM,CACpB,GAAI,CAAC,KAAK,MAAM,WACd,KAAK,MAAM,WAAa,GACxB,KAAK,MAAM,kBAAoB,KAAK,IAAI,EACxC,KAAK,WAAW,aAAa,EAG7B,UAAK,WAAW,cAAc,EAEhC,KAAK,WAAW,YAAY,EAC7B,EAGD,KAAK,GAAG,QAAS,IAAM,CACrB,KAAK,MAAM,aACX,KAAK,gBAAgB,EACrB,KAAK,WAAW,aAAa,EAC9B,EAGG,KAAK,OAAO,aACd,KAAK,GAAG,UAAW,IAAM,CACvB,KAAK,MAAM,YACZ,EAED,KAAK,GAAG,SAAU,IAAM,CACtB,IAAM,EAAc,KAAK,QAAQ,MACjC,KAAK,WAAW,aAAc,CAC5B,OAAQ,GAAa,aAAe,CACtC,CAAC,EACF,EAIH,GAAI,KAAK,OAAO,eACd,KAAK,GAAG,UAAW,IAAM,CACvB,GAAI,CAAC,KAAK,MAAM,YACd,KAAK,MAAM,YAAc,GACzB,KAAK,MAAM,gBAAkB,KAAK,IAAI,EACtC,KAAK,MAAM,cACX,KAAK,WAAW,oBAAoB,EAEvC,EAED,KAAK,GAAG,UAAW,IAAM,CACvB,GAAI,KAAK,MAAM,YAAa,CAE1B,GADA,KAAK,MAAM,YAAc,GACrB,KAAK,MAAM,gBACb,KAAK,MAAM,aAAe,KAAK,IAAI,EAAI,KAAK,MAAM,iBAAmB,KAEvE,KAAK,WAAW,mBAAoB,CAClC,eAAgB,KAAK,MAAM,iBACtB,KAAK,IAAI,EAAI,KAAK,MAAM,iBAAmB,KAC5C,CACN,CAAC,EACD,KAAK,MAAM,gBAAkB,MAEhC,EASH,GALA,KAAK,GAAG,aAAc,IAAM,CAC1B,KAAK,gBAAgB,EACtB,EAGG,KAAK,OAAO,oBACd,KAAK,GAAG,gBAAiB,CAAC,IAAiC,CACzD,KAAK,MAAM,qBACX,KAAK,WAAW,uBAAwB,CACtC,QAAS,EAAU,GAAG,EAAQ,UAAY,OAC1C,QAAS,GAAS,OACpB,CAAC,EACF,EAsEH,GAlEA,KAAK,GAAG,eAAgB,IAAM,CAC5B,IAAM,EAAc,KAAK,QAAQ,MACjC,GAAI,GAAa,MACf,KAAK,WAAW,YAAY,EAG5B,UAAK,WAAW,sBAAuB,CACrC,OAAQ,GAAa,MACvB,CAAC,EAEJ,EAGD,KAAK,GAAG,mBAAoB,CAAC,IAA0B,CACrD,KAAK,WAAW,EAAe,yBAA2B,uBAAuB,EAClF,EAGD,KAAK,GAAG,YAAa,CAAC,IAAmB,CACvC,KAAK,WAAW,EAAQ,kBAAoB,gBAAgB,EAC7D,EAGD,KAAK,GAAG,kBAAmB,CAAC,IAA4B,CACtD,GAAI,EACF,KAAK,WAAW,uBAAwB,CACtC,SAAU,EAAM,SAChB,MAAO,EAAM,KACf,CAAC,EAGD,UAAK,WAAW,uBAAuB,EAE1C,EAGD,KAAK,GAAG,mBAAoB,CAAC,IAA6B,CACxD,GAAI,EACF,KAAK,WAAW,2BAA4B,CAC1C,SAAU,EAAM,SAChB,MAAO,EAAM,KACf,CAAC,EAEJ,EAGD,KAAK,GAAG,aAAc,CAAC,IAAiB,CACtC,KAAK,WAAW,qBAAsB,CAAE,MAAO,CAAK,CAAC,EACtD,EAGD,KAAK,GAAG,QAAS,IAAM,CACrB,GAAI,CAAC,KAAK,MAAM,aACd,KAAK,MAAM,aAAe,GAC1B,KAAK,gBAAgB,EACrB,KAAK,WAAW,iBAAkB,CAChC,UAAW,KAAK,MAAM,UACtB,WAAY,KAAK,MAAM,WACvB,UAAW,KAAK,MAAM,UACtB,YAAa,KAAK,MAAM,YACxB,WAAY,KAAK,MAAM,UACzB,CAAC,EAEJ,EAGG,KAAK,OAAO,YACd,KAAK,GAAG,QAAS,CAAC,IAAe,CAC/B,KAAK,WAAW,cAAe,CAC7B,KAAM,GAAO,KACb,QAAS,GAAO,OAClB,CAAC,EACF,EAOG,EAAE,CAAC,EAAe,EAAyC,CACjE,GAAI,CAAC,KAAK,OAAQ,OAClB,KAAK,OAAO,GAAG,EAAc,CAAO,EACpC,KAAK,cAAc,KAAK,IAAM,KAAK,QAAQ,IAAI,EAAc,CAAO,CAAC,EAM/D,eAAe,EAAS,CAC9B,IAAM,EAAc,KAAK,QAAQ,MACjC,GAAI,CAAC,GAAe,CAAC,EAAY,SAAU,OAE3C,IAAM,EAAY,EAAY,YAAc,EAAY,SAAY,IAGpE,QAAW,KAAa,KAAK,OAAO,YAAc,CAAC,EACjD,GAAI,GAAY,GAAa,CAAC,KAAK,MAAM,kBAAkB,SAAS,CAAS,EAC3E,KAAK,MAAM,kBAAkB,KAAK,CAAS,EAC3C,KAAK,WAAW,kBAAmB,CAAE,WAAU,CAAC,EAKpD,IAAM,EAAY,CAAC,GAAI,GAAI,GAAI,GAAG,EAClC,QAAW,KAAY,EACrB,GAAI,GAAY,GAAY,CAAC,KAAK,MAAM,iBAAiB,SAAS,CAAQ,EACxE,KAAK,MAAM,iBAAiB,KAAK,CAAQ,EACzC,KAAK,WAAW,iBAAkB,CAAE,UAAS,CAAC,EAQ5C,eAAe,EAAS,CAC9B,GAAI,KAAK,MAAM,kBACb,KAAK,MAAM,YAAc,KAAK,IAAI,EAAI,KAAK,MAAM,mBAAqB,KACtE,KAAK,MAAM,kBAAoB,KAAK,IAAI,EAOpC,cAAc,EAAS,CAC7B,GAAI,KAAK,eAAgB,OAEzB,IAAM,GAAY,KAAK,OAAO,mBAnSC,IAmSkD,KAEjF,KAAK,eAAiB,YAAY,IAAM,CACtC,IAAM,EAAc,KAAK,QAAQ,MACjC,GAAI,GAAa,gBAAkB,UACjC,KAAK,gBAAgB,EACrB,KAAK,WAAW,kBAAmB,CACjC,UAAW,KAAK,MAAM,UACtB,YAAa,EAAY,WAC3B,CAAC,GAEF,CAAQ,EAML,aAAa,EAAS,CAC5B,GAAI,KAAK,eACP,cAAc,KAAK,cAAc,EACjC,KAAK,eAAiB,KAO1B,UAAU,CAAC,EAAsB,EAA4C,CAC3E,GAAI,KAAK,OAAO,SAAU,OAE1B,IAAM,EAAc,KAAK,QAAQ,MAE3B,EAAoB,CACxB,OACA,UAAW,IAAI,KACf,YAAa,GAAa,aAAe,EACzC,SAAU,GAAa,UAAY,KAAK,OAAO,UAAY,EAC3D,YACF,EAEA,KAAK,WAAW,KAAK,CAAK,EAC1B,KAAK,cAAc,EAMb,aAAa,EAAS,CAC5B,GAAI,KAAK,WAAY,OAErB,KAAK,WAAa,WAAW,IAAM,CACjC,KAAK,MAAM,EACX,KAAK,WAAa,MACjB,IAAI,OAMH,MAAK,EAAkB,CAC3B,GAAI,KAAK,WAAW,SAAW,EAAG,OAElC,IAAM,EAAS,CAAC,GAAG,KAAK,UAAU,EAClC,KAAK,WAAa,CAAC,EAEnB,GAAI,CACF,MAAM,KAAK,WAAW,CAAM,EAE9B,MAAO,EAAO,CAEZ,QAAQ,MAAM,mCAAoC,CAAK,EACvD,KAAK,WAAW,QAAQ,GAAG,CAAM,QAOvB,WAAU,CAAC,EAAqC,CAC5D,IAAM,EAAU,EAAO,IAAI,KAAS,KAAK,kBAAkB,CAAK,CAAC,EAGjE,QAAW,KAAK,EACd,MAAM,MAAM,KAAK,OAAO,YAAa,CACnC,OAAQ,OACR,QAAS,CACP,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAU,CAAC,EACtB,UAAW,EACb,CAAC,EAOG,iBAAiB,CAAC,EAA4C,CACpE,MAAO,CACL,EAAG,KAAK,OAAO,OACf,IAAK,KAAK,UACV,IAAK,KAAK,UACV,EAAG,QACH,EAAG,OAAO,OAAW,IAAc,OAAO,SAAS,KAAO,GAC1D,EAAG,CAED,UAAW,EAAM,KACjB,QAAS,KAAK,OAAO,QACrB,WAAY,KAAK,OAAO,WACxB,YAAa,KAAK,OAAO,YACzB,SAAU,KAAK,OAAO,SACtB,UAAW,KAAK,OAAO,UACvB,SAAU,KAAK,OAAO,SACtB,QAAS,KAAK,OAAO,QACrB,OAAQ,KAAK,OAAO,OACpB,YAAa,EAAM,YACnB,SAAU,EAAM,SAChB,SAAU,EAAM,SAAW,EAAK,EAAM,YAAc,EAAM,SAAY,IAAM,EAC5E,UAAW,KAAK,MAAM,aACnB,KAAK,OAAO,oBACZ,EAAM,UACX,CACF,EAMM,UAAU,EAAW,CAC3B,MAAO,GAAG,KAAK,IAAI,KAAK,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,EAAG,CAAC,IAM3D,oBAAoB,EAAW,CACrC,GAAI,OAAO,aAAiB,IAC1B,OAAO,KAAK,WAAW,EAGzB,IAAM,EAAM,kBACR,EAAY,aAAa,QAAQ,CAAG,EAExC,GAAI,CAAC,EACH,EAAY,KAAK,WAAW,EAC5B,aAAa,QAAQ,EAAK,CAAS,EAGrC,OAAO,EAMT,QAAQ,EAAwB,CAC9B,MAAO,IAAK,KAAK,KAAM,EAMzB,UAAU,EAA4B,CACpC,MAAO,CACL,QAAS,KAAK,OAAO,QACrB,UAAW,KAAK,UAChB,WAAY,KAAK,MAAM,WACvB,aAAc,KAAK,MAAM,aACzB,UAAW,KAAK,MAAM,UACtB,WAAY,KAAK,MAAM,WACvB,UAAW,KAAK,MAAM,UACtB,YAAa,KAAK,MAAM,YACxB,WAAY,KAAK,MAAM,WACvB,mBAAoB,KAAK,MAAM,mBAC/B,kBAAmB,KAAK,MAAM,kBAC9B,eAAgB,KAAK,MAAM,iBAAiB,OAAS,EACjD,KAAK,IAAI,GAAG,KAAK,MAAM,gBAAgB,EACvC,CACN,EAMF,KAAK,EAAS,CACZ,KAAK,MAAM,EACX,KAAK,MAAQ,KAAK,mBAAmB,EACrC,KAAK,UAAY,KAAK,WAAW,EAMnC,OAAO,EAAS,CAEd,GADA,KAAK,OAAO,EACR,KAAK,WACP,aAAa,KAAK,UAAU,EAGlC,CAMO,SAAS,EAAoB,CAAC,EAA8C,CACjF,OAAO,IAAI,EAAe,CAAM,EAU3B,SAAS,CAAe,CAAC,EAAiK,CAC/L,MAAO,CACL,KAAM,YAEN,OAAO,CAAC,EAAgB,EAAuD,CAC7E,IAAM,EAAY,GAAqB,IAClC,KACA,CACL,CAAC,EAOD,OALA,EAAU,OAAO,CAAM,EAGrB,EAAe,UAAY,EAEtB,IAAM,CACX,EAAU,QAAQ,EAClB,OAAQ,EAAe,WAG7B,EC1fK,MAAM,CAAW,CACd,aACA,QACA,cAAgB,EAExB,WAAW,CAAC,EAAuD,CAAC,EAAG,CACrE,KAAK,aAAe,EAAQ,cAAgB,EAC5C,KAAK,QAAU,EAAQ,SAAW,SAM9B,aAAY,CAAC,EAA4B,CAE7C,OADA,KAAK,cAAgB,EACd,KAAK,cAAc,CAAG,EAM/B,YAAY,CAAC,EAAmB,CAE9B,IAAM,EADS,IAAI,UAAU,EACV,gBAAgB,EAAK,UAAU,EAClD,OAAO,KAAK,cAAc,CAAG,OAMjB,cAAa,CAAC,EAA4B,CACtD,GAAI,KAAK,eAAiB,KAAK,aAC7B,MAAU,MAAM,yBAAyB,EAG3C,IAAM,EAAa,IAAI,gBACjB,EAAY,WAAW,IAAM,EAAW,MAAM,EAAG,KAAK,OAAO,EAEnE,GAAI,CAEF,IAAM,EAAO,MADI,MAAM,MAAM,EAAK,CAAE,OAAQ,EAAW,MAAO,CAAC,GACnC,KAAK,EAE3B,EADS,IAAI,UAAU,EACV,gBAAgB,EAAM,UAAU,EAG7C,EAAU,EAAI,cAAc,SAAS,EAC3C,GAAI,EAAS,CACX,IAAM,EAAe,EAAQ,cAAc,cAAc,GAAG,aAAa,KAAK,EAC9E,GAAI,EAEF,OADA,KAAK,gBACE,KAAK,cAAc,CAAY,EAI1C,OAAO,KAAK,cAAc,CAAG,SAE/B,CACE,aAAa,CAAS,GAOlB,aAAa,CAAC,EAAqB,CACzC,IAAM,EAAY,CAAC,EAsInB,OArImB,EAAI,iBAAiB,IAAI,EAEjC,QAAQ,CAAC,IAAS,CAC3B,IAAM,EAAS,EAAK,cAAc,QAAQ,EAC1C,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAS,CACb,GAAI,EAAK,aAAa,IAAI,GAAK,KAAK,WAAW,EAC/C,KAAM,SACN,SAAU,EAAO,cAAc,UAAU,GAAG,aAAa,KAAK,EAC9D,MAAO,EAAO,cAAc,SAAS,GAAG,aAAa,KAAK,EAC1D,YAAa,EAAO,cAAc,aAAa,GAAG,aAAa,KAAK,EACpE,SAAU,EACV,WAAY,CAAC,EACb,eAAgB,CAAC,CACnB,EAGM,EAAS,EAAO,cAAc,QAAQ,EAC5C,GAAI,EAAQ,CAEV,IAAM,EAAc,EAAO,cAAc,UAAU,GAAG,aAAa,KAAK,EACxE,GAAI,EACF,EAAG,SAAW,KAAK,cAAc,CAAW,EAI9C,IAAM,EAAa,EAAO,aAAa,YAAY,EACnD,GAAI,EACF,EAAG,WAAa,KAAK,YAAY,EAAY,EAAG,QAAQ,EAI1D,EAAO,iBAAiB,WAAW,EAAE,QAAQ,CAAC,IAAO,CACnD,IAAM,EAAM,EAAG,aAAa,KAAK,EACjC,GAAI,EACF,EAAG,WAAW,KAAK,CACjB,MACA,KAAM,EAAG,aAAa,MAAM,GAAK,YACjC,MAAO,OAAO,SAAS,EAAG,aAAa,OAAO,GAAK,IAAK,EAAE,EAC1D,OAAQ,OAAO,SAAS,EAAG,aAAa,QAAQ,GAAK,IAAK,EAAE,EAC5D,QAAS,EAAG,aAAa,SAAS,EAC9B,OAAO,SAAS,EAAG,aAAa,SAAS,EAAI,EAAE,EAC/C,OACJ,SAAW,EAAG,aAAa,UAAU,GAAa,aACpD,CAAC,EAEJ,EAGD,IAAM,EAAe,EAAO,cAAc,4BAA4B,EACtE,GAAI,EACF,EAAG,gBAAkB,EAAa,aAAa,KAAK,EAItD,EAAO,iBAAiB,2BAA2B,EAAE,QAAQ,CAAC,IAAa,CACzE,IAAM,EAAQ,EAAS,aAAa,OAAO,EACrC,EAAM,EAAS,aAAa,KAAK,EACvC,GAAI,GAAS,EAAK,CAChB,IAAM,EAAiC,CAAE,QAAO,KAAI,EAGpD,GAAI,IAAU,WAAY,CACxB,IAAM,EAAS,EAAS,aAAa,QAAQ,EAC7C,GAAI,EACF,EAAc,OAAS,KAAK,YAAY,EAAQ,EAAG,QAAQ,EAI/D,EAAG,eAAe,KAAK,CAAa,GAEvC,EAGD,EAAO,iBAAiB,cAAc,EAAE,QAAQ,CAAC,IAAS,CACxD,IAAM,EAAiB,EAAK,cAAc,gBAAgB,EAC1D,GAAI,GAAgB,YAClB,EAAG,MAAQ,EAAG,OAAS,CAAC,EACxB,EAAG,MAAM,KAAK,CACZ,IAAK,EAAe,YAAY,KAAK,EACrC,MAAO,OAAO,SAAS,EAAK,aAAa,OAAO,GAAK,IAAK,EAAE,EAC5D,OAAQ,OAAO,SAAS,EAAK,aAAa,QAAQ,GAAK,IAAK,EAAE,EAC9D,UAAW,EAAK,aAAa,WAAW,GAAK,QAC7C,UAAW,EAAK,aAAa,WAAW,GAAK,MAC7C,SAAU,EAAK,cAAc,+BAA+B,GAAG,aAAa,KAAK,CACnF,CAAC,EAEJ,EAwCH,GApCA,EAAO,iBAAiB,0BAA0B,EAAE,QAAQ,CAAC,IAAS,CACpE,EAAG,WAAa,EAAG,YAAc,CAAC,EAElC,IAAM,EAAiB,EAAK,cAAc,gBAAgB,EACpD,EAAiB,EAAK,cAAc,gBAAgB,EACpD,EAAe,EAAK,cAAc,cAAc,EAElD,EAAc,GACd,EAA4C,SAEhD,GAAI,GAAgB,YAClB,EAAc,EAAe,YAAY,KAAK,EAC9C,EAAe,SAEZ,QAAI,GAAgB,YACvB,EAAc,EAAe,YAAY,KAAK,EAC9C,EAAe,SAEZ,QAAI,GAAc,YACrB,EAAc,EAAa,YAAY,KAAK,EAC5C,EAAe,OAGjB,GAAI,EACF,EAAG,WAAW,KAAK,CACjB,MAAO,OAAO,SAAS,EAAK,aAAa,OAAO,GAAK,IAAK,EAAE,EAC5D,OAAQ,OAAO,SAAS,EAAK,aAAa,QAAQ,GAAK,IAAK,EAAE,EAC9D,cACA,eACA,gBAAiB,EAAK,cAAc,uBAAuB,GAAG,aAAa,KAAK,EAChF,QAAS,EAAK,aAAa,SAAS,GAAK,OACzC,YAAa,EAAK,cAAc,2BAA2B,GAAG,aAAa,KAAK,CAClF,CAAC,EAEJ,EAEG,EAAG,WAAW,OAAS,EACzB,EAAI,KAAK,CAAE,EAEd,EAEM,EAMD,aAAa,CAAC,EAA0B,CAC9C,IAAM,EAAQ,EAAS,MAAM,GAAG,EAChC,GAAI,EAAM,SAAW,EAAG,MAAO,GAE/B,IAAM,EAAQ,OAAO,WAAW,EAAM,EAAE,EAClC,EAAU,OAAO,WAAW,EAAM,EAAE,EACpC,EAAU,OAAO,WAAW,EAAM,EAAE,EAE1C,OAAO,EAAQ,KAAO,EAAU,GAAK,EAM/B,WAAW,CAAC,EAAgB,EAA0B,CAC5D,GAAI,EAAO,SAAS,GAAG,EAAG,CACxB,IAAM,EAAU,OAAO,WAAW,CAAM,EAAI,IAC5C,OAAO,EAAW,EAEpB,OAAO,KAAK,cAAc,CAAM,EAM1B,UAAU,EAAW,CAC3B,MAAO,MAAM,KAAK,IAAI,KAAK,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,EAAG,CAAC,IAExE,CAMO,MAAM,CAAW,CACd,OAAwB,KACxB,OACA,OACA,MACA,UAAgC,KAChC,QAAmC,KACnC,YAAkC,KAClC,WAAuC,KACvC,SAAsB,CAAC,EACvB,WAAmB,CAAC,EACpB,eAAiB,EACjB,UAAmD,KACnD,cAAgB,GAIhB,UAAwC,KAExC,aAA2C,KAE3C,SAAuC,KAEvC,UAAwC,KAExC,kBAAuD,KACvD,iBAAwC,KAEhD,WAAW,CAAC,EAAmB,CAAC,EAAG,CA4BjC,GA3BA,KAAK,OAAS,CACZ,UAAW,EACX,aAAc,EACd,QAAS,IACT,aAAc,GACd,WAAY,GACZ,MAAO,MACJ,CACL,EAEA,KAAK,OAAS,IAAI,EAAW,CAC3B,aAAc,KAAK,OAAO,aAC1B,QAAS,KAAK,OAAO,OACvB,CAAC,EAED,KAAK,MAAQ,CACX,UAAW,GACX,UAAW,KACX,aAAc,KACd,cAAe,EACf,QAAS,GACT,cAAe,EACf,UAAW,GACX,MAAO,IACT,EAGI,KAAK,OAAO,SACd,KAAK,SAAW,KAAK,OAAO,SAEzB,QAAI,KAAK,OAAO,SAAW,KAAK,OAAO,QAE1C,KAAK,SAAW,CAAC,CACf,KAAM,UACN,QAAS,KAAK,OAAO,QACrB,QAAS,KAAK,OAAO,OACvB,CAAC,EAOL,MAAM,CAAC,EAAgB,EAA8B,CAQnD,GAPA,KAAK,OAAS,EACd,KAAK,UAAY,EAEjB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAGpB,KAAK,OAAO,WACd,KAAK,WAAW,EAOZ,iBAAiB,EAAS,CAChC,GAAI,CAAC,KAAK,UAAW,OAErB,KAAK,YAAc,SAAS,cAAc,KAAK,EAC/C,KAAK,YAAY,UAAY,oBAC7B,KAAK,YAAY,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASjC,KAAK,QAAU,SAAS,cAAc,OAAO,EAC7C,KAAK,QAAQ,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA,MAK7B,KAAK,QAAQ,YAAc,GAG3B,IAAM,EAAU,SAAS,cAAc,KAAK,EAC5C,EAAQ,UAAY,kBACpB,EAAQ,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASxB,IAAM,EAAS,SAAS,cAAc,KAAK,EAC3C,EAAO,UAAY,eACnB,EAAO,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWvB,EAAO,YAAc,KAGrB,KAAK,WAAa,SAAS,cAAc,QAAQ,EACjD,KAAK,WAAW,UAAY,eAC5B,KAAK,WAAW,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAehC,KAAK,WAAW,iBAAiB,QAAS,IAAM,KAAK,OAAO,CAAC,EAG7D,IAAM,EAAY,SAAS,cAAc,QAAQ,EACjD,EAAU,UAAY,qBACtB,EAAU,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAe1B,EAAU,YAAc,aACxB,EAAU,iBAAiB,QAAS,IAAM,KAAK,cAAc,CAAC,EAE9D,EAAQ,YAAY,CAAM,EAC1B,EAAQ,YAAY,KAAK,UAAU,EACnC,EAAQ,YAAY,CAAS,EAE7B,KAAK,YAAY,YAAY,KAAK,OAAO,EACzC,KAAK,YAAY,YAAY,CAAO,EACpC,KAAK,UAAU,YAAY,KAAK,WAAW,EAG3C,KAAK,QAAQ,iBAAiB,aAAc,IAAM,KAAK,eAAe,CAAC,EACvE,KAAK,QAAQ,iBAAiB,QAAS,IAAM,KAAK,UAAU,CAAC,EAC7D,KAAK,QAAQ,iBAAiB,QAAS,IAAM,KAAK,cAAc,CAAC,EAM3D,kBAAkB,EAAS,CACjC,GAAI,CAAC,KAAK,OAAQ,OAGlB,IAAI,EAAY,GAChB,KAAK,OAAO,GAAG,OAAQ,SAAY,CACjC,GAAI,EAAW,CACb,EAAY,GACZ,IAAM,EAAU,KAAK,SAAS,KAAK,KAAK,EAAE,OAAS,WAAa,CAAC,EAAE,MAAM,EACzE,GAAI,EACF,MAAM,KAAK,YAAY,CAAO,GAGnC,EAGD,KAAK,OAAO,GAAG,aAAc,IAAM,CACjC,IAAM,EAAQ,KAAK,QAAQ,MAC3B,GAAI,CAAC,EAAO,OAEZ,QAAW,KAAW,KAAK,SACzB,GACE,EAAQ,OAAS,WACd,CAAC,EAAQ,QACT,EAAQ,YACR,EAAM,aAAe,EAAQ,WAChC,CACA,KAAK,YAAY,CAAO,EACxB,OAGL,EAGD,KAAK,OAAO,GAAG,QAAS,SAAY,CAClC,IAAM,EAAW,KAAK,SAAS,KAAK,KAAK,EAAE,OAAS,YAAc,CAAC,EAAE,MAAM,EAC3E,GAAI,EACF,MAAM,KAAK,YAAY,CAAQ,EAElC,OAMW,WAAU,EAAkB,CACxC,QAAW,KAAW,KAAK,SACzB,GAAI,EAAQ,QACV,GAAI,CAEF,MAAM,KAAK,OAAO,aAAa,EAAQ,OAAO,EAEhD,MAAO,EAAO,CACZ,KAAK,IAAI,wBAAyB,CAAK,QASzC,YAAW,CAAC,EAAiC,CACjD,GAAI,KAAK,MAAM,UAAW,OAE1B,KAAK,MAAM,aAAe,EAC1B,KAAK,MAAM,UAAY,GAGvB,KAAK,aAAa,EAElB,GAAI,CAEF,GAAI,EAAQ,QACV,KAAK,WAAa,MAAM,KAAK,OAAO,aAAa,EAAQ,OAAO,EAE7D,QAAI,EAAQ,QACf,KAAK,WAAa,KAAK,OAAO,aAAa,EAAQ,OAAO,EAG5D,GAAI,KAAK,WAAW,SAAW,EAC7B,MAAU,MAAM,+BAA+B,EAGjD,KAAK,eAAiB,EACtB,EAAQ,OAAS,GAEjB,MAAM,KAAK,OAAO,KAAK,WAAW,EAAE,EAEtC,MAAO,EAAO,CACZ,KAAK,MAAM,MAAS,EAAgB,QACpC,KAAK,MAAM,UAAY,GACvB,KAAK,oBAAqB,EAAgB,OAAO,EACjD,KAAK,cAAc,QAOT,OAAM,CAAC,EAAuB,CAC1C,GAAI,CAAC,KAAK,SAAW,CAAC,KAAK,YAAa,OAExC,KAAK,MAAM,UAAY,EACvB,KAAK,MAAM,UAAY,GACvB,KAAK,MAAM,UAAY,GACvB,KAAK,MAAM,cAAgB,EAAG,SAC9B,KAAK,MAAM,QAAU,GACrB,KAAK,MAAM,cAAgB,KAAK,OAAO,WAAa,EAGpD,IAAM,EAAY,KAAK,gBAAgB,EAAG,UAAU,EACpD,GAAI,CAAC,EACH,MAAU,MAAM,8BAA8B,EAIhD,KAAK,YAAY,MAAM,QAAU,QAGjC,KAAK,QAAQ,IAAM,EAAU,IAC7B,MAAM,KAAK,QAAQ,KAAK,EAGxB,KAAK,WAAW,EAAI,YAAY,EAChC,KAAK,WAAW,EAAI,OAAO,EAG3B,KAAK,eAAe,CAAE,EAEtB,KAAK,YAAY,CAAE,EAMb,eAAe,CAAC,EAA+C,CAQrE,MANe,CAAC,GAAG,CAAU,EAAE,KAAK,CAAC,EAAG,IAAM,CAC5C,GAAI,EAAE,KAAK,SAAS,KAAK,GAAK,CAAC,EAAE,KAAK,SAAS,KAAK,EAAG,MAAO,GAC9D,GAAI,CAAC,EAAE,KAAK,SAAS,KAAK,GAAK,EAAE,KAAK,SAAS,KAAK,EAAG,MAAO,GAC9D,OAAQ,EAAE,SAAW,IAAM,EAAE,SAAW,GACzC,EAEa,IAAM,KAMd,cAAc,CAAC,EAAc,CACnC,IAAM,EAAY,KAAK,OAAO,YAAc,GACvC,EAAG,YAAc,EACjB,KAAK,OAAO,WAAa,EAE9B,GAAI,GAAa,EAAG,CAClB,KAAK,MAAM,QAAU,GACrB,KAAK,iBAAiB,EACtB,OAGF,KAAK,MAAM,cAAgB,EAE3B,KAAK,UAAY,YAAY,IAAM,CAGjC,GAFA,KAAK,MAAM,gBAEP,KAAK,MAAM,eAAiB,GAE9B,GADA,KAAK,MAAM,QAAU,GACjB,KAAK,UACP,cAAc,KAAK,SAAS,EAC5B,KAAK,UAAY,KAIrB,KAAK,iBAAiB,GACrB,IAAI,EAMD,gBAAgB,EAAS,CAC/B,GAAI,CAAC,KAAK,WAAY,OAEtB,GAAI,KAAK,MAAM,QACb,KAAK,WAAW,YAAc,UAC9B,KAAK,WAAW,MAAM,QAAU,QAE7B,QAAI,KAAK,MAAM,cAAgB,EAClC,KAAK,WAAW,YAAc,WAAW,KAAK,MAAM,iBACpD,KAAK,WAAW,MAAM,QAAU,QAChC,KAAK,WAAW,MAAM,OAAS,UAG/B,UAAK,WAAW,MAAM,QAAU,OAOpC,MAAM,EAAS,CACb,GAAI,CAAC,KAAK,MAAM,SAAW,CAAC,KAAK,MAAM,UAAW,OAElD,KAAK,WAAW,KAAK,MAAM,UAAW,MAAM,EAC5C,KAAK,WAAW,KAAK,MAAM,SAAS,EACpC,KAAK,UAAU,EAMT,aAAa,EAAS,CAC5B,GAAI,CAAC,KAAK,MAAM,WAAW,gBAAiB,OAG5C,KAAK,SAAS,MAAM,EAGpB,KAAK,WAAW,KAAK,MAAM,UAAW,cAAc,EACpD,KAAK,YAAY,KAAK,MAAM,SAAS,EAGrC,OAAO,KAAK,KAAK,MAAM,UAAU,gBAAiB,QAAQ,EAMpD,cAAc,EAAS,CAC7B,GAAI,CAAC,KAAK,SAAW,CAAC,KAAK,MAAM,UAAW,OAE5C,IAAM,EAAc,KAAK,QAAQ,YAC3B,EAAW,KAAK,MAAM,UAAU,SAEtC,KAAK,MAAM,cAAgB,KAAK,IAAI,EAAG,EAAW,CAAW,EAG7D,IAAM,EAAW,EAAc,EAC/B,GAAI,GAAY,KAAM,KAAK,WAAW,KAAK,MAAM,UAAW,eAAe,EAC3E,GAAI,GAAY,IAAK,KAAK,WAAW,KAAK,MAAM,UAAW,UAAU,EACrE,GAAI,GAAY,KAAM,KAAK,WAAW,KAAK,MAAM,UAAW,eAAe,EAG3E,QAAW,KAAS,KAAK,MAAM,UAAU,eACvC,GAAI,EAAM,QAAU,YAAc,EAAM,QAAU,GAAe,EAAM,OACrE,KAAK,SAAS,EAAM,GAAG,EAQrB,SAAS,EAAS,CACxB,GAAI,KAAK,MAAM,UACb,KAAK,WAAW,KAAK,MAAM,UAAW,UAAU,EAChD,KAAK,eAAe,KAAK,MAAM,SAAS,EAI1C,GAAI,KAAK,UACP,cAAc,KAAK,SAAS,EAC5B,KAAK,UAAY,KAKnB,GADA,KAAK,iBACD,KAAK,eAAiB,KAAK,WAAW,OACxC,KAAK,OAAO,KAAK,WAAW,KAAK,eAAe,EAGhD,UAAK,cAAc,EAOf,aAAa,EAAS,CAE5B,KAAK,MAAM,MADG,qBAEd,KAAK,oBAFS,oBAEgB,EAC9B,KAAK,WAAW,KAAK,MAAM,UAAY,OAAO,EAC9C,KAAK,cAAc,EAMb,aAAa,EAAS,CAQ5B,GAPA,KAAK,MAAM,UAAY,GACvB,KAAK,MAAM,UAAY,KACvB,KAAK,MAAM,aAAe,KAC1B,KAAK,WAAa,CAAC,EACnB,KAAK,eAAiB,EAGlB,KAAK,YACP,KAAK,YAAY,MAAM,QAAU,OAGnC,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAMb,YAAY,EAAS,CAC3B,GAAI,CAAC,KAAK,OAAQ,OAGlB,GADc,KAAK,OAAO,MAChB,gBAAkB,UAC1B,KAAK,cAAgB,GACrB,KAAK,OAAO,MAAM,EAOd,aAAa,EAAS,CAC5B,GAAI,CAAC,KAAK,OAAQ,OAElB,GAAI,KAAK,cACP,KAAK,cAAgB,GACrB,KAAK,OAAO,KAAK,EAOb,UAAU,CAAC,EAAQ,EAAyB,CAClD,IAAM,EAAS,EAAG,eAAe,OAAO,KAAK,EAAE,QAAU,CAAS,EAClE,QAAW,KAAS,EAClB,KAAK,SAAS,EAAM,GAAG,EAOnB,QAAQ,CAAC,EAAmB,CAClC,IAAM,EAAM,IAAI,MAChB,EAAI,IAAM,EAMJ,GAAG,IAAI,EAAuB,CACpC,GAAI,KAAK,OAAO,MAEd,QAAQ,IAAI,aAAc,GAAG,CAAI,EAOrC,QAAQ,EAAY,CAClB,MAAO,IAAK,KAAK,KAAM,EAYzB,EAAE,CAAC,EAAe,EAAyC,CACzD,OAAQ,OACD,UACH,KAAK,UAAY,EACjB,UACG,aACH,KAAK,aAAe,EACpB,UACG,SACH,KAAK,SAAW,EAChB,UACG,UACH,KAAK,UAAY,EACjB,UACG,UACH,KAAK,kBAAoB,EACzB,UACG,iBACH,KAAK,iBAAmB,EACxB,OAON,OAAO,EAAS,CACd,GAAI,KAAK,UACP,cAAc,KAAK,SAAS,EAG9B,GAAI,KAAK,YACP,KAAK,YAAY,OAAO,EAG1B,KAAK,OAAS,KACd,KAAK,UAAY,KACjB,KAAK,QAAU,KACf,KAAK,YAAc,KAEvB,CAMO,SAAS,EAAgB,CAAC,EAA+B,CAC9D,OAAO,IAAI,EAAW,CAAM,EAMvB,SAAS,EAAS,CAAC,EAAqG,CAC7H,MAAO,CACL,KAAM,MAEN,OAAO,CAAC,EAAgB,EAAwB,CAC9C,IAAM,EAAM,GAAiB,CAAM,EAMnC,OALA,EAAI,OAAO,EAAQ,CAAS,EAG1B,EAAe,IAAM,EAEhB,IAAM,CACX,EAAI,QAAQ,EACZ,OAAQ,EAAe,KAG7B,ECh9BF,IAAM,GAAiD,CACrD,MAAO,aACP,MAAO,aACP,MAAO,aACP,QAAS,eACT,QAAS,eACT,QAAS,OACT,OAAQ,MACV,EAMO,MAAM,CAAoB,CACvB,OAAwB,KACxB,OACA,MACA,UAAgC,KAChC,OAAmC,KACnC,iBAAuC,KACvC,eAAwD,KACxD,UAAkD,KAClD,cAAmC,CAAC,EAIpC,OAAmD,KAEnD,eAA2D,KAE3D,cAA0D,KAElE,WAAW,CAAC,EAA4B,CACtC,KAAK,OAAS,CACZ,SAAU,EAAO,UAAY,CAAC,EAC9B,sBAAuB,EAAO,uBAAyB,IACvD,qBAAsB,EAAO,sBAAwB,EACrD,eAAgB,EAAO,gBAAkB,eACzC,YAAa,EAAO,aAAe,GACnC,YAAa,EAAO,aAAe,yBACnC,iBAAkB,EAAO,kBAAoB,EAC/C,EAEA,KAAK,MAAQ,CACX,cAAe,KACf,cAAe,GACf,UAAW,EACX,eAAgB,EAClB,EAMF,MAAM,CAAC,EAAgB,EAA8B,CAOnD,GANA,KAAK,OAAS,EACd,KAAK,UAAY,EAEjB,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAEtB,KAAK,OAAO,YACd,KAAK,cAAc,EAOf,YAAY,EAAS,CAC3B,GAAI,CAAC,KAAK,UAAW,OAErB,KAAK,OAAS,SAAS,cAAc,QAAQ,EAC7C,KAAK,OAAO,UAAY,mBACxB,KAAK,OAAO,KAAO,SAEnB,IAAM,EAAiB,KAAK,kBAAkB,EAE9C,KAAK,OAAO,MAAM,QAAU;AAAA;AAAA,QAExB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAeJ,KAAK,OAAO,iBAAiB,aAAc,IAAM,CAC/C,KAAK,OAAQ,MAAM,WAAa,2BAChC,KAAK,OAAQ,MAAM,UAAY,cAChC,EAED,KAAK,OAAO,iBAAiB,aAAc,IAAM,CAC/C,KAAK,OAAQ,MAAM,WAAa,sBAChC,KAAK,OAAQ,MAAM,UAAY,WAChC,EAED,KAAK,OAAO,iBAAiB,QAAS,IAAM,KAAK,KAAK,CAAC,EAEvD,KAAK,UAAU,YAAY,KAAK,MAAM,EAMhC,iBAAiB,EAAW,CAClC,OAAQ,KAAK,OAAO,oBACb,WACH,MAAO,6BACJ,YACH,MAAO,8BACJ,cACH,MAAO,gCACJ,uBAEH,MAAO,8BAOL,aAAa,EAAS,EAO9B,iBAAiB,CAAC,EAA+B,CAC/C,KAAK,iBAAmB,SAAS,cAAc,KAAK,EACpD,KAAK,iBAAiB,UAAY,oBAClC,KAAK,iBAAiB,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAStC,QAAW,KAAW,KAAK,OAAO,SAAU,CAC1C,IAAM,EAAS,SAAS,cAAc,KAAK,EAC3C,EAAO,UAAY,mBAEnB,IAAM,EAAgB,EAAQ,UAAY,EAAY,IAChD,GAAiB,EAAQ,QAAU,EAAQ,WAAa,EAAY,IAE1E,EAAO,MAAM,QAAU;AAAA;AAAA,gBAEb;AAAA,iBACC;AAAA;AAAA;AAAA,sBAGK,KAAK,OAAO;AAAA;AAAA,QAI5B,KAAK,iBAAiB,YAAY,CAAM,EAG1C,OAAO,KAAK,iBAMN,oBAAoB,EAAS,CACnC,GAAI,CAAC,KAAK,OAAQ,OAGlB,IAAM,EAAmB,IAAM,CAC7B,IAAM,EAAQ,KAAK,QAAQ,MAC3B,GAAI,CAAC,EAAO,OAEZ,KAAK,cAAc,EAAM,WAAW,GAOtC,GAJA,KAAK,OAAO,GAAG,aAAc,CAAgB,EAC7C,KAAK,cAAc,KAAK,IAAM,KAAK,QAAQ,IAAI,aAAc,CAAgB,CAAC,EAG1E,KAAK,OAAO,iBAAkB,CAChC,IAAM,EAAgB,CAAC,IAAqB,CAC1C,GAAI,EAAE,MAAQ,SAAW,KAAK,MAAM,eAAiB,KAAK,MAAM,cAC9D,EAAE,eAAe,EACjB,KAAK,KAAK,GAId,SAAS,iBAAiB,UAAW,CAAa,EAClD,KAAK,cAAc,KAAK,IAAM,SAAS,oBAAoB,UAAW,CAAa,CAAC,GAOhF,aAAa,CAAC,EAA2B,CAC/C,IAAM,EAAkB,KAAK,MAAM,cAG/B,EAAoC,KACxC,QAAW,KAAW,KAAK,OAAO,SAChC,GAAI,GAAe,EAAQ,WAAa,EAAc,EAAQ,QAAS,CACrE,EAAgB,EAChB,MAKJ,GAAI,IAAkB,EAAiB,CACrC,GAAI,EACF,KAAK,gBAAgB,CAAe,EACpC,KAAK,WAAW,EAGlB,GAAI,EACF,KAAK,MAAM,cAAgB,EAC3B,KAAK,iBAAiB,CAAa,EACnC,KAAK,WAAW,CAAa,EAG7B,UAAK,MAAM,cAAgB,MAQzB,UAAU,CAAC,EAA4B,CAC7C,GAAI,CAAC,KAAK,OAAQ,OAGlB,IAAM,EAAO,EAAQ,YAAc,GAAkB,EAAQ,MAC7D,KAAK,OAAO,YAAc,EAE1B,KAAK,MAAM,cAAgB,GAC3B,KAAK,OAAO,MAAM,QAAU,QAG5B,KAAK,OAAO,MAAM,QAAU,IAC5B,KAAK,OAAO,MAAM,UAAY,mBAC9B,sBAAsB,IAAM,CAC1B,KAAK,OAAQ,MAAM,WAAa,2CAChC,KAAK,OAAQ,MAAM,QAAU,IAC7B,KAAK,OAAQ,MAAM,UAAY,gBAChC,EAGD,IAAM,EAAgB,EAAQ,eAAiB,KAAK,OAAO,qBAC3D,GAAI,EAAgB,EAClB,KAAK,cAAc,EAAe,CAAO,EAI3C,GAAI,KAAK,OAAO,sBAAwB,GAAK,CAAC,EAC5C,KAAK,UAAY,WAAW,IAAM,CAChC,KAAK,WAAW,GACf,KAAK,OAAO,qBAAqB,EAOhC,UAAU,EAAS,CACzB,GAAI,CAAC,KAAK,OAAQ,OAOlB,GALA,KAAK,MAAM,cAAgB,GAC3B,KAAK,MAAM,eAAiB,GAC5B,KAAK,MAAM,UAAY,EAGnB,KAAK,eACP,cAAc,KAAK,cAAc,EACjC,KAAK,eAAiB,KAExB,GAAI,KAAK,UACP,aAAa,KAAK,SAAS,EAC3B,KAAK,UAAY,KAInB,KAAK,OAAO,MAAM,QAAU,IAC5B,KAAK,OAAO,MAAM,UAAY,mBAE9B,WAAW,IAAM,CACf,GAAI,KAAK,QAAU,CAAC,KAAK,MAAM,cAC7B,KAAK,OAAO,MAAM,QAAU,QAE7B,GAAG,EAMA,aAAa,CAAC,EAAe,EAA4B,CAC/D,KAAK,MAAM,eAAiB,GAC5B,KAAK,MAAM,UAAY,EAEvB,KAAK,iBAAiB,CAAO,EAE7B,KAAK,eAAiB,YAAY,IAAM,CAItC,GAHA,KAAK,MAAM,YACX,KAAK,iBAAiB,CAAO,EAEzB,KAAK,MAAM,WAAa,EAAG,CAC7B,GAAI,KAAK,eACP,cAAc,KAAK,cAAc,EACjC,KAAK,eAAiB,KAExB,KAAK,KAAK,IAEX,IAAI,EAMD,gBAAgB,CAAC,EAA4B,CACnD,GAAI,CAAC,KAAK,OAAQ,OAElB,IAAM,EAAW,EAAQ,YAAc,GAAkB,EAAQ,MAEjE,GAAI,KAAK,MAAM,gBAAkB,KAAK,MAAM,UAAY,GAAK,EAAQ,gBAAkB,GACrF,KAAK,OAAO,YAAc,GAAG,MAAa,KAAK,MAAM,aAGrD,UAAK,OAAO,YAAc,EAO9B,IAAI,EAAS,CACX,GAAI,CAAC,KAAK,MAAM,eAAiB,CAAC,KAAK,OAAQ,OAE/C,IAAM,EAAU,KAAK,MAAM,cACrB,EAAS,EAAQ,QAAU,EAAQ,QAEzC,KAAK,OAAO,OAAO,CAAM,EACzB,KAAK,SAAS,CAAO,EACrB,KAAK,WAAW,EAMlB,UAAU,CAAC,EAA4B,CACrC,KAAK,OAAO,SAAS,KAAK,CAAO,EAMnC,aAAa,CAAC,EAAyB,CACrC,KAAK,OAAO,SAAW,KAAK,OAAO,SAAS,OAAO,KAAK,EAAE,OAAS,CAAI,EAMzE,aAAa,EAAS,CACpB,KAAK,OAAO,SAAW,CAAC,EACxB,KAAK,WAAW,EAMlB,WAAW,CAAC,EAA+B,CACzC,KAAK,OAAO,SAAW,EAMzB,WAAW,EAAkB,CAC3B,MAAO,CAAC,GAAG,KAAK,OAAO,QAAQ,EAMjC,QAAQ,EAAsB,CAC5B,MAAO,IAAK,KAAK,KAAM,EASzB,EAAE,CAAC,EAAe,EAAyC,CACzD,OAAQ,OACD,OACH,KAAK,OAAS,EACd,UACG,eACH,KAAK,eAAiB,EACtB,UACG,cACH,KAAK,cAAgB,EACrB,OAON,OAAO,EAAS,CACd,GAAI,KAAK,eACP,cAAc,KAAK,cAAc,EAEnC,GAAI,KAAK,UACP,aAAa,KAAK,SAAS,EAG7B,KAAK,cAAc,QAAQ,KAAS,EAAM,CAAC,EAE3C,KAAK,QAAQ,OAAO,EACpB,KAAK,kBAAkB,OAAO,EAE9B,KAAK,OAAS,KACd,KAAK,UAAY,KAErB,CAMO,SAAS,EAAkB,CAAC,EAAiD,CAClF,OAAO,IAAI,EAAoB,CAAM,EAMhC,SAAS,EAAkB,CAAC,EAA+G,CAChJ,MAAO,CACL,KAAM,eAEN,OAAO,CAAC,EAAgB,EAAoC,CAC1D,IAAM,EAAe,GAAmB,CAAM,EAM9C,OALA,EAAa,OAAO,EAAQ,CAAS,EAGnC,EAAe,aAAe,EAEzB,IAAM,CACX,EAAa,QAAQ,EACrB,OAAQ,EAAe,cAG7B,ECjcK,MAAM,CAAiB,CACpB,OAAwB,KACxB,OACA,MACA,UAAgC,KAChC,QAA8B,KAC9B,eAAwD,KACxD,UAAkD,KAClD,cAAmC,CAAC,EAIpC,sBAA0E,KAC1E,SAAgC,KAEhC,WAA+D,KAC/D,OAA8B,KAC9B,OAA8B,KAEtC,WAAW,CAAC,EAA0B,CAAC,EAAG,CACxC,KAAK,OAAS,CACZ,QAAS,EAAO,SAAW,GAC3B,UAAW,EAAO,WAAa,EAC/B,gBAAiB,EAAO,iBAAmB,CAAC,EAC5C,QAAS,EAAO,SAAW,CAAC,EAC5B,WAAY,EAAO,YAAc,GACjC,UAAW,EAAO,WAAa,GAC/B,kBAAmB,EAAO,mBAAqB,EAC/C,MAAO,EAAO,OAAS,GACvB,SAAU,EAAO,UAAY,GAC7B,gBAAiB,EAAO,iBAAmB,MAC7C,EAEA,KAAK,MAAQ,CACX,QAAS,GACT,UAAW,EACX,eAAgB,GAChB,cAAe,CACjB,EAMF,MAAM,CAAC,EAAgB,EAA8B,CACnD,KAAK,OAAS,EACd,KAAK,UAAY,EAEjB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAMpB,aAAa,EAAS,CAC5B,GAAI,CAAC,KAAK,UAAW,OAErB,KAAK,QAAU,SAAS,cAAc,KAAK,EAC3C,KAAK,QAAQ,UAAY,kBAEzB,KAAK,QAAQ,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAc7B,KAAK,qBAAqB,EAC1B,KAAK,UAAU,YAAY,KAAK,OAAO,EAMjC,oBAAoB,EAAS,CACnC,GAAI,CAAC,KAAK,QAAS,OAEnB,OAAQ,KAAK,OAAO,qBACb,OACH,KAAK,QAAQ,MAAM,WAAa,sBAChC,KAAK,QAAQ,MAAM,eAAiB,aACpC,UACG,OACH,KAAK,QAAQ,MAAM,WAAa,sBAChC,UACG,WACH,KAAK,QAAQ,MAAM,WAAa,wEAChC,UACG,OACH,KAAK,QAAQ,MAAM,WAAa,cAChC,OAOE,MAAM,EAAS,CACrB,GAAI,CAAC,KAAK,QAAS,OAGnB,GAAI,KAAK,OAAO,SAAU,CACxB,KAAK,QAAQ,UAAY,KAAK,OAAO,SACrC,OAMF,GAHA,KAAK,QAAQ,UAAY,GAGrB,KAAK,OAAO,MAAO,CACrB,IAAM,EAAQ,SAAS,cAAc,IAAI,EACzC,EAAM,UAAY,wBAClB,EAAM,YAAc,KAAK,OAAO,MAChC,EAAM,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA,QAMtB,KAAK,QAAQ,YAAY,CAAK,EAIhC,GAAI,KAAK,OAAO,gBAAgB,OAAS,EAAG,CAC1C,IAAM,EAAO,SAAS,cAAc,KAAK,EACzC,EAAK,UAAY,uBACjB,EAAK,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASrB,KAAK,OAAO,gBAAgB,QAAQ,CAAC,EAAK,IAAU,CAClD,IAAM,EAAO,KAAK,yBAAyB,EAAK,CAAK,EACrD,EAAK,YAAY,CAAI,EACtB,EAED,KAAK,QAAQ,YAAY,CAAI,EAI/B,IAAM,EAAa,SAAS,cAAc,KAAK,EAU/C,GATA,EAAW,UAAY,0BACvB,EAAW,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA,MAQvB,KAAK,OAAO,WAAY,CAC1B,IAAM,EAAY,KAAK,mBAAmB,CACxC,MAAO,SACP,KAAM;AAAA;AAAA,gBAGN,MAAO,YACP,QAAS,IAAM,KAAK,OAAO,CAC7B,CAAC,EACD,EAAW,YAAY,CAAS,EAIlC,GAAI,KAAK,OAAO,UAAW,CACzB,IAAM,EAAW,KAAK,mBAAmB,CACvC,MAAO,QACP,KAAM;AAAA;AAAA,gBAGN,MAAO,UACP,QAAS,IAAM,KAAK,MAAM,CAC5B,CAAC,EACD,EAAW,YAAY,CAAQ,EAIjC,QAAW,KAAU,KAAK,OAAO,QAAS,CACxC,IAAM,EAAM,KAAK,mBAAmB,CAAM,EAC1C,EAAW,YAAY,CAAG,EAM5B,GAHA,KAAK,QAAQ,YAAY,CAAU,EAG/B,KAAK,MAAM,gBAAkB,KAAK,OAAO,gBAAgB,OAAS,EAAG,CACvE,IAAM,EAAY,SAAS,cAAc,KAAK,EAC9C,EAAU,UAAY,4BACtB,EAAU,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA,QAM1B,EAAU,UAAY;AAAA,gCACI,KAAK,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYnB,EAAU,cAAc,yBAAyB,GACxD,iBAAiB,QAAS,IAAM,KAAK,eAAe,CAAC,EAEhE,KAAK,QAAQ,YAAY,CAAS,GAO9B,wBAAwB,CAAC,EAA8B,EAA4B,CACzF,IAAM,EAAO,SAAS,cAAc,KAAK,EACzC,EAAK,UAAY,uBACjB,EAAK,SAAW,EAEhB,IAAM,EAAa,IAAU,KAAK,MAAM,eAAiB,KAAK,MAAM,eAEpE,EAAK,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMjB,EAAa,8EAAgF;AAAA,MAIjG,IAAM,EAAY,SAAS,cAAc,KAAK,EAS9C,GARA,EAAU,UAAY,4BACtB,EAAU,MAAM,QAAU;AAAA;AAAA;AAAA,wBAGN,EAAI;AAAA,MAIpB,EAAI,SAAU,CAChB,IAAM,EAAW,SAAS,cAAc,MAAM,EAC9C,EAAS,UAAY,2BACrB,EAAS,YAAc,EAAI,SAC3B,EAAS,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAUzB,EAAU,YAAY,CAAQ,EAGhC,EAAK,YAAY,CAAS,EAG1B,IAAM,EAAO,SAAS,cAAc,KAAK,EACzC,EAAK,UAAY,uBACjB,EAAK,MAAM,QAAU;AAAA;AAAA,MAIrB,IAAM,EAAQ,SAAS,cAAc,IAAI,EAezC,GAdA,EAAM,UAAY,6BAClB,EAAM,YAAc,EAAI,MACxB,EAAM,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUtB,EAAK,YAAY,CAAK,EAElB,EAAI,QAAS,CACf,IAAM,EAAU,SAAS,cAAc,GAAG,EAC1C,EAAQ,UAAY,0BACpB,EAAQ,YAAc,EAAI,QAC1B,EAAQ,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA,QAKxB,EAAK,YAAY,CAAO,EAG1B,GAAI,EAAI,OAAS,EAAI,KAAM,CACzB,IAAM,EAAO,SAAS,cAAc,GAAG,EACvC,EAAK,UAAY,uBACjB,EAAK,YAAc,CAAC,EAAI,MAAO,EAAI,IAAI,EAAE,OAAO,OAAO,EAAE,KAAK,KAAI,EAClE,EAAK,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA,QAKrB,EAAK,YAAY,CAAI,EA2BvB,OAxBA,EAAK,YAAY,CAAI,EAGrB,EAAK,iBAAiB,aAAc,IAAM,CACxC,EAAK,MAAM,UAAY,cACvB,EAAK,MAAM,WAAa,4BACzB,EAED,EAAK,iBAAiB,aAAc,IAAM,CACxC,EAAK,MAAM,UAAY,WACvB,EAAK,MAAM,WAAa,2BACzB,EAGD,EAAK,iBAAiB,QAAS,IAAM,CAEnC,GADA,KAAK,wBAAwB,CAAG,EAC5B,EAAI,QACN,EAAI,QAAQ,EAET,QAAI,EAAI,IACX,OAAO,SAAS,KAAO,EAAI,IAE9B,EAEM,EAMD,kBAAkB,CAAC,EAA4C,CACrE,IAAM,EAAM,SAAS,cAAc,QAAQ,EAC3C,EAAI,UAAY,4CAA4C,EAAO,OAAS,cAC5E,EAAI,KAAO,SAEX,IAAI,EAAU,2BACV,EAAc,cAElB,OAAQ,EAAO,WACR,UACH,EAAU,qCACV,UACG,UACH,EAAU,cACV,EAAc,2BACd,MAmBJ,GAhBA,EAAI,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA,oBAKJ;AAAA,0BACM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUlB,EAAO,KAAM,CACf,IAAM,EAAO,SAAS,cAAc,MAAM,EAC1C,EAAK,UAAY,EAAO,KACxB,EAAK,MAAM,QAAU,OACrB,EAAI,YAAY,CAAI,EAGtB,IAAM,EAAQ,SAAS,cAAc,MAAM,EAc3C,OAbA,EAAM,YAAc,EAAO,MAC3B,EAAI,YAAY,CAAK,EAErB,EAAI,iBAAiB,aAAc,IAAM,CACvC,EAAI,MAAM,UAAY,cACvB,EAED,EAAI,iBAAiB,aAAc,IAAM,CACvC,EAAI,MAAM,UAAY,WACvB,EAED,EAAI,iBAAiB,QAAS,EAAO,OAAO,EAErC,EAMD,oBAAoB,EAAS,CACnC,GAAI,CAAC,KAAK,OAAQ,OAElB,IAAM,EAAc,IAAM,CACxB,GAAI,KAAK,OAAO,QACd,KAAK,UAAY,WAAW,IAAM,CAChC,KAAK,KAAK,GACT,KAAK,OAAO,SAAS,GAI5B,KAAK,OAAO,GAAG,QAAS,CAAW,EACnC,KAAK,cAAc,KAAK,IAAM,KAAK,QAAQ,IAAI,QAAS,CAAW,CAAC,EAGpE,IAAM,EAAa,IAAM,CACvB,GAAI,KAAK,MAAM,QACb,KAAK,KAAK,GAId,KAAK,OAAO,GAAG,OAAQ,CAAU,EACjC,KAAK,cAAc,KAAK,IAAM,KAAK,QAAQ,IAAI,OAAQ,CAAU,CAAC,EAGlE,IAAM,EAAgB,CAAC,IAAqB,CAC1C,GAAI,CAAC,KAAK,MAAM,QAAS,OAEzB,OAAQ,EAAE,SACH,SACH,KAAK,KAAK,EACV,UACG,YACH,KAAK,eAAe,EACpB,UACG,aACH,KAAK,WAAW,EAChB,UACG,QACH,KAAK,cAAc,EACnB,QAIN,SAAS,iBAAiB,UAAW,CAAa,EAClD,KAAK,cAAc,KAAK,IAAM,SAAS,oBAAoB,UAAW,CAAa,CAAC,EAMtF,IAAI,EAAS,CACX,GAAI,CAAC,KAAK,SAAW,KAAK,MAAM,QAAS,OAMzC,GAJA,KAAK,MAAM,QAAU,GACrB,KAAK,MAAM,cAAgB,EAGvB,KAAK,OAAO,kBAAoB,GAAK,KAAK,OAAO,gBAAgB,OAAS,EAC5E,KAAK,uBAAuB,EAG9B,KAAK,OAAO,EAEZ,KAAK,QAAQ,MAAM,QAAU,OAC7B,KAAK,QAAQ,MAAM,QAAU,IAC7B,sBAAsB,IAAM,CAC1B,KAAK,QAAS,MAAM,WAAa,qBACjC,KAAK,QAAS,MAAM,QAAU,IAC/B,EAED,KAAK,SAAS,EAMhB,IAAI,EAAS,CACX,GAAI,CAAC,KAAK,SAAW,CAAC,KAAK,MAAM,QAAS,OAE1C,KAAK,MAAM,QAAU,GACrB,KAAK,eAAe,EAEpB,KAAK,QAAQ,MAAM,QAAU,IAC7B,WAAW,IAAM,CACf,GAAI,KAAK,SAAW,CAAC,KAAK,MAAM,QAC9B,KAAK,QAAQ,MAAM,QAAU,QAE9B,GAAG,EAEN,KAAK,SAAS,EAMR,sBAAsB,EAAS,CACrC,KAAK,MAAM,eAAiB,GAC5B,KAAK,MAAM,UAAY,KAAK,OAAO,kBAEnC,KAAK,eAAiB,YAAY,IAAM,CAItC,GAHA,KAAK,MAAM,YACX,KAAK,OAAO,EAER,KAAK,MAAM,WAAa,EAC1B,KAAK,cAAc,GAEpB,IAAI,EAMT,cAAc,EAAS,CAIrB,GAHA,KAAK,MAAM,eAAiB,GAC5B,KAAK,MAAM,UAAY,EAEnB,KAAK,eACP,cAAc,KAAK,cAAc,EACjC,KAAK,eAAiB,KAGxB,GAAI,KAAK,MAAM,QACb,KAAK,OAAO,EAOR,aAAa,EAAS,CAC5B,IAAM,EAAM,KAAK,OAAO,gBAAgB,KAAK,MAAM,eACnD,GAAI,GAEF,GADA,KAAK,aAAa,CAAG,EACjB,EAAI,QACN,EAAI,QAAQ,EAET,QAAI,EAAI,IACX,OAAO,SAAS,KAAO,EAAI,KAQzB,cAAc,EAAS,CAC7B,GAAI,KAAK,OAAO,gBAAgB,SAAW,EAAG,OAC9C,KAAK,MAAM,eAAiB,KAAK,MAAM,cAAgB,EAAI,KAAK,OAAO,gBAAgB,QACnF,KAAK,OAAO,gBAAgB,OAChC,KAAK,OAAO,EAMN,UAAU,EAAS,CACzB,GAAI,KAAK,OAAO,gBAAgB,SAAW,EAAG,OAC9C,KAAK,MAAM,eAAiB,KAAK,MAAM,cAAgB,GAAK,KAAK,OAAO,gBAAgB,OACxF,KAAK,OAAO,EAMN,aAAa,EAAS,CAC5B,IAAM,EAAM,KAAK,OAAO,gBAAgB,KAAK,MAAM,eACnD,GAAI,GAEF,GADA,KAAK,wBAAwB,CAAG,EAC5B,EAAI,QACN,EAAI,QAAQ,EAET,QAAI,EAAI,IACX,OAAO,SAAS,KAAO,EAAI,KAQzB,MAAM,EAAS,CACrB,KAAK,KAAK,EACV,KAAK,QAAQ,OAAO,CAAC,EACrB,KAAK,QAAQ,KAAK,EAClB,KAAK,WAAW,EAMV,KAAK,EAAS,CACpB,GAAI,UAAU,MACZ,UAAU,MAAM,CACd,MAAO,SAAS,MAChB,IAAK,OAAO,SAAS,IACvB,CAAC,EAID,eAAU,WAAW,UAAU,OAAO,SAAS,IAAI,EAOvD,kBAAkB,CAAC,EAAkD,CAEnE,GADA,KAAK,OAAO,gBAAkB,EAC1B,KAAK,MAAM,QACb,KAAK,OAAO,EAOhB,QAAQ,EAAmB,CACzB,MAAO,IAAK,KAAK,KAAM,EAUzB,EAAE,CAAC,EAAe,EAAyC,CACzD,OAAQ,OACD,sBACH,KAAK,sBAAwB,EAC7B,UACG,SACH,KAAK,SAAW,EAChB,UACG,WACH,KAAK,WAAa,EAClB,UACG,OACH,KAAK,OAAS,EACd,UACG,OACH,KAAK,OAAS,EACd,OAON,OAAO,EAAS,CACd,GAAI,KAAK,eACP,cAAc,KAAK,cAAc,EAEnC,GAAI,KAAK,UACP,aAAa,KAAK,SAAS,EAG7B,KAAK,cAAc,QAAQ,KAAS,EAAM,CAAC,EAE3C,KAAK,SAAS,OAAO,EAErB,KAAK,OAAS,KACd,KAAK,UAAY,KAErB,CAMO,SAAS,EAAe,CAAC,EAA4C,CAC1E,OAAO,IAAI,EAAiB,CAAM,EAM7B,SAAS,EAAe,CAAC,EAA4G,CAC1I,MAAO,CACL,KAAM,YAEN,OAAO,CAAC,EAAgB,EAAwB,CAC9C,IAAM,EAAY,GAAgB,CAAM,EAMxC,OALA,EAAU,OAAO,EAAQ,CAAS,EAGhC,EAAe,UAAY,EAEtB,IAAM,CACX,EAAU,QAAQ,EAClB,OAAQ,EAAe,WAG7B,EC1rBK,MAAM,CAAiB,CACpB,OAAwB,KACxB,UAAgC,KAChC,SAAqC,IAAI,IACzC,cAA6D,IAAI,IACjE,cAAmC,CAAC,EAE5C,WAAW,EAAG,EAKd,MAAM,CAAC,EAAgB,EAA8B,CACnD,KAAK,OAAS,EACd,KAAK,UAAY,EAMnB,GAAG,CAAC,EAAY,EAAsC,CACpD,GAAI,CAAC,KAAK,UACR,MAAU,MAAM,6CAA6C,EAI/D,KAAK,OAAO,CAAE,EAEd,IAAM,EAAU,KAAK,cAAc,CAAM,EAOzC,GANA,EAAQ,QAAQ,YAAc,EAE9B,KAAK,SAAS,IAAI,EAAI,CAAO,EAC7B,KAAK,UAAU,YAAY,CAAO,EAG9B,EAAO,wBAA0B,KAAK,OACxC,KAAK,wBAAwB,EAAI,CAAO,EAI1C,GAAI,EAAO,WAAa,EAAO,UAAU,OAAS,OAChD,KAAK,eAAe,EAAS,EAAO,SAAS,EAG/C,OAAO,EAMD,aAAa,CAAC,EAAsC,CAC1D,IAAM,EAAU,SAAS,cAAc,KAAK,EAC5C,EAAQ,UAAY,iBAEpB,IAAM,EAAiB,KAAK,kBAAkB,EAAO,UAAY,eAAgB,EAAO,MAAM,EAa9F,GAXA,EAAQ,MAAM,QAAU;AAAA;AAAA,QAEpB;AAAA;AAAA,wBAEgB,EAAO,IAAM,OAAS;AAAA,iBAC7B,EAAO,SAAW;AAAA,4BACP,EAAO,QAAU;AAAA,QACrC,EAAO,OAAS,qBAAqB,EAAO,cAAgB;AAAA,MAI5D,EAAO,OACT,OAAO,OAAO,EAAQ,MAAO,EAAO,MAAM,EAI5C,IAAI,EAEJ,GAAI,EAAO,KACT,EAAU,SAAS,cAAc,KAAK,EACtC,EAAQ,UAAY,EAAO,KAExB,QAAI,EAAO,MAAO,CACrB,IAAM,EAAM,SAAS,cAAc,KAAK,EACxC,EAAI,IAAM,EAAO,MACjB,EAAI,IAAM,YACV,EAAI,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA,UAIhB,EAAO,KAAO,UAAU,EAAO,QAAU;AAAA,QAE7C,EAAU,EAEP,QAAI,EAAO,KACd,EAAU,SAAS,cAAc,MAAM,EACvC,EAAQ,YAAc,EAAO,KAC7B,EAAQ,MAAM,QAAU;AAAA;AAAA,qBAET,EAAO,MAAQ;AAAA;AAAA;AAAA,QAM9B,OAAU,SAAS,cAAc,MAAM,EAIzC,GAAI,EAAO,IAAK,CACd,IAAM,EAAO,SAAS,cAAc,GAAG,EACvC,EAAK,KAAO,EAAO,IACnB,EAAK,OAAS,EAAO,OAAS,SAAW,QACzC,EAAK,IAAM,EAAO,OAAS,sBAAwB,GACnD,EAAK,MAAM,eAAiB,OAC5B,EAAK,YAAY,CAAO,EACxB,EAAQ,YAAY,CAAI,EAGxB,OAAQ,YAAY,CAAO,EAG7B,OAAO,EAMD,iBAAiB,CACvB,EACA,EACQ,CACR,IAAM,EAAU,OAAO,GAAQ,IAAM,SAAW,GAAG,EAAO,MAAS,GAAQ,GAAK,OAC1E,EAAU,OAAO,GAAQ,IAAM,SAAW,GAAG,EAAO,MAAS,GAAQ,GAAK,OAE1E,EAA+C,CACnD,WAAY,QAAQ,YAAkB,KACtC,aAAc,QAAQ,6CACtB,YAAa,QAAQ,aAAmB,KACxC,cAAe,mBAAmB,kCAClC,OAAU,yDACV,eAAgB,oBAAoB,kCACpC,cAAe,WAAW,YAAkB,KAC5C,gBAAiB,WAAW,6CAC5B,eAAgB,WAAW,aAAmB,IAChD,EAEA,OAAO,EAAU,IAAa,EAAU,gBAMlC,uBAAuB,CAAC,EAAa,EAA4B,CACvE,GAAI,CAAC,KAAK,OAAQ,OAElB,EAAQ,MAAM,QAAU,IAExB,IAAM,EAAa,IAAM,CACvB,EAAQ,MAAM,QAAU,IAGpB,EAAc,IAAM,CACxB,EAAQ,MAAM,QAAU,KAG1B,KAAK,OAAO,GAAG,OAAQ,CAAU,EACjC,KAAK,OAAO,GAAG,QAAS,CAAW,EACnC,KAAK,OAAO,GAAG,QAAS,CAAW,EAEnC,KAAK,cAAc,KAAK,IAAM,CAC5B,KAAK,QAAQ,IAAI,OAAQ,CAAU,EACnC,KAAK,QAAQ,IAAI,QAAS,CAAW,EACrC,KAAK,QAAQ,IAAI,QAAS,CAAW,EACtC,EAMK,cAAc,CAAC,EAAsB,EAAqC,CAChF,IAAM,EAAW,EAAU,UAAY,KACjC,EAAQ,EAAU,OAAS,EAC3B,EAAS,EAAU,OAAS,WAAa,IAG/C,GAAI,CAAC,SAAS,cAAc,4BAA4B,EAAG,CACzD,IAAM,EAAQ,SAAS,cAAc,OAAO,EAC5C,EAAM,GAAK,4BACX,EAAM,YAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAmBpB,SAAS,KAAK,YAAY,CAAK,EAGjC,IAAM,EAAgB,kBAAkB,EAAU,OAClD,EAAQ,MAAM,UAAY,GAAG,KAAiB,YAAmB,OAAW,IAM9E,UAAU,CAAC,EAAY,EAA0B,EAA8B,CAAC,EAAgB,CAC9F,IAAM,EAAU,IAAc,CAC5B,IAAM,EAAkB,CAAC,EAEzB,GAAI,EAAO,OACT,EAAM,KAAK,EAAO,MAAM,EAG1B,GAAI,EAAO,cACT,EAAM,KAAK,IAAI,KAAK,EAAE,YAAY,CAAC,EAGrC,GAAI,EAAO,WACT,EAAM,KAAK,EAAO,UAAU,EAG9B,OAAO,EAAM,KAAK,KAAK,GAGnB,EAAiC,CACrC,WAAY,YAAa,cAAe,eACxC,cAAe,cACjB,EAEI,EAAkB,EAAW,UAAY,eAEvC,EAAU,KAAK,IAAI,EAAI,IACxB,EACH,KAAM,EAAQ,EACd,SAAU,EACV,QAAS,EAAW,SAAW,GACjC,CAAC,EAGD,GAAI,EAAO,gBAAkB,EAAO,eAAiB,EAAG,CACtD,IAAM,EAAY,YAAY,IAAM,CAClC,IAAM,EAAS,EAAQ,cAAc,MAAM,EAC3C,GAAI,EACF,EAAO,YAAc,EAAQ,GAE9B,EAAO,cAAc,EAExB,KAAK,cAAc,IAAI,GAAG,SAAW,CAAS,EAIhD,GAAI,EAAO,mBAAqB,EAAO,cAAgB,EAAO,aAAe,EAAG,CAC9E,IAAM,EAAY,YAAY,IAAM,CAClC,IAAM,EAAc,KAAK,MAAM,KAAK,OAAO,EAAI,EAAU,MAAM,EAC/D,EAAkB,EAAU,GAE5B,IAAM,EAAiB,KAAK,kBAAkB,EAAiB,EAAW,MAAM,EAChF,EAAQ,MAAM,QAAU,EAAQ,MAAM,QAAQ,QAC5C,4DACA,EACF,EACA,EAAQ,MAAM,SAAW,GACxB,EAAO,YAAY,EAEtB,KAAK,cAAc,IAAI,GAAG,SAAW,CAAS,EAGhD,OAAO,EAMT,MAAM,CAAC,EAAkB,CACvB,IAAM,EAAU,KAAK,SAAS,IAAI,CAAE,EACpC,GAAI,EACF,EAAQ,OAAO,EACf,KAAK,SAAS,OAAO,CAAE,EAIzB,IAAM,EAAY,KAAK,cAAc,IAAI,GAAG,QAAS,EACrD,GAAI,EACF,cAAc,CAAS,EACvB,KAAK,cAAc,OAAO,GAAG,QAAS,EAGxC,IAAM,EAAY,KAAK,cAAc,IAAI,GAAG,QAAS,EACrD,GAAI,EACF,cAAc,CAAS,EACvB,KAAK,cAAc,OAAO,GAAG,QAAS,EAO1C,SAAS,EAAS,CAChB,QAAW,KAAM,KAAK,SAAS,KAAK,EAClC,KAAK,OAAO,CAAE,EAOlB,MAAM,CAAC,EAAY,EAAwC,CACzD,IAAM,EAAU,KAAK,SAAS,IAAI,CAAE,EACpC,GAAI,CAAC,EAAS,OAEd,GAAI,EAAO,UAAY,OACrB,EAAQ,MAAM,QAAU,OAAO,EAAO,OAAO,EAG/C,GAAI,EAAO,SAAU,CACnB,IAAM,EAAiB,KAAK,kBAAkB,EAAO,SAAU,EAAO,MAAM,EAC5E,EAAQ,MAAM,QAAU,EAAQ,MAAM,QAAQ,QAC5C,4DACA,EACF,EACA,EAAQ,MAAM,SAAW,EAG3B,GAAI,EAAO,KAAM,CACf,IAAM,EAAS,EAAQ,cAAc,MAAM,EAC3C,GAAI,EACF,EAAO,YAAc,EAAO,KAIhC,GAAI,EAAO,MAAO,CAChB,IAAM,EAAQ,EAAQ,cAAc,KAAK,EACzC,GAAI,EACF,EAAM,IAAM,EAAO,OAQzB,IAAI,CAAC,EAAkB,CACrB,IAAM,EAAU,KAAK,SAAS,IAAI,CAAE,EACpC,GAAI,EACF,EAAQ,MAAM,QAAU,GAO5B,IAAI,CAAC,EAAkB,CACrB,IAAM,EAAU,KAAK,SAAS,IAAI,CAAE,EACpC,GAAI,EACF,EAAQ,MAAM,QAAU,OAO5B,OAAO,EAAS,CACd,QAAW,KAAM,KAAK,SAAS,KAAK,EAClC,KAAK,KAAK,CAAE,EAOhB,OAAO,EAAS,CACd,QAAW,KAAM,KAAK,SAAS,KAAK,EAClC,KAAK,KAAK,CAAE,EAOhB,GAAG,CAAC,EAAqC,CACvC,OAAO,KAAK,SAAS,IAAI,CAAE,EAM7B,MAAM,EAAa,CACjB,OAAO,MAAM,KAAK,KAAK,SAAS,KAAK,CAAC,EAMxC,OAAO,EAAS,CACd,KAAK,UAAU,EACf,KAAK,cAAc,QAAQ,KAAS,EAAM,CAAC,EAC3C,KAAK,OAAS,KACd,KAAK,UAAY,KAErB,CAMO,SAAS,EAAsB,EAAqB,CACzD,OAAO,IAAI,EAMN,SAAS,EAAe,CAAC,EAAwI,CACtK,MAAO,CACL,KAAM,YAEN,OAAO,CAAC,EAAgB,EAAoC,CAC1D,IAAM,EAAU,GAAuB,EACvC,EAAQ,OAAO,EAAQ,CAAS,EAGhC,QAAa,QAAO,KAAY,EAC9B,EAAQ,IAAI,EAAI,CAAM,EAMxB,OAFE,EAAe,UAAY,EAEtB,IAAM,CACX,EAAQ,QAAQ,EAChB,OAAQ,EAAe,WAG7B,ECthBK,IAAM,EAAsC,WAAW,aAAe,KAAM,CAAC,ECUpF,IAAM,GAAiB,CAAC,MAAO,SAAU,WAAY,OAAQ,QAAS,WAAY,SAAU,gBAAiB,UAAW,cAAe,cAAe,eAAgB,4BAA4B,EAI3L,MAAM,WAA2B,CAAgB,CAC9C,QAAyB,KACzB,WAAiC,KACjC,cAAgB,YAEb,mBAAkB,EAAa,CACxC,MAAO,CAAC,GAAG,EAAc,KAGvB,OAAM,EAAkB,CAC1B,OAAO,KAAK,QAGd,iBAAiB,EAAS,CAExB,GADA,KAAK,gBACD,KAAK,QAAS,OAGlB,IAAM,EAAS,KAAK,YAAc,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EAGpE,KAAK,WAAa,SAAS,cAAc,KAAK,EAC9C,KAAK,WAAW,aAAa,OAAQ,WAAW,EAChD,KAAK,WAAW,MAAM,QAAU,4CAGhC,IAAM,EAAO,SAAS,cAAc,MAAM,EAC1C,KAAK,WAAW,YAAY,CAAI,EAEhC,EAAO,YAAY,KAAK,UAAU,EAGlC,IAAM,EAAU,KAAK,aAAa,EAGlC,KAAK,QAAU,IAAI,EAAO,KAAK,WAAY,CAAO,EAGlD,KAAK,cAAc,EAGrB,oBAAoB,EAAS,CAC3B,GAAI,KAAK,aAAa,YAAY,EAAG,OACrC,IAAM,EAAe,EAAE,KAAK,cAC5B,eAAe,IAAM,CACnB,GAAI,KAAK,aAAe,IAAiB,KAAK,cAAe,OAC7D,KAAK,SAAS,QAAQ,EACtB,KAAK,QAAU,KACf,KAAK,WAAa,KAClB,KAAK,YAAY,gBAAgB,EAClC,EAGH,wBAAwB,CAAC,EAAc,EAAqB,EAA4B,CACtF,GAAI,CAAC,KAAK,QAAS,OAEnB,OAAQ,OACD,MACH,GAAI,EAAO,KAAK,QAAQ,OAAO,CAAK,EACpC,UACG,QACH,KAAK,QAAQ,SAAS,IAAU,IAAI,EACpC,UACG,SACH,GAAI,IAAU,KAAM,KAAK,QAAQ,UAAU,WAAW,CAAK,CAAC,EAC5D,UACG,gBACH,GAAI,IAAU,KAAM,KAAK,QAAQ,gBAAgB,WAAW,CAAK,CAAC,EAClE,OAIE,YAAY,EAAkB,CACpC,IAAM,EAAyB,CAAC,EAE1B,EAAS,KAAK,iBAAiB,EACrC,GAAI,EAAO,SAAS,OAAQ,EAAQ,IAAM,EAAO,QACjD,GAAI,EAAO,QAAQ,OAAQ,EAAQ,OAAS,EAAO,OAEnD,IAAM,EAAM,KAAK,aAAa,KAAK,EACnC,GAAI,GAAO,CAAC,EAAQ,IAAK,EAAQ,IAAM,EAAO,IAAM,CAAE,MAAK,KAAM,uBAAwB,IAAK,EAAO,GAAI,EAAI,EAE7G,IAAM,EAAS,KAAK,aAAa,QAAQ,EACzC,GAAI,EAAQ,EAAQ,OAAS,EAE7B,IAAM,EAAQ,KAAK,aAAa,OAAO,EACvC,GAAI,EAAO,EAAQ,MAAQ,EAE3B,GAAI,KAAK,aAAa,UAAU,EAAG,EAAQ,SAAW,GACtD,GAAI,KAAK,aAAa,MAAM,EAAG,EAAQ,KAAO,GAC9C,GAAI,KAAK,aAAa,OAAO,EAAG,EAAQ,MAAQ,GAEhD,IAAM,EAAS,KAAK,aAAa,QAAQ,EACzC,GAAI,EAAQ,EAAQ,OAAS,WAAW,CAAM,EAE9C,IAAM,EAAO,KAAK,aAAa,eAAe,EAC9C,GAAI,EAAM,EAAQ,aAAe,WAAW,CAAI,EAEhD,IAAM,EAAU,KAAK,aAAa,SAAS,EAC3C,GAAI,EAAS,EAAQ,QAAU,EAE/B,GAAI,KAAK,aAAa,UAAU,EAAG,EAAQ,SAAW,GACtD,EAAQ,YAAc,KAAK,aAAa,aAAa,IAAM,QAE3D,IAAM,EAAc,KAAK,aAAa,aAAa,EACnD,GAAI,IAAgB,KAAM,EAAQ,YAAc,EAEhD,IAAM,EAAe,KAAK,aAAa,cAAc,EACrD,GAAI,EAAc,EAAQ,aAAe,EAAa,MAAM,KAAK,EAGjE,OAFA,EAAQ,wBAA0B,KAAK,aAAa,4BAA4B,EAEzE,EAGD,gBAAgB,EAAqE,CAC3F,IAAM,EAAS,KAAK,cAAc,oDAAoD,EAChF,EAAQ,KAAK,aAAa,mBAAmB,GAAG,KAAK,GAAK,GAAQ,aAAa,KAAK,EAC1F,GAAI,CAAC,EAAO,MAAO,CAAC,EACpB,GAAI,EAAM,OAAS,MAAQ,MAAU,UAAU,yCAAyC,EACxF,GAAI,CACF,IAAM,EAAS,KAAK,MAAM,CAAK,EAC/B,GAAI,EAAO,UAAY,SAAc,CAAC,MAAM,QAAQ,EAAO,OAAO,GAAK,EAAO,QAAQ,KAAK,KAAU,CAAC,GAAU,OAAO,IAAW,UAAY,OAAQ,EAA6B,MAAQ,QAAQ,GACjM,MAAU,UAAU,kCAAkC,EAExD,IAAM,EAAa,IAAI,IAAI,CAAC,YAAa,WAAY,eAAgB,WAAY,UAAU,CAAC,EAC5F,GAAI,EAAO,SAAW,SAAc,CAAC,MAAM,QAAQ,EAAO,MAAM,GAAK,EAAO,OAAO,KAAK,KAAS,CAAC,GAAS,OAAO,IAAU,UAAY,OAAQ,EAA4B,MAAQ,UAAY,CAAC,EAAW,IAAI,OAAQ,EAA6B,IAAI,CAAC,CAAC,GACzP,MAAU,UAAU,iCAAiC,EAEvD,MAAO,CAAE,QAAS,EAAO,QAA8B,IAAK,EAAO,IAA8B,OAAQ,EAAO,MAAwC,EAE1J,MAAO,EAAO,CACZ,GAAI,aAAiB,UAAW,MAAM,EACtC,MAAU,UAAU,4CAA4C,GAI5D,aAAa,EAAS,CAC5B,GAAI,CAAC,KAAK,QAAS,OAEnB,IAAM,EAAmC,CACvC,OAAQ,QAAS,UAAW,UAAW,QACvC,aAAc,iBAAkB,eAChC,mBAAoB,YAAa,QACjC,YAAa,aAAc,iBAC3B,UAAW,iBAAkB,UAAW,SACxC,aAAc,gBAAiB,cAC/B,oBACF,EAEA,QAAW,KAAS,EAClB,KAAK,QAAQ,GAAG,EAAQ,IAAI,IAAoB,CAC9C,KAAK,cAAc,IAAI,YAAY,EAAO,CACxC,OAAQ,EAAK,SAAW,EAAI,EAAK,GAAK,EACtC,QAAS,GACT,SAAU,EACZ,CAAC,CAAC,EACK,EAGf,CCnKO,SAAS,CAAU,CAAC,EAAiB,EAAQ,EAAW,CAC7D,GAAI,CAAC,SAAS,CAAO,GAAK,MAAM,CAAO,EAAG,MAAO,OAEjD,IAAM,EAAI,KAAK,MAAM,CAAO,EACtB,EAAI,KAAK,MAAM,EAAI,IAAI,EACvB,EAAI,KAAK,MAAO,EAAI,KAAQ,EAAE,EAC9B,EAAM,EAAI,GAIhB,GAFkB,EAAI,GAAK,GAAS,KAGlC,MAAO,GAAG,KAAK,EAAE,SAAS,EAAE,SAAS,EAAG,GAAG,KAAK,EAAI,SAAS,EAAE,SAAS,EAAG,GAAG,IAGhF,MAAO,GAAG,KAAK,EAAI,SAAS,EAAE,SAAS,EAAG,GAAG,IAOxC,SAAS,CAAgB,CAAC,EAAyB,CACxD,GAAI,CAAC,SAAS,CAAO,GAAK,MAAM,CAAO,EAAG,MAAO,YAEjD,IAAM,EAAI,KAAK,MAAM,CAAO,EACtB,EAAI,KAAK,MAAM,EAAI,IAAI,EACvB,EAAI,KAAK,MAAO,EAAI,KAAQ,EAAE,EAC9B,EAAM,EAAI,GAEV,EAAkB,CAAC,EACzB,GAAI,EAAI,EAAG,EAAM,KAAK,GAAG,KAAK,IAAM,EAAI,OAAS,SAAS,EAC1D,GAAI,EAAI,EAAG,EAAM,KAAK,GAAG,KAAK,IAAM,EAAI,SAAW,WAAW,EAC9D,GAAI,EAAM,GAAK,EAAM,SAAW,EAAG,EAAM,KAAK,GAAG,KAAO,IAAQ,EAAI,SAAW,WAAW,EAE1F,OAAO,EAAM,KAAK,IAAI,EAOjB,SAAS,EAAa,CAAC,EAAyB,CACrD,GAAI,CAAC,SAAS,CAAO,GAAK,MAAM,CAAO,EAAG,MAAO,OAEjD,IAAM,EAAI,KAAK,MAAM,CAAO,EACtB,EAAI,KAAK,MAAM,EAAI,IAAI,EACvB,EAAI,KAAK,MAAO,EAAI,KAAQ,EAAE,EAC9B,EAAM,EAAI,GAEZ,EAAS,KACb,GAAI,EAAI,EAAG,GAAU,GAAG,KACxB,GAAI,EAAI,EAAG,GAAU,GAAG,KACxB,GAAI,EAAM,GAAK,IAAW,KAAM,GAAU,GAAG,KAE7C,OAAO,EAOF,SAAS,CAAa,CAAC,EAA6B,CACzD,IAAM,EAAQ,EAAG,aAAa,KAAK,EACnC,GAAI,EAEF,OADe,SAAS,eAAe,CAAK,GACpB,QAAU,KAIpC,OADa,EAAG,QAAQ,cAAc,GAChB,QAAU,KC5D3B,MAAM,WAAkB,CAAgB,CACrC,UAA+B,CAAC,EAExC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QA8DrB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,UAAU,QAAQ,CAAC,IAAO,EAAG,CAAC,EACnC,KAAK,UAAY,CAAC,EAGZ,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,CAAC,EAAQ,OAGb,IAAM,EAAQ,EAAO,UAAU,CAAC,IAAe,CAC7C,GAAI,EAAM,gBACR,KAAK,gBAAgB,iBAAiB,EAEtC,UAAK,aAAa,kBAAmB,EAAE,EAIzC,KAAK,QAAQ,WAAa,EAAM,uBAChC,KAAK,QAAQ,IAAM,EAAM,gBACzB,KAAK,QAAQ,OAAS,EAAM,mBAC5B,KAAK,QAAQ,SAAW,OAAO,EAAM,WAAW,OAAS,CAAC,EAC1D,KAAK,QAAQ,UAAY,OAAO,EAAM,UAAU,OAAS,CAAC,EAC3D,EAGK,EAAU,KAAK,WAAY,cAAc,UAAU,EACnD,EAAiB,IAAM,EAAO,WAAW,EAC/C,EAAQ,iBAAiB,QAAS,CAAc,EAEhD,KAAK,UAAU,KACb,EACA,IAAM,EAAQ,oBAAoB,QAAS,CAAc,CAC3D,EAEJ,CCtHA,IAAM,GAAY,gBACZ,GAAa,kCAIZ,MAAM,WAAwB,CAAgB,CAC3C,SAAgC,KAChC,SAAgC,KAExC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAYF;AAAA;AAAA;AAAA,QAOnB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,IAAM,EAAS,KAAK,YAAY,cAAc,QAAQ,EACtD,GAAI,GAAU,KAAK,SAAU,EAAO,oBAAoB,QAAS,KAAK,QAAQ,EAC9E,KAAK,WAAW,EAChB,KAAK,SAAW,KAChB,KAAK,SAAW,KAGV,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAM,KAAK,WAAY,cAAc,QAAQ,EACnD,KAAK,SAAW,IAAM,EAAO,WAAW,EACxC,EAAI,iBAAiB,QAAS,KAAK,QAAQ,EAE3C,IAAM,EAAQ,EAAO,UAAU,SAAU,CAAC,IAAe,CACvD,IAAM,EAAS,EAAM,OACrB,EAAI,aAAa,aAAc,EAAS,OAAS,OAAO,EACxD,EAAI,aAAa,eAAgB,OAAO,CAAC,CAAM,CAAC,EACnC,KAAK,WAAY,cAAc,MAAM,EAC7C,aAAa,IAAK,EAAS,GAAY,EAAU,EACvD,EAED,KAAK,SAAW,EAEpB,CC5DA,IAAM,GAAa,8VACb,GAAW,uFACX,GAAY,0LAElB,SAAS,EAAa,CAAC,EAAgB,EAAwB,CAC7D,GAAI,GAAS,IAAW,EAAG,OAAO,GAClC,GAAI,EAAS,IAAK,OAAO,GACzB,OAAO,GAKF,MAAM,WAAwB,CAAgB,CAC3C,SAAgC,KAExC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAYF;AAAA;AAAA;AAAA,QAMnB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,WAAW,EAChB,KAAK,SAAW,KAGV,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAM,KAAK,WAAY,cAAc,QAAQ,EACnD,EAAI,iBAAiB,QAAS,IAAM,EAAO,WAAW,CAAC,EAEvD,IAAM,EAAQ,EAAO,UAAU,CAAC,IAAe,CAC7C,IAAM,EAAO,GAAc,EAAM,MAAO,EAAM,MAAM,EACpD,EAAI,aAAa,aAAc,EAAM,MAAQ,SAAW,MAAM,EACjD,KAAK,WAAY,cAAc,MAAM,EAC7C,aAAa,IAAK,CAAI,EAC5B,EAED,KAAK,SAAW,EAEpB,CC5DA,IAAM,GAAW,iFACX,GAAU,gFAIT,MAAM,WAA8B,CAAgB,CACjD,SAAgC,KAExC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAcF;AAAA;AAAA;AAAA,QAMnB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,WAAW,EAChB,KAAK,SAAW,KAGV,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAM,KAAK,WAAY,cAAc,QAAQ,EACnD,EAAI,iBAAiB,QAAS,IAAM,EAAO,iBAAiB,CAAC,EAE7D,IAAM,EAAQ,EAAO,UAAU,CAAC,IAAe,CAC7C,IAAM,EAAQ,EAAM,uBACpB,GAAI,IAAU,cACZ,KAAK,aAAa,SAAU,EAAE,EAE9B,UAAK,gBAAgB,QAAQ,EAE/B,EAAI,SAAW,IAAU,YAEzB,IAAM,EAAK,EAAM,WACjB,EAAI,aAAa,aAAc,EAAK,kBAAoB,kBAAkB,EAC7D,KAAK,WAAY,cAAc,MAAM,EAC7C,aAAa,IAAK,EAAK,GAAU,EAAQ,EAC/C,EAED,KAAK,SAAW,EAEpB,CC/DA,IAAM,GAAW,6GAIV,MAAM,WAAuB,CAAgB,CAC1C,SAAgC,KAExC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAcF;AAAA;AAAA;AAAA,QAMnB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,WAAW,EAChB,KAAK,SAAW,KAGV,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAM,KAAK,WAAY,cAAc,QAAQ,EACnD,EAAI,iBAAiB,QAAS,IAAM,EAAO,UAAU,CAAC,EAEtD,IAAM,EAAQ,EAAO,UAAU,CAAC,IAAe,CAC7C,IAAM,EAAQ,EAAM,gBACpB,GAAI,IAAU,cACZ,KAAK,aAAa,SAAU,EAAE,EAE9B,UAAK,gBAAgB,QAAQ,EAE/B,EAAI,SAAW,IAAU,YAC1B,EAED,KAAK,SAAW,EAEpB,CCpDO,MAAM,WAAyB,CAAgB,CAC5C,SAAgC,KAChC,eAAiB,aAEd,mBAAkB,EAAa,CACxC,MAAO,CAAC,MAAM,KAGZ,KAAI,EAAW,CACjB,OAAO,KAAK,aAAa,MAAM,GAAK,UAGtC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAiBrB,KAAK,iBAAiB,QAAS,KAAK,WAAW,EAE/C,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,WAAW,EAChB,KAAK,SAAW,KAChB,KAAK,oBAAoB,QAAS,KAAK,WAAW,EAG5C,YAAc,IAAY,CAChC,GAAI,KAAK,OAAS,WAAY,CAC5B,KAAK,eAAiB,CAAC,KAAK,eAE5B,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,EAAQ,KAAK,OAAO,EAAO,KAAK,IAIhC,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAQ,EAAO,UAAU,CAAC,IAAe,CAC7C,KAAK,OAAO,CAAK,EAClB,EAED,KAAK,SAAW,EAGV,MAAM,CAAC,EAAkB,CAC/B,IAAM,EAAS,KAAK,YAAY,cAAc,MAAM,EACpD,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAO,KAAK,KACd,EAEJ,GAAI,IAAS,aAAgB,IAAS,YAAc,KAAK,eAAiB,CACxE,EAAU,KAAK,IAAI,EAAM,SAAW,EAAM,YAAa,CAAC,EACxD,IAAM,EAAY,IAAI,EAAW,EAAS,EAAM,QAAQ,IACxD,EAAO,YAAc,EAChB,QAAI,IAAS,WAClB,EAAU,EAAM,SAChB,EAAO,YAAc,EAAW,CAAO,EAEvC,OAAU,EAAM,YAChB,EAAO,YAAc,EAAW,EAAS,EAAM,QAAQ,EAGzD,EAAO,aAAa,WAAY,GAAc,CAAO,CAAC,EACtD,EAAO,aAAa,iBAAkB,EAAiB,CAAO,CAAC,EAEnE,CC9EO,MAAM,WAAuB,CAAgB,CAClD,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAgBzB,CC7BO,MAAM,WAA2B,CAAgB,CACtD,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAgBrB,KAAK,aAAa,OAAQ,WAAW,EACrC,KAAK,aAAa,cAAe,MAAM,EAE3C,CCpBO,MAAM,WAAyB,CAAgB,CAC5C,UAA+B,CAAC,EAExC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAerB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,UAAU,QAAQ,CAAC,IAAO,EAAG,CAAC,EACnC,KAAK,UAAY,CAAC,EAGZ,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,CAAC,EAAQ,OAGb,IAAM,EACJ,KAAK,QAAQ,oBAAoB,GACjC,KAAK,eAAe,cAAc,oBAAoB,EAExD,GAAI,EAAa,CACf,IAAM,EAAgB,CAAC,IAAa,CAClC,IAAM,EAAK,EAGL,GAFS,EAAY,YAAY,cAAc,QAAQ,GAC3D,GACiB,sBAAsB,EAEnC,EADU,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAG,QAAU,EAAK,MAAQ,EAAK,KAAK,CAAC,GACtD,EAAO,MAAM,UAAY,GACjD,KAAK,WAAW,EAAM,EAAO,MAAM,QAAQ,GAG7C,EAAY,iBAAiB,cAAe,CAAa,EACzD,KAAK,UAAU,KAAK,IAAM,EAAY,oBAAoB,cAAe,CAAa,CAAC,GAInF,UAAU,CAAC,EAAc,EAAwB,CACvD,IAAM,EAAO,KAAK,YAAY,cAAc,MAAM,EAClD,GAAI,EACF,EAAK,YAAc,EAAW,EAAM,CAAQ,EAGlD,CC9DO,MAAM,WAAyB,CAAgB,CAC5C,UAA+B,CAAC,EAChC,YAAc,GAEtB,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QA+DrB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,UAAU,QAAQ,CAAC,IAAO,EAAG,CAAC,EACnC,KAAK,UAAY,CAAC,EAGZ,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAY,KAAK,WAAY,cAAc,YAAY,EACvD,EAAQ,KAAK,WAAY,cAAc,QAAQ,EAC/C,EAAO,KAAK,WAAY,cAAc,OAAO,EAC7C,EAAQ,KAAK,WAAY,cAAc,QAAQ,EAC/C,EAAW,KAAK,WAAY,cAAc,WAAW,EACrD,EAAU,KAAK,WAAY,cAAc,UAAU,EACnD,EAAW,KAAK,aAAa,UAAU,EAC7C,GAAI,EAAU,CACZ,IAAM,EAAW,IAAI,IAAI,EAAU,SAAS,OAAO,EACnD,GAAI,CAAC,QAAS,SAAU,OAAO,EAAE,SAAS,EAAS,QAAQ,GAAK,EAAS,KAAK,WAAW,aAAa,EAAG,CACvG,IAAM,EAAQ,KAAK,WAAY,cAAc,WAAW,EACxD,EAAM,MAAM,gBAAkB,OAAO,KAAK,UAAU,EAAS,IAAI,MAIrE,IAAM,EAAgB,CAAC,IAAoB,CACzC,IAAM,EAAO,EAAM,sBAAsB,EAEnC,EADU,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAE,QAAU,EAAK,MAAQ,EAAK,KAAK,CAAC,GACrD,EAAO,MAAM,UAAY,GAEjD,EAAQ,YAAc,EAAW,EAAM,EAAO,MAAM,QAAQ,EAG5D,IAAM,EAAgB,EAAU,sBAAsB,EAChD,EAAe,EAAQ,YACvB,GAAU,EAAE,QAAU,EAAc,KACpC,GAAU,EAAe,EACzB,GAAU,EAAc,MAAQ,EAAe,EAC/C,GAAc,KAAK,IAAI,GAAS,KAAK,IAAI,GAAS,EAAO,CAAC,EAEhE,EAAQ,MAAM,KAAO,GAAG,QAGpB,EAAgB,CAAC,IAAoB,CACzC,IAAM,EAAO,EAAM,sBAAsB,EAEnC,EADU,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAE,QAAU,EAAK,MAAQ,EAAK,KAAK,CAAC,GACrD,EAAO,MAAM,UAAY,GACjD,EAAO,OAAO,CAAI,GAGd,EAAgB,CAAC,IAAoB,CACzC,KAAK,YAAc,GACnB,EAAU,kBAAkB,EAAE,SAAS,EACvC,EAAc,CAAC,GAGX,EAAgB,CAAC,IAAoB,CAEzC,GADA,EAAc,CAAC,EACX,CAAC,KAAK,YAAa,OACvB,EAAc,CAAC,GAGX,EAAc,CAAC,IAAoB,CACvC,GAAI,CAAC,KAAK,YAAa,OACvB,KAAK,YAAc,GACnB,EAAU,sBAAsB,EAAE,SAAS,GAGvC,EAAY,CAAC,IAAqB,CAEtC,IAAI,EAAU,EAAO,MAAM,YAE3B,OAAQ,EAAE,SACH,aAAc,EAAU,KAAK,IAAI,EAAO,MAAM,SAAU,EAJlD,CAIgE,EAAG,UACzE,YAAa,EAAU,KAAK,IAAI,EAAG,EAL7B,CAK2C,EAAG,UACpD,OAAQ,EAAU,EAAG,UACrB,MAAO,EAAU,EAAO,MAAM,SAAU,cACpC,OAGX,EAAE,eAAe,EACjB,EAAO,OAAO,CAAO,GAGvB,EAAU,iBAAiB,cAAe,CAAa,EACvD,EAAU,iBAAiB,cAAe,CAAa,EACvD,EAAU,iBAAiB,YAAa,CAAW,EACnD,EAAU,iBAAiB,UAAW,CAAS,EAE/C,KAAK,UAAU,KACb,IAAM,EAAU,oBAAoB,cAAe,CAAa,EAChE,IAAM,EAAU,oBAAoB,cAAe,CAAa,EAChE,IAAM,EAAU,oBAAoB,YAAa,CAAW,EAC5D,IAAM,EAAU,oBAAoB,UAAW,CAAS,CAC1D,EAEA,IAAM,EAAQ,EAAO,UAAU,CAAC,IAAe,CAC7C,GAAI,KAAK,YAAa,OAEtB,IAAM,EAAW,EAAM,UAAY,EAC7B,EAAU,EAAW,EAAK,EAAM,YAAc,EAAY,IAAM,EAEtE,EAAK,MAAM,MAAQ,GAAG,KACtB,EAAM,MAAM,KAAO,GAAG,KAEtB,EAAU,aAAa,gBAAiB,OAAO,KAAK,MAAM,CAAO,CAAC,CAAC,EACnE,EAAU,aAAa,iBAAkB,GAAG,EAAiB,EAAM,WAAW,QAAQ,EAAiB,CAAQ,GAAG,EAGlH,IAAM,GAAe,EAAM,gBAAkB,GAAK,IAClD,EAAS,MAAM,MAAQ,GAAG,KAC3B,EAED,KAAK,UAAU,KAAK,CAAK,EAE7B,CC3LO,MAAM,WAA0B,CAAgB,CAC7C,UAA+B,CAAC,EAChC,YAAc,GAEtB,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAqCrB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,UAAU,QAAQ,CAAC,IAAO,EAAG,CAAC,EACnC,KAAK,UAAY,CAAC,EAGZ,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAY,KAAK,WAAY,cAAc,YAAY,EACvD,EAAQ,KAAK,WAAY,cAAc,QAAQ,EAC/C,EAAO,KAAK,WAAY,cAAc,OAAO,EAC7C,EAAQ,KAAK,WAAY,cAAc,QAAQ,EAE/C,EAAe,CAAC,IAAoB,CACxC,IAAM,EAAO,EAAM,sBAAsB,EACnC,EAAS,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAE,QAAU,EAAK,MAAQ,EAAK,KAAK,CAAC,EAE5E,GADA,EAAO,UAAU,CAAM,EACnB,EAAS,GAAK,EAAO,MAAM,MAAO,EAAO,SAAS,EAAK,GAGvD,EAAgB,CAAC,IAAoB,CACzC,KAAK,YAAc,GACnB,EAAU,kBAAkB,EAAE,SAAS,EACvC,EAAa,CAAC,GAEV,EAAgB,CAAC,IAAoB,CACzC,GAAI,CAAC,KAAK,YAAa,OACvB,EAAa,CAAC,GAEV,EAAc,CAAC,IAAoB,CACvC,GAAI,CAAC,KAAK,YAAa,OACvB,KAAK,YAAc,GACnB,EAAU,sBAAsB,EAAE,SAAS,GAEvC,EAAY,CAAC,IAAqB,CACtC,IAAI,EAAS,EAAO,MAAM,OACpB,EAAO,KAEb,OAAQ,EAAE,SACH,iBACA,UAAW,EAAS,KAAK,IAAI,EAAG,EAAS,CAAI,EAAG,UAChD,gBACA,YAAa,EAAS,KAAK,IAAI,EAAG,EAAS,CAAI,EAAG,cAC9C,OAGX,EAAE,eAAe,EACjB,EAAO,UAAU,CAAM,GAGzB,EAAU,iBAAiB,cAAe,CAAa,EACvD,EAAU,iBAAiB,cAAe,CAAa,EACvD,EAAU,iBAAiB,YAAa,CAAW,EACnD,EAAU,iBAAiB,UAAW,CAAS,EAE/C,KAAK,UAAU,KACb,IAAM,EAAU,oBAAoB,cAAe,CAAa,EAChE,IAAM,EAAU,oBAAoB,cAAe,CAAa,EAChE,IAAM,EAAU,oBAAoB,YAAa,CAAW,EAC5D,IAAM,EAAU,oBAAoB,UAAW,CAAS,CAC1D,EAEA,IAAM,EAAQ,EAAO,UAAU,CAAC,IAAe,CAC7C,GAAI,EAAM,qBAAuB,cAAe,CAC9C,KAAK,aAAa,SAAU,EAAE,EAC9B,OAEF,KAAK,gBAAgB,QAAQ,EAG7B,IAAM,GADM,EAAM,MAAQ,EAAI,EAAM,QACd,IAEtB,EAAK,MAAM,MAAQ,GAAG,KACtB,EAAM,MAAM,KAAO,GAAG,KACtB,EAAU,aAAa,gBAAiB,OAAO,KAAK,MAAM,CAAO,CAAC,CAAC,EACpE,EAED,KAAK,UAAU,KAAK,CAAK,EAE7B,CCnGO,SAAS,EAAe,CAC7B,EACA,EACA,EAA2B,CAAC,EACZ,CAChB,IACE,YAAY,MACZ,SAAS,EACT,WAAW,KACX,UAAU,GACR,EAEE,EAAa,EAAO,sBAAsB,EAC1C,EAAc,EAAQ,sBAAsB,EAE5C,EAAe,EACjB,EAAS,sBAAsB,EAC/B,CAAE,IAAK,EAAG,KAAM,EAAG,OAAQ,OAAO,YAAa,MAAO,OAAO,WAAY,MAAO,OAAO,WAAY,OAAQ,OAAO,WAAY,EAE5H,EAAe,EAAW,IAAM,EAAa,IAAM,EACnD,EAAkB,EAAa,OAAS,EAAW,OAAS,EAG9D,EACJ,GAAI,EAAU,WAAW,KAAK,EAC5B,EAAW,EAAY,OAAS,GAAU,EAAe,MAAQ,SAEjE,OAAW,EAAY,OAAS,GAAU,EAAkB,SAAW,MAIzE,IAAI,EACJ,GAAI,IAAa,MACf,EAAM,EAAW,IAAM,EAAY,OAAS,EAE5C,OAAM,EAAW,OAAS,EAI5B,IAAI,EAAgC,GACpC,GAAI,EAAU,SAAS,QAAQ,EAAG,EAAQ,SACrC,QAAI,EAAU,SAAS,MAAM,EAAG,EAAQ,OAG7C,IAAI,EACJ,OAAQ,OACD,SACH,EAAO,EAAW,KAClB,UACG,OACH,EAAO,EAAW,MAAQ,EAAY,MACtC,cAEA,EAAO,EAAW,MAAQ,EAAW,MAAQ,EAAY,OAAS,EAClE,MAIJ,IAAM,EAAU,EAAa,KAAO,EAC9B,EAAU,EAAa,MAAQ,EAAY,MAAQ,EACzD,EAAO,KAAK,IAAI,EAAS,KAAK,IAAI,EAAS,CAAI,CAAC,EAGhD,IAAM,EAAS,EAAa,IAAM,EAC5B,EAAS,EAAa,OAAS,EAAY,OAAS,EAC1D,EAAM,KAAK,IAAI,EAAQ,KAAK,IAAI,EAAQ,CAAG,CAAC,EAE5C,IAAM,EAA4B,GAAG,IAAW,IAEhD,MAAO,CAAE,MAAK,OAAM,UAAW,CAAe,ECjGhD,IAAM,GAAY,yrBAEZ,GAAS,CAAC,KAAM,IAAK,KAAM,EAAG,KAAM,IAAK,KAAM,CAAC,EAI/C,MAAM,WAA0B,CAAgB,CAC7C,SAAgC,KAChC,QAAU,GACV,cAAqE,OAE7E,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAkCF;AAAA;AAAA;AAAA;AAAA,QAOnB,IAAM,EAAU,KAAK,WAAY,cAAc,UAAU,EACzD,KAAK,gBAAkB,IAAM,KAAK,OAAO,EACzC,EAAQ,iBAAiB,QAAS,KAAK,eAAe,EAGtD,KAAK,YAAc,CAAC,IAAkB,CACpC,GAAI,KAAK,SAAW,CAAC,KAAK,SAAS,EAAE,MAAc,EAAG,KAAK,MAAM,GAEnE,SAAS,iBAAiB,QAAS,KAAK,WAAW,EAGnD,KAAK,cAAgB,CAAC,IAAqB,CACzC,GAAI,EAAE,MAAQ,UAAY,KAAK,QAAS,KAAK,MAAM,GAErD,SAAS,iBAAiB,UAAW,KAAK,aAAa,EAEvD,eAAe,IAAM,KAAK,OAAO,CAAC,EAG5B,gBAAuC,KAEvC,YAAgD,KAEhD,cAAqD,KAE7D,oBAAoB,EAAS,CAG3B,GAFA,KAAK,WAAW,EAChB,KAAK,SAAW,KACZ,KAAK,gBACP,KAAK,YAAY,cAAc,UAAU,GAAG,oBAAoB,QAAS,KAAK,eAAe,EAE/F,GAAI,KAAK,YAAa,SAAS,oBAAoB,QAAS,KAAK,WAAW,EAC5E,GAAI,KAAK,cAAe,SAAS,oBAAoB,UAAW,KAAK,aAAa,EAGpF,MAAM,EAAS,CACb,GAAI,KAAK,QAAS,KAAK,MAAM,EACxB,UAAK,KAAK,EAGjB,IAAI,EAAS,CACX,KAAK,QAAU,GACf,KAAK,cAAgB,OACrB,KAAK,aAAa,OAAQ,EAAE,EAC5B,KAAK,WAAY,cAAc,UAAU,EAAG,aAAa,gBAAiB,MAAM,EAChF,KAAK,WAAY,cAAc,OAAO,EAAG,aAAa,cAAe,OAAO,EAC5E,KAAK,YAAY,EACjB,KAAK,aAAa,EAGpB,KAAK,EAAS,CACZ,KAAK,QAAU,GACf,KAAK,gBAAgB,MAAM,EAC3B,KAAK,WAAY,cAAc,UAAU,EAAG,aAAa,gBAAiB,OAAO,EACjF,KAAK,WAAY,cAAc,OAAO,EAAG,aAAa,cAAe,MAAM,EAGrE,YAAY,EAAS,CAC3B,IAAM,EAAU,KAAK,WAAY,cAAc,UAAU,EACnD,EAAO,KAAK,WAAY,cAAc,OAAO,EACnD,GAAI,CAAC,GAAW,CAAC,EAAM,OAGvB,IAAM,EAAW,KAAK,QAAQ,gCAAgC,EAExD,EAAS,GAAgB,EAAS,EAAM,CAC5C,UAAW,UACX,OAAQ,EACR,SAAU,EACV,QAAS,CACX,CAAC,EAED,EAAK,MAAM,IAAM,GAAG,EAAO,QAC3B,EAAK,MAAM,KAAO,GAAG,EAAO,SAGtB,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAQ,EAAO,UAAU,IAAM,CACnC,GAAI,KAAK,QAAS,KAAK,YAAY,EACpC,EAED,KAAK,SAAW,EAGV,WAAW,EAAS,CAC1B,IAAM,EAAS,EAAc,IAAI,EACjC,GAAI,CAAC,EAAQ,OAEb,IAAM,EAAO,KAAK,WAAY,cAAc,OAAO,EAGnD,OAFA,EAAK,UAAY,GAET,KAAK,mBACN,OAAQ,KAAK,WAAW,EAAM,CAAM,EAAG,UACvC,QAAS,KAAK,YAAY,EAAM,CAAM,EAAG,UACzC,UAAW,KAAK,cAAc,EAAM,CAAM,EAAG,UAC7C,WAAY,KAAK,eAAe,EAAM,CAAM,EAAG,UAC/C,QAAS,KAAK,YAAY,EAAM,CAAM,EAAG,MAIhD,GAAI,KAAK,QACP,eAAe,IAAM,KAAK,aAAa,CAAC,EAIpC,UAAU,CAAC,EAAe,EAAmB,CACnD,IAAM,EAAQ,EAAO,MASrB,GANA,KAAK,QAAQ,EAAM,UAAU,EAAM,gBAAiB,IAAM,CACxD,KAAK,cAAgB,QACrB,KAAK,YAAY,EAClB,EAGG,EAAM,UAAU,OAAS,EAAG,CAC9B,IAAM,EAAU,EAAM,UAAU,KAAK,CAAC,IAAW,EAAE,QAAQ,EACrD,EAAQ,EAAM,YAAc,OAAU,EAAU,GAAG,EAAQ,UAAY,OAC7E,KAAK,QAAQ,EAAM,YAAY,IAAS,IAAM,CAC5C,KAAK,cAAgB,UACrB,KAAK,YAAY,EAClB,EAIH,GAAI,EAAM,WAAW,OAAS,EAAG,CAC/B,IAAM,EAAU,EAAM,WAAW,KAAK,CAAC,IAAW,EAAE,OAAS,SAAS,EACtE,KAAK,QAAQ,EAAM,aAAa,GAAS,OAAS,QAAS,IAAM,CAC/D,KAAK,cAAgB,WACrB,KAAK,YAAY,EAClB,EAGH,GAAI,EAAM,YAAY,OAAS,EAAG,CAChC,IAAM,EAAW,EAAM,YAAY,KAAK,CAAC,IAAiC,EAAM,QAAQ,EACxF,KAAK,QAAQ,EAAM,UAAU,GAAU,OAAS,YAAa,IAAM,CACjE,KAAK,cAAgB,QACrB,KAAK,YAAY,EAClB,GAIG,WAAW,CAAC,EAAe,EAAmB,CACpD,KAAK,QAAQ,EAAM,OAAQ,IAAM,CAC/B,KAAK,cAAgB,OACrB,KAAK,YAAY,GAChB,GAAO,EAAI,EAEd,QAAW,KAAS,GAAQ,CAC1B,IAAM,EAAU,EAAO,MAAM,eAAiB,EAC9C,KAAK,QAAQ,EAAM,GAAG,KAAU,IAAM,CACpC,EAAO,gBAAgB,CAAK,EAC5B,KAAK,MAAM,GACV,CAAO,GAIN,aAAa,CAAC,EAAe,EAAmB,CACtD,KAAK,QAAQ,EAAM,OAAQ,IAAM,CAC/B,KAAK,cAAgB,OACrB,KAAK,YAAY,GAChB,GAAO,EAAI,EAGd,KAAK,QAAQ,EAAM,OAAQ,IAAM,CAC/B,EAAO,WAAW,MAAM,EACxB,KAAK,MAAM,GACV,EAAO,MAAM,WAAW,EAE3B,QAAW,KAAK,EAAO,MAAM,UAC3B,KAAK,QAAQ,EAAM,GAAG,EAAE,UAAW,IAAM,CACvC,EAAO,WAAW,CAAC,EACnB,KAAK,MAAM,GACV,EAAE,UAAY,CAAC,EAAO,MAAM,WAAW,EAItC,cAAc,CAAC,EAAe,EAAmB,CACvD,KAAK,QAAQ,EAAM,OAAQ,IAAM,CAC/B,KAAK,cAAgB,OACrB,KAAK,YAAY,GAChB,GAAO,EAAI,EAGd,IAAM,EAAa,EAAO,MAAM,WAAW,KAAK,CAAC,IAAW,EAAE,OAAS,SAAS,EAChF,KAAK,QAAQ,EAAM,MAAO,IAAM,CAC9B,QAAW,KAAK,EAAO,MAAM,WAC3B,GAAI,EAAE,OAAS,UAAW,EAAO,aAAa,EAAE,GAAI,UAAU,EAEhE,KAAK,MAAM,GACV,CAAC,CAAU,EAEd,QAAW,KAAS,EAAO,MAAM,WAC/B,GAAI,EAAM,OAAS,aAAe,EAAM,OAAS,WAC/C,KAAK,QAAQ,EAAM,EAAM,MAAO,IAAM,CACpC,EAAO,aAAa,EAAM,GAAI,SAAS,EACvC,KAAK,MAAM,GACV,EAAM,OAAS,SAAS,EAKzB,WAAW,CAAC,EAAe,EAAmB,CACpD,KAAK,QAAQ,EAAM,OAAQ,IAAM,CAC/B,KAAK,cAAgB,OACrB,KAAK,YAAY,GAChB,GAAO,EAAI,EAEd,QAAW,KAAS,EAAO,MAAM,YAC/B,KAAK,QAAQ,EAAM,EAAM,OAAS,EAAM,UAAY,cAAe,IAAM,CACvE,EAAO,cAAc,EAAM,EAAE,EAC7B,KAAK,MAAM,GACV,EAAM,QAAQ,EAIb,OAAO,CACb,EACA,EACA,EACA,EAAU,GACV,EAAS,GACH,CACN,IAAM,EAAM,SAAS,cAAc,QAAQ,EAG3C,GAFA,EAAI,UAAY,OAAO,EAAS,QAAU,KAC1C,EAAI,aAAa,OAAQ,UAAU,EAC/B,CAAC,EAAQ,EAAI,aAAa,eAAgB,OAAO,CAAO,CAAC,EAC7D,EAAI,YAAc,EAClB,EAAI,iBAAiB,QAAS,CAAO,EACrC,EAAK,YAAY,CAAG,EAExB,CCxSO,MAAM,WAAwB,CAAgB,CAC3C,QAA+B,KAC/B,QAA+B,KAEvC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAWrB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,IAAM,EAAS,KAAK,YAAY,cAAc,QAAQ,EACtD,GAAI,GAAU,KAAK,QAAS,EAAO,oBAAoB,QAAS,KAAK,OAAO,EAC5E,KAAK,UAAU,EACf,KAAK,QAAU,KACf,KAAK,QAAU,KAGT,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EAC3B,EAAS,KAAK,YAAY,cAAc,QAAQ,EACtD,GAAI,CAAC,GAAU,CAAC,EAAQ,OACxB,IAAM,EAAU,OAAO,WAAW,KAAK,aAAa,SAAS,GAAK,IAAI,EAChE,EAAS,OAAO,SAAS,CAAO,GAAK,IAAY,EAAI,EAAU,GAC/D,EAAQ,EAAS,EAAI,WAAW,YAAmB,QAAQ,KAAK,IAAI,CAAM,YAChF,EAAO,YAAc,EAAS,EAAI,IAAI,IAAW,OAAO,CAAM,EAC9D,EAAO,aAAa,aAAc,CAAK,EACvC,KAAK,QAAU,IAAM,EAAO,OAAO,CAAM,EACzC,EAAO,iBAAiB,QAAS,KAAK,OAAO,EAC7C,KAAK,QAAU,EAAO,UAAU,CAAC,IAAuB,CACtD,EAAO,SAAW,EAAS,EAAI,EAAM,aAAe,EAAI,EAAM,SAAW,GAAK,EAAM,aAAe,EAAM,SAC1G,EAEL,CC9CA,IAAM,GAAe,CAAC,IAAK,KAAM,EAAG,KAAM,IAAK,CAAC,EAIzC,MAAM,WAAgC,CAAgB,CACnD,QAA+B,KAC/B,QAA+B,KAEvC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAUrB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,IAAM,EAAS,KAAK,YAAY,cAAc,QAAQ,EACtD,GAAI,GAAU,KAAK,QAAS,EAAO,oBAAoB,QAAS,KAAK,OAAO,EAC5E,KAAK,UAAU,EACf,KAAK,QAAU,KACf,KAAK,QAAU,KAGT,QAAQ,EAAa,CAC3B,IAAM,GAAS,KAAK,aAAa,OAAO,GAAK,IAC1C,MAAM,GAAG,EACT,IAAI,KAAS,OAAO,WAAW,EAAM,KAAK,CAAC,CAAC,EAC5C,OAAO,KAAS,OAAO,SAAS,CAAK,GAAK,GAAS,MAAQ,GAAS,CAAC,EACxE,MAAO,CAAC,GAAG,IAAI,IAAI,EAAM,OAAS,EAAI,EAAQ,EAAY,CAAC,EAAE,KAAK,CAAC,EAAG,IAAM,EAAI,CAAC,EAG3E,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EAC3B,EAAS,KAAK,YAAY,cAAc,QAAQ,EACtD,GAAI,CAAC,GAAU,CAAC,EAAQ,OACxB,IAAM,EAAQ,KAAK,SAAS,EAC5B,KAAK,QAAU,IAAM,CACnB,IAAM,EAAe,EAAM,UAAU,KAAQ,EAAO,EAAO,MAAM,aAAe,KAAK,EACrF,EAAO,gBAAgB,EAAM,EAAe,EAAI,EAAI,EAAa,GAEnE,EAAO,iBAAiB,QAAS,KAAK,OAAO,EAC7C,KAAK,QAAU,EAAO,UAAU,eAAgB,CAAC,IAAuB,CACtE,EAAO,YAAc,GAAG,EAAM,gBAC9B,EAAO,aAAa,aAAc,kBAAkB,EAAM,eAAe,EAC1E,EAEL,CCjDA,IAAM,GAAe,0HAOd,MAAM,WAA2B,CAAgB,CAC9C,QAA+B,KAEvC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qGAS4E;AAAA;AAAA,QAIjG,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,UAAU,EACf,KAAK,QAAU,KAGT,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EAC3B,EAAS,KAAK,YAAY,cAAc,QAAQ,EACtD,GAAI,CAAC,GAAU,CAAC,EAAQ,OAExB,IAAI,EAA6B,KAC3B,EAAe,IAAY,EAAO,EAClC,EAAS,IAAY,CACzB,IAAM,EAAQ,EAAO,UAAU,aACzB,EAAY,aAAiB,iBAAmB,EAAwB,KAC9E,GAAI,IAAc,EAChB,GAAO,oBAAoB,0CAA2C,CAAY,EAClF,GAAO,oBAAoB,+CAAgD,CAAY,EACvF,GAAW,iBAAiB,0CAA2C,CAAY,EACnF,GAAW,iBAAiB,+CAAgD,CAAY,EACxF,EAAQ,EAEV,IAAM,EAAY,OAAO,GAAO,iCAAmC,WACnE,KAAK,gBAAgB,YAAa,CAAS,EAC3C,KAAK,gBAAgB,YAAa,GAAO,wCAA0C,EAAI,EACvF,EAAO,aAAa,eAAgB,OAAO,GAAO,wCAA0C,EAAI,CAAC,GAE7F,EAAQ,IAAY,GAAO,iCAAiC,EAClE,EAAO,iBAAiB,QAAS,CAAK,EACtC,IAAM,EAAc,EAAO,UAAU,CAAM,EAC3C,EAAO,EAEP,KAAK,QAAU,IAAM,CACnB,EAAY,EACZ,EAAO,oBAAoB,QAAS,CAAK,EACzC,GAAO,oBAAoB,0CAA2C,CAAY,EAClF,GAAO,oBAAoB,+CAAgD,CAAY,GAG7F,CCpEO,MAAM,WAAwB,CAAgB,CAC3C,QAA+B,KAEvC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAWrB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,UAAU,EACf,KAAK,QAAU,KAGT,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EAC3B,EAAS,KAAK,YAAY,cAAc,QAAQ,EACtD,GAAI,CAAC,GAAU,CAAC,EAAQ,OACxB,IAAM,EAAY,KAAK,IAAI,EAAG,OAAO,KAAK,aAAa,WAAW,GAAK,EAAE,CAAC,EACpE,EAAS,IAAY,CACzB,IAAM,EAAQ,EAAO,MACf,EAAM,EAAM,SAAS,GAAG,EAAE,GAAG,IAC7B,EAAgB,EAAO,UAA0C,SAAW,GAC5E,EAAY,IAAQ,SAAc,GAAgB,CAAC,OAAO,SAAS,EAAM,QAAQ,GACjF,EAAa,GAAa,EAAM,EAAM,aAAe,EAC3D,KAAK,gBAAgB,YAAa,CAAS,EAC3C,KAAK,gBAAgB,eAAgB,CAAU,EAC/C,EAAO,aAAa,eAAgB,OAAO,CAAU,CAAC,GAElD,EAAQ,IAAY,CACxB,IAAM,EAAM,EAAO,MAAM,SAAS,GAAG,EAAE,GAAG,IAC1C,GAAI,IAAQ,OACV,EAAO,OAAO,CAAG,EACZ,EAAO,KAAK,GAGrB,EAAO,iBAAiB,QAAS,CAAK,EACtC,IAAM,EAAc,EAAO,UAAU,CAAM,EAC3C,EAAO,EACP,KAAK,QAAU,IAAM,CACnB,EAAY,EACZ,EAAO,oBAAoB,QAAS,CAAK,GAG/C,CCvDO,MAAM,WAA0B,CAAgB,CAC7C,QAA+B,KAEvC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASrB,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,UAAU,EACf,KAAK,QAAU,KAGT,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EAC3B,EAAU,KAAK,YAAY,cAAc,GAAG,EAClD,GAAI,CAAC,GAAU,CAAC,EAAS,OACzB,IAAM,EAAS,IAAY,CACzB,IAAM,EAAQ,EAAO,MAAM,MAG3B,GAFA,KAAK,gBAAgB,OAAQ,CAAC,CAAC,CAAK,EACpC,EAAQ,YAAc,GAAO,SAAW,GACpC,GAAO,OAAS,OAAW,KAAK,aAAa,YAAa,OAAO,EAAM,IAAI,CAAC,EAC3E,UAAK,gBAAgB,WAAW,GAEvC,KAAK,QAAU,EAAO,UAAU,CAAM,EACtC,EAAO,EAEX,CClCA,IAAM,GAAc,+KASb,MAAM,WAAkC,CAAgB,CACrD,QAA+B,KAEvC,iBAAiB,EAAS,CACxB,GAAI,CAAC,KAAK,WAAY,CACpB,IAAM,EAAS,KAAK,aAAa,CAAE,KAAM,MAAO,CAAC,EACjD,EAAO,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qGAS4E;AAAA;AAAA,QAIjG,eAAe,IAAM,KAAK,OAAO,CAAC,EAGpC,oBAAoB,EAAS,CAC3B,KAAK,UAAU,EACf,KAAK,QAAU,KAGT,MAAM,EAAS,CACrB,IAAM,EAAS,EAAc,IAAI,EAC3B,EAAS,KAAK,YAAY,cAAc,QAAQ,EACtD,GAAI,CAAC,GAAU,CAAC,EAAQ,OACxB,IAAI,EAAsC,KACtC,EAAyB,KACzB,EAAa,EAEX,EAAe,IAAY,CAC/B,IAAM,EAAY,GAAQ,QAAU,YACpC,KAAK,gBAAgB,YAAa,CAAS,EAC3C,EAAO,aAAa,eAAgB,OAAO,CAAS,CAAC,GAEjD,EAAe,IAAY,CAK/B,GAJA,IACA,GAAQ,oBAAoB,aAAc,CAAY,EACtD,GAAQ,oBAAoB,UAAW,CAAY,EACnD,GAAQ,oBAAoB,aAAc,CAAY,EAClD,GAAU,IAAY,KAAW,EAAO,wBAAwB,CAAO,EAAE,MAAM,IAAM,EAAE,EAC3F,EAAS,KACT,EAAU,KACV,KAAK,gBAAgB,WAAW,EAChC,KAAK,gBAAgB,WAAW,GAE5B,EAAS,IAAY,CACzB,IAAM,EAAQ,EAAO,UAAU,aACzB,EAAa,aAAiB,iBAC/B,EAA+D,QAAU,KAC1E,KACJ,GAAI,IAAe,EAAQ,OAE3B,GADA,EAAa,EACT,CAAC,EAAY,OACjB,EAAS,EACT,IAAM,EAAoB,EAC1B,EAAO,iBAAiB,aAAc,CAAY,EAClD,EAAO,iBAAiB,UAAW,CAAY,EAC/C,EAAO,iBAAiB,aAAc,CAAY,EAClD,EAAa,EACR,EAAO,kBAAkB,CAAC,IAAc,CAC3C,GAAI,IAAe,GAAqB,IAAW,EAAY,KAAK,gBAAgB,YAAa,CAAS,EAC3G,EACE,KAAK,CAAC,IAAO,CACZ,GAAI,IAAe,GAAqB,IAAW,EAAY,EAAU,EACpE,KAAK,EAAW,wBAAwB,CAAE,EAAE,MAAM,IAAM,EAAE,EAChE,EACA,MAAM,IAAM,KAAK,gBAAgB,WAAW,CAAC,GAE5C,EAAQ,IAAY,CACnB,GAAQ,OAAO,EAAE,MAAM,IAAM,EAAE,GAEtC,EAAO,iBAAiB,QAAS,CAAK,EACtC,IAAM,EAAc,EAAO,UAAU,CAAM,EAC3C,EAAO,EACP,KAAK,QAAU,IAAM,CACnB,EAAY,EACZ,EAAO,oBAAoB,QAAS,CAAK,EACzC,EAAa,GAGnB,CC1DA,IAAM,GAAW,CACf,CAAC,eAAgB,EAAkB,EACnC,CAAC,aAAc,EAAS,EACxB,CAAC,oBAAqB,EAAe,EACrC,CAAC,oBAAqB,EAAe,EACrC,CAAC,0BAA2B,EAAqB,EACjD,CAAC,mBAAoB,EAAc,EACnC,CAAC,qBAAsB,EAAgB,EACvC,CAAC,mBAAoB,EAAc,EACnC,CAAC,uBAAwB,EAAkB,EAC3C,CAAC,qBAAsB,EAAgB,EACvC,CAAC,qBAAsB,EAAgB,EACvC,CAAC,sBAAuB,EAAiB,EACzC,CAAC,sBAAuB,EAAiB,EACzC,CAAC,oBAAqB,EAAe,EACrC,CAAC,6BAA8B,EAAuB,EACtD,CAAC,uBAAwB,EAAkB,EAC3C,CAAC,oBAAqB,EAAe,EACrC,CAAC,sBAAuB,EAAiB,EACzC,CAAC,+BAAgC,EAAyB,CAC5D,EAMO,SAAS,EAAgB,EAAS,CACvC,QAAY,EAAM,KAAS,GACzB,GAAI,CAAC,eAAe,IAAI,CAAI,EAC1B,eAAe,OAAO,EAAM,CAAI,EClDtC,GAAI,OAAO,OAAW,IACpB,GAAiB,EAChB,OAAe,YAAc,CAC5B,gBACA,SACA,eACA,aACA,gBACA,kBACA,cACA,eACA,cACA,eACA,OACA,KACA,UACA,uBACA,oBACA,QAAS,CACP,iBACA,kBACA,aACA,aACA,sBACA,sBACA,mBACA,mBACA,mBACA,kBACF,CACF",
|
|
52
|
+
"debugId": "918E637EBFE6D21164756E2164756E21",
|
|
53
|
+
"names": []
|
|
54
|
+
}
|