boss-web-player-sdk 1.1.21 → 1.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boss-web-player-sdk.css +1 -1
- package/dist/index.cjs +5 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1171 -1074
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode"],"sources":["../../ui-controls/src/ErrorScreen.tsx","../../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.mjs","../../../node_modules/lucide-react/dist/esm/defaultAttributes.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.mjs","../../../node_modules/lucide-react/dist/esm/context.mjs","../../../node_modules/lucide-react/dist/esm/Icon.mjs","../../../node_modules/lucide-react/dist/esm/createLucideIcon.mjs","../../../node_modules/lucide-react/dist/esm/icons/airplay.mjs","../../../node_modules/lucide-react/dist/esm/icons/captions.mjs","../../../node_modules/lucide-react/dist/esm/icons/cast.mjs","../../../node_modules/lucide-react/dist/esm/icons/check.mjs","../../../node_modules/lucide-react/dist/esm/icons/chevron-left.mjs","../../../node_modules/lucide-react/dist/esm/icons/external-link.mjs","../../../node_modules/lucide-react/dist/esm/icons/gauge.mjs","../../../node_modules/lucide-react/dist/esm/icons/languages.mjs","../../../node_modules/lucide-react/dist/esm/icons/list-video.mjs","../../../node_modules/lucide-react/dist/esm/icons/maximize.mjs","../../../node_modules/lucide-react/dist/esm/icons/minimize.mjs","../../../node_modules/lucide-react/dist/esm/icons/pause.mjs","../../../node_modules/lucide-react/dist/esm/icons/picture-in-picture.mjs","../../../node_modules/lucide-react/dist/esm/icons/play.mjs","../../../node_modules/lucide-react/dist/esm/icons/rotate-ccw.mjs","../../../node_modules/lucide-react/dist/esm/icons/rotate-cw.mjs","../../../node_modules/lucide-react/dist/esm/icons/volume-1.mjs","../../../node_modules/lucide-react/dist/esm/icons/volume-2.mjs","../../../node_modules/lucide-react/dist/esm/icons/volume-x.mjs","../../../node_modules/lucide-react/dist/esm/icons/volume.mjs","../../ui-controls/src/WebVideoPlayer.tsx","../src/api.ts","../src/delegate.ts","../src/BossVideoPlayer.tsx","../src/create-player.ts"],"sourcesContent":["interface ErrorScreenProps {\r\n title?: string;\r\n subtitle?: string;\r\n onRetry: () => void;\r\n}\r\n\r\nexport function ErrorScreen({\r\n title = 'Something Went Wrong',\r\n subtitle = 'We encountered an error while playing this video.\\nPlease try again.',\r\n onRetry,\r\n}: ErrorScreenProps) {\r\n return (\r\n <>\r\n <div style={{\r\n width: 80, height: 80, borderRadius: '50%',\r\n background: 'rgba(255,255,255,0.08)',\r\n display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,\r\n }}>\r\n <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"rgba(255,255,255,0.85)\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/>\r\n <line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\r\n </svg>\r\n </div>\r\n <div style={{ textAlign: 'center', padding: '0 24px' }}>\r\n <p style={{ color: '#ffffff', fontSize: 22, fontWeight: 700, margin: '0 0 8px', letterSpacing: '-0.2px' }}>\r\n {title}\r\n </p>\r\n <p style={{ color: 'rgba(255,255,255,0.5)', fontSize: 14, margin: 0, lineHeight: 1.6, whiteSpace: 'pre-line' }}>\r\n {subtitle}\r\n </p>\r\n </div>\r\n <button\r\n onClick={onRetry}\r\n style={{\r\n display: 'flex', alignItems: 'center', gap: 10,\r\n background: '#ffffff', color: '#09090e',\r\n border: 'none', borderRadius: 10, padding: '14px 32px',\r\n fontSize: 16, fontWeight: 700, cursor: 'pointer', letterSpacing: '-0.1px',\r\n }}\r\n >\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 .49-3.51\"/>\r\n </svg>\r\n Retry Now\r\n </button>\r\n </>\r\n );\r\n}\r\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst mergeClasses = (...classes) => classes.filter((className, index, array) => {\n return Boolean(className) && className.trim() !== \"\" && array.indexOf(className) === index;\n}).join(\" \").trim();\n\nexport { mergeClasses };\n//# sourceMappingURL=mergeClasses.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n\nexport { toKebabCase };\n//# sourceMappingURL=toKebabCase.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst toCamelCase = (string) => string.replace(\n /^([A-Z])|[\\s-_]+(\\w)/g,\n (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()\n);\n\nexport { toCamelCase };\n//# sourceMappingURL=toCamelCase.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { toCamelCase } from './toCamelCase.mjs';\n\nconst toPascalCase = (string) => {\n const camelCase = toCamelCase(string);\n return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);\n};\n\nexport { toPascalCase };\n//# sourceMappingURL=toPascalCase.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nvar defaultAttributes = {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: 24,\n height: 24,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 2,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\"\n};\n\nexport { defaultAttributes as default };\n//# sourceMappingURL=defaultAttributes.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst hasA11yProp = (props) => {\n for (const prop in props) {\n if (prop.startsWith(\"aria-\") || prop === \"role\" || prop === \"title\") {\n return true;\n }\n }\n return false;\n};\n\nexport { hasA11yProp };\n//# sourceMappingURL=hasA11yProp.mjs.map\n","\"use strict\";\n\"use client\";\n/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { createContext, useContext, useMemo, createElement } from 'react';\n\nconst LucideContext = createContext({});\nfunction LucideProvider({\n children,\n size,\n color,\n strokeWidth,\n absoluteStrokeWidth,\n className\n}) {\n const value = useMemo(\n () => ({\n size,\n color,\n strokeWidth,\n absoluteStrokeWidth,\n className\n }),\n [size, color, strokeWidth, absoluteStrokeWidth, className]\n );\n return createElement(LucideContext.Provider, { value }, children);\n}\nconst useLucideContext = () => useContext(LucideContext);\n\nexport { LucideProvider, useLucideContext };\n//# sourceMappingURL=context.mjs.map\n","\"use strict\";\n\"use client\";\n/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { forwardRef, createElement } from 'react';\nimport defaultAttributes from './defaultAttributes.mjs';\nimport { hasA11yProp } from './shared/src/utils/hasA11yProp.mjs';\nimport { mergeClasses } from './shared/src/utils/mergeClasses.mjs';\nimport { useLucideContext } from './context.mjs';\n\nconst Icon = forwardRef(\n ({ color, size, strokeWidth, absoluteStrokeWidth, className = \"\", children, iconNode, ...rest }, ref) => {\n const {\n size: contextSize = 24,\n strokeWidth: contextStrokeWidth = 2,\n absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,\n color: contextColor = \"currentColor\",\n className: contextClass = \"\"\n } = useLucideContext() ?? {};\n const calculatedStrokeWidth = absoluteStrokeWidth ?? contextAbsoluteStrokeWidth ? Number(strokeWidth ?? contextStrokeWidth) * 24 / Number(size ?? contextSize) : strokeWidth ?? contextStrokeWidth;\n return createElement(\n \"svg\",\n {\n ref,\n ...defaultAttributes,\n width: size ?? contextSize ?? defaultAttributes.width,\n height: size ?? contextSize ?? defaultAttributes.height,\n stroke: color ?? contextColor,\n strokeWidth: calculatedStrokeWidth,\n className: mergeClasses(\"lucide\", contextClass, className),\n ...!children && !hasA11yProp(rest) && { \"aria-hidden\": \"true\" },\n ...rest\n },\n [\n ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),\n ...Array.isArray(children) ? children : [children]\n ]\n );\n }\n);\n\nexport { Icon as default };\n//# sourceMappingURL=Icon.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { forwardRef, createElement } from 'react';\nimport { mergeClasses } from './shared/src/utils/mergeClasses.mjs';\nimport { toKebabCase } from './shared/src/utils/toKebabCase.mjs';\nimport { toPascalCase } from './shared/src/utils/toPascalCase.mjs';\nimport Icon from './Icon.mjs';\n\nconst createLucideIcon = (iconName, iconNode) => {\n const Component = forwardRef(\n ({ className, ...props }, ref) => createElement(Icon, {\n ref,\n iconNode,\n className: mergeClasses(\n `lucide-${toKebabCase(toPascalCase(iconName))}`,\n `lucide-${iconName}`,\n className\n ),\n ...props\n })\n );\n Component.displayName = toPascalCase(iconName);\n return Component;\n};\n\nexport { createLucideIcon as default };\n//# sourceMappingURL=createLucideIcon.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1\",\n key: \"ns4c3b\"\n }\n ],\n [\"path\", { d: \"m12 15 5 6H7Z\", key: \"14qnn2\" }]\n];\nconst Airplay = createLucideIcon(\"airplay\", __iconNode);\n\nexport { __iconNode, Airplay as default };\n//# sourceMappingURL=airplay.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"rect\", { width: \"18\", height: \"14\", x: \"3\", y: \"5\", rx: \"2\", ry: \"2\", key: \"12ruh7\" }],\n [\"path\", { d: \"M7 15h4M15 15h2M7 11h2M13 11h4\", key: \"1ueiar\" }]\n];\nconst Captions = createLucideIcon(\"captions\", __iconNode);\n\nexport { __iconNode, Captions as default };\n//# sourceMappingURL=captions.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6\", key: \"3zrzxg\" }],\n [\"path\", { d: \"M2 12a9 9 0 0 1 8 8\", key: \"g6cvee\" }],\n [\"path\", { d: \"M2 16a5 5 0 0 1 4 4\", key: \"1y1dii\" }],\n [\"line\", { x1: \"2\", x2: \"2.01\", y1: \"20\", y2: \"20\", key: \"xu2jvo\" }]\n];\nconst Cast = createLucideIcon(\"cast\", __iconNode);\n\nexport { __iconNode, Cast as default };\n//# sourceMappingURL=cast.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [[\"path\", { d: \"M20 6 9 17l-5-5\", key: \"1gmf2c\" }]];\nconst Check = createLucideIcon(\"check\", __iconNode);\n\nexport { __iconNode, Check as default };\n//# sourceMappingURL=check.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [[\"path\", { d: \"m15 18-6-6 6-6\", key: \"1wnfg3\" }]];\nconst ChevronLeft = createLucideIcon(\"chevron-left\", __iconNode);\n\nexport { __iconNode, ChevronLeft as default };\n//# sourceMappingURL=chevron-left.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M15 3h6v6\", key: \"1q9fwt\" }],\n [\"path\", { d: \"M10 14 21 3\", key: \"gplh6r\" }],\n [\"path\", { d: \"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\", key: \"a6xqqp\" }]\n];\nconst ExternalLink = createLucideIcon(\"external-link\", __iconNode);\n\nexport { __iconNode, ExternalLink as default };\n//# sourceMappingURL=external-link.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"m12 14 4-4\", key: \"9kzdfg\" }],\n [\"path\", { d: \"M3.34 19a10 10 0 1 1 17.32 0\", key: \"19p75a\" }]\n];\nconst Gauge = createLucideIcon(\"gauge\", __iconNode);\n\nexport { __iconNode, Gauge as default };\n//# sourceMappingURL=gauge.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"m5 8 6 6\", key: \"1wu5hv\" }],\n [\"path\", { d: \"m4 14 6-6 2-3\", key: \"1k1g8d\" }],\n [\"path\", { d: \"M2 5h12\", key: \"or177f\" }],\n [\"path\", { d: \"M7 2h1\", key: \"1t2jsx\" }],\n [\"path\", { d: \"m22 22-5-10-5 10\", key: \"don7ne\" }],\n [\"path\", { d: \"M14 18h6\", key: \"1m8k6r\" }]\n];\nconst Languages = createLucideIcon(\"languages\", __iconNode);\n\nexport { __iconNode, Languages as default };\n//# sourceMappingURL=languages.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M21 5H3\", key: \"1fi0y6\" }],\n [\"path\", { d: \"M10 12H3\", key: \"1ulcyk\" }],\n [\"path\", { d: \"M10 19H3\", key: \"108z41\" }],\n [\n \"path\",\n {\n d: \"M15 12.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997a1 1 0 0 1-1.517-.86z\",\n key: \"ms4nik\"\n }\n ]\n];\nconst ListVideo = createLucideIcon(\"list-video\", __iconNode);\n\nexport { __iconNode, ListVideo as default };\n//# sourceMappingURL=list-video.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M8 3H5a2 2 0 0 0-2 2v3\", key: \"1dcmit\" }],\n [\"path\", { d: \"M21 8V5a2 2 0 0 0-2-2h-3\", key: \"1e4gt3\" }],\n [\"path\", { d: \"M3 16v3a2 2 0 0 0 2 2h3\", key: \"wsl5sc\" }],\n [\"path\", { d: \"M16 21h3a2 2 0 0 0 2-2v-3\", key: \"18trek\" }]\n];\nconst Maximize = createLucideIcon(\"maximize\", __iconNode);\n\nexport { __iconNode, Maximize as default };\n//# sourceMappingURL=maximize.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M8 3v3a2 2 0 0 1-2 2H3\", key: \"hohbtr\" }],\n [\"path\", { d: \"M21 8h-3a2 2 0 0 1-2-2V3\", key: \"5jw1f3\" }],\n [\"path\", { d: \"M3 16h3a2 2 0 0 1 2 2v3\", key: \"198tvr\" }],\n [\"path\", { d: \"M16 21v-3a2 2 0 0 1 2-2h3\", key: \"ph8mxp\" }]\n];\nconst Minimize = createLucideIcon(\"minimize\", __iconNode);\n\nexport { __iconNode, Minimize as default };\n//# sourceMappingURL=minimize.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"rect\", { x: \"14\", y: \"3\", width: \"5\", height: \"18\", rx: \"1\", key: \"kaeet6\" }],\n [\"rect\", { x: \"5\", y: \"3\", width: \"5\", height: \"18\", rx: \"1\", key: \"1wsw3u\" }]\n];\nconst Pause = createLucideIcon(\"pause\", __iconNode);\n\nexport { __iconNode, Pause as default };\n//# sourceMappingURL=pause.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M2 10h6V4\", key: \"zwrco\" }],\n [\"path\", { d: \"m2 4 6 6\", key: \"ug085t\" }],\n [\"path\", { d: \"M21 10V7a2 2 0 0 0-2-2h-7\", key: \"git5jr\" }],\n [\"path\", { d: \"M3 14v2a2 2 0 0 0 2 2h3\", key: \"1f7fh3\" }],\n [\"rect\", { x: \"12\", y: \"14\", width: \"10\", height: \"7\", rx: \"1\", key: \"1wjs3o\" }]\n];\nconst PictureInPicture = createLucideIcon(\"picture-in-picture\", __iconNode);\n\nexport { __iconNode, PictureInPicture as default };\n//# sourceMappingURL=picture-in-picture.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\",\n key: \"10ikf1\"\n }\n ]\n];\nconst Play = createLucideIcon(\"play\", __iconNode);\n\nexport { __iconNode, Play as default };\n//# sourceMappingURL=play.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\", key: \"1357e3\" }],\n [\"path\", { d: \"M3 3v5h5\", key: \"1xhq8a\" }]\n];\nconst RotateCcw = createLucideIcon(\"rotate-ccw\", __iconNode);\n\nexport { __iconNode, RotateCcw as default };\n//# sourceMappingURL=rotate-ccw.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8\", key: \"1p45f6\" }],\n [\"path\", { d: \"M21 3v5h-5\", key: \"1q7to0\" }]\n];\nconst RotateCw = createLucideIcon(\"rotate-cw\", __iconNode);\n\nexport { __iconNode, RotateCw as default };\n//# sourceMappingURL=rotate-cw.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z\",\n key: \"uqj9uw\"\n }\n ],\n [\"path\", { d: \"M16 9a5 5 0 0 1 0 6\", key: \"1q6k2b\" }]\n];\nconst Volume1 = createLucideIcon(\"volume-1\", __iconNode);\n\nexport { __iconNode, Volume1 as default };\n//# sourceMappingURL=volume-1.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z\",\n key: \"uqj9uw\"\n }\n ],\n [\"path\", { d: \"M16 9a5 5 0 0 1 0 6\", key: \"1q6k2b\" }],\n [\"path\", { d: \"M19.364 18.364a9 9 0 0 0 0-12.728\", key: \"ijwkga\" }]\n];\nconst Volume2 = createLucideIcon(\"volume-2\", __iconNode);\n\nexport { __iconNode, Volume2 as default };\n//# sourceMappingURL=volume-2.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z\",\n key: \"uqj9uw\"\n }\n ],\n [\"line\", { x1: \"22\", x2: \"16\", y1: \"9\", y2: \"15\", key: \"1ewh16\" }],\n [\"line\", { x1: \"16\", x2: \"22\", y1: \"9\", y2: \"15\", key: \"5ykzw1\" }]\n];\nconst VolumeX = createLucideIcon(\"volume-x\", __iconNode);\n\nexport { __iconNode, VolumeX as default };\n//# sourceMappingURL=volume-x.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z\",\n key: \"uqj9uw\"\n }\n ]\n];\nconst Volume = createLucideIcon(\"volume\", __iconNode);\n\nexport { __iconNode, Volume as default };\n//# sourceMappingURL=volume.mjs.map\n","import React, { useRef, useState, useEffect, useMemo, useCallback } from 'react';\r\nimport { createPortal } from 'react-dom';\r\nimport { ErrorScreen } from './ErrorScreen';\r\nimport Hls from 'hls.js';\r\nimport {\r\n Play,\r\n Pause,\r\n RotateCcw,\r\n RotateCw,\r\n Volume2,\r\n VolumeX,\r\n Volume1,\r\n Volume,\r\n Subtitles,\r\n Maximize,\r\n Minimize,\r\n Cast,\r\n Airplay,\r\n ListVideo,\r\n Check,\r\n ChevronLeft,\r\n PictureInPicture,\r\n Languages,\r\n Gauge,\r\n ExternalLink,\r\n} from 'lucide-react';\r\nimport { VideoPlayerSDK } from '@goboss/core-player';\r\nimport { FirebaseAnalyticsProvider } from '@goboss/analytics';\r\nimport type { FirebaseConfig } from '@goboss/analytics';\r\nimport type { VideoQuality, Chapter, KeyMoment, SubtitleTrack, AnalyticsEvent, AnalyticsSessionSnapshot, SourceItem } from '@goboss/types';\r\nimport './player.css';\r\n\r\n// How long to wait for a switched-to source to actually start playing before\r\n// assuming the format is unsupported and falling back to the first source.\r\nconst SOURCE_FALLBACK_TIMEOUT_MS = 10000;\r\n\r\n// Guard so the \"missing stylesheet\" hint is logged at most once per session,\r\n// even if several players mount.\r\nlet cssWarningShown = false;\r\n\r\n// Self-contained stylesheet for the custom Document Picture-in-Picture window.\r\n// The PiP window is a separate `document` with no access to the host page's\r\n// stylesheets, so its controls need their own rules injected directly.\r\nconst PIP_STYLES = `\r\n html, body { margin: 0; padding: 0; height: 100%; background: #000; overflow: hidden; }\r\n * { box-sizing: border-box; }\r\n .gb-pip-video { width: 100%; height: 100%; object-fit: contain; display: block; background: #000; }\r\n .gb-pip-overlay {\r\n position: absolute; inset: 0; z-index: 2;\r\n display: flex; flex-direction: column; justify-content: space-between;\r\n font-family: system-ui, -apple-system, sans-serif; color: #fff;\r\n opacity: 0; transition: opacity 0.15s ease;\r\n background: linear-gradient(to bottom, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0) 25%, rgba(0,0,0,0) 65%, rgba(0,0,0,0.55) 100%);\r\n }\r\n .gb-pip-overlay:hover, .gb-pip-overlay.gb-pip-paused { opacity: 1; }\r\n .gb-pip-center {\r\n flex: 1; display: flex; align-items: center; justify-content: center; gap: 28px;\r\n }\r\n .gb-pip-btn {\r\n background: rgba(0,0,0,0.35); border: none; color: #fff; border-radius: 50%;\r\n width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;\r\n cursor: pointer; padding: 0;\r\n }\r\n .gb-pip-btn:hover { background: rgba(0,0,0,0.55); }\r\n .gb-pip-btn.gb-pip-play { width: 56px; height: 56px; }\r\n .gb-pip-bottom { padding: 6px 10px 8px; display: flex; flex-direction: column; gap: 4px; }\r\n .gb-pip-progress { position: relative; height: 4px; border-radius: 2px; background: rgba(255,255,255,0.3); cursor: pointer; }\r\n .gb-pip-progress-fill { position: absolute; inset: 0; width: 0%; border-radius: 2px; background: #fff; }\r\n .gb-pip-time { font-size: 11px; font-variant-numeric: tabular-nums; opacity: 0.9; }\r\n`;\r\n\r\nconst extractTitleFromManifest = (text: string): string | null => {\r\n const titleTag = text.match(/#EXT-X-TITLE:(.+)/);\r\n if (titleTag?.[1]?.trim()) return titleTag[1].trim();\r\n const sessionData = text.match(/#EXT-X-SESSION-DATA:[^\\n]*DATA-ID=\"[^\"]*title[^\"]*\"[^\\n]*VALUE=\"([^\"]+)\"/i);\r\n if (sessionData?.[1]?.trim()) return sessionData[1].trim();\r\n return null;\r\n};\r\n\r\nconst parseID3Title = (data: Uint8Array): string | null => {\r\n if (!data || data.length < 10) return null;\r\n if (data[0] !== 0x49 || data[1] !== 0x44 || data[2] !== 0x33) return null;\r\n const version = data[3];\r\n const totalSize = ((data[6] & 0x7f) << 21) | ((data[7] & 0x7f) << 14) | ((data[8] & 0x7f) << 7) | (data[9] & 0x7f);\r\n const end = Math.min(10 + totalSize, data.length);\r\n let offset = 10;\r\n while (offset + 10 <= end) {\r\n const id = String.fromCharCode(data[offset], data[offset+1], data[offset+2], data[offset+3]);\r\n const frameSize = version >= 4\r\n ? ((data[offset+4] & 0x7f) << 21) | ((data[offset+5] & 0x7f) << 14) | ((data[offset+6] & 0x7f) << 7) | (data[offset+7] & 0x7f)\r\n : (data[offset+4] << 24) | (data[offset+5] << 16) | (data[offset+6] << 8) | data[offset+7];\r\n if (frameSize <= 0 || offset + 10 + frameSize > end) break;\r\n if (id === 'TIT2') {\r\n const enc = data[offset + 10];\r\n const raw = data.slice(offset + 11, offset + 10 + frameSize);\r\n const decoded = enc === 1 || enc === 2\r\n ? new TextDecoder('utf-16').decode(raw)\r\n : new TextDecoder(enc === 3 ? 'utf-8' : 'latin1').decode(raw);\r\n const result = decoded.replace(/\\0/g, '').trim();\r\n if (result) return result;\r\n }\r\n offset += 10 + frameSize;\r\n }\r\n return null;\r\n};\r\n\r\n\r\nconst HdIcon = ({ size = 20 }: { size?: number }) => (\r\n <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <rect x=\"2\" y=\"5\" width=\"20\" height=\"14\" rx=\"3\" />\r\n <path d=\"M7 9v6M7 12h3M10 9v6\" />\r\n <path d=\"M14 9h1.5a3 3 0 0 1 0 6H14z\" />\r\n </svg>\r\n);\r\n\r\n// Simple \"HD\" badge — used for the multi-source menu\r\nconst MultiSourceHdIcon = ({ size = 20 }: { size?: number }) => (\r\n <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <rect x=\"2\" y=\"5\" width=\"20\" height=\"14\" rx=\"3\" />\r\n <path d=\"M7 9v6M7 12h3M10 9v6\" />\r\n <path d=\"M14 9h1.5a3 3 0 0 1 0 6H14z\" />\r\n </svg>\r\n);\r\n\r\nconst hexToRgb = (hex: string): string => {\r\n const cleanHex = hex.replace('#', '');\r\n if (cleanHex.length === 6) {\r\n const r = parseInt(cleanHex.substring(0, 2), 16);\r\n const g = parseInt(cleanHex.substring(2, 4), 16);\r\n const b = parseInt(cleanHex.substring(4, 6), 16);\r\n return `${r}, ${g}, ${b}`;\r\n } else if (cleanHex.length === 3) {\r\n const r = parseInt(cleanHex[0] + cleanHex[0], 16);\r\n const g = parseInt(cleanHex[1] + cleanHex[1], 16);\r\n const b = parseInt(cleanHex[2] + cleanHex[2], 16);\r\n return `${r}, ${g}, ${b}`;\r\n }\r\n return '99, 102, 241';\r\n};\r\n\r\nconst languageNameMap: Record<string, string> = {\r\n en: 'English',\r\n eng: 'English',\r\n fr: 'French',\r\n fre: 'French',\r\n fra: 'French',\r\n es: 'Spanish',\r\n spa: 'Spanish',\r\n de: 'German',\r\n ger: 'German',\r\n deu: 'German',\r\n it: 'Italian',\r\n ita: 'Italian',\r\n pt: 'Portuguese',\r\n por: 'Portuguese',\r\n ja: 'Japanese',\r\n zh: 'Chinese',\r\n ko: 'Korean',\r\n ru: 'Russian',\r\n ar: 'Arabic',\r\n hi: 'Hindi',\r\n ta: 'Tamil',\r\n tam: 'Tamil',\r\n};\r\n\r\nconst formatLanguageName = (value: string): string | null => {\r\n if (!value) return null;\r\n const normalized = value.trim().toLowerCase();\r\n const prefixMatch = normalized.match(/^(en|eng|fr|fre|fra|es|spa|de|ger|deu|it|ita|pt|por|ja|zh|ko|ru|ar|hi|ta|tam)/);\r\n if (prefixMatch && languageNameMap[prefixMatch[0]]) {\r\n return languageNameMap[prefixMatch[0]];\r\n }\r\n for (const [key, name] of Object.entries(languageNameMap)) {\r\n if (normalized.includes(key)) {\r\n return name;\r\n }\r\n }\r\n if (/english|anglais|ingles/i.test(normalized)) return 'English';\r\n if (/french|français/i.test(normalized)) return 'French';\r\n if (/spanish|español/i.test(normalized)) return 'Spanish';\r\n if (/german|deutsch/i.test(normalized)) return 'German';\r\n if (/italian|italiano/i.test(normalized)) return 'Italian';\r\n if (/portuguese|português/i.test(normalized)) return 'Portuguese';\r\n if (/japanese|nihongo/i.test(normalized)) return 'Japanese';\r\n if (/chinese|中文/i.test(normalized)) return 'Chinese';\r\n if (/korean|한국어/i.test(normalized)) return 'Korean';\r\n if (/tamil|தமிழ்/i.test(normalized)) return 'Tamil';\r\n if (/russian|русский/i.test(normalized)) return 'Russian';\r\n if (/arabic|العربية/i.test(normalized)) return 'Arabic';\r\n if (/hindi|हिन्दी/i.test(normalized)) return 'Hindi';\r\n return null;\r\n};\r\n\r\nconst getLanguageLabel = (track: { label: string; lang: string }): string => {\r\n if (track.lang) {\r\n const normalized = formatLanguageName(track.lang);\r\n if (normalized) return normalized;\r\n }\r\n\r\n if (track.label) {\r\n const label = track.label.trim();\r\n const parts = label.split(/[#\\d\\.\\s\\-_/|:]+/).filter(Boolean);\r\n for (let i = parts.length - 1; i >= 0; i -= 1) {\r\n const candidate = formatLanguageName(parts[i]);\r\n if (candidate) return candidate;\r\n }\r\n const cleaned = label.replace(/^[#\\d\\.\\s]+/, '').trim();\r\n return formatLanguageName(cleaned) || cleaned;\r\n }\r\n\r\n return 'Unknown';\r\n};\r\n\r\nconst dedupeSubtitleTracks = (tracks: SubtitleTrack[]): SubtitleTrack[] => {\r\n const seen = new Set<string>();\r\n return tracks.filter((track) => {\r\n const label = getLanguageLabel(track).toLowerCase();\r\n const lang = (track.lang || '').trim().toLowerCase();\r\n const key = `${label}|${lang}`;\r\n if (seen.has(key)) {\r\n return false;\r\n }\r\n seen.add(key);\r\n return true;\r\n });\r\n};\r\n\r\ninterface SubtitleStyle {\r\n size?: 'small' | 'medium' | 'large';\r\n background?: 'none' | 'dim' | 'solid';\r\n color?: string;\r\n}\r\n\r\ninterface OverlayAdItem {\r\n availId: string;\r\n startTimeSeconds: number;\r\n durationSeconds: number;\r\n imageUrl: string;\r\n imageWidth: number;\r\n imageHeight: number;\r\n title: string;\r\n clickThrough: string;\r\n clickTrackingUrl: string;\r\n impressionBeacons: string[];\r\n position: string;\r\n dismissible: boolean;\r\n}\r\n\r\nconst parseHHMMSS = (t: string): number => {\r\n const parts = (t ?? '').split(':').map(Number);\r\n return (parts[0] || 0) * 3600 + (parts[1] || 0) * 60 + (parts[2] || 0);\r\n};\r\n\r\nconst isWebUrl = (url: string | undefined | null): url is string => {\r\n if (!url) return false;\r\n try {\r\n const { protocol } = new URL(url, window.location.href);\r\n return protocol === 'http:' || protocol === 'https:';\r\n } catch {\r\n return false;\r\n }\r\n};\r\n\r\nconst parseSkipOffsetSeconds = (value: unknown): number | null => {\r\n if (typeof value === 'number') return Number.isFinite(value) && value >= 0 ? value : null;\r\n if (typeof value !== 'string') return null;\r\n const trimmed = value.trim();\r\n if (!trimmed) return null;\r\n if (/^\\d+:\\d{1,2}:\\d{1,2}(\\.\\d+)?$/.test(trimmed)) return parseHHMMSS(trimmed);\r\n const seconds = Number(trimmed);\r\n return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;\r\n};\r\n\r\nconst AD_METADATA_POLL_DELAYS_MS = [0, 1000, 2000, 3000, 5000, 8000];\r\n\r\nfunction pollAdMetadata<T>(\r\n url: string,\r\n extract: (data: any) => T | null,\r\n onResolved: (value: T) => void\r\n): () => void {\r\n let cancelled = false;\r\n let timer: ReturnType<typeof setTimeout> | undefined;\r\n\r\n const attempt = (index: number) => {\r\n timer = setTimeout(() => {\r\n if (cancelled) return;\r\n fetch(url)\r\n .then(r => r.json())\r\n .then(data => {\r\n if (cancelled) return;\r\n const value = extract(data);\r\n if (value !== null) {\r\n onResolved(value);\r\n return;\r\n }\r\n if (index + 1 < AD_METADATA_POLL_DELAYS_MS.length) attempt(index + 1);\r\n })\r\n .catch(() => {\r\n if (cancelled) return;\r\n if (index + 1 < AD_METADATA_POLL_DELAYS_MS.length) attempt(index + 1);\r\n });\r\n }, AD_METADATA_POLL_DELAYS_MS[index]);\r\n };\r\n\r\n attempt(0);\r\n return () => {\r\n cancelled = true;\r\n if (timer) clearTimeout(timer);\r\n };\r\n}\r\n\r\ninterface SeekAttribution {\r\n eventType: 'fast_forward' | 'fast_backward' | 'key_moments';\r\n skipSeconds?: number;\r\n momentTitle?: string;\r\n momentTime?: number;\r\n}\r\n\r\ninterface AdCtaInfo {\r\n text: string;\r\n url: string;\r\n title?: string;\r\n description?: string;\r\n}\r\n\r\ninterface AdPodItem {\r\n startTimeSeconds: number;\r\n endTimeSeconds: number;\r\n durationSeconds: number;\r\n skippable: boolean;\r\n skipOffsetSeconds: number;\r\n cta: AdCtaInfo | null;\r\n podIndex: number;\r\n podTotal: number;\r\n}\r\n\r\ninterface WebVideoPlayerProps {\r\n src: string;\r\n autoplay?: boolean;\r\n muted?: boolean;\r\n chapters?: Chapter[];\r\n keyMoments?: KeyMoment[];\r\n subtitles?: SubtitleTrack[];\r\n watermark?: string;\r\n themeColor?: string;\r\n drmConfig?: any;\r\n firebaseConfig?: FirebaseConfig;\r\n onAnalyticsEvent?: (event: AnalyticsEvent) => void;\r\n onAdBreakChange?: (active: boolean) => void;\r\n onKeyMomentsLoaded?: (keyMoments: KeyMoment[]) => void;\r\n enableControls?: boolean;\r\n enableChromecast?: boolean;\r\n enableKeyMoments?: boolean;\r\n enableSubtitles?: boolean;\r\n enableQuality?: boolean;\r\n enablePlaybackSpeed?: boolean;\r\n enablePiP?: boolean;\r\n enableFullscreen?: boolean;\r\n autoFullscreen?: boolean;\r\n watchProgressInterval?: number; // in seconds, default 30\r\n subtitleStyle?: SubtitleStyle;\r\n /**\r\n * Pre-resolved entitlement package from BossVideoPlayer.\r\n * When provided, features are determined exclusively by this package (API mode).\r\n * When absent, features fall back to explicit props with true defaults (standalone mode).\r\n */\r\n resolvedEntitlement?: { package: string; is_active: boolean; features: Record<string, boolean>; limits: { streaming_minutes: number } } | null;\r\n licenseKey?: string | null;\r\n title?: string;\r\n chaptersDisplayMode?: 'strip' | 'panel';\r\n volumeControlType?: 'slider' | 'button';\r\n urlTransformer?: (url: string) => string;\r\n pauseAdOnTabSwitch?: boolean;\r\n enableSkipAd?: boolean;\r\n skipAdAfterSeconds?: number;\r\n overlayAdUrl?: string;\r\n adTrackingUrl?: string;\r\n videoId?: string;\r\n sources?: SourceItem[];\r\n activeSourceIndex?: number;\r\n onSourceChange?: (index: number) => void;\r\n /** Seek to this position (in seconds) as soon as the video is ready. Use for resume-playback. */\r\n initialTime?: number;\r\n /** Called on every timeupdate with the current playback position in seconds. Use to persist watch progress. */\r\n onTimeUpdate?: (currentTime: number) => void;\r\n}\r\n\r\nexport const WebVideoPlayer: React.FC<WebVideoPlayerProps> = ({\r\n src,\r\n autoplay = false,\r\n muted = false,\r\n chapters = [],\r\n keyMoments,\r\n subtitles = [],\r\n watermark,\r\n themeColor = '#6366f1',\r\n drmConfig,\r\n firebaseConfig,\r\n videoId,\r\n onAnalyticsEvent,\r\n onAdBreakChange,\r\n onKeyMomentsLoaded,\r\n enableControls = true,\r\n enableChromecast,\r\n enableKeyMoments,\r\n enableSubtitles,\r\n enableQuality,\r\n enablePlaybackSpeed,\r\n enablePiP,\r\n enableFullscreen = true,\r\n autoFullscreen = false,\r\n watchProgressInterval = 30,\r\n subtitleStyle = {},\r\n resolvedEntitlement,\r\n licenseKey,\r\n title,\r\n chaptersDisplayMode = 'strip',\r\n volumeControlType = 'slider',\r\n urlTransformer,\r\n pauseAdOnTabSwitch = true,\r\n enableSkipAd = false,\r\n skipAdAfterSeconds = 5,\r\n overlayAdUrl,\r\n adTrackingUrl,\r\n sources,\r\n activeSourceIndex = 0,\r\n onSourceChange,\r\n initialTime,\r\n onTimeUpdate,\r\n}) => {\r\n const transformUrl = urlTransformer ?? ((url: string) => url);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const firebaseProviderRef = useRef<FirebaseAnalyticsProvider | null>(null);\r\n const hasAutoFullscreenedRef = useRef(false);\r\n const seekbarRef = useRef<HTMLDivElement>(null);\r\n const seekbarWidthRef = useRef(0);\r\n const canvasRef = useRef<HTMLCanvasElement>(null);\r\n const hiddenVideoRef = useRef<HTMLVideoElement | null>(null);\r\n const hiddenHlsRef = useRef<Hls | null>(null);\r\n const hoverSeekTimer = useRef<number | null>(null);\r\n const isScrubbing = useRef(false);\r\n const isSeeking = useRef(false);\r\n const seekTargetTime = useRef<number | null>(null);\r\n const preSeekPositionRef = useRef<number | null>(null);\r\n const seekGuardTimeout = useRef<number | null>(null);\r\n const castButtonRef = useRef<HTMLDivElement>(null);\r\n const toggleFullscreenRef = useRef<() => void>(() => {});\r\n const adSkipCoverRef = useRef<HTMLDivElement | null>(null);\r\n const adSkipCoverUpRef = useRef(false);\r\n // Force-mute bookkeeping for the ad-skip cover: the cover only hides pixels, so\r\n // audio from the covered frames would still leak. Holds the user's muted state\r\n // while force-muted; null = not force-muted.\r\n const coverMuteRef = useRef<boolean | null>(null);\r\n const muteCoverAudio = (vid: HTMLVideoElement | null | undefined) => {\r\n if (!vid || coverMuteRef.current !== null) return;\r\n coverMuteRef.current = vid.muted;\r\n vid.muted = true;\r\n };\r\n const restoreCoverAudio = (vid: HTMLVideoElement | null | undefined) => {\r\n if (coverMuteRef.current === null) return;\r\n if (vid) vid.muted = coverMuteRef.current;\r\n coverMuteRef.current = null;\r\n };\r\n const triggerAdSkipRef = useRef<((endTime: number, withinPod?: boolean) => void) | null>(null);\r\n // True while an in-flight ad skip is expected to land on another ad of the same break.\r\n const adSkipWithinPodRef = useRef(false);\r\n const pendingSeekTargetRef = useRef<number | null>(null);\r\n const performSeekRef = useRef<((time: number, attribution?: SeekAttribution) => void) | null>(null);\r\n const sourceChangeSeekRef = useRef<number | null>(null);\r\n const sourceChangeFallbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n // Tracks an in-flight source switch so the new player's effect can start a\r\n // \"did playback actually start?\" watchdog. Survives the player re-init because it's a ref.\r\n const pendingSourceSwitchRef = useRef<{ targetIdx: number } | null>(null);\r\n // Latest sources / onSourceChange, readable from inside the player effect closure\r\n // (whose deps are [src, watermark, retryKey], so it would otherwise capture stale values).\r\n const sourcesRef = useRef(sources);\r\n const onSourceChangeRef = useRef(onSourceChange);\r\n const initialSeekAppliedRef = useRef(false);\r\n // Resume-playback cover: isolated from the ad-skip machinery so it never touches\r\n // isSeeking / seekTargetTime / adSkipCoverRef. Raised at mount when initialTime > 0 so\r\n // frame 0 is never shown, then dropped once playback actually lands at the resume point.\r\n const resumeCoverRef = useRef<HTMLDivElement | null>(null);\r\n const resumeSeekTargetRef = useRef<number | null>(null);\r\n const resumeGuardTimeout = useRef<number | null>(null);\r\n const resumeAdRecheckRef = useRef<number | null>(null);\r\n // Last confirmed playback position from timeupdate. Revert point for the native-seek\r\n // guard: seeks that bypass Media Session (e.g. Chrome's PiP window scrubber seeks the\r\n // <video> element directly) are snapped back here before being blocked or re-routed.\r\n const lastStableTimeRef = useRef(0);\r\n // Custom Document Picture-in-Picture window (Chromium only). Mirrors the `pipWindow`\r\n // state into a ref so effect cleanups (which run inside closures captured at a different\r\n // render) can always reach the live window instead of a stale null.\r\n const pipWindowRef = useRef<Window | null>(null);\r\n\r\n const [isMobile, setIsMobile] = useState(false);\r\n const isMobileRef = useRef(false);\r\n const lastTapRef = useRef<{ time: number; x: number } | null>(null);\r\n const [doubleTapSide, setDoubleTapSide] = useState<'left' | 'right' | null>(null);\r\n\r\n // One-time, console-only hint if the SDK stylesheet wasn't imported. The\r\n // `.sdk-player-container` rule sets `overflow: hidden` and `border-radius: 12px`,\r\n // neither of which is applied inline — so if both read as browser defaults after\r\n // paint, the consumer almost certainly forgot `import 'boss-web-player-sdk/css'`.\r\n // Browser-only, runs once, never throws — it cannot affect rendering or playback.\r\n useEffect(() => {\r\n if (typeof window === 'undefined' || cssWarningShown) return;\r\n const raf = window.requestAnimationFrame(() => {\r\n const el = containerRef.current;\r\n if (!el) return;\r\n const cs = window.getComputedStyle(el);\r\n const stylesheetMissing = cs.overflow !== 'hidden' && cs.borderTopLeftRadius === '0px';\r\n if (stylesheetMissing) {\r\n cssWarningShown = true;\r\n console.warn(\r\n \"[boss-web-player-sdk] Player styles appear to be missing — the player will look broken/unstyled.\\n\" +\r\n \"Import the stylesheet once at your app entry point:\\n\" +\r\n \" import 'boss-web-player-sdk/css';\"\r\n );\r\n }\r\n });\r\n return () => window.cancelAnimationFrame(raf);\r\n }, []);\r\n\r\n useEffect(() => {\r\n const el = containerRef.current;\r\n if (!el) return;\r\n const ro = new ResizeObserver(entries => {\r\n for (const entry of entries) {\r\n setIsMobile(entry.contentRect.width <= 768);\r\n }\r\n });\r\n ro.observe(el);\r\n setIsMobile(el.getBoundingClientRect().width <= 768);\r\n return () => ro.disconnect();\r\n }, []);\r\n\r\n const [player, setPlayer] = useState<VideoPlayerSDK | null>(null);\r\n const [manifestReady, setManifestReady] = useState(false);\r\n\r\n // Entitlement is always pre-resolved by BossVideoPlayer before this component mounts.\r\n // When resolvedEntitlement is provided (API mode): features come exclusively from the API.\r\n // When absent (standalone/test mode): features come from explicit props or true defaults.\r\n const entitlementPackage = resolvedEntitlement ?? null;\r\n const hasEntitlement = entitlementPackage != null;\r\n const ef = entitlementPackage?.features ?? {};\r\n\r\n // DRM: block playback if the plan requires a license key but none was provided.\r\n const isEntitlementBlocked = hasEntitlement\r\n ? Boolean(ef.drm_protection && !licenseKey)\r\n : false;\r\n\r\n // API-gated features — controlled by the init API when a playerKey is used.\r\n // In standalone mode (no resolvedEntitlement), fall back to explicit props or true defaults.\r\n const effectiveFeatures = {\r\n enableSubtitles: hasEntitlement ? Boolean(ef.subtitle_support) : (typeof enableSubtitles === 'boolean' ? enableSubtitles : true),\r\n enableChromecast: hasEntitlement ? Boolean(ef.chromecast) : (typeof enableChromecast === 'boolean' ? enableChromecast : true),\r\n enableKeyMoments: hasEntitlement ? Boolean(ef.key_moments) : (typeof enableKeyMoments === 'boolean' ? enableKeyMoments : true),\r\n enableQuality: hasEntitlement ? Boolean(ef.multiple_resolution) : (typeof enableQuality === 'boolean' ? enableQuality : true),\r\n enablePlaybackSpeed: hasEntitlement ? Boolean(ef.playback_speed) : (typeof enablePlaybackSpeed === 'boolean' ? enablePlaybackSpeed : true),\r\n enablePiP: hasEntitlement ? Boolean(ef.picture_in_picture) : (typeof enablePiP === 'boolean' ? enablePiP : true),\r\n allowAirplay: hasEntitlement ? Boolean(ef.airplay) : false,\r\n allowMultipleAudio: hasEntitlement ? Boolean(ef.multiple_audio) : true,\r\n m3u8_playback: hasEntitlement ? Boolean(ef.m3u8_playback) : true,\r\n drm_protection: hasEntitlement ? Boolean(ef.drm_protection) : false,\r\n };\r\n const [playerError, setPlayerError] = useState(false);\r\n const [retryKey, setRetryKey] = useState(0);\r\n const [isPlaying, setIsPlaying] = useState(false);\r\n const [currentTime, setCurrentTime] = useState(0);\r\n const [duration, setDuration] = useState(0);\r\n const [volume, setVolume] = useState(1);\r\n const [isMuted, setIsMuted] = useState(false);\r\n const [isBuffering, setIsBuffering] = useState(false);\r\n const [isFullscreen, setIsFullscreen] = useState(false);\r\n const [isSimulatedFullscreen, setIsSimulatedFullscreen] = useState(false);\r\n // Tracks whether the container was in mobile layout when fullscreen was entered.\r\n // Keeps mobile controls active during landscape rotation without affecting desktop fullscreen.\r\n const [wasMobileOnFullscreenEnter, setWasMobileOnFullscreenEnter] = useState(false);\r\n const useMobileLayout = isMobile || ((isFullscreen || isSimulatedFullscreen) && wasMobileOnFullscreenEnter);\r\n const [isPiP, setIsPiP] = useState(false);\r\n // Non-null while a custom Document Picture-in-Picture window is open. Distinct from\r\n // `isPiP` (which also covers the native video-element PiP fallback) because the custom\r\n // window needs its own React portal target and its own on-page video placeholder.\r\n const [pipWindow, setPipWindow] = useState<Window | null>(null);\r\n const [isEnded, setIsEnded] = useState(false);\r\n const [adBreaks, setAdBreaks] = useState<Array<{ startTime: number; endTime: number }>>([]);\r\n const adBreaksRef = useRef<Array<{ startTime: number; endTime: number }>>([]);\r\n const watchedAdBreaksRef = useRef<Set<number>>(new Set());\r\n // Stable identifier for the underlying content. Every entry in `sources` is the same video\r\n // in different formats sharing one SSAI session (identical ad-break positions), so a format\r\n // switch — which only changes the active `src` — must be treated as a single continuous\r\n // session. Keying watched-ad state on the *set of source URLs* rather than the active `src`\r\n // preserves it across a switch, while still resetting on a genuine content change (a\r\n // different `sources` set, or a new single `src` in standalone mode).\r\n const contentKey = sources && sources.length > 0 ? sources.map(s => s.url).join('|') : src;\r\n // React state mirror of watchedAdBreaksRef — drives the cue-marker filter in render.\r\n // Reset during render (not in an effect) using the \"update during render\" pattern so\r\n // no setState call is ever needed inside a useEffect body. State and ref are reset together\r\n // and only when contentKey changes, so switching formats keeps watched ads skipped/hidden.\r\n const [watchedAdBreaksSet, setWatchedAdBreaksSet] = useState<Set<number>>(new Set());\r\n const [watchedAdBreaksContentKey, setWatchedAdBreaksContentKey] = useState(contentKey);\r\n // Stable raw-analytics session identity, keyed on contentKey like the watched-ad\r\n // state. Every player re-init (source switch, unsupported-format fallback, error\r\n // retry) constructs a fresh AnalyticsModule that generates a new internal sessionId\r\n // and re-fires session_start — but a format switch is the same viewing session, so\r\n // the first module's id is adopted, re-stamped onto later events, and the duplicate\r\n // session_start events are swallowed before reaching Firebase / onAnalyticsEvent\r\n // (see the analyticsCallback wrapper). This mirrors what BossVideoPlayer already\r\n // does for the Events API, so raw-event consumers see the same session continuity.\r\n const analyticsSessionIdRef = useRef<string | null>(null);\r\n const analyticsSessionStartFiredRef = useRef(false);\r\n // Session-scoped analytics counters, snapshotted from the outgoing player at teardown\r\n // and seeded into its successor so raw-event consumers (Firebase / onAnalyticsEvent)\r\n // see continuous totalWatchTime/streamingTime/buffering counters and no re-fired\r\n // quartile milestones across a re-init. Tagged with the contentKey the snapshot came\r\n // from: the player effect's cleanup runs AFTER the render that resets contentKey-scoped\r\n // state, so a plain ref would be repopulated with the old content's counters on a\r\n // genuine content change — the tag lets the next init discard a stale snapshot instead.\r\n const analyticsSeedRef = useRef<{ key: string; snapshot: AnalyticsSessionSnapshot } | null>(null);\r\n if (watchedAdBreaksContentKey !== contentKey) {\r\n setWatchedAdBreaksContentKey(contentKey);\r\n setWatchedAdBreaksSet(new Set());\r\n watchedAdBreaksRef.current = new Set();\r\n // Genuine content change — a new raw session identity is correct here.\r\n analyticsSessionIdRef.current = null;\r\n analyticsSessionStartFiredRef.current = false;\r\n }\r\n const activeAdBreakStartRef = useRef<number | null>(null);\r\n const wasAdPausedByVisibilityRef = useRef(false);\r\n\r\n // Overlay (non-linear) ads\r\n const [overlayAds, setOverlayAds] = useState<OverlayAdItem[]>([]);\r\n const [activeOverlay, setActiveOverlay] = useState<OverlayAdItem | null>(null);\r\n const overlayAdsRef = useRef<OverlayAdItem[]>([]);\r\n const activeOverlayRef = useRef<OverlayAdItem | null>(null);\r\n const dismissedOverlayIdsRef = useRef<Set<string>>(new Set());\r\n\r\n // Per-ad skip/CTA info from avails metadata — one entry per individual ad within a pod\r\n const [adPodItems, setAdPodItems] = useState<AdPodItem[]>([]);\r\n const adPodItemsRef = useRef<AdPodItem[]>([]);\r\n // Break-level (avail) start/end ranges derived from the tracking URL, fed into core-player\r\n const [availBreaks, setAvailBreaks] = useState<Array<{ startTime: number; endTime: number }>>([]);\r\n\r\n // Resolves the current ad's [start, end) window for progress/countdown purposes.\r\n // Prefers the individual pod ad (per-ad progress within a multi-ad break) and falls back\r\n // to the whole ad break when avails/pod metadata isn't available — e.g. breaks detected\r\n // from HLS SCTE markers rather than the ad tracking URL.\r\n const getActiveAdWindow = (t: number): { start: number; end: number } | null => {\r\n const pod = adPodItemsRef.current.find(a => t >= a.startTimeSeconds && t < a.endTimeSeconds);\r\n if (pod) return { start: pod.startTimeSeconds, end: pod.endTimeSeconds };\r\n const ab = adBreaksRef.current.find(b => t >= b.startTime && t < b.endTime);\r\n return ab ? { start: ab.startTime, end: ab.endTime } : null;\r\n };\r\n\r\n // Total ad duration summed from all detected breaks\r\n const totalAdDuration = useMemo(\r\n () => adBreaks.reduce((sum, ab) => sum + (ab.endTime - ab.startTime), 0),\r\n [adBreaks]\r\n );\r\n\r\n // Content-only duration (video duration minus all spliced ad time).\r\n // Falls back to raw duration until adBreaks are loaded so the display is never blank.\r\n const contentDuration = duration > 0 ? Math.max(0, duration - totalAdDuration) : 0;\r\n const displayDuration = adBreaks.length > 0 && contentDuration > 0 ? contentDuration : duration;\r\n\r\n // Convert raw video time → content-only time (subtracts elapsed ad time before `t`).\r\n // During an ad the returned value is frozen at the content timestamp where the ad started.\r\n const videoTimeToContentTime = useCallback((t: number): number => {\r\n let ct = t;\r\n for (const ab of adBreaks) {\r\n if (t >= ab.endTime) {\r\n ct -= (ab.endTime - ab.startTime);\r\n } else if (t >= ab.startTime) {\r\n ct -= (t - ab.startTime);\r\n }\r\n }\r\n return Math.max(0, ct);\r\n }, [adBreaks]);\r\n\r\n // Convert content-only time → raw video time (adds back ad durations).\r\n // Used to translate a seekbar click position into the actual seek target.\r\n const contentTimeToVideoTime = useCallback((ct: number): number => {\r\n let vt = ct;\r\n for (const ab of adBreaks) {\r\n if (vt >= ab.startTime) {\r\n vt += (ab.endTime - ab.startTime);\r\n }\r\n }\r\n return vt;\r\n }, [adBreaks]);\r\n\r\n // Lists & Selections\r\n const [qualities, setQualities] = useState<VideoQuality[]>([]);\r\n const [activeQuality, setActiveQuality] = useState<VideoQuality>({\r\n id: -1,\r\n height: 0,\r\n width: 0,\r\n bitrate: 0,\r\n label: 'Auto',\r\n });\r\n const [subtitleText, setSubtitleText] = useState('');\r\n const [activeSubtitleTrack, setActiveSubtitleTrack] = useState('off');\r\n const [availableSubtitles, setAvailableSubtitles] = useState<SubtitleTrack[]>(dedupeSubtitleTracks(subtitles));\r\n const [audioTracks, setAudioTracks] = useState<Array<{ id: number; label: string; lang: string }>>([]);\r\n const [activeAudioTrack, setActiveAudioTrack] = useState<number>(0);\r\n\r\n // Stable key — avoids infinite loops when parent passes inline [] on every render\r\n const subtitlesKey = subtitles.map(s => s.id).join(',');\r\n\r\n // Sync external subtitles prop dynamically and merge with discovered tracks\r\n useEffect(() => {\r\n if (player) {\r\n const playerTracks = player.subtitles.getTracks();\r\n const merged = [...subtitles, ...playerTracks.filter(t => !subtitles.some(s => s.id === t.id))];\r\n setAvailableSubtitles(dedupeSubtitleTracks(merged));\r\n } else {\r\n setAvailableSubtitles(dedupeSubtitleTracks(subtitles));\r\n }\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [subtitlesKey, player]);\r\n\r\n const [activeChapter, setActiveChapter] = useState<{ chapter: KeyMoment | null; index: number }>({\r\n chapter: null,\r\n index: -1,\r\n });\r\n\r\n const [localKeyMoments, setLocalKeyMoments] = useState<KeyMoment[]>(keyMoments || chapters || []);\r\n\r\n // UI Dropdowns\r\n const [activeMenu, setActiveMenu] = useState<'none' | 'quality' | 'speed' | 'subtitles' | 'chapters' | 'audio' | 'sources' | 'mobile-settings'>('none');\r\n const [playbackRate, setPlaybackRate] = useState(1);\r\n const [showChaptersSidebar, setShowChaptersSidebar] = useState(false);\r\n const [chapterThumbnails, setChapterThumbnails] = useState<Record<number, string>>({});\r\n\r\n // Accessibility: live-region announcement text\r\n const [announcement, setAnnouncement] = useState('');\r\n const [sourceFallbackNotice, setSourceFallbackNotice] = useState<string | null>(null);\r\n const announceMountedRef = useRef(false);\r\n\r\n const keyMomentsKey = JSON.stringify(localKeyMoments.map(c => ({ title: c.title, startTime: c.startTime })));\r\n\r\n // Stable key for incoming props — avoids infinite loops when parent passes inline [] on every render\r\n const keyMomentsPropsKey = (keyMoments || chapters || []).map(k => `${k.startTime}:${k.title}`).join(',');\r\n\r\n // Sync props to state dynamically\r\n useEffect(() => {\r\n setLocalKeyMoments(keyMoments || chapters || []);\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [keyMomentsPropsKey]);\r\n\r\n // Dynamically extract actual video frames at chapter start times\r\n useEffect(() => {\r\n if (!src || !localKeyMoments || localKeyMoments.length === 0) return;\r\n\r\n setChapterThumbnails({});\r\n\r\n const tempVideo = document.createElement('video');\r\n tempVideo.muted = true;\r\n tempVideo.preload = 'metadata';\r\n tempVideo.crossOrigin = 'anonymous';\r\n // position:fixed with opacity:0.01 forces the browser compositor to decode frames, bypassing display:none optimizations\r\n tempVideo.style.cssText = 'position:fixed;top:0;left:0;width:10px;height:10px;opacity:0.01;pointer-events:none;z-index:-9999;';\r\n document.body.appendChild(tempVideo);\r\n\r\n let tempHls: Hls | null = null;\r\n const isHls = src.includes('.m3u8');\r\n\r\n const tempCanvas = document.createElement('canvas');\r\n tempCanvas.width = 160;\r\n tempCanvas.height = 90;\r\n const ctx = tempCanvas.getContext('2d');\r\n\r\n let currentIndex = 0;\r\n let isStarted = false;\r\n\r\n const captureNext = () => {\r\n if (currentIndex >= localKeyMoments.length) {\r\n cleanup();\r\n return;\r\n }\r\n const targetTime = localKeyMoments[currentIndex].startTime;\r\n // Seek to target. If 0, seek to 0.01 to ensure the browser registers a currentTime change and triggers 'seeked'\r\n tempVideo.currentTime = targetTime === 0 ? 0.01 : targetTime;\r\n };\r\n\r\n const onSeeked = () => {\r\n if (ctx && tempVideo.readyState >= 2) {\r\n ctx.drawImage(tempVideo, 0, 0, tempCanvas.width, tempCanvas.height);\r\n const dataUrl = tempCanvas.toDataURL('image/jpeg', 0.8);\r\n setChapterThumbnails((prev) => ({ ...prev, [currentIndex]: dataUrl }));\r\n currentIndex++;\r\n captureNext();\r\n } else {\r\n // Fallback retry if video frame buffer wasn't ready\r\n setTimeout(onSeeked, 100);\r\n }\r\n };\r\n\r\n const startCapture = () => {\r\n if (isStarted) return;\r\n isStarted = true;\r\n captureNext();\r\n };\r\n\r\n tempVideo.addEventListener('seeked', onSeeked);\r\n tempVideo.addEventListener('canplay', startCapture);\r\n\r\n if (isHls && Hls.isSupported()) {\r\n tempHls = new Hls({\r\n enableWorker: false,\r\n maxBufferLength: 5,\r\n xhrSetup: (xhr, url) => {\r\n // Only use an explicit auth token passed via `drmConfig`.\r\n // Do NOT infer or parse tokens from the stream URL.\r\n const token = drmConfig?.authToken;\r\n try {\r\n const proxiedUrl = transformUrl(url);\r\n const urlObj = new URL(proxiedUrl, window.location.origin);\r\n if (token) {\r\n urlObj.searchParams.set('token', token);\r\n }\r\n xhr.open('GET', urlObj.toString(), true);\r\n } catch (e) {\r\n console.error('[WebVideoPlayer tempHls] xhrSetup error:', e);\r\n }\r\n }\r\n });\r\n tempHls.loadSource(transformUrl(src));\r\n tempHls.attachMedia(tempVideo);\r\n } else {\r\n tempVideo.src = src;\r\n }\r\n\r\n // If readyState is already satisfied, start capturing immediately\r\n if (tempVideo.readyState >= 2) {\r\n startCapture();\r\n }\r\n\r\n const cleanup = () => {\r\n tempVideo.removeEventListener('seeked', onSeeked);\r\n tempVideo.removeEventListener('canplay', startCapture);\r\n if (tempHls) {\r\n tempHls.destroy();\r\n }\r\n if (document.body.contains(tempVideo)) {\r\n document.body.removeChild(tempVideo);\r\n }\r\n };\r\n\r\n return cleanup;\r\n }, [src, keyMomentsKey]);\r\n\r\n // Cast state\r\n const [castDevice, setCastDevice] = useState<string | null>(null);\r\n const [remoteState, setRemoteState] = useState<{ currentTime: number; duration: number; isPaused: boolean; volumeLevel: number; isMuted: boolean } | null>(null);\r\n const preCastMutedRef = useRef<boolean>(false);\r\n const [isAirPlayAvailable, setIsAirPlayAvailable] = useState(false);\r\n const [isAirPlayConnected, setIsAirPlayConnected] = useState(false);\r\n const [airPlayTargetName, setAirPlayTargetName] = useState<string | null>(null);\r\n\r\n // Scrubber Hover\r\n const [hoverX, setHoverX] = useState<number | null>(null);\r\n const [tooltipLeft, setTooltipLeft] = useState(0);\r\n const [hoverTime, setHoverTime] = useState<number>(0);\r\n const [hoverThumbnail, setHoverThumbnail] = useState<string | null>(null);\r\n const [hoverChapter, setHoverChapter] = useState<KeyMoment | null>(null);\r\n const [scrubbingTime, setScrubbingTime] = useState<number | null>(null);\r\n\r\n // Inject Native Google Cast Button (Invisible Overlay)\r\n useEffect(() => {\r\n if (castButtonRef.current && !castButtonRef.current.querySelector('google-cast-launcher')) {\r\n const launcher = document.createElement('google-cast-launcher');\r\n launcher.style.cursor = 'pointer';\r\n launcher.style.width = '100%';\r\n launcher.style.height = '100%';\r\n launcher.style.display = 'block';\r\n castButtonRef.current.appendChild(launcher);\r\n }\r\n }, []);\r\n\r\n useEffect(() => {\r\n if (!player) {\r\n setIsAirPlayAvailable(false);\r\n setIsAirPlayConnected(false);\r\n setAirPlayTargetName(null);\r\n return;\r\n }\r\n\r\n const updateAvailability = (available: boolean) => setIsAirPlayAvailable(available);\r\n const updateConnection = () => {\r\n const video = player.getVideoElement();\r\n const anyVideo = video as any;\r\n const connected = Boolean(anyVideo?.webkitCurrentPlaybackTargetIsWireless);\r\n setIsAirPlayConnected(connected);\r\n setAirPlayTargetName(\r\n connected\r\n ? anyVideo.webkitCurrentPlaybackTargetName || 'AirPlay device'\r\n : null\r\n );\r\n };\r\n\r\n updateAvailability(player.casting.isAirPlayAvailable());\r\n updateConnection();\r\n\r\n const video = player.getVideoElement();\r\n const availabilityHandler = (event: any) => {\r\n updateAvailability(event.availability === 'available');\r\n };\r\n const connectionHandler = () => {\r\n updateConnection();\r\n };\r\n\r\n if ('webkitShowPlaybackTargetPicker' in video) {\r\n video.addEventListener('webkitplaybacktargetavailabilitychanged', availabilityHandler as EventListener);\r\n video.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', connectionHandler as EventListener);\r\n }\r\n\r\n return () => {\r\n if ('webkitShowPlaybackTargetPicker' in video) {\r\n video.removeEventListener('webkitplaybacktargetavailabilitychanged', availabilityHandler as EventListener);\r\n video.removeEventListener('webkitcurrentplaybacktargetiswirelesschanged', connectionHandler as EventListener);\r\n }\r\n };\r\n }, [player]);\r\n\r\n // Control UI visibility timeout\r\n const [extractedTitle, setExtractedTitle] = useState('');\r\n const [controlsVisible, setControlsVisible] = useState(true);\r\n const [isAdPlaying, setIsAdPlaying] = useState(false);\r\n // True from the instant performSeek redirects a forward seek into an unwatched ad break\r\n // until the ad actually appears (adbreakchange(true), which takes over via isAdPlaying)\r\n // or the redirect's guard timeout fires. Without this, the seekbar/PiP/media-session seek\r\n // entry points stay fully interactive during that gap — since setting video.currentTime\r\n // updates the readback synchronously, any seek landing in that window (e.g. a fast\r\n // double-click, or a drag that ends right after the redirect fires) re-issues a real seek\r\n // that overrides the pending redirect at the browser level and skips the ad outright.\r\n const [isAdRedirectPending, setIsAdRedirectPending] = useState(false);\r\n const isAdRedirectPendingRef = useRef(false);\r\n const controlsTimeoutRef = useRef<number | null>(null);\r\n // Ref so the timeout callback always reads the *current* isPlaying value (avoids stale closure)\r\n const isPlayingRef = useRef(false);\r\n const isAdPlayingRef = useRef(false);\r\n const activeMenuRef = useRef<typeof activeMenu>('none');\r\n const availableSubtitlesRef = useRef<SubtitleTrack[]>(subtitles);\r\n\r\n const resetControlsTimer = () => {\r\n setControlsVisible(true);\r\n if (controlsTimeoutRef.current) {\r\n window.clearTimeout(controlsTimeoutRef.current);\r\n }\r\n const delay = isMobileRef.current ? 3500 : 10000;\r\n controlsTimeoutRef.current = window.setTimeout(() => {\r\n if (isPlayingRef.current && activeMenuRef.current === 'none') {\r\n setControlsVisible(false);\r\n }\r\n }, delay);\r\n };\r\n\r\n // Falls back to the first source when the source the user switched to can't be played.\r\n // Uses refs only, so it's safe to call from inside the player effect closure regardless\r\n // of when it was captured. Keeps sourceChangeSeekRef intact so the fallback source\r\n // resumes at the position the user was watching, and leaves the resume cover and the\r\n // switch-forced spinner up (raised in handleSourceChange) so no frame-0 flash shows\r\n // during the fallback re-init — both drop once the fallback source lands at the\r\n // restored position.\r\n const runSourceFallback = useCallback((failedIdx: number) => {\r\n if (sourceChangeFallbackTimerRef.current) {\r\n clearTimeout(sourceChangeFallbackTimerRef.current);\r\n sourceChangeFallbackTimerRef.current = null;\r\n }\r\n const srcs = sourcesRef.current;\r\n const failedLabel = srcs?.[failedIdx]?.label ?? 'This format';\r\n const fallbackLabel = srcs?.[0]?.label ?? 'default';\r\n pendingSourceSwitchRef.current = null;\r\n setSourceFallbackNotice(`${failedLabel} isn't supported on this device. Switched to ${fallbackLabel}.`);\r\n window.setTimeout(() => setSourceFallbackNotice(null), 4000);\r\n onSourceChangeRef.current?.(0);\r\n }, []);\r\n\r\n useEffect(() => {\r\n if (!containerRef.current) return;\r\n\r\n // ── Hidden video for position-accurate thumbnail capture ──────────────\r\n const hiddenVideo = document.createElement('video');\r\n hiddenVideo.muted = true;\r\n hiddenVideo.preload = 'metadata';\r\n hiddenVideo.crossOrigin = 'anonymous';\r\n hiddenVideo.style.cssText = 'display:none;position:absolute;pointer-events:none;';\r\n document.body.appendChild(hiddenVideo);\r\n hiddenVideoRef.current = hiddenVideo;\r\n\r\n const isHls = src.includes('.m3u8');\r\n if (isHls && Hls.isSupported()) {\r\n const hls = new Hls({\r\n enableWorker: false,\r\n maxBufferLength: 5,\r\n xhrSetup: (xhr, url) => {\r\n const token = drmConfig?.authToken;\r\n try {\r\n const proxiedUrl = transformUrl(url);\r\n const urlObj = new URL(proxiedUrl, window.location.origin);\r\n if (token) {\r\n urlObj.searchParams.set('token', token);\r\n }\r\n xhr.open('GET', urlObj.toString(), true);\r\n } catch (e) {\r\n console.error('[WebVideoPlayer hiddenHls] xhrSetup error:', e);\r\n }\r\n }\r\n });\r\n hls.loadSource(transformUrl(src));\r\n hls.attachMedia(hiddenVideo);\r\n hiddenHlsRef.current = hls;\r\n\r\n hls.on(Hls.Events.MANIFEST_LOADED, (_e: string, data: { networkDetails?: { responseText?: string } }) => {\r\n if (title) return;\r\n const found = extractTitleFromManifest(data?.networkDetails?.responseText ?? '');\r\n if (found) setExtractedTitle(found);\r\n });\r\n\r\n hls.on(Hls.Events.FRAG_PARSING_METADATA, (_e: string, data: { samples?: Array<{ data: Uint8Array }> }) => {\r\n if (title || extractedTitle) return;\r\n for (const sample of data?.samples ?? []) {\r\n const found = parseID3Title(sample.data);\r\n if (found) { setExtractedTitle(found); break; }\r\n }\r\n });\r\n } else if (isHls && hiddenVideo.canPlayType('application/vnd.apple.mpegurl')) {\r\n hiddenVideo.src = src;\r\n } else {\r\n hiddenVideo.src = src;\r\n }\r\n\r\n const onSeeked = () => {\r\n const canvas = canvasRef.current;\r\n if (!canvas) return;\r\n const ctx = canvas.getContext('2d');\r\n if (ctx && hiddenVideo.readyState >= 2) {\r\n ctx.drawImage(hiddenVideo, 0, 0, canvas.width, canvas.height);\r\n const dataUrl = canvas.toDataURL('image/jpeg', 0.82);\r\n setHoverThumbnail(dataUrl);\r\n }\r\n };\r\n hiddenVideo.addEventListener('seeked', onSeeked);\r\n\r\n // ── Firebase Analytics Provider ───────────────────────────────────────\r\n let firebaseCallback: ((event: import('@goboss/types').AnalyticsEvent) => void) | undefined;\r\n if (firebaseConfig) {\r\n const provider = new FirebaseAnalyticsProvider(firebaseConfig);\r\n firebaseProviderRef.current = provider;\r\n firebaseCallback = provider.createCallback();\r\n }\r\n\r\n // ── Main SDK player ───────────────────────────────────────────────────\r\n if (isEntitlementBlocked) {\r\n return;\r\n }\r\n\r\n const sdkInstance = new VideoPlayerSDK({\r\n container: containerRef.current,\r\n src,\r\n videoId,\r\n autoplay,\r\n muted,\r\n keyMoments: localKeyMoments,\r\n subtitles,\r\n drm: {\r\n watermarkText: watermark,\r\n allowedDomains: ['localhost', '127.0.0.1', window.location.hostname],\r\n ...drmConfig,\r\n },\r\n analyticsCallback: (event) => {\r\n // Normalize session identity across player re-inits (see analyticsSessionIdRef):\r\n // adopt the first module's sessionId, re-stamp events from re-created modules\r\n // that carry a regenerated id, and drop the duplicate session_start each new\r\n // module fires on its first play.\r\n if (event.sessionId) {\r\n if (analyticsSessionIdRef.current === null) {\r\n analyticsSessionIdRef.current = event.sessionId;\r\n } else if (event.sessionId !== analyticsSessionIdRef.current) {\r\n event = { ...event, sessionId: analyticsSessionIdRef.current };\r\n }\r\n }\r\n if (event.eventType === 'session_start') {\r\n if (analyticsSessionStartFiredRef.current) return;\r\n analyticsSessionStartFiredRef.current = true;\r\n }\r\n firebaseCallback?.(event);\r\n if (onAnalyticsEvent) onAnalyticsEvent(event);\r\n },\r\n // Seed only a snapshot taken for this same content — a stale snapshot from a\r\n // previous content is discarded by the key check (fresh session, as before).\r\n analyticsSessionSeed:\r\n analyticsSeedRef.current?.key === contentKey\r\n ? analyticsSeedRef.current.snapshot\r\n : undefined,\r\n watchProgressInterval: watchProgressInterval * 1000,\r\n themeColor,\r\n urlTransformer,\r\n });\r\n\r\n sdkInstance.on('keymomentsloaded', (parsedKeyMoments: KeyMoment[]) => {\r\n setLocalKeyMoments(parsedKeyMoments);\r\n if (onKeyMomentsLoaded) {\r\n onKeyMomentsLoaded(parsedKeyMoments);\r\n }\r\n });\r\n\r\n sdkInstance.on('chaptersloaded', (parsedChapters: Chapter[]) => {\r\n setLocalKeyMoments(parsedChapters);\r\n if (onKeyMomentsLoaded) {\r\n onKeyMomentsLoaded(parsedChapters);\r\n }\r\n });\r\n\r\n // Subscriptions to core player events\r\n sdkInstance.on('play', () => {\r\n setIsPlaying(true);\r\n setIsEnded(false);\r\n syncMediaSessionPosition(sdkInstance.getCurrentTime(), false);\r\n });\r\n sdkInstance.on('pause', () => {\r\n setIsPlaying(false);\r\n syncMediaSessionPosition(sdkInstance.getCurrentTime(), true);\r\n });\r\n sdkInstance.on('ended', () => {\r\n setIsPlaying(false);\r\n setIsEnded(true);\r\n });\r\n // Shared helper — hide video, seek to endTime, restore once the seek lands.\r\n // All ad-skip paths (timeupdate pre-skip and adbreakchange fallback) use this\r\n // so opacity management and the guard timeout are always in sync.\r\n const showAdSkipCover = () => {\r\n // opacity change on a will-change:opacity element is a pure compositor operation —\r\n // no layout/paint required, takes effect in the current composite frame.\r\n adSkipCoverUpRef.current = true;\r\n setIsBuffering(false);\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '1';\r\n // Audio from the covered frames would still leak through the black cover.\r\n muteCoverAudio(sdkInstance.getVideoElement());\r\n };\r\n\r\n const hideAdSkipCover = () => {\r\n // Double rAF: wait for the browser to commit a fresh post-seek content frame\r\n // before removing the cover, so no ad frame bleeds through.\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => {\r\n adSkipCoverUpRef.current = false;\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '0';\r\n restoreCoverAudio(sdkInstance.getVideoElement());\r\n const vid = sdkInstance.getVideoElement();\r\n if (vid && !vid.paused && vid.readyState < 3) setIsBuffering(true);\r\n });\r\n });\r\n };\r\n\r\n // `withinPod` marks a skip that lands on the next ad of the same break rather than\r\n // on content, which changes how a queued seek target is handled (see below).\r\n const triggerAdSkip = (endTime: number, withinPod = false) => {\r\n isSeeking.current = true;\r\n // If this skip is part of a seek-forward redirect, jump straight to the user's original\r\n // target in one seek. This avoids a double-seek race: without this, triggerAdSkip would\r\n // seek to adEnd+0.5, then adbreakchange(false) would immediately seek again to the\r\n // pending target — the timeupdate from the first seek would fire between the two seeks\r\n // with seekTargetTime already updated to the second target, triggering a false \"landed\"\r\n // detection and prematurely hiding the cover before the second seek completes.\r\n //\r\n // A mid-pod skip is the exception: it stays inside the break, so the queued target has\r\n // to survive until the remaining ads have played. Consuming it here would let skipping\r\n // a pod's first ad release the viewer to their original target and bypass the rest.\r\n const skipTarget = withinPod ? endTime + 0.5 : (pendingSeekTargetRef.current ?? endTime + 0.5);\r\n if (!withinPod) pendingSeekTargetRef.current = null;\r\n adSkipWithinPodRef.current = withinPod;\r\n seekTargetTime.current = skipTarget;\r\n preSeekPositionRef.current = sdkInstance.getCurrentTime();\r\n showAdSkipCover();\r\n sdkInstance.seek(skipTarget);\r\n if (seekGuardTimeout.current) window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = window.setTimeout(() => {\r\n isSeeking.current = false;\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n seekGuardTimeout.current = null;\r\n adSkipWithinPodRef.current = false;\r\n hideAdSkipCover();\r\n }, 3000);\r\n };\r\n\r\n triggerAdSkipRef.current = triggerAdSkip;\r\n\r\n // `isPaused` drives navigator.mediaSession.playbackState, which is what actually\r\n // freezes Chrome's PiP-window timeline extrapolation. setPositionState's own\r\n // `playbackRate` field must stay a positive number — the spec throws a TypeError\r\n // for 0, which the try/catch below would silently swallow, leaving the browser's\r\n // last-known rate (1) in effect and the PiP timeline drifting forward while paused.\r\n const syncMediaSessionPosition = (time: number, isPaused: boolean) => {\r\n if (!('mediaSession' in navigator) || !navigator.mediaSession.setPositionState) return;\r\n navigator.mediaSession.playbackState = isPaused ? 'paused' : 'playing';\r\n const rawDuration = sdkInstance.getDuration();\r\n const activeAdBreak = adBreaksRef.current.find(ab => time >= ab.startTime && time < ab.endTime);\r\n if (activeAdBreak) {\r\n const adDur = activeAdBreak.endTime - activeAdBreak.startTime;\r\n const adPos = Math.max(0, time - activeAdBreak.startTime);\r\n if (adDur > 0) {\r\n try {\r\n navigator.mediaSession.setPositionState({\r\n duration: adDur,\r\n playbackRate: 1,\r\n position: Math.min(adPos, adDur),\r\n });\r\n } catch (_) { /* unsupported browser */ }\r\n }\r\n } else {\r\n const totalAdDur = adBreaksRef.current.reduce((s, ab) => s + (ab.endTime - ab.startTime), 0);\r\n const contentDur = rawDuration > 0 ? Math.max(0, rawDuration - totalAdDur) : 0;\r\n let ct = time;\r\n for (const ab of adBreaksRef.current) {\r\n if (time >= ab.endTime) ct -= (ab.endTime - ab.startTime);\r\n else if (time >= ab.startTime) ct -= (time - ab.startTime);\r\n }\r\n ct = Math.max(0, ct);\r\n const dispDur = adBreaksRef.current.length > 0 && contentDur > 0 ? contentDur : rawDuration;\r\n if (dispDur > 0) {\r\n try {\r\n navigator.mediaSession.setPositionState({\r\n duration: dispDur,\r\n playbackRate: 1,\r\n position: Math.min(ct, dispDur),\r\n });\r\n } catch (_) { /* unsupported browser */ }\r\n }\r\n }\r\n };\r\n\r\n sdkInstance.on('timeupdate', (time: number) => {\r\n if (!isSeeking.current && resumeSeekTargetRef.current === null) {\r\n const watchedBreak = adBreaksRef.current.find(\r\n b => watchedAdBreaksRef.current.has(b.startTime)\r\n && time >= b.startTime - 0.5\r\n && time < b.endTime\r\n );\r\n if (watchedBreak) {\r\n triggerAdSkip(watchedBreak.endTime);\r\n return;\r\n }\r\n\r\n if (pendingSeekTargetRef.current !== null) {\r\n const endingBreak = adBreaksRef.current.find(\r\n b => time >= b.endTime - 0.5 && time < b.endTime\r\n );\r\n if (endingBreak) showAdSkipCover();\r\n }\r\n }\r\n\r\n if (isSeeking.current && seekTargetTime.current !== null) {\r\n const target = seekTargetTime.current;\r\n const isBackwardSeek = preSeekPositionRef.current !== null && target < preSeekPositionRef.current;\r\n const landed = isBackwardSeek\r\n ? (time <= target + 2.0 && time >= target - 2.0)\r\n : time >= target - 2.0;\r\n if (landed) {\r\n isSeeking.current = false;\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n if (seekGuardTimeout.current) {\r\n window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = null;\r\n }\r\n const landedInAd = adBreaksRef.current.some(\r\n b => time >= b.startTime && time < b.endTime\r\n );\r\n // Landing inside a break normally means a redirect-into-ad seek, where the cover\r\n // must stay up until adbreakchange(true) commits the first ad frame. A mid-pod\r\n // skip lands inside a break by design though, and adbreakchange never fires\r\n // because the break was never left — so this is its only chance to reveal.\r\n const wasWithinPodSkip = adSkipWithinPodRef.current;\r\n adSkipWithinPodRef.current = false;\r\n if (wasWithinPodSkip || (!landedInAd && pendingSeekTargetRef.current === null)) {\r\n hideAdSkipCover();\r\n }\r\n } else {\r\n // Still seeking — skip this stale update\r\n return;\r\n }\r\n }\r\n\r\n setCurrentTime(time);\r\n // Only positions that survived the seek guards above count as \"stable\" — a mid-seek\r\n // timeupdate must never become the native-seek guard's revert point.\r\n lastStableTimeRef.current = time;\r\n const dur = sdkInstance.getDuration();\r\n setDuration(dur);\r\n\r\n // Drop the resume cover once playback has actually reached the resume point.\r\n // Checked BEFORE the arm block below so it never runs on the same tick it was armed\r\n // (resumeSeekTargetRef is still null then). The -2.0 tolerance mirrors the ad-skip\r\n // landing detection to absorb HLS keyframe snapping. Isolated from ad-skip state.\r\n if (resumeSeekTargetRef.current !== null && time >= resumeSeekTargetRef.current - 2.0) {\r\n resumeSeekTargetRef.current = null;\r\n if (resumeGuardTimeout.current) {\r\n window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = null;\r\n }\r\n // Keyframe snapping can land the resume seek inside an already-watched break\r\n // (saved position just past its end). Trigger the skip NOW, before the reveal\r\n // below — the ad-skip cover goes up synchronously inside triggerAdSkip, so the\r\n // reveal exposes that cover's black instead of watched-ad frames.\r\n const landedWatchedBreak = adBreaksRef.current.find(\r\n b => watchedAdBreaksRef.current.has(b.startTime) && time >= b.startTime && time < b.endTime\r\n );\r\n if (landedWatchedBreak) triggerAdSkip(landedWatchedBreak.endTime);\r\n // Double rAF: wait for the browser to commit a fresh post-seek frame before revealing.\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => {\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n });\r\n });\r\n }\r\n\r\n // Apply one-time seek to resume position once the stream has a valid duration.\r\n // The cover was already raised at mount (JSX initial opacity) so frame 0 never showed;\r\n // here we arm the landing detection above so it hides only after the seek lands.\r\n // Skipped while a source-switch restore is pending (sourceChangeSeekRef non-null):\r\n // the effect cleanup resets initialSeekAppliedRef on every re-init, so after a\r\n // switch this block would otherwise re-apply initialTime and fight the restore\r\n // seek for the final position. The saved switch position is authoritative.\r\n if (!initialSeekAppliedRef.current && sourceChangeSeekRef.current === null && initialTime && initialTime > 0 && dur > 0 && initialTime < dur) {\r\n initialSeekAppliedRef.current = true;\r\n const landingBreak = adBreaksRef.current.find(\r\n b => initialTime >= b.startTime && initialTime < b.endTime\r\n );\r\n const resumeTarget = landingBreak ? landingBreak.startTime : initialTime;\r\n resumeAdRecheckRef.current = landingBreak ? null : initialTime;\r\n resumeSeekTargetRef.current = resumeTarget;\r\n sdkInstance.seek(resumeTarget);\r\n // Fallback: if playback never reaches the target (e.g. seek fails), reveal anyway\r\n // so the cover can't get stuck up.\r\n if (resumeGuardTimeout.current) window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = window.setTimeout(() => {\r\n resumeSeekTargetRef.current = null;\r\n resumeGuardTimeout.current = null;\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n }, 4000);\r\n }\r\n\r\n onTimeUpdate?.(time);\r\n\r\n // Overlay (non-linear) ad: driven by content time, not wall-clock\r\n if (overlayAdsRef.current.length > 0) {\r\n if (activeOverlayRef.current) {\r\n const o = activeOverlayRef.current;\r\n const endTime = o.startTimeSeconds + o.durationSeconds;\r\n if (time >= endTime || time < o.startTimeSeconds) {\r\n setActiveOverlay(null);\r\n activeOverlayRef.current = null;\r\n }\r\n } else {\r\n const due = overlayAdsRef.current.find(\r\n o => time >= o.startTimeSeconds\r\n && time < o.startTimeSeconds + o.durationSeconds\r\n && !dismissedOverlayIdsRef.current.has(o.availId)\r\n );\r\n if (due) {\r\n setActiveOverlay(due);\r\n activeOverlayRef.current = due;\r\n due.impressionBeacons.forEach(url => { fetch(url, { mode: 'no-cors' }).catch(() => {}); });\r\n }\r\n }\r\n }\r\n\r\n // Update chapter badge\r\n const activeCh = sdkInstance.keyMoments.getCurrentMoment(time);\r\n setActiveChapter({ chapter: activeCh.keyMoment, index: activeCh.index });\r\n\r\n // Keep the browser's media-session position in sync so native PiP controls\r\n // (iOS Safari, macOS, Chrome's PiP scrub bar, etc.) display the correct timeline.\r\n // Some MSE/HLS.js playback paths fire stray `timeupdate` events while the video\r\n // is genuinely paused (buffer appends nudging currentTime) — trusting the event\r\n // name alone would mark the session 'playing' again and resume the PiP\r\n // extrapolation the pause handler just froze. Read the element's real paused\r\n // state instead.\r\n syncMediaSessionPosition(time, sdkInstance.getVideoElement().paused);\r\n });\r\n\r\n sdkInstance.on('qualitylevels', (levels: VideoQuality[]) => {\r\n setQualities(levels);\r\n });\r\n\r\n sdkInstance.on('qualitychange', (quality: VideoQuality) => {\r\n setActiveQuality(quality);\r\n });\r\n\r\n sdkInstance.on('buffering', (buffering: boolean) => {\r\n setIsBuffering(buffering && !adSkipCoverUpRef.current);\r\n // 'buffering: false' maps to the native 'playing' event — proof that frames are\r\n // actually rendering. This is the success signal for a source switch.\r\n if (!buffering) {\r\n // Playback started — cancel the unsupported-format watchdog\r\n if (sourceChangeFallbackTimerRef.current) {\r\n clearTimeout(sourceChangeFallbackTimerRef.current);\r\n sourceChangeFallbackTimerRef.current = null;\r\n }\r\n pendingSourceSwitchRef.current = null;\r\n // Restore the playback position the user was at before the switch.\r\n // The resume cover has been up since handleSourceChange — arm the timeupdate\r\n // landing detection so it drops only after playback actually reaches the\r\n // saved position, plus a guard so it can never get stuck up if the seek fails.\r\n if (sourceChangeSeekRef.current !== null) {\r\n const seekTarget = sourceChangeSeekRef.current;\r\n sourceChangeSeekRef.current = null;\r\n // The switch restore supersedes the initialTime resume: the effect cleanup\r\n // reset initialSeekAppliedRef, so without this the initialTime block below\r\n // would re-apply on this player and yank playback away from the restored\r\n // position.\r\n initialSeekAppliedRef.current = true;\r\n resumeSeekTargetRef.current = seekTarget;\r\n if (resumeGuardTimeout.current) window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = window.setTimeout(() => {\r\n resumeSeekTargetRef.current = null;\r\n resumeGuardTimeout.current = null;\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n }, 4000);\r\n sdkInstance.seek(seekTarget);\r\n }\r\n }\r\n });\r\n\r\n sdkInstance.on('adbreakchange', (active: boolean) => {\r\n sdkInstance.analytics.setAdBreak(active, sdkInstance.getVideoElement());\r\n if (onAdBreakChange) onAdBreakChange(active);\r\n if (active) {\r\n const currentT = sdkInstance.getCurrentTime();\r\n const currentBreak = adBreaksRef.current.find(b => currentT >= b.startTime && currentT < b.endTime);\r\n // The ad is confirmed on screen (or about to be skipped as already-watched) —\r\n // isAdPlaying (or the skip cover) takes over gating seeks from here, so the\r\n // redirect-pending lock has done its job.\r\n isAdRedirectPendingRef.current = false;\r\n setIsAdRedirectPending(false);\r\n if (currentBreak && watchedAdBreaksRef.current.has(currentBreak.startTime)) {\r\n // Already watched — cover immediately (belt-and-suspenders with the timeupdate\r\n // pre-skip: if the 500ms look-ahead somehow missed this frame, adbreakchange is\r\n // the last synchronous chance to block it before the next compositor pass).\r\n showAdSkipCover();\r\n if (!isSeeking.current) {\r\n triggerAdSkip(currentBreak.endTime);\r\n }\r\n return;\r\n }\r\n if (currentBreak) {\r\n activeAdBreakStartRef.current = currentBreak.startTime;\r\n }\r\n setIsAdPlaying(true);\r\n setActiveMenu('none');\r\n // The ad is on screen now — drop the redirect-seek cover (no-op if it was\r\n // never raised). Double rAF inside ensures the first ad frame is committed\r\n // before the reveal.\r\n hideAdSkipCover();\r\n } else {\r\n // Ad finished naturally — mark this break as watched for the rest of the session\r\n if (activeAdBreakStartRef.current !== null) {\r\n watchedAdBreaksRef.current.add(activeAdBreakStartRef.current);\r\n setWatchedAdBreaksSet(new Set(watchedAdBreaksRef.current));\r\n activeAdBreakStartRef.current = null;\r\n }\r\n setIsAdPlaying(false);\r\n\r\n // If this ad was part of a seek-forward redirect, resume at the original target.\r\n // Only one (nearest) ad is ever enforced per seek, so no further queuing needed.\r\n if (pendingSeekTargetRef.current !== null) {\r\n const target = pendingSeekTargetRef.current;\r\n pendingSeekTargetRef.current = null;\r\n isSeeking.current = true;\r\n seekTargetTime.current = target;\r\n preSeekPositionRef.current = sdkInstance.getCurrentTime();\r\n showAdSkipCover();\r\n sdkInstance.seek(target);\r\n if (seekGuardTimeout.current) window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = window.setTimeout(() => {\r\n isSeeking.current = false;\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n seekGuardTimeout.current = null;\r\n hideAdSkipCover();\r\n }, 3000);\r\n }\r\n }\r\n });\r\n\r\n sdkInstance.on('adbreaksloaded', (breaks: Array<{ startTime: number; endTime: number }>) => {\r\n // Sync the ref immediately so timeupdate/adbreakchange handlers don't read a stale empty array\r\n // during the React render cycle that follows setAdBreaks.\r\n adBreaksRef.current = breaks;\r\n setAdBreaks(breaks);\r\n\r\n const pendingResume = resumeAdRecheckRef.current;\r\n if (pendingResume === null) return;\r\n resumeAdRecheckRef.current = null;\r\n\r\n const landingBreak = breaks.find(\r\n b => pendingResume >= b.startTime && pendingResume < b.endTime\r\n );\r\n if (!landingBreak || watchedAdBreaksRef.current.has(landingBreak.startTime)) return;\r\n\r\n const now = sdkInstance.getCurrentTime();\r\n if (now < landingBreak.startTime || now >= landingBreak.endTime) return;\r\n\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '1';\r\n resumeSeekTargetRef.current = landingBreak.startTime;\r\n if (resumeGuardTimeout.current) window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = window.setTimeout(() => {\r\n resumeSeekTargetRef.current = null;\r\n resumeGuardTimeout.current = null;\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n }, 4000);\r\n sdkInstance.seek(landingBreak.startTime);\r\n });\r\n\r\n sdkInstance.on('volumechange', (data: { volume: number; muted: boolean }) => {\r\n setVolume(data.volume);\r\n // Ignore muted-state changes caused by the ad-skip cover's force-mute so the\r\n // UI mute icon doesn't flicker; the user's real state is restored on reveal.\r\n if (coverMuteRef.current === null) setIsMuted(data.muted);\r\n });\r\n\r\n sdkInstance.on('pipchange', (active: boolean) => {\r\n setIsPiP(active);\r\n });\r\n\r\n sdkInstance.on('error', () => {\r\n setPlayerError(true);\r\n setIsBuffering(false);\r\n // The error screen renders at z-index 50, below the covers (100) — drop the\r\n // resume cover and its landing state so a failed switch or resume never leaves\r\n // the user staring at black with the error screen hidden behind it.\r\n // sourceChangeSeekRef is intentionally kept: a retry re-init restores position.\r\n resumeSeekTargetRef.current = null;\r\n if (resumeGuardTimeout.current) {\r\n window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = null;\r\n }\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n });\r\n\r\n // Layer 1 (primary): the browser reported this stream's codecs as undecodable.\r\n // Only auto-fall back when this player was created by a user switch to a non-default\r\n // source and there's a first source to fall back to. The default source loading\r\n // normally never emits this, so default playback is unaffected.\r\n sdkInstance.on('unsupportedcodec', () => {\r\n const pending = pendingSourceSwitchRef.current;\r\n if (pending && pending.targetIdx !== 0 && (sourcesRef.current?.length ?? 0) > 1) {\r\n runSourceFallback(pending.targetIdx);\r\n }\r\n });\r\n\r\n // Subtitles track rendering hook\r\n sdkInstance.subtitles.onSubtitleChange((text: string) => {\r\n setSubtitleText(text);\r\n });\r\n\r\n sdkInstance.on('subtitlesloaded', (tracks: SubtitleTrack[]) => {\r\n // Subtitles are gated by the player-key entitlement — skip entirely when not enabled.\r\n if (!effectiveFeatures.enableSubtitles) return;\r\n\r\n setAvailableSubtitles(dedupeSubtitleTracks([...tracks]));\r\n\r\n // Auto-select default subtitle track\r\n const defaultTrack = tracks.find((t) => t.isDefault);\r\n if (defaultTrack) {\r\n sdkInstance.subtitles.setActiveTrack(defaultTrack.id);\r\n setActiveSubtitleTrack(defaultTrack.id);\r\n }\r\n });\r\n\r\n // Audio track discovery\r\n sdkInstance.on('audiotracksloaded', (tracks: Array<{ id: number; label: string; lang: string }>) => {\r\n setAudioTracks(tracks);\r\n setActiveAudioTrack(tracks[0]?.id ?? 0);\r\n });\r\n\r\n sdkInstance.on('audiotrackchange', (id: number) => {\r\n setActiveAudioTrack(id);\r\n });\r\n\r\n // Cast state syncer — pauses local video on cast start, restores on stop\r\n sdkInstance.casting.onCastStateChange((isCasting: boolean, deviceName: string) => {\r\n if (isCasting) {\r\n preCastMutedRef.current = sdkInstance.isMuted();\r\n sdkInstance.pause();\r\n // Mute local element so audio doesn't double-play\r\n const vid = sdkInstance.getVideoElement();\r\n vid.muted = true;\r\n setCastDevice(deviceName);\r\n } else {\r\n // Restore local video from where remote cast left off. Marked as an internal\r\n // seek (isSeeking + target) so the native-seek guard doesn't intercept it and\r\n // re-route through performSeek — whose closure may still hold the not-yet-cleared\r\n // castDevice and would send the seek to the just-disconnected remote session.\r\n const remoteSnap = sdkInstance.casting.getRemoteState();\r\n if (remoteSnap && remoteSnap.currentTime > 0) {\r\n isSeeking.current = true;\r\n seekTargetTime.current = remoteSnap.currentTime;\r\n preSeekPositionRef.current = sdkInstance.getCurrentTime();\r\n sdkInstance.seek(remoteSnap.currentTime);\r\n if (seekGuardTimeout.current) window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = window.setTimeout(() => {\r\n isSeeking.current = false;\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n seekGuardTimeout.current = null;\r\n }, 3000);\r\n }\r\n const vid = sdkInstance.getVideoElement();\r\n vid.muted = preCastMutedRef.current;\r\n setCastDevice(null);\r\n setRemoteState(null);\r\n }\r\n });\r\n\r\n sdkInstance.casting.onRemoteStateChange((state) => {\r\n setRemoteState(state);\r\n });\r\n\r\n // Sync HTML5 video volume initially\r\n setVolume(sdkInstance.getVolume());\r\n setIsMuted(sdkInstance.isMuted());\r\n\r\n // When the user seeks from the native PiP window the browser fires this action with\r\n // a seek time expressed in the content timeline (because we set setPositionState with\r\n // content duration). Convert it back to raw video time then route through performSeek\r\n // so ad-break enforcement (block during ads, redirect over missed breaks) applies\r\n // identically to PiP seeks and normal seekbar seeks.\r\n if ('mediaSession' in navigator) {\r\n try {\r\n navigator.mediaSession.setActionHandler('seekto', (details) => {\r\n if (details.seekTime == null || isAdPlayingRef.current || isAdRedirectPendingRef.current) return;\r\n let rawT = details.seekTime;\r\n for (const ab of adBreaksRef.current) {\r\n if (rawT >= ab.startTime) rawT += (ab.endTime - ab.startTime);\r\n }\r\n performSeekRef.current?.(rawT);\r\n });\r\n\r\n // The native PiP window / media-hub skip buttons fire seekforward / seekbackward.\r\n // Without explicit handlers the browser performs a DEFAULT seek straight on the\r\n // media element, bypassing ad-break enforcement entirely — that let the user skip\r\n // past (or straight out of) an ad from the PiP window. Registering handlers\r\n // suppresses the default and lets us apply the same rules as the on-screen controls:\r\n // • while an ad is on screen the handler no-ops, so seeking / skipping is disabled\r\n // in PiP during ads (only play/pause remain functional), and\r\n // • otherwise a forward skip over an unwatched break is routed through performSeek,\r\n // which redirects into the ad first before landing at the target.\r\n // Content↔raw conversion mirrors the arrow-key handlers below.\r\n const pipSkip = (forward: boolean, rawOffset?: number) => {\r\n if (isAdPlayingRef.current || isAdRedirectPendingRef.current) return;\r\n const offset = rawOffset && rawOffset > 0 ? rawOffset : 10;\r\n const rawT = sdkInstance.getCurrentTime();\r\n let ct = rawT;\r\n for (const ab of adBreaksRef.current) {\r\n if (rawT >= ab.endTime) ct -= (ab.endTime - ab.startTime);\r\n else if (rawT >= ab.startTime) ct -= (rawT - ab.startTime);\r\n }\r\n if (forward) {\r\n const totalAdDur = adBreaksRef.current.reduce((s, ab) => s + (ab.endTime - ab.startTime), 0);\r\n const contentDur = Math.max(0, sdkInstance.getDuration() - totalAdDur);\r\n const newCt = Math.min(contentDur, ct + offset);\r\n let newRaw = newCt;\r\n for (const ab of adBreaksRef.current) {\r\n if (newRaw >= ab.startTime) newRaw += (ab.endTime - ab.startTime);\r\n }\r\n performSeekRef.current?.(newRaw, { eventType: 'fast_forward', skipSeconds: offset });\r\n } else {\r\n const newCt = Math.max(0, ct - offset);\r\n let newRaw = newCt;\r\n for (const ab of adBreaksRef.current) {\r\n if (newRaw >= ab.startTime) newRaw += (ab.endTime - ab.startTime);\r\n }\r\n performSeekRef.current?.(newRaw, { eventType: 'fast_backward', skipSeconds: offset });\r\n }\r\n };\r\n navigator.mediaSession.setActionHandler('seekforward', (details) => pipSkip(true, details.seekOffset ?? undefined));\r\n navigator.mediaSession.setActionHandler('seekbackward', (details) => pipSkip(false, details.seekOffset ?? undefined));\r\n } catch (_) { /* unsupported browser */ }\r\n }\r\n\r\n // ── Native-seek guard (backstop for PiP / external controls) ──────────\r\n // Chrome's PiP window scrubber (and some media-hub / hardware controls) seek the\r\n // <video> element DIRECTLY — the Media Session handlers above never fire — so ad\r\n // enforcement was bypassed entirely from the PiP window. Every internal seek path\r\n // sets isSeeking / a resume ref BEFORE calling seek, so a 'seeking' event arriving\r\n // without any of those flags can only be a native seek. Handle it like the\r\n // on-screen controls would:\r\n // • ad on screen → snap straight back (seeking fully disabled; only play/pause\r\n // remain usable from the PiP window), or\r\n // • otherwise → snap back to the origin, then re-issue via performSeek so a\r\n // forward jump over unwatched ad breaks plays the nearest missed ad first and\r\n // only then resumes at the target — identical rules to the normal player.\r\n // The revert itself re-fires 'seeking', but lands within 0.5s of the stable\r\n // position and is swallowed by the noise threshold (also covers HLS gap-jumps).\r\n const guardedVideoEl = sdkInstance.getVideoElement();\r\n const guardNativeSeek = () => {\r\n // Only guard while THIS video is in PiP: on-page controls are fully custom and\r\n // the media hub goes through the 'seekto' handler, so outside PiP the only\r\n // unflagged seeks are hls.js's own gap-jumps — reverting those would loop\r\n // playback into a stall at the buffer hole.\r\n const inPiP =\r\n document.pictureInPictureElement === guardedVideoEl ||\r\n (guardedVideoEl as any).webkitPresentationMode === 'picture-in-picture';\r\n if (!inPiP) return;\r\n if (\r\n isSeeking.current ||\r\n resumeSeekTargetRef.current !== null ||\r\n sourceChangeSeekRef.current !== null\r\n ) return; // internal seek — already ad-aware\r\n const target = guardedVideoEl.currentTime;\r\n const from = lastStableTimeRef.current;\r\n if (Math.abs(target - from) < 0.5) return;\r\n if (isAdPlayingRef.current) {\r\n guardedVideoEl.currentTime = from;\r\n return;\r\n }\r\n // Restore the true origin first so performSeek's missed-break scan sees the\r\n // real skipped range (currentTime already holds the native target here).\r\n guardedVideoEl.currentTime = from;\r\n performSeekRef.current?.(target);\r\n };\r\n guardedVideoEl.addEventListener('seeking', guardNativeSeek);\r\n\r\n setPlayer(sdkInstance);\r\n\r\n // Layer 2 (backstop): watchdog for streams whose codecs aren't declared in the manifest,\r\n // so Layer 1 has nothing to inspect. If this player was created by a switch to a\r\n // non-default source and real playback ('buffering: false') hasn't fired by the deadline,\r\n // decide based on the buffer:\r\n // • buffer still empty → segments downloaded but couldn't be decoded → fall back.\r\n // • buffer has grown → it's just a slow network, the format IS loading → keep waiting.\r\n // This buffer check is what prevents a valid-but-slow stream from being misjudged.\r\n // The timer is started here (not in handleSourceChange) so the source-switch re-init's\r\n // own cleanup doesn't clear it before it can fire.\r\n const pendingSwitch = pendingSourceSwitchRef.current;\r\n if (pendingSwitch && pendingSwitch.targetIdx !== 0) {\r\n if (sourceChangeFallbackTimerRef.current) {\r\n clearTimeout(sourceChangeFallbackTimerRef.current);\r\n }\r\n sourceChangeFallbackTimerRef.current = setTimeout(() => {\r\n sourceChangeFallbackTimerRef.current = null;\r\n const buffered = sdkInstance.getBufferedRanges();\r\n const hasBufferedData = buffered.some((r) => r.end > r.start);\r\n // Slow network — the chosen format is genuinely loading; don't interrupt it.\r\n if (hasBufferedData) return;\r\n runSourceFallback(pendingSwitch.targetIdx);\r\n }, SOURCE_FALLBACK_TIMEOUT_MS);\r\n }\r\n\r\n // Stall safety net for the source-switch resume cover: if this player never\r\n // reaches buffering:false (total stall with no error/unsupportedcodec event),\r\n // drop the cover so the user is never left on a black screen. Slightly longer\r\n // than SOURCE_FALLBACK_TIMEOUT_MS so the watchdog above gets to run a fallback\r\n // first (whose re-init re-arms this timer via the effect re-run). The normal\r\n // path clears/re-arms this guard when buffering:false issues the restore seek.\r\n if (sourceChangeSeekRef.current !== null) {\r\n if (resumeGuardTimeout.current) window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = window.setTimeout(() => {\r\n resumeGuardTimeout.current = null;\r\n resumeSeekTargetRef.current = null;\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n // Also clear the switch-forced spinner (see handleSourceChange) so a dead\r\n // stall doesn't leave an eternal spinner after the cover is abandoned.\r\n setIsBuffering(false);\r\n }, SOURCE_FALLBACK_TIMEOUT_MS + 2000);\r\n }\r\n\r\n // Attempt fullscreen immediately on player init when autoFullscreen is requested.\r\n // Browsers allow this when the page was opened by a user action (URL entry, link click).\r\n if (autoFullscreen && containerRef.current) {\r\n hasAutoFullscreenedRef.current = true;\r\n containerRef.current.requestFullscreen().catch(() => {\r\n // Browser denied — video still autoplays in normal mode, no harm done\r\n hasAutoFullscreenedRef.current = false;\r\n });\r\n }\r\n\r\n // Watch for fullscreen change globally to sync state and track analytics\r\n const handleFullscreenChange = () => {\r\n const anyDoc = document as any;\r\n const isFs = !!(document.fullscreenElement || anyDoc.webkitFullscreenElement);\r\n setIsFullscreen(isFs);\r\n // Clear simulated fullscreen flag when native fullscreen exits\r\n if (!isFs) setIsSimulatedFullscreen(false);\r\n const videoEl = sdkInstance.getVideoElement();\r\n sdkInstance.analytics.track(\r\n isFs ? 'fullscreen_enter' : 'fullscreen_exit',\r\n videoEl\r\n );\r\n };\r\n document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n\r\n // Global keyboard controls handler\r\n const handleKeyDown = (e: KeyboardEvent) => {\r\n const activeEl = document.activeElement;\r\n if (activeEl && (\r\n activeEl.tagName === 'INPUT' ||\r\n activeEl.tagName === 'TEXTAREA' ||\r\n activeEl.getAttribute('contenteditable') === 'true' ||\r\n activeEl.getAttribute('role') === 'slider'\r\n )) {\r\n return;\r\n }\r\n\r\n switch (e.key.toLowerCase()) {\r\n case ' ': // Space bar\r\n case 'k':\r\n e.preventDefault();\r\n if (sdkInstance.getVideoElement().paused) {\r\n sdkInstance.play();\r\n } else {\r\n sdkInstance.pause();\r\n try {\r\n const v = sdkInstance.getVideoElement();\r\n if (v && !v.paused) v.pause();\r\n } catch (e) {}\r\n if (hiddenVideoRef.current && !hiddenVideoRef.current.paused) {\r\n try { hiddenVideoRef.current.pause(); } catch (e) {}\r\n }\r\n }\r\n break;\r\n case 'arrowleft': // Seek back 10s\r\n case 'j': {\r\n e.preventDefault();\r\n if (!isAdPlayingRef.current) {\r\n const rawT = sdkInstance.getCurrentTime();\r\n let ct = rawT;\r\n for (const ab of adBreaksRef.current) {\r\n if (rawT >= ab.endTime) ct -= (ab.endTime - ab.startTime);\r\n else if (rawT >= ab.startTime) ct -= (rawT - ab.startTime);\r\n }\r\n const newCt = Math.max(0, ct - 10);\r\n let newRaw = newCt;\r\n for (const ab of adBreaksRef.current) {\r\n if (newRaw >= ab.startTime) newRaw += (ab.endTime - ab.startTime);\r\n }\r\n // Route through performSeek so isSeeking is set before the seek — a raw\r\n // sdkInstance.seek here would be misread by the native-seek guard as an\r\n // external seek and re-routed (consuming the attribution twice).\r\n performSeekRef.current?.(newRaw, { eventType: 'fast_backward', skipSeconds: 10 });\r\n }\r\n break;\r\n }\r\n case 'arrowright': // Seek forward 10s\r\n case 'l': {\r\n e.preventDefault();\r\n if (!isAdPlayingRef.current) {\r\n const rawT = sdkInstance.getCurrentTime();\r\n const totalAdDur = adBreaksRef.current.reduce((s, ab) => s + (ab.endTime - ab.startTime), 0);\r\n const contentDur = Math.max(0, sdkInstance.getDuration() - totalAdDur);\r\n let ct = rawT;\r\n for (const ab of adBreaksRef.current) {\r\n if (rawT >= ab.endTime) ct -= (ab.endTime - ab.startTime);\r\n else if (rawT >= ab.startTime) ct -= (rawT - ab.startTime);\r\n }\r\n const newCt = Math.min(contentDur, ct + 10);\r\n let newRaw = newCt;\r\n for (const ab of adBreaksRef.current) {\r\n if (newRaw >= ab.startTime) newRaw += (ab.endTime - ab.startTime);\r\n }\r\n // Route through performSeek so any unwatched ad break in the skipped\r\n // range is enforced — same validation as scrubber and mobile buttons.\r\n performSeekRef.current?.(newRaw, { eventType: 'fast_forward', skipSeconds: 10 });\r\n }\r\n break;\r\n }\r\n case 'arrowup': // Volume up 10%\r\n e.preventDefault();\r\n sdkInstance.setVolume(sdkInstance.getVolume() + 0.1);\r\n break;\r\n case 'arrowdown': // Volume down 10%\r\n e.preventDefault();\r\n sdkInstance.setVolume(sdkInstance.getVolume() - 0.1);\r\n break;\r\n case 'm': // Toggle Mute\r\n e.preventDefault();\r\n sdkInstance.setMute(!sdkInstance.isMuted());\r\n break;\r\n case 'f': // Toggle Fullscreen\r\n e.preventDefault();\r\n toggleFullscreenRef.current();\r\n break;\r\n case 'p': // Toggle Picture-in-Picture\r\n e.preventDefault();\r\n if (!isAdPlayingRef.current) sdkInstance.togglePictureInPicture();\r\n break;\r\n case 'c': // Toggle Subtitles\r\n e.preventDefault();\r\n if (effectiveFeatures.enableSubtitles && !isAdPlayingRef.current) {\r\n const activeTrackId = sdkInstance.subtitles.getActiveTrackId();\r\n if (activeTrackId !== 'off') {\r\n sdkInstance.subtitles.setActiveTrack('off');\r\n setActiveSubtitleTrack('off');\r\n } else {\r\n const tracks = availableSubtitlesRef.current;\r\n if (tracks && tracks.length > 0) {\r\n sdkInstance.subtitles.setActiveTrack(tracks[0].id);\r\n setActiveSubtitleTrack(tracks[0].id);\r\n }\r\n }\r\n }\r\n break;\r\n default:\r\n return;\r\n }\r\n resetControlsTimer();\r\n };\r\n\r\n document.addEventListener('keydown', handleKeyDown);\r\n\r\n // Reset controls visibility timer\r\n resetControlsTimer();\r\n\r\n return () => {\r\n // Snapshot session-scoped analytics counters before destroy so the successor\r\n // instance (source switch / fallback / retry) continues the same analytics\r\n // session. Tagged with this player's contentKey — see analyticsSeedRef.\r\n analyticsSeedRef.current = { key: contentKey, snapshot: sdkInstance.analytics.getSessionSnapshot() };\r\n // If a custom PiP window is open, reclaim the <video> element and close the\r\n // floating window BEFORE destroy() runs — otherwise destroy() detaches the video\r\n // from the (still-open) PiP document, leaving a dangling empty window, and the\r\n // next player instance would append a second <video> into containerRef alongside\r\n // whatever this cleanup left behind.\r\n if (pipWindowRef.current) {\r\n const pipWin = pipWindowRef.current;\r\n const videoEl = sdkInstance.getVideoElement();\r\n if (containerRef.current && videoEl.ownerDocument !== document) {\r\n videoEl.classList.remove('gb-pip-video');\r\n containerRef.current.insertBefore(videoEl, containerRef.current.firstChild);\r\n }\r\n pipWindowRef.current = null;\r\n setPipWindow(null);\r\n setIsPiP(false);\r\n pipWin.close();\r\n }\r\n sdkInstance.destroy();\r\n firebaseProviderRef.current?.destroy();\r\n firebaseProviderRef.current = null;\r\n // Restore body scroll in case the component unmounts while simulated fullscreen is active\r\n document.body.style.overflow = '';\r\n document.body.style.touchAction = '';\r\n document.removeEventListener('fullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('keydown', handleKeyDown);\r\n if (controlsTimeoutRef.current) {\r\n window.clearTimeout(controlsTimeoutRef.current);\r\n }\r\n // Cancel any in-flight ad-skip seek and remove the cover so the player is\r\n // never left blacked-out if the component unmounts mid-skip.\r\n if (seekGuardTimeout.current) {\r\n window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = null;\r\n }\r\n if (sourceChangeFallbackTimerRef.current) {\r\n clearTimeout(sourceChangeFallbackTimerRef.current);\r\n sourceChangeFallbackTimerRef.current = null;\r\n }\r\n adSkipCoverUpRef.current = false;\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '0';\r\n guardedVideoEl.removeEventListener('seeking', guardNativeSeek);\r\n if ('mediaSession' in navigator) {\r\n try { navigator.mediaSession.setActionHandler('seekto', null); } catch (_) {}\r\n try { navigator.mediaSession.setActionHandler('seekforward', null); } catch (_) {}\r\n try { navigator.mediaSession.setActionHandler('seekbackward', null); } catch (_) {}\r\n }\r\n if (hiddenHlsRef.current) {\r\n hiddenHlsRef.current.destroy();\r\n hiddenHlsRef.current = null;\r\n }\r\n if (hiddenVideoRef.current) {\r\n document.body.removeChild(hiddenVideoRef.current);\r\n hiddenVideoRef.current = null;\r\n }\r\n initialSeekAppliedRef.current = false;\r\n resumeSeekTargetRef.current = null;\r\n if (resumeGuardTimeout.current) {\r\n window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = null;\r\n }\r\n };\r\n }, [src, watermark, retryKey]);\r\n\r\n // Synchronize key moments prop dynamically to the running player instance\r\n useEffect(() => {\r\n if (player) {\r\n player.keyMoments.setKeyMoments(localKeyMoments);\r\n \r\n const video = containerRef.current?.querySelector('video');\r\n if (video) {\r\n const activeCh = player.keyMoments.getCurrentMoment(video.currentTime);\r\n setActiveChapter({ chapter: activeCh.keyMoment, index: activeCh.index });\r\n }\r\n }\r\n }, [keyMomentsKey, player]);\r\n\r\n // Close any open control drop-up menu when clicking outside of it\r\n useEffect(() => {\r\n const handleDocumentClick = (e: MouseEvent) => {\r\n if (activeMenu === 'none') return;\r\n\r\n const target = e.target as HTMLElement;\r\n if (target.closest('.sdk-menu-container')) {\r\n return;\r\n }\r\n\r\n setActiveMenu('none');\r\n };\r\n\r\n document.addEventListener('click', handleDocumentClick);\r\n return () => {\r\n document.removeEventListener('click', handleDocumentClick);\r\n };\r\n }, [activeMenu]);\r\n\r\n // Keep refs in sync with latest state so timeout callbacks are never stale\r\n useEffect(() => { isPlayingRef.current = isPlaying; }, [isPlaying]);\r\n useEffect(() => { isAdPlayingRef.current = isAdPlaying; }, [isAdPlaying]);\r\n useEffect(() => { isAdRedirectPendingRef.current = isAdRedirectPending; }, [isAdRedirectPending]);\r\n\r\n // Drives the seekbar's interactive/disabled rendering: locked both while an ad is\r\n // actually on screen and during the brief redirect-in-flight window beforehand.\r\n const seekLocked = isAdPlaying || isAdRedirectPending;\r\n useEffect(() => { pipWindowRef.current = pipWindow; }, [pipWindow]);\r\n useEffect(() => { activeMenuRef.current = activeMenu; }, [activeMenu]);\r\n\r\n useEffect(() => {\r\n if (!isAdPlaying || !player) return;\r\n\r\n let raf = 0;\r\n let anchorTime = player.getCurrentTime();\r\n let anchorWall = performance.now();\r\n\r\n const tick = () => {\r\n const video = player.getVideoElement?.();\r\n const live = player.getCurrentTime();\r\n const now = performance.now();\r\n\r\n let est: number;\r\n if (live !== anchorTime) {\r\n // Media clock advanced (or seeked) — re-anchor to the true position.\r\n anchorTime = live;\r\n anchorWall = now;\r\n est = live;\r\n } else if (video && !video.paused && !video.ended) {\r\n // Media clock hasn't ticked this frame — glide forward with the wall clock.\r\n const rate = video.playbackRate || 1;\r\n est = anchorTime + ((now - anchorWall) / 1000) * rate;\r\n } else {\r\n est = anchorTime; // paused/ended/stalled — hold position\r\n }\r\n\r\n const win = getActiveAdWindow(live);\r\n if (win) {\r\n // Never let interpolation overshoot the true clock (guards against buffering\r\n // stalls where currentTime freezes) or the current ad's boundary.\r\n const capped = Math.min(est, live + 0.4, win.end);\r\n const pct = Math.min(100, Math.max(0, ((capped - win.start) / (win.end - win.start)) * 100));\r\n const fills = containerRef.current?.querySelectorAll<HTMLElement>('.sdk-ad-progress-fill');\r\n fills?.forEach(el => { el.style.width = `${pct}%`; });\r\n }\r\n\r\n raf = requestAnimationFrame(tick);\r\n };\r\n\r\n raf = requestAnimationFrame(tick);\r\n return () => cancelAnimationFrame(raf);\r\n }, [isAdPlaying, player]);\r\n useEffect(() => { availableSubtitlesRef.current = availableSubtitles; }, [availableSubtitles]);\r\n useEffect(() => { isMobileRef.current = isMobile; }, [isMobile]);\r\n useEffect(() => { adBreaksRef.current = adBreaks; }, [adBreaks]);\r\n useEffect(() => { overlayAdsRef.current = overlayAds; }, [overlayAds]);\r\n useEffect(() => { activeOverlayRef.current = activeOverlay; }, [activeOverlay]);\r\n useEffect(() => { adPodItemsRef.current = adPodItems; }, [adPodItems]);\r\n useEffect(() => { sourcesRef.current = sources; }, [sources]);\r\n useEffect(() => { onSourceChangeRef.current = onSourceChange; }, [onSourceChange]);\r\n useEffect(() => {\r\n activeAdBreakStartRef.current = null;\r\n wasAdPausedByVisibilityRef.current = false;\r\n pendingSeekTargetRef.current = null;\r\n resumeAdRecheckRef.current = null;\r\n // Reset overlay ad state on source change\r\n setActiveOverlay(null);\r\n activeOverlayRef.current = null;\r\n dismissedOverlayIdsRef.current = new Set();\r\n setPlayerError(false);\r\n }, [src]);\r\n\r\n useEffect(() => { setManifestReady(false); }, [src]);\r\n\r\n useEffect(() => {\r\n if (!player) return;\r\n const video = player.getVideoElement();\r\n if (video && video.readyState >= 1) {\r\n setManifestReady(true);\r\n return;\r\n }\r\n const onReady = () => setManifestReady(true);\r\n player.on('qualitylevels', onReady);\r\n video?.addEventListener('loadedmetadata', onReady);\r\n return () => {\r\n player.off('qualitylevels', onReady);\r\n video?.removeEventListener('loadedmetadata', onReady);\r\n };\r\n }, [player]);\r\n\r\n // Fetch and parse overlay (non-linear) ad metadata\r\n useEffect(() => {\r\n setOverlayAds([]);\r\n if (!overlayAdUrl || !manifestReady) return;\r\n return pollAdMetadata(\r\n overlayAdUrl,\r\n (data: any) => {\r\n const seen = new Set<string>();\r\n const parsed: OverlayAdItem[] = [];\r\n for (const avail of (data.nonLinearAvails ?? [])) {\r\n if (seen.has(avail.availId)) continue;\r\n seen.add(avail.availId);\r\n const adsList = avail.nonLinearAdsList?.[0];\r\n const ad = adsList?.nonLinearAdList?.[0];\r\n if (!ad?.staticResource) continue;\r\n const impressionBeacons: string[] = (adsList?.trackingEvents ?? [])\r\n .filter((e: any) => e.eventType === 'impression')\r\n .flatMap((e: any) => e.beaconUrls ?? []);\r\n let adParams: { position?: string; dismissible?: boolean } = {};\r\n try { adParams = JSON.parse(ad.adParameters ?? '{}'); } catch (_) {}\r\n const position = adParams.position ?? 'bottom_center';\r\n parsed.push({\r\n availId: avail.availId,\r\n startTimeSeconds: parseHHMMSS(avail.startTime ?? ''),\r\n durationSeconds: parseHHMMSS(ad.minSuggestedDuration ?? '00:00:15'),\r\n imageUrl: ad.staticResource,\r\n imageWidth: parseInt(ad.width ?? '320', 10),\r\n imageHeight: parseInt(ad.height ?? '50', 10),\r\n title: ad.adTitle ?? '',\r\n clickThrough: isWebUrl(ad.clickThrough) ? ad.clickThrough : '',\r\n clickTrackingUrl: ad.clickTracking ?? '',\r\n impressionBeacons,\r\n position,\r\n dismissible: adParams.dismissible === true,\r\n });\r\n }\r\n return parsed.length > 0 ? parsed : null;\r\n },\r\n setOverlayAds\r\n );\r\n }, [overlayAdUrl, src, manifestReady]);\r\n\r\n // Fetch and parse avails (linear ad) metadata. Each avail is one ad break and may carry\r\n // a pod of multiple back-to-back ads (avail.ads) — every ad in the pod is parsed, not just\r\n // the first, so skip/CTA/countdown state is correct throughout the whole break.\r\n useEffect(() => {\r\n setAdPodItems([]);\r\n setAvailBreaks([]);\r\n if (!adTrackingUrl || !manifestReady) return;\r\n return pollAdMetadata(\r\n adTrackingUrl,\r\n (data: any) => {\r\n const pods: AdPodItem[] = [];\r\n const breaks: Array<{ startTime: number; endTime: number }> = [];\r\n for (const avail of (data.avails ?? [])) {\r\n const ads = avail.ads ?? [];\r\n if (ads.length === 0) continue;\r\n const breakStart = avail.startTimeInSeconds ?? ads[0].startTimeInSeconds ?? 0;\r\n const lastAd = ads[ads.length - 1];\r\n const podEnd = (lastAd?.startTimeInSeconds ?? breakStart) + (lastAd?.durationInSeconds ?? 0);\r\n breaks.push({\r\n startTime: breakStart,\r\n endTime: avail.durationInSeconds != null\r\n ? breakStart + avail.durationInSeconds\r\n : Math.max(podEnd, breakStart),\r\n });\r\n ads.forEach((ad: any, podIndex: number) => {\r\n const skipOffsetSeconds = parseSkipOffsetSeconds(ad?.skipOffset);\r\n const skippable = skipOffsetSeconds !== null;\r\n let adParams: { ctaText?: string; redirectUrl?: string; title?: string; description?: string } = {};\r\n try { adParams = JSON.parse(ad?.adParameters ?? '{}'); } catch (_) {}\r\n const cta: AdCtaInfo | null = adParams.ctaText && isWebUrl(adParams.redirectUrl)\r\n ? {\r\n text: adParams.ctaText,\r\n url: adParams.redirectUrl,\r\n title: adParams.title || undefined,\r\n description: adParams.description || undefined,\r\n }\r\n : null;\r\n const startTimeSeconds = ad.startTimeInSeconds ?? 0;\r\n const durationSeconds = ad.durationInSeconds ?? 0;\r\n pods.push({\r\n startTimeSeconds,\r\n endTimeSeconds: startTimeSeconds + durationSeconds,\r\n durationSeconds,\r\n skippable,\r\n skipOffsetSeconds: skipOffsetSeconds ?? 0,\r\n cta,\r\n podIndex,\r\n podTotal: ads.length,\r\n });\r\n });\r\n }\r\n return pods.length > 0 ? { pods, breaks } : null;\r\n },\r\n ({ pods, breaks }) => {\r\n setAdPodItems(pods);\r\n setAvailBreaks(breaks);\r\n }\r\n );\r\n }, [adTrackingUrl, src, manifestReady]);\r\n\r\n // Feed avails-derived ad breaks into core-player so adbreakchange, cue markers,\r\n // and ad mode all work even when the HLS manifest parser doesn't detect them.\r\n // Runs whenever the player initialises or avails data arrives (whichever is last).\r\n useEffect(() => {\r\n if (!player || availBreaks.length === 0) return;\r\n player.setAdBreaks(availBreaks);\r\n }, [player, availBreaks]);\r\n\r\n useEffect(() => {\r\n if (!player) return;\r\n\r\n const handleVisibilityChange = () => {\r\n if (document.hidden) {\r\n if (pauseAdOnTabSwitch && isAdPlayingRef.current && isPlayingRef.current) {\r\n player.pause();\r\n wasAdPausedByVisibilityRef.current = true;\r\n }\r\n } else {\r\n if (wasAdPausedByVisibilityRef.current) {\r\n player.play();\r\n wasAdPausedByVisibilityRef.current = false;\r\n }\r\n }\r\n };\r\n\r\n document.addEventListener('visibilitychange', handleVisibilityChange);\r\n return () => {\r\n document.removeEventListener('visibilitychange', handleVisibilityChange);\r\n };\r\n }, [player, pauseAdOnTabSwitch]);\r\n\r\n const handleMouseMove = () => {\r\n resetControlsTimer();\r\n };\r\n\r\n const triggerAutoFullscreen = () => {\r\n if (autoFullscreen && !hasAutoFullscreenedRef.current && containerRef.current) {\r\n hasAutoFullscreenedRef.current = true;\r\n containerRef.current.requestFullscreen().catch(() => {});\r\n }\r\n };\r\n\r\n\r\n const togglePlay = () => {\r\n if (!player) return;\r\n if (castDevice) {\r\n player.casting.remotePlayPause();\r\n resetControlsTimer();\r\n return;\r\n }\r\n if (isEnded) {\r\n performSeek(0);\r\n triggerAutoFullscreen();\r\n player.play();\r\n setIsEnded(false);\r\n } else if (isPlaying) {\r\n player.pause();\r\n // Ensure all underlying HTMLMediaElements are paused as a defensive measure\r\n try {\r\n const videoEl = player.getVideoElement?.();\r\n if (videoEl && !videoEl.paused) videoEl.pause();\r\n } catch (e) {}\r\n if (containerRef.current) {\r\n const vids = containerRef.current.querySelectorAll('video');\r\n vids.forEach(v => { try { (v as HTMLVideoElement).pause(); } catch (e) {} });\r\n }\r\n if (hiddenVideoRef.current && !hiddenVideoRef.current.paused) {\r\n try { hiddenVideoRef.current.pause(); } catch (e) {}\r\n }\r\n } else {\r\n triggerAutoFullscreen();\r\n player.play();\r\n }\r\n resetControlsTimer();\r\n };\r\n\r\n const seekFromEvent = (e: React.MouseEvent<HTMLDivElement> | MouseEvent) => {\r\n if (!seekbarRef.current || !player || displayDuration === 0) return 0;\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\r\n return contentTimeToVideoTime(percent * displayDuration);\r\n };\r\n\r\n const handleSeekMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {\r\n if (!player || isAdPlaying || isAdRedirectPending) return;\r\n isScrubbing.current = true;\r\n const initialTime = seekFromEvent(e);\r\n setScrubbingTime(initialTime);\r\n\r\n const onMouseMove = (ev: MouseEvent) => {\r\n if (!isScrubbing.current || !seekbarRef.current) return;\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (ev.clientX - rect.left) / rect.width));\r\n const targetTime = contentTimeToVideoTime(percent * displayDuration);\r\n\r\n setScrubbingTime(targetTime);\r\n setHoverX(ev.clientX - rect.left);\r\n setHoverTime(targetTime);\r\n\r\n if (hiddenVideoRef.current) {\r\n hiddenVideoRef.current.currentTime = targetTime;\r\n }\r\n resetControlsTimer();\r\n };\r\n\r\n const onMouseUp = (ev: MouseEvent) => {\r\n isScrubbing.current = false;\r\n if (seekbarRef.current) {\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (ev.clientX - rect.left) / rect.width));\r\n performSeek(contentTimeToVideoTime(percent * displayDuration));\r\n }\r\n setScrubbingTime(null);\r\n setHoverX(null);\r\n setHoverThumbnail(null);\r\n setHoverChapter(null);\r\n document.removeEventListener('mousemove', onMouseMove);\r\n document.removeEventListener('mouseup', onMouseUp);\r\n };\r\n\r\n document.addEventListener('mousemove', onMouseMove);\r\n document.addEventListener('mouseup', onMouseUp);\r\n };\r\n\r\n const performSeek = (time: number, attribution?: SeekAttribution) => {\r\n if (!player) return;\r\n resumeAdRecheckRef.current = null;\r\n if (castDevice) {\r\n player.casting.remoteSeek(time);\r\n return;\r\n }\r\n // Clamp to valid range so time never goes negative or past duration\r\n const clampedTime = Math.max(0, duration > 0 ? Math.min(time, duration) : time);\r\n\r\n // Telemetry: report skip-button / key-moment seeks under their own event type.\r\n // Applies to whichever player.seek fires next (direct or ad-redirect).\r\n if (attribution) {\r\n const { eventType, ...extras } = attribution;\r\n player.attributeNextSeek(eventType, extras);\r\n }\r\n\r\n // Forward seek: enforce any unplayed ad breaks in the skipped range.\r\n // Find breaks that overlap [rawCurrentTime, clampedTime) and have not yet been\r\n // watched this session. Uses endTime > rawCurrentTime (not startTime > rawCurrentTime)\r\n // so a break the player is already sitting at/inside still counts as unmissed: setting\r\n // video.currentTime updates the readback synchronously, so a second seek fired quickly\r\n // after a redirect-to-ad seek (e.g. a fast double-click on the seekbar landing while\r\n // isAdPlaying hasn't flipped yet) would otherwise see rawCurrentTime already equal to\r\n // the break's startTime and no longer treat it as \"ahead\", skipping the ad entirely.\r\n const rawCurrentTime = player.getCurrentTime();\r\n if (clampedTime > rawCurrentTime && adBreaksRef.current.length > 0) {\r\n const missed = adBreaksRef.current\r\n .filter(b =>\r\n b.startTime < clampedTime &&\r\n b.endTime > rawCurrentTime &&\r\n !watchedAdBreaksRef.current.has(b.startTime)\r\n )\r\n .sort((a, b) => a.startTime - b.startTime);\r\n\r\n if (missed.length > 0) {\r\n // Only enforce the nearest (last) missed ad before the target — earlier skipped\r\n // breaks are left unwatched. After that single ad plays, adbreakchange(false)\r\n // resumes at the original target directly.\r\n pendingSeekTargetRef.current = clampedTime;\r\n const nearestMissed = missed[missed.length - 1];\r\n // If we're already at/inside the break (the race above), hold position instead\r\n // of seeking backward to its startTime.\r\n const redirectTarget = Math.max(nearestMissed.startTime, rawCurrentTime);\r\n isSeeking.current = true;\r\n seekTargetTime.current = redirectTarget;\r\n preSeekPositionRef.current = rawCurrentTime;\r\n setCurrentTime(redirectTarget);\r\n // Lock out further seeking immediately (ref first — synchronous — so any seek\r\n // handler invoked before the next render still sees it) until the ad is actually\r\n // on screen (isAdPlaying takes over) or the guard timeout below gives up.\r\n isAdRedirectPendingRef.current = true;\r\n setIsAdRedirectPending(true);\r\n // Cover the redirect seek: keyframe snapping can land slightly before the\r\n // break, briefly exposing pre-break content. adbreakchange(true) drops the\r\n // cover once the first ad frame is committed. Mute alongside — the cover\r\n // only hides pixels, not the covered frames' audio.\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '1';\r\n muteCoverAudio(player.getVideoElement());\r\n player.seek(redirectTarget);\r\n if (seekGuardTimeout.current) window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = window.setTimeout(() => {\r\n isSeeking.current = false;\r\n isAdRedirectPendingRef.current = false;\r\n setIsAdRedirectPending(false);\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n seekGuardTimeout.current = null;\r\n adSkipCoverUpRef.current = false;\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '0';\r\n restoreCoverAudio(player.getVideoElement());\r\n }, 3000);\r\n return;\r\n }\r\n }\r\n\r\n // No missed ads (backward seek, or all breaks already watched) — seek normally.\r\n // Also clear any stale pending target so a previous queue doesn't interfere.\r\n pendingSeekTargetRef.current = null;\r\n const nearbyWatchedBreak = adBreaksRef.current.find(\r\n b => watchedAdBreaksRef.current.has(b.startTime) && clampedTime >= b.startTime - 0.5 && clampedTime < b.endTime + 2\r\n );\r\n const actualTarget = nearbyWatchedBreak ? Math.max(clampedTime, nearbyWatchedBreak.endTime + 0.5) : clampedTime;\r\n isSeeking.current = true;\r\n seekTargetTime.current = actualTarget;\r\n preSeekPositionRef.current = rawCurrentTime;\r\n setCurrentTime(actualTarget);\r\n adSkipCoverUpRef.current = false;\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '0';\r\n // Clear any dangling force-mute from an abandoned ad redirect (e.g. the user\r\n // seeked backward while a redirect seek was still in flight).\r\n restoreCoverAudio(player.getVideoElement());\r\n player.seek(actualTarget);\r\n if (seekGuardTimeout.current) window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = window.setTimeout(() => {\r\n isSeeking.current = false;\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n seekGuardTimeout.current = null;\r\n adSkipCoverUpRef.current = false;\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '0';\r\n }, 3000);\r\n };\r\n\r\n // Keep ref current on every render so the keyboard handler (inside the useEffect\r\n // closure) always calls the latest performSeek with up-to-date state.\r\n performSeekRef.current = performSeek;\r\n\r\n const toggleMute = () => {\r\n if (!player) return;\r\n if (castDevice) {\r\n player.casting.remoteSetMute(!(remoteState?.isMuted ?? false));\r\n return;\r\n }\r\n const nextMute = !isMuted;\r\n player.setMute(nextMute);\r\n setIsMuted(nextMute);\r\n };\r\n\r\n const handleSpeedChange = (rate: number) => {\r\n if (!player) return;\r\n player.setPlaybackRate(rate);\r\n setPlaybackRate(rate);\r\n setActiveMenu('none');\r\n setAnnouncement(`Playback speed: ${rate === 1 ? 'Normal' : `${rate}x`}`);\r\n };\r\n\r\n const handleQualityChange = (qualityId: number) => {\r\n if (!player) return;\r\n player.setQuality(qualityId);\r\n\r\n const selected = qualities.find((q) => q.id === qualityId);\r\n if (selected) {\r\n setActiveQuality(selected);\r\n setAnnouncement(`Quality: ${selected.label}`);\r\n }\r\n setActiveMenu('none');\r\n };\r\n\r\n const handleSubtitleChange = (trackId: string) => {\r\n if (!player) return;\r\n player.subtitles.setActiveTrack(trackId);\r\n setActiveSubtitleTrack(trackId);\r\n setActiveMenu('none');\r\n if (trackId === 'off') {\r\n setAnnouncement('Captions off');\r\n } else {\r\n const track = availableSubtitles.find((t) => t.id === trackId);\r\n setAnnouncement(`Captions: ${track ? getLanguageLabel(track) : 'on'}`);\r\n }\r\n };\r\n\r\n const handleSourceChange = (idx: number) => {\r\n if (!player) return;\r\n // Clear any previous fallback watchdog before starting a new switch\r\n if (sourceChangeFallbackTimerRef.current) {\r\n clearTimeout(sourceChangeFallbackTimerRef.current);\r\n sourceChangeFallbackTimerRef.current = null;\r\n }\r\n setSourceFallbackNotice(null);\r\n // Raise the resume cover for the entire switch. It survives the player re-init\r\n // (the effect cleanup intentionally doesn't touch it) and any unsupported-format\r\n // fallback, and is dropped by the timeupdate landing detection once playback\r\n // reaches the restored position — so neither frame 0 of the new source nor any\r\n // watched-ad frames near the restore point are ever composited visibly.\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '1';\r\n // Show the spinner (it renders above the cover) for the whole attempt, not just\r\n // while 'buffering' events fire: an unsupported format may never emit them — it\r\n // stalls silently until the fallback watchdog — which would leave the user staring\r\n // at the bare cover with no feedback. Cleared by the new source's buffering:false\r\n // (native 'playing'), the error handler, or the stall safety net — the same\r\n // signals that resolve the switch itself.\r\n setIsBuffering(true);\r\n // Save current position so the new source resumes where the user left off.\r\n // Only capture a fresh position if the previous switch already resolved — otherwise\r\n // the current player may be a stalled (unsupported) source sitting at 0, and we want\r\n // to keep the original position the user was actually watching.\r\n if (pendingSourceSwitchRef.current === null) {\r\n sourceChangeSeekRef.current = player.getCurrentTime();\r\n }\r\n // Flag the switch so the new player's effect starts the unsupported-format watchdog\r\n pendingSourceSwitchRef.current = { targetIdx: idx };\r\n setActiveMenu('none');\r\n setAnnouncement(`Source: ${sources?.[idx]?.label ?? ''}`);\r\n onSourceChange?.(idx);\r\n };\r\n\r\n const handleChapterClick = (startTime: number, moment?: KeyMoment) => {\r\n performSeek(startTime, {\r\n eventType: 'key_moments',\r\n momentTitle: moment?.title ?? '',\r\n momentTime: startTime,\r\n });\r\n setActiveMenu('none');\r\n };\r\n\r\n // Moves the real <video> element (still owned by core-player, wherever it currently\r\n // lives) back into the on-page container and clears PiP state. Shared by the manual\r\n // toggle-off path, the PiP window's own close button ('pagehide'), and the player\r\n // effect's cleanup (source change / unmount) so a closed window never leaves a stale ref.\r\n const closeCustomPip = useCallback(() => {\r\n const pipWin = pipWindowRef.current;\r\n if (!pipWin) return;\r\n const videoEl = player?.getVideoElement?.();\r\n if (videoEl && containerRef.current && videoEl.ownerDocument !== document) {\r\n videoEl.classList.remove('gb-pip-video');\r\n videoEl.style.objectFit = '';\r\n containerRef.current.insertBefore(videoEl, containerRef.current.firstChild);\r\n }\r\n pipWindowRef.current = null;\r\n setPipWindow(null);\r\n setIsPiP(false);\r\n }, [player]);\r\n\r\n const togglePiP = async () => {\r\n if (!player) return;\r\n const docPip = (window as any).documentPictureInPicture;\r\n\r\n // Chromium's Document Picture-in-Picture API lets us open a real floating window and\r\n // render our own controls inside it — sidestepping the native PiP overlay entirely,\r\n // which (per the countdown-drift bug) reads the real <video> element's currentTime\r\n // directly and isn't reliably frozen on pause. Where it's unsupported (Firefox, Safari)\r\n // fall back to the existing native video.requestPictureInPicture() flow.\r\n if (!docPip) {\r\n player.togglePictureInPicture();\r\n return;\r\n }\r\n\r\n if (pipWindowRef.current) {\r\n pipWindowRef.current.close();\r\n return;\r\n }\r\n\r\n try {\r\n const videoEl = player.getVideoElement();\r\n const aspect = videoEl.videoWidth && videoEl.videoHeight\r\n ? videoEl.videoWidth / videoEl.videoHeight\r\n : 16 / 9;\r\n const width = 360;\r\n const pipWin: Window = await docPip.requestWindow({ width, height: Math.round(width / aspect) });\r\n\r\n const style = pipWin.document.createElement('style');\r\n style.textContent = PIP_STYLES;\r\n pipWin.document.head.appendChild(style);\r\n\r\n videoEl.classList.add('gb-pip-video');\r\n pipWin.document.body.appendChild(videoEl);\r\n\r\n // The window can be dismissed via its own native close button, not just our\r\n // toggle — 'pagehide' fires either way, so it's the single source of truth for cleanup.\r\n pipWin.addEventListener('pagehide', () => closeCustomPip(), { once: true });\r\n\r\n pipWindowRef.current = pipWin;\r\n setPipWindow(pipWin);\r\n setIsPiP(true);\r\n } catch (err) {\r\n console.error('[PlayerSDK PiP] Failed to open custom Picture-in-Picture window:', err);\r\n }\r\n };\r\n\r\n // Simple click-to-seek for the custom PiP window's progress bar — deliberately not\r\n // wired into the main seekbar's drag/scrub state machine (hover thumbnails, chapter\r\n // preview, etc.), since none of that UI exists in the small floating window.\r\n const handlePipSeekClick = (e: React.MouseEvent<HTMLDivElement>) => {\r\n if (isAdPlaying || isAdRedirectPending) return;\r\n const rect = e.currentTarget.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\r\n performSeek(contentTimeToVideoTime(percent * displayDuration));\r\n };\r\n\r\n const toggleFullscreen = () => {\r\n if (!containerRef.current) return;\r\n const container = containerRef.current;\r\n const anyDoc = document as any;\r\n const anyContainer = container as any;\r\n const isNativeFs = !!(document.fullscreenElement || anyDoc.webkitFullscreenElement);\r\n\r\n const enterSimulated = () => {\r\n setIsSimulatedFullscreen(true);\r\n setIsFullscreen(true);\r\n document.body.style.overflow = 'hidden';\r\n document.body.style.touchAction = 'none';\r\n player?.analytics.track('fullscreen_enter', player.getVideoElement());\r\n };\r\n\r\n const exitSimulated = () => {\r\n setIsSimulatedFullscreen(false);\r\n setIsFullscreen(false);\r\n document.body.style.overflow = '';\r\n document.body.style.touchAction = '';\r\n player?.analytics.track('fullscreen_exit', player.getVideoElement());\r\n };\r\n\r\n if (isNativeFs || isSimulatedFullscreen) {\r\n setWasMobileOnFullscreenEnter(false);\r\n if (document.fullscreenElement) {\r\n document.exitFullscreen();\r\n } else if (anyDoc.webkitFullscreenElement) {\r\n anyDoc.webkitExitFullscreen?.();\r\n } else {\r\n exitSimulated();\r\n }\r\n } else {\r\n setWasMobileOnFullscreenEnter(isMobileRef.current);\r\n const requestFs: (() => Promise<void>) | undefined =\r\n container.requestFullscreen?.bind(container) ??\r\n anyContainer.webkitRequestFullscreen?.bind(anyContainer);\r\n if (requestFs) {\r\n requestFs().catch(enterSimulated);\r\n } else {\r\n // iOS Safari: no Fullscreen API — use CSS fixed overlay\r\n enterSimulated();\r\n }\r\n }\r\n };\r\n\r\n // Keep ref current so the stale-closure-safe keyboard handler always calls the latest version\r\n useEffect(() => {\r\n toggleFullscreenRef.current = toggleFullscreen;\r\n });\r\n\r\n const handleSkipAd = () => {\r\n if (!isAdPlaying) return;\r\n const currentAdBreak = adBreaks.find(ab => currentTime >= ab.startTime && currentTime < ab.endTime);\r\n\r\n // Each ad in a pod carries its own skipOffset in the avails metadata, so Skip applies to\r\n // the ad on screen only — skipping the first ad of a pod advances to the second, which\r\n // then runs its own countdown. Skipping the last ad leaves the break entirely.\r\n const currentAd = adPodItems.find(a => currentTime >= a.startTimeSeconds && currentTime < a.endTimeSeconds);\r\n if (currentAd) {\r\n const isLastInPod = currentAd.podIndex >= currentAd.podTotal - 1;\r\n triggerAdSkipRef.current?.(\r\n isLastInPod ? (currentAdBreak?.endTime ?? currentAd.endTimeSeconds) : currentAd.endTimeSeconds,\r\n !isLastInPod\r\n );\r\n return;\r\n }\r\n\r\n // No per-ad metadata (tracking URL absent or returned no avails) — the break is the\r\n // smallest unit we can resolve, so skip it whole.\r\n if (!currentAdBreak) return;\r\n triggerAdSkipRef.current?.(currentAdBreak.endTime);\r\n };\r\n\r\n const dismissOverlay = () => {\r\n if (activeOverlayRef.current) {\r\n dismissedOverlayIdsRef.current.add(activeOverlayRef.current.availId);\r\n }\r\n setActiveOverlay(null);\r\n activeOverlayRef.current = null;\r\n };\r\n\r\n const handleOverlayClick = (overlay: OverlayAdItem) => {\r\n if (overlay.clickTrackingUrl) fetch(overlay.clickTrackingUrl, { mode: 'no-cors' }).catch(() => {});\r\n if (overlay.clickThrough) window.open(overlay.clickThrough, '_blank', 'noopener,noreferrer');\r\n };\r\n\r\n const handleAdCtaClick = (cta: AdCtaInfo) => {\r\n window.open(cta.url, '_blank', 'noopener,noreferrer');\r\n };\r\n\r\n const handleCastToggle = () => {\r\n if (!player) return;\r\n if (castDevice) {\r\n player.casting.stopCasting();\r\n } else {\r\n player.casting.requestSession(src, currentTime);\r\n }\r\n };\r\n\r\n const handleAirPlayToggle = () => {\r\n if (!player) return;\r\n const video = player.getVideoElement();\r\n\r\n if (isAirPlayConnected) {\r\n player.casting.stopAirPlay(video);\r\n return;\r\n }\r\n\r\n if (typeof (video as any).webkitShowPlaybackTargetPicker === 'function') {\r\n player.casting.requestAirPlaySession(video);\r\n } else if (player.casting.isAirPlayAvailable()) {\r\n player.casting.requestAirPlaySession(video);\r\n } else {\r\n console.warn('[PlayerSDK AirPlay] AirPlay is not available on this device.');\r\n }\r\n };\r\n\r\n const handleSeekbarHover = (e: React.MouseEvent<HTMLDivElement>) => {\r\n if (!seekbarRef.current || displayDuration === 0 || !player || isAdPlaying || isAdRedirectPending) return;\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n seekbarWidthRef.current = rect.width;\r\n const tooltipW = 192;\r\n setTooltipLeft(Math.max(tooltipW / 2, Math.min(e.clientX - rect.left, rect.width - tooltipW / 2)));\r\n const percent = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\r\n const videoTarget = contentTimeToVideoTime(percent * displayDuration);\r\n\r\n setHoverX(e.clientX - rect.left);\r\n setHoverTime(videoTarget);\r\n\r\n if (hoverSeekTimer.current) window.clearTimeout(hoverSeekTimer.current);\r\n hoverSeekTimer.current = window.setTimeout(() => {\r\n if (hiddenVideoRef.current) {\r\n hiddenVideoRef.current.currentTime = videoTarget;\r\n }\r\n }, 100);\r\n\r\n const hoverCh = player.keyMoments.getCurrentMoment(videoTarget);\r\n setHoverChapter(hoverCh.keyMoment);\r\n };\r\n\r\n const seekTimeFromTouch = (touch: React.Touch): number => {\r\n if (!seekbarRef.current || displayDuration === 0) return 0;\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (touch.clientX - rect.left) / rect.width));\r\n return contentTimeToVideoTime(percent * displayDuration);\r\n };\r\n\r\n const handleSeekTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {\r\n if (!player || isAdPlaying || isAdRedirectPending || displayDuration === 0) return;\r\n e.preventDefault();\r\n isScrubbing.current = true;\r\n const initialTime = seekTimeFromTouch(e.touches[0]);\r\n setScrubbingTime(initialTime);\r\n\r\n const onTouchMove = (ev: TouchEvent) => {\r\n if (!isScrubbing.current || !seekbarRef.current) return;\r\n const t = ev.touches[0];\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (t.clientX - rect.left) / rect.width));\r\n const targetTime = contentTimeToVideoTime(percent * displayDuration);\r\n setScrubbingTime(targetTime);\r\n setHoverTime(targetTime);\r\n resetControlsTimer();\r\n };\r\n\r\n const onTouchEnd = (ev: TouchEvent) => {\r\n isScrubbing.current = false;\r\n if (seekbarRef.current && ev.changedTouches[0]) {\r\n const t = ev.changedTouches[0];\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (t.clientX - rect.left) / rect.width));\r\n performSeek(contentTimeToVideoTime(percent * displayDuration));\r\n }\r\n setScrubbingTime(null);\r\n document.removeEventListener('touchmove', onTouchMove);\r\n document.removeEventListener('touchend', onTouchEnd);\r\n };\r\n\r\n document.addEventListener('touchmove', onTouchMove, { passive: false });\r\n document.addEventListener('touchend', onTouchEnd);\r\n };\r\n\r\n const handleSeekbarLeave = () => {\r\n setHoverX(null);\r\n setHoverThumbnail(null);\r\n setHoverChapter(null);\r\n if (hoverSeekTimer.current) {\r\n window.clearTimeout(hoverSeekTimer.current);\r\n }\r\n };\r\n\r\n const formatTime = (seconds: number) => {\r\n if (isNaN(seconds) || seconds < 0) return '00:00';\r\n const s = Math.floor(seconds);\r\n const hrs = Math.floor(s / 3600);\r\n const mins = Math.floor((s % 3600) / 60);\r\n const secs = s % 60;\r\n\r\n const pad = (num: number) => String(num).padStart(2, '0');\r\n\r\n if (hrs > 0) {\r\n return `${pad(hrs)}:${pad(mins)}:${pad(secs)}`;\r\n }\r\n return `${pad(mins)}:${pad(secs)}`;\r\n };\r\n\r\n // Announce play/pause and mute/unmute to screen readers via the live region.\r\n // announceMountedRef prevents false announcements on initial mount.\r\n useEffect(() => {\r\n if (!announceMountedRef.current) return;\r\n setAnnouncement(isPlaying ? 'Playing' : 'Paused');\r\n }, [isPlaying]);\r\n\r\n useEffect(() => {\r\n if (!announceMountedRef.current) return;\r\n setAnnouncement(isMuted ? 'Muted' : 'Unmuted');\r\n }, [isMuted]);\r\n\r\n useEffect(() => { announceMountedRef.current = true; }, []);\r\n\r\n // Close any open menu when Escape is pressed\r\n useEffect(() => {\r\n const handleEscape = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape' && activeMenuRef.current !== 'none') {\r\n e.preventDefault();\r\n setActiveMenu('none');\r\n }\r\n };\r\n document.addEventListener('keydown', handleEscape);\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, []);\r\n\r\n // When casting, these reflect the remote device's state; otherwise local state\r\n const ctrlIsPlaying = castDevice ? !(remoteState?.isPaused ?? true) : isPlaying;\r\n const ctrlCurrentTime = castDevice ? (remoteState?.currentTime ?? 0) : currentTime;\r\n const ctrlDuration = castDevice ? (remoteState?.duration ?? 0) : displayDuration;\r\n const ctrlVolume = castDevice ? (remoteState?.volumeLevel ?? 1) : volume;\r\n const ctrlMuted = castDevice ? (remoteState?.isMuted ?? false) : isMuted;\r\n\r\n const handleMobileOverlayClick = useCallback((e: React.MouseEvent) => {\r\n if (isAdPlaying || isAdRedirectPending) return;\r\n if ((e.target as HTMLElement).closest('.sdk-mobile-ctrl')) return;\r\n const now = Date.now();\r\n const rect = containerRef.current?.getBoundingClientRect();\r\n const tapX = e.clientX - (rect?.left ?? 0);\r\n const last = lastTapRef.current;\r\n if (last && now - last.time < 300) {\r\n lastTapRef.current = null;\r\n const isLeft = tapX < (rect?.width ?? 0) / 2;\r\n const delta = isLeft ? -10 : 10;\r\n const newContentTime = Math.max(0, Math.min(displayDuration, videoTimeToContentTime(ctrlCurrentTime) + delta));\r\n performSeek(contentTimeToVideoTime(newContentTime), {\r\n eventType: isLeft ? 'fast_backward' : 'fast_forward',\r\n skipSeconds: 10,\r\n });\r\n setDoubleTapSide(isLeft ? 'left' : 'right');\r\n window.setTimeout(() => setDoubleTapSide(null), 700);\r\n resetControlsTimer();\r\n } else {\r\n lastTapRef.current = { time: now, x: tapX };\r\n if (controlsVisible) {\r\n setControlsVisible(false);\r\n if (controlsTimeoutRef.current) window.clearTimeout(controlsTimeoutRef.current);\r\n } else {\r\n resetControlsTimer();\r\n }\r\n }\r\n }, [isAdPlaying, isAdRedirectPending, displayDuration, videoTimeToContentTime, ctrlCurrentTime, contentTimeToVideoTime, performSeek, controlsVisible]);\r\n\r\n const getVolumeIcon = () => {\r\n if (ctrlMuted || ctrlVolume === 0) return <VolumeX size={18} />;\r\n if (ctrlVolume < 0.3) return <Volume size={18} />;\r\n if (ctrlVolume < 0.7) return <Volume1 size={18} />;\r\n return <Volume2 size={18} />;\r\n };\r\n\r\n // Returns a keyboard handler for listbox option items (Enter/Space activates, Escape closes)\r\n const menuItemKeyDown = (action: () => void) => (e: React.KeyboardEvent) => {\r\n if (e.key === 'Enter' || e.key === ' ') {\r\n e.preventDefault();\r\n action();\r\n } else if (e.key === 'Escape') {\r\n e.preventDefault();\r\n setActiveMenu('none');\r\n }\r\n };\r\n\r\n // Returns a keyboard handler for elements acting as buttons (e.g. chapter cards as divs)\r\n const cardKeyDown = (action: () => void) => (e: React.KeyboardEvent) => {\r\n if (e.key === 'Enter' || e.key === ' ') {\r\n e.preventDefault();\r\n action();\r\n }\r\n };\r\n\r\n // Keyboard handler for the seek slider (role=\"slider\") — arrow keys seek ±5 s\r\n const handleSeekbarKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\r\n if (isAdPlaying || isAdRedirectPending) return;\r\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\r\n e.preventDefault();\r\n const delta = e.key === 'ArrowLeft' ? -5 : 5;\r\n const newCt = Math.max(0, Math.min(displayDuration, videoTimeToContentTime(ctrlCurrentTime) + delta));\r\n performSeek(contentTimeToVideoTime(newCt));\r\n } else if (e.key === 'Home') {\r\n e.preventDefault();\r\n performSeek(contentTimeToVideoTime(0));\r\n } else if (e.key === 'End') {\r\n e.preventDefault();\r\n performSeek(contentTimeToVideoTime(displayDuration));\r\n }\r\n };\r\n\r\n const menuToggle = (menu: typeof activeMenu) => {\r\n setActiveMenu(activeMenu === menu ? 'none' : menu);\r\n };\r\n\r\n // Keeps a drop-up menu inside the player bounds. Menus are centered on their\r\n // trigger by default; for triggers near an edge that centered box would be\r\n // clipped, so we nudge it horizontally by exactly the amount it overflows\r\n // (plus a small margin) — just enough to fit, no more. The ref callback runs\r\n // after layout but before paint, so the centered box is measured and the\r\n // shift applied before the open animation plays.\r\n const clampDropupMenu = useCallback((el: HTMLDivElement | null) => {\r\n if (!el) return;\r\n el.style.left = '';\r\n const container = containerRef.current;\r\n if (!container) return;\r\n const menuRect = el.getBoundingClientRect();\r\n const contRect = container.getBoundingClientRect();\r\n const margin = 8;\r\n const overflowRight = menuRect.right - (contRect.right - margin);\r\n const overflowLeft = (contRect.left + margin) - menuRect.left;\r\n if (overflowRight > 0) {\r\n el.style.left = `calc(50% - ${Math.ceil(overflowRight)}px)`;\r\n } else if (overflowLeft > 0) {\r\n el.style.left = `calc(50% + ${Math.ceil(overflowLeft)}px)`;\r\n }\r\n }, []);\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n role=\"region\"\r\n aria-label={title || extractedTitle ? `${title || extractedTitle} video player` : 'Video player'}\r\n className={`sdk-player-container ${controlsVisible ? 'controls-active' : ''} ${isSimulatedFullscreen ? 'sdk-fs-simulated' : ''}`}\r\n onMouseMove={handleMouseMove}\r\n onTouchStart={useMobileLayout ? undefined : resetControlsTimer}\r\n onMouseLeave={() => {\r\n // Don't snap-hide on mouse-leave; let the running 10s timer handle it naturally\r\n // This way controls stay visible briefly as the cursor exits the player\r\n }}\r\n style={{\r\n border: isSimulatedFullscreen ? 'none' : `1px solid rgba(255, 255, 255, 0.08)`,\r\n '--sdk-theme-color': themeColor,\r\n '--sdk-theme-color-rgb': hexToRgb(themeColor),\r\n } as React.CSSProperties}\r\n >\r\n <canvas\r\n ref={canvasRef}\r\n width={160}\r\n height={90}\r\n style={{ display: 'none', position: 'absolute', pointerEvents: 'none' }}\r\n aria-hidden=\"true\"\r\n />\r\n\r\n {/* Screen-reader live region for dynamic state announcements */}\r\n <div\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n aria-atomic=\"true\"\r\n className=\"sdk-sr-only\"\r\n >\r\n {announcement}\r\n </div>\r\n\r\n {/* Black cover used during ad-skip seeks to prevent any ad frame from bleeding through */}\r\n <div\r\n ref={adSkipCoverRef}\r\n style={{ opacity: 0, position: 'absolute', inset: 0, background: '#000', zIndex: 100, pointerEvents: 'none', willChange: 'opacity' }}\r\n aria-hidden=\"true\"\r\n />\r\n\r\n {/* The real <video> element lives inside the custom PiP window while it's open —\r\n this fills the space it left behind on the page (mirrors what Chrome's native\r\n PiP shows automatically, which this custom window bypasses). */}\r\n {pipWindow && (\r\n <div\r\n className=\"sdk-pip-placeholder\"\r\n aria-hidden=\"true\"\r\n >\r\n <PictureInPicture size={32} aria-hidden=\"true\" />\r\n <span>Playing in Picture-in-Picture</span>\r\n </div>\r\n )}\r\n\r\n {/* Resume-playback cover: raised from the first paint when initialTime > 0 so frame 0 is\r\n never shown, then dropped once playback lands at the resume point (see timeupdate). */}\r\n <div\r\n ref={resumeCoverRef}\r\n style={{ opacity: initialTime && initialTime > 0 ? 1 : 0, position: 'absolute', inset: 0, background: '#000', zIndex: 100, pointerEvents: 'none', willChange: 'opacity' }}\r\n aria-hidden=\"true\"\r\n />\r\n\r\n <div className={`sdk-spinner-overlay ${isBuffering ? 'active' : ''}`} aria-hidden=\"true\">\r\n <div className=\"sdk-spinner\"></div>\r\n </div>\r\n\r\n {playerError && (\r\n <div style={{\r\n position: 'absolute', inset: 0, zIndex: 50,\r\n background: 'rgba(9,9,14,0.96)',\r\n display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',\r\n gap: 20, padding: '0 24px',\r\n }}>\r\n <ErrorScreen\r\n onRetry={() => {\r\n setPlayerError(false);\r\n setRetryKey(k => k + 1);\r\n }}\r\n />\r\n </div>\r\n )}\r\n\r\n {sourceFallbackNotice && (\r\n <div style={{\r\n // Centered, offset just below the buffering spinner (60px tall, dead-center)\r\n // so the two read as one \"loading + why\" unit during the fallback re-init.\r\n // z-index 102: above the black covers (100) and the spinner (101) — the\r\n // notice fires while the cover is up, so anything lower is invisible for\r\n // most of its 4s lifetime.\r\n position: 'absolute', top: '50%', left: '50%',\r\n transform: 'translate(-50%, calc(-50% + 70px))',\r\n background: 'rgba(0,0,0,0.82)', color: '#fff', fontSize: 13, fontWeight: 500,\r\n padding: '8px 16px', borderRadius: 8, zIndex: 102, pointerEvents: 'none',\r\n maxWidth: '85%', textAlign: 'center', backdropFilter: 'blur(8px)',\r\n border: '1px solid rgba(255,255,255,0.12)',\r\n }}>\r\n {sourceFallbackNotice}\r\n </div>\r\n )}\r\n\r\n {/* Hidden during ad breaks: content captions must never render over an ad */}\r\n {effectiveFeatures.enableSubtitles && !isAdPlaying && subtitleText && activeSubtitleTrack !== 'off' && (\r\n <div className=\"sdk-subtitles-overlay\">\r\n <span\r\n className=\"sdk-subtitles-text\"\r\n style={{\r\n fontSize: subtitleStyle.size === 'small' ? '14px' : subtitleStyle.size === 'large' ? '24px' : '18px',\r\n color: subtitleStyle.color ?? '#ffffff',\r\n background: subtitleStyle.background === 'none'\r\n ? 'transparent'\r\n : subtitleStyle.background === 'solid'\r\n ? 'rgba(0, 0, 0, 0.92)'\r\n : 'rgba(15, 23, 42, 0.78)',\r\n border: subtitleStyle.background === 'none' ? 'none' : '1px solid rgba(255,255,255,0.08)',\r\n backdropFilter: subtitleStyle.background === 'none' ? 'none' : 'blur(8px)',\r\n }}\r\n dangerouslySetInnerHTML={{ __html: subtitleText }}\r\n />\r\n </div>\r\n )}\r\n\r\n {/* Non-linear overlay ad banner */}\r\n {activeOverlay && (\r\n <div className={`sdk-overlay-ad sdk-overlay-ad--${activeOverlay.position}`} role=\"complementary\" aria-label=\"Advertisement\">\r\n <div className=\"sdk-overlay-ad-topbar\">\r\n <span className=\"sdk-overlay-ad-label\">Ad</span>\r\n {activeOverlay.dismissible && (\r\n <button\r\n className=\"sdk-overlay-ad-close\"\r\n onClick={dismissOverlay}\r\n aria-label=\"Close advertisement\"\r\n >\r\n ✕\r\n </button>\r\n )}\r\n </div>\r\n <img\r\n src={activeOverlay.imageUrl}\r\n width={activeOverlay.imageWidth}\r\n height={activeOverlay.imageHeight}\r\n alt={activeOverlay.title || 'Advertisement'}\r\n className=\"sdk-overlay-ad-img\"\r\n onClick={() => handleOverlayClick(activeOverlay)}\r\n />\r\n </div>\r\n )}\r\n\r\n {isAdPlaying && (() => {\r\n // The current individual ad within the pod (if the break came from the tracking URL\r\n // and carries avails/pod metadata) — falls back to the whole ad break otherwise.\r\n const currentAd = adPodItems.find(a => currentTime >= a.startTimeSeconds && currentTime < a.endTimeSeconds);\r\n const currentAdBreak = adBreaks.find(ab => currentTime >= ab.startTime && currentTime < ab.endTime);\r\n const remainingAdTime = currentAd\r\n ? Math.max(0, currentAd.endTimeSeconds - currentTime)\r\n : (currentAdBreak ? Math.max(0, currentAdBreak.endTime - currentTime) : 0);\r\n const adPlayedSeconds = currentAd\r\n ? currentTime - currentAd.startTimeSeconds\r\n : (currentAdBreak ? currentTime - currentAdBreak.startTime : 0);\r\n const isPod = !!currentAd && currentAd.podTotal > 1;\r\n const effectiveSkippable = currentAd ? currentAd.skippable : enableSkipAd;\r\n const effectiveSkipAfter = currentAd ? currentAd.skipOffsetSeconds : skipAdAfterSeconds;\r\n const canSkip = effectiveSkippable && adPlayedSeconds >= effectiveSkipAfter;\r\n const skipCountdown = Math.ceil(effectiveSkipAfter - adPlayedSeconds);\r\n return (\r\n <>\r\n {/* Countdown badge — shows \"Ad X of Y\" when the break is a multi-ad pod */}\r\n <div\r\n className=\"sdk-ad-countdown\"\r\n role=\"timer\"\r\n aria-label={isPod\r\n ? `Advertisement ${currentAd!.podIndex + 1} of ${currentAd!.podTotal} — ${formatTime(remainingAdTime)} remaining`\r\n : `Advertisement — ${formatTime(remainingAdTime)} remaining`}\r\n aria-live=\"off\"\r\n >\r\n <span className=\"sdk-ad-countdown-label\" aria-hidden=\"true\">\r\n {isPod ? `Ad ${currentAd!.podIndex + 1} of ${currentAd!.podTotal}` : 'Ad'}\r\n </span>\r\n {!isPod && <span className=\"sdk-ad-countdown-sep\" aria-hidden=\"true\">·</span>}\r\n <span className=\"sdk-ad-countdown-time\" aria-hidden=\"true\">{formatTime(remainingAdTime)}</span>\r\n </div>\r\n\r\n {/* Skip Ad button — shown when ad is skippable per avails metadata or enableSkipAd prop */}\r\n {effectiveSkippable && (\r\n <button\r\n className={`sdk-skip-ad-btn ${canSkip ? 'sdk-skip-ad-btn--ready' : ''}`}\r\n onClick={canSkip ? handleSkipAd : undefined}\r\n disabled={!canSkip}\r\n aria-label={canSkip ? 'Skip advertisement' : `Skip advertisement available in ${skipCountdown} seconds`}\r\n aria-disabled={!canSkip}\r\n >\r\n {canSkip ? 'Skip Ad ›' : `Skip in ${skipCountdown}s`}\r\n </button>\r\n )}\r\n\r\n {/* Ad CTA — rendered only when the current ad carries CTA metadata in its\r\n adParameters. Anchored bottom-left, opposite the Skip Ad button, so the\r\n two never collide. Layout adapts to the metadata present:\r\n button-only / title + button / title + description + button. */}\r\n {currentAd?.cta && (() => {\r\n const cta = currentAd.cta;\r\n const ctaButton = (\r\n <button\r\n className=\"sdk-ad-cta-btn\"\r\n onClick={() => handleAdCtaClick(cta)}\r\n aria-label={`${cta.text} — opens advertiser site in a new tab`}\r\n >\r\n <span className=\"sdk-ad-cta-btn-text\">{cta.text}</span>\r\n <ExternalLink size={15} aria-hidden=\"true\" />\r\n </button>\r\n );\r\n return (\r\n <div\r\n className=\"sdk-ad-cta\"\r\n role=\"complementary\"\r\n aria-label=\"Advertisement call to action\"\r\n >\r\n {cta.title ? (\r\n <div className=\"sdk-ad-cta-card\">\r\n <div className=\"sdk-ad-cta-info\">\r\n <div className=\"sdk-ad-cta-title\">{cta.title}</div>\r\n {cta.description && (\r\n <div className=\"sdk-ad-cta-desc\">{cta.description}</div>\r\n )}\r\n </div>\r\n {ctaButton}\r\n </div>\r\n ) : ctaButton}\r\n </div>\r\n );\r\n })()}\r\n </>\r\n );\r\n })()}\r\n\r\n {isEntitlementBlocked && (\r\n <div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',background:'rgba(0,0,0,0.72)',color:'#fff',zIndex:60}}>\r\n <div style={{maxWidth:480,textAlign:'center',padding:24}}>\r\n <h3 style={{margin:'0 0 8px'}}>Playback restricted</h3>\r\n <p style={{margin:0}}>Your account is missing a required license key. Please contact your administrator.</p>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Dark background when casting — controls overlay renders on top */}\r\n {(castDevice || isAirPlayConnected) && (\r\n <div className=\"sdk-cast-bg\" />\r\n )}\r\n\r\n {/* ── Mobile layout ───────────────────────────────────────────── */}\r\n {useMobileLayout && (\r\n <div\r\n className={`sdk-mobile-overlay ${controlsVisible ? 'active' : ''}`}\r\n onClick={handleMobileOverlayClick}\r\n >\r\n {/* Double-tap seek indicator */}\r\n {doubleTapSide && (\r\n <div className={`sdk-mobile-doubletap ${doubleTapSide}`}>\r\n <div className=\"sdk-mobile-doubletap-icon\">\r\n {doubleTapSide === 'left'\r\n ? <RotateCcw size={26} />\r\n : <RotateCw size={26} />}\r\n <span className=\"sdk-mobile-doubletap-label\">10s</span>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {enableControls && (\r\n <>\r\n {/* Header: back + title (landscape) | PiP + Cast + AirPlay */}\r\n <div className=\"sdk-mobile-header sdk-mobile-ctrl\">\r\n <div className=\"sdk-mobile-header-left\">\r\n {(isFullscreen || isSimulatedFullscreen) && (\r\n <button\r\n className=\"sdk-mobile-back-btn\"\r\n onClick={toggleFullscreen}\r\n aria-label=\"Exit fullscreen\"\r\n >\r\n <ChevronLeft size={22} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n {(title || extractedTitle) && (\r\n <div className=\"sdk-mobile-title\">{title || extractedTitle}</div>\r\n )}\r\n </div>\r\n <div className=\"sdk-mobile-header-actions\">\r\n {effectiveFeatures.enablePiP && (\r\n <button\r\n className=\"sdk-mobile-hdr-btn\"\r\n onClick={() => { if (!isAdPlaying) togglePiP(); }}\r\n style={{ color: isPiP ? themeColor : '' }}\r\n aria-label={isPiP ? 'Exit picture-in-picture' : 'Enter picture-in-picture'}\r\n aria-pressed={isPiP}\r\n >\r\n <PictureInPicture size={20} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n {effectiveFeatures.enableChromecast && !isAdPlaying && (\r\n <button\r\n className=\"sdk-mobile-hdr-btn\"\r\n onClick={handleCastToggle}\r\n data-tooltip={castDevice ? `Casting to ${castDevice} — tap to disconnect` : 'Cast to device'}\r\n aria-label={castDevice ? `Stop casting to ${castDevice}` : 'Cast to device'}\r\n aria-pressed={!!castDevice}\r\n style={{ color: castDevice ? '#3b82f6' : '' }}\r\n >\r\n <Cast size={20} fill={castDevice ? 'currentColor' : 'none'} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n {effectiveFeatures.allowAirplay && isAirPlayAvailable && (\r\n <button\r\n className=\"sdk-mobile-hdr-btn\"\r\n onClick={handleAirPlayToggle}\r\n aria-label={isAirPlayConnected ? `Stop AirPlay to ${airPlayTargetName || 'device'}` : 'AirPlay to device'}\r\n aria-pressed={isAirPlayConnected}\r\n style={{ color: isAirPlayConnected ? themeColor : '' }}\r\n >\r\n <Airplay size={20} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {/* Center controls: rewind · play/pause · forward */}\r\n <div className=\"sdk-mobile-center-controls sdk-mobile-ctrl\">\r\n {!isAdPlaying && (\r\n <button\r\n className=\"sdk-mobile-seek-btn\"\r\n onClick={() => performSeek(contentTimeToVideoTime(Math.max(0, videoTimeToContentTime(ctrlCurrentTime) - 10)), { eventType: 'fast_backward', skipSeconds: 10 })}\r\n aria-label=\"Rewind 10 seconds\"\r\n >\r\n <RotateCcw size={22} aria-hidden=\"true\" />\r\n <span className=\"sdk-mobile-seek-label\" aria-hidden=\"true\">10</span>\r\n </button>\r\n )}\r\n <button\r\n className=\"sdk-mobile-play-btn\"\r\n onClick={togglePlay}\r\n aria-label={isEnded ? 'Replay' : ctrlIsPlaying ? 'Pause' : 'Play'}\r\n >\r\n {isEnded ? (\r\n <RotateCcw size={38} aria-hidden=\"true\" />\r\n ) : ctrlIsPlaying ? (\r\n <Pause size={38} fill=\"currentColor\" aria-hidden=\"true\" />\r\n ) : (\r\n <Play size={38} fill=\"currentColor\" style={{ marginLeft: '3px' }} aria-hidden=\"true\" />\r\n )}\r\n </button>\r\n {!isAdPlaying && (\r\n <button\r\n className=\"sdk-mobile-seek-btn\"\r\n onClick={() => performSeek(contentTimeToVideoTime(Math.min(displayDuration, videoTimeToContentTime(ctrlCurrentTime) + 10)), { eventType: 'fast_forward', skipSeconds: 10 })}\r\n aria-label=\"Skip forward 10 seconds\"\r\n >\r\n <RotateCw size={22} aria-hidden=\"true\" />\r\n <span className=\"sdk-mobile-seek-label\" aria-hidden=\"true\">10</span>\r\n </button>\r\n )}\r\n </div>\r\n\r\n {/* Bottom: chapter strip + seekbar + controls row */}\r\n <div className=\"sdk-mobile-bottom-bar sdk-mobile-ctrl\">\r\n {/* Horizontal key moments strip */}\r\n {effectiveFeatures.enableKeyMoments && showChaptersSidebar && localKeyMoments.length > 0 && (\r\n <div className=\"sdk-mobile-chapters-scroll\">\r\n {localKeyMoments.map((ch, idx) => {\r\n const isActive = activeChapter.index === idx;\r\n return (\r\n <div\r\n key={idx}\r\n role=\"button\"\r\n tabIndex={0}\r\n className={`sdk-mobile-chapter-card${isActive ? ' active' : ''}`}\r\n style={{ borderColor: isActive ? themeColor : undefined }}\r\n aria-label={`${ch.title}, ${formatTime(ch.startTime)}${isActive ? ', current chapter' : ''}`}\r\n aria-current={isActive ? 'true' : undefined}\r\n onClick={() => { handleChapterClick(ch.startTime, ch); }}\r\n onKeyDown={cardKeyDown(() => handleChapterClick(ch.startTime, ch))}\r\n >\r\n <div className=\"sdk-mobile-chapter-thumb\">\r\n {(chapterThumbnails[idx] || ch.thumbnail) ? (\r\n <img\r\n src={chapterThumbnails[idx] || ch.thumbnail}\r\n alt=\"\"\r\n aria-hidden=\"true\"\r\n style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}\r\n />\r\n ) : (\r\n <ListVideo size={16} aria-hidden=\"true\" style={{ color: isActive ? themeColor : 'rgba(255,255,255,0.3)' }} />\r\n )}\r\n <span className=\"sdk-mobile-chapter-time-badge\" aria-hidden=\"true\">{formatTime(ch.startTime)}</span>\r\n </div>\r\n <div className=\"sdk-mobile-chapter-title\" style={{ color: isActive ? themeColor : undefined }}>\r\n {ch.title}\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n\r\n {/* Seekbar / Ad Progress Bar — edge to edge */}\r\n <div\r\n ref={seekbarRef}\r\n role={seekLocked ? undefined : 'slider'}\r\n aria-label={seekLocked ? undefined : 'Seek'}\r\n aria-valuemin={seekLocked ? undefined : 0}\r\n aria-valuemax={seekLocked ? undefined : Math.round(displayDuration)}\r\n aria-valuenow={seekLocked ? undefined : Math.round(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))}\r\n aria-valuetext={seekLocked ? undefined : `${formatTime(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))} of ${formatTime(displayDuration)}`}\r\n tabIndex={seekLocked ? undefined : 0}\r\n className={`sdk-seekbar-container sdk-mobile-seekbar${isAdPlaying ? ' sdk-ad-mode' : ''}`}\r\n onMouseDown={seekLocked ? undefined : handleSeekMouseDown}\r\n onMouseMove={seekLocked ? undefined : handleSeekbarHover}\r\n onMouseLeave={seekLocked ? undefined : handleSeekbarLeave}\r\n onTouchStart={seekLocked ? undefined : handleSeekTouchStart}\r\n onKeyDown={seekLocked ? undefined : handleSeekbarKeyDown}\r\n style={{ pointerEvents: seekLocked ? 'none' : undefined, touchAction: 'none' }}\r\n >\r\n {isAdPlaying ? (\r\n <div\r\n className=\"sdk-ad-progress-fill\"\r\n style={{\r\n // Baseline width for first paint / re-renders — sourced from the live\r\n // media clock (not the throttled `currentTime` state) so it's always\r\n // accurate. The rAF loop above takes over for smooth per-frame motion.\r\n // Tracks the current individual ad within a pod, not the whole break.\r\n width: (() => {\r\n const t = player ? player.getCurrentTime() : currentTime;\r\n const win = getActiveAdWindow(t);\r\n return win ? `${Math.min(100, Math.max(0, ((t - win.start) / (win.end - win.start)) * 100))}%` : '0%';\r\n })(),\r\n }}\r\n />\r\n ) : (\r\n <>\r\n <div\r\n className=\"sdk-seekbar-buffered\"\r\n style={{ width: `${duration ? (player ? (player.getBufferedRanges()[0]?.end || 0) : 0) / duration * 100 : 0}%` }}\r\n />\r\n {displayDuration > 0 && adBreaks.filter(ab => !watchedAdBreaksSet.has(ab.startTime)).map((ab, idx) => (\r\n <div\r\n key={idx}\r\n className=\"sdk-seekbar-adbreak\"\r\n style={{\r\n left: `${(videoTimeToContentTime(ab.startTime) / displayDuration) * 100}%`,\r\n width: '3px',\r\n }}\r\n />\r\n ))}\r\n <div\r\n className=\"sdk-seekbar-progress\"\r\n style={{\r\n width: castDevice\r\n ? `${ctrlDuration > 0 ? (ctrlCurrentTime / ctrlDuration) * 100 : 0}%`\r\n : `${displayDuration ? (videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime) / displayDuration) * 100 : 0}%`,\r\n background: '#ffffff',\r\n boxShadow: 'none',\r\n }}\r\n >\r\n <div className=\"sdk-seekbar-handle\" style={{ opacity: 1, transform: 'scale(1)' }} />\r\n </div>\r\n {effectiveFeatures.enableKeyMoments && localKeyMoments.map((ch, idx) => {\r\n if (displayDuration === 0) return null;\r\n return <div key={idx} className=\"sdk-seekbar-chapter-marker sdk-desktop-chapter-dot\" style={{ left: `${(videoTimeToContentTime(ch.startTime) / displayDuration) * 100}%` }} />;\r\n })}\r\n </>\r\n )}\r\n </div>\r\n\r\n {/* Controls row: time | icon buttons */}\r\n <div className=\"sdk-mobile-controls-row\">\r\n {!isAdPlaying && (\r\n <span className=\"sdk-time-display\">\r\n {castDevice\r\n ? `${formatTime(ctrlCurrentTime)} / ${formatTime(ctrlDuration)}`\r\n : `${formatTime(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))} / ${formatTime(displayDuration)}`}\r\n </span>\r\n )}\r\n <div className=\"sdk-mobile-bottom-icons\">\r\n {effectiveFeatures.enableKeyMoments && localKeyMoments.length > 0 && !isAdPlaying && (\r\n <button\r\n className={`sdk-mobile-icon-btn${showChaptersSidebar ? ' active' : ''}`}\r\n onClick={() => { setShowChaptersSidebar(!showChaptersSidebar); }}\r\n aria-label={showChaptersSidebar ? 'Hide chapters' : 'Show chapters'}\r\n aria-pressed={showChaptersSidebar}\r\n >\r\n <ListVideo size={18} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n\r\n {effectiveFeatures.enableSubtitles && !isAdPlaying && availableSubtitles.some(t => !t.isNative) && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-mobile-icon-btn${activeSubtitleTrack !== 'off' ? ' active' : ''}`}\r\n onClick={() => { menuToggle('subtitles'); }}\r\n aria-label=\"Captions\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'subtitles'}\r\n >\r\n <Subtitles size={18} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'subtitles' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Captions\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Subtitles</div>\r\n <div\r\n role=\"option\"\r\n aria-selected={activeSubtitleTrack === 'off'}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSubtitleTrack === 'off' ? 'active' : ''}`}\r\n onClick={() => handleSubtitleChange('off')}\r\n onKeyDown={menuItemKeyDown(() => handleSubtitleChange('off'))}\r\n >\r\n <span>Off</span>\r\n {activeSubtitleTrack === 'off' && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n {availableSubtitles.map((track) => (\r\n <div\r\n key={track.id}\r\n role=\"option\"\r\n aria-selected={activeSubtitleTrack === track.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSubtitleTrack === track.id ? 'active' : ''}`}\r\n onClick={() => handleSubtitleChange(track.id)}\r\n onKeyDown={menuItemKeyDown(() => handleSubtitleChange(track.id))}\r\n >\r\n <span>{getLanguageLabel(track)}</span>\r\n {activeSubtitleTrack === track.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.allowMultipleAudio && audioTracks.length > 1 && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-mobile-icon-btn${activeMenu === 'audio' ? ' active' : ''}`}\r\n onClick={() => { menuToggle('audio'); }}\r\n aria-label=\"Audio track\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'audio'}\r\n >\r\n <Languages size={18} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'audio' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Audio track\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Audio Track</div>\r\n {audioTracks.map((track) => (\r\n <div\r\n key={track.id}\r\n role=\"option\"\r\n aria-selected={activeAudioTrack === track.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeAudioTrack === track.id ? 'active' : ''}`}\r\n onClick={() => { player?.setAudioTrack(track.id); setActiveAudioTrack(track.id); setActiveMenu('none'); setAnnouncement(`Audio: ${getLanguageLabel(track)}`); }}\r\n onKeyDown={menuItemKeyDown(() => { player?.setAudioTrack(track.id); setActiveAudioTrack(track.id); setActiveMenu('none'); setAnnouncement(`Audio: ${getLanguageLabel(track)}`); })}\r\n >\r\n <span>{getLanguageLabel(track)}</span>\r\n {activeAudioTrack === track.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.enablePlaybackSpeed && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-mobile-icon-btn${activeMenu === 'speed' ? ' active' : ''}`}\r\n onClick={() => { menuToggle('speed'); }}\r\n aria-label=\"Playback speed\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'speed'}\r\n >\r\n <Gauge size={18} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'speed' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Playback speed\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Speed</div>\r\n {[0.5, 0.75, 1, 1.25, 1.5, 2].map((rate) => (\r\n <div\r\n key={rate}\r\n role=\"option\"\r\n aria-selected={playbackRate === rate}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${playbackRate === rate ? 'active' : ''}`}\r\n onClick={() => handleSpeedChange(rate)}\r\n onKeyDown={menuItemKeyDown(() => handleSpeedChange(rate))}\r\n >\r\n <span>{rate === 1 ? 'Normal' : `${rate}x`}</span>\r\n {playbackRate === rate && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.enableQuality && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-mobile-icon-btn${activeQuality.id !== -1 ? ' active' : ''}`}\r\n onClick={() => { menuToggle('quality'); }}\r\n aria-label=\"Video quality\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'quality'}\r\n >\r\n <HdIcon size={18} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'quality' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu large\" role=\"listbox\" aria-label=\"Video quality\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Quality</div>\r\n {qualities.map((q) => (\r\n <div\r\n key={q.id}\r\n role=\"option\"\r\n aria-selected={activeQuality.id === q.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeQuality.id === q.id ? 'active' : ''}`}\r\n onClick={() => handleQualityChange(q.id)}\r\n onKeyDown={menuItemKeyDown(() => handleQualityChange(q.id))}\r\n >\r\n <span>{q.label}</span>\r\n {activeQuality.id === q.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {sources && sources.length > 1 && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-mobile-icon-btn${activeMenu === 'sources' ? ' active' : ''}`}\r\n onClick={() => { menuToggle('sources'); }}\r\n aria-label=\"Select source\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'sources'}\r\n >\r\n <MultiSourceHdIcon size={18} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'sources' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Select source\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Source</div>\r\n {sources.map((s, idx) => (\r\n <div\r\n key={idx}\r\n role=\"option\"\r\n aria-selected={activeSourceIndex === idx}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSourceIndex === idx ? 'active' : ''}`}\r\n onClick={() => handleSourceChange(idx)}\r\n onKeyDown={menuItemKeyDown(() => handleSourceChange(idx))}\r\n >\r\n <span>{s.label}</span>\r\n {activeSourceIndex === idx && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n <button\r\n className=\"sdk-mobile-icon-btn\"\r\n onClick={toggleFullscreen}\r\n aria-label={isFullscreen || isSimulatedFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\r\n aria-pressed={isFullscreen || isSimulatedFullscreen}\r\n >\r\n {isFullscreen ? <Minimize size={18} aria-hidden=\"true\" /> : <Maximize size={18} aria-hidden=\"true\" />}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* ── Desktop layout ──────────────────────────────────────────── */}\r\n {!useMobileLayout && (\r\n <div\r\n className=\"sdk-controls-overlay\"\r\n onClick={(e) => {\r\n if (isAdPlaying) return;\r\n if ((e.target as HTMLElement).closest('.sdk-desktop-header') ||\r\n (e.target as HTMLElement).closest('.sdk-desktop-center-controls') ||\r\n (e.target as HTMLElement).closest('.sdk-desktop-bottom')) return;\r\n togglePlay();\r\n }}\r\n >\r\n {/* Top header */}\r\n <div className=\"sdk-desktop-header\">\r\n <div className=\"sdk-desktop-header-left\">\r\n {(isFullscreen || isSimulatedFullscreen) && (\r\n <button\r\n className=\"sdk-desktop-back-btn\"\r\n onClick={(e) => { e.stopPropagation(); toggleFullscreen(); }}\r\n aria-label=\"Exit fullscreen\"\r\n >\r\n <ChevronLeft size={22} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n {(title || extractedTitle) && <span className=\"sdk-desktop-title\">{title || extractedTitle}</span>}\r\n </div>\r\n <div className=\"sdk-desktop-header-right\">\r\n {effectiveFeatures.enableChromecast && !isAdPlaying && (\r\n <button\r\n className=\"sdk-desktop-hdr-btn\"\r\n onClick={(e) => { e.stopPropagation(); handleCastToggle(); }}\r\n data-tooltip={castDevice ? `Casting to ${castDevice} — click to disconnect` : 'Cast to device'}\r\n aria-label={castDevice ? `Stop casting to ${castDevice}` : 'Cast to device'}\r\n aria-pressed={!!castDevice}\r\n style={{ color: castDevice ? '#3b82f6' : '' }}\r\n >\r\n <Cast size={20} fill={castDevice ? 'currentColor' : 'none'} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n {castDevice && (\r\n <span className=\"sdk-cast-indicator\" aria-hidden=\"true\">\r\n <Cast size={10} fill=\"currentColor\" aria-hidden=\"true\" />\r\n {castDevice}\r\n </span>\r\n )}\r\n {effectiveFeatures.allowAirplay && isAirPlayAvailable && (\r\n <button\r\n className=\"sdk-desktop-hdr-btn\"\r\n onClick={(e) => { e.stopPropagation(); handleAirPlayToggle(); }}\r\n data-tooltip={isAirPlayConnected ? `AirPlay to ${airPlayTargetName || 'device'} — click to disconnect` : 'AirPlay to device'}\r\n aria-label={isAirPlayConnected ? `Stop AirPlay to ${airPlayTargetName || 'device'}` : 'AirPlay to device'}\r\n aria-pressed={isAirPlayConnected}\r\n style={{ color: isAirPlayConnected ? themeColor : '' }}\r\n >\r\n <Airplay size={20} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {enableControls && (\r\n <>\r\n {/* Center play / seek controls */}\r\n <div className=\"sdk-desktop-center-controls\" style={{ pointerEvents: 'none' }}>\r\n {!isAdPlaying && (\r\n <button\r\n className=\"sdk-desktop-seek-btn\"\r\n style={{ pointerEvents: 'auto' }}\r\n onClick={(e) => { e.stopPropagation(); performSeek(contentTimeToVideoTime(Math.max(0, videoTimeToContentTime(ctrlCurrentTime) - 10)), { eventType: 'fast_backward', skipSeconds: 10 }); }}\r\n aria-label=\"Rewind 10 seconds\"\r\n >\r\n <div style={{ position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\r\n <RotateCcw size={30} aria-hidden=\"true\" />\r\n <span className=\"sdk-desktop-seek-label\" aria-hidden=\"true\">10</span>\r\n </div>\r\n </button>\r\n )}\r\n <button\r\n className=\"sdk-desktop-play-btn\"\r\n style={{ pointerEvents: 'auto' }}\r\n onClick={(e) => { e.stopPropagation(); togglePlay(); }}\r\n aria-label={isEnded ? 'Replay' : ctrlIsPlaying ? 'Pause' : 'Play'}\r\n >\r\n {isEnded\r\n ? <RotateCcw size={38} aria-hidden=\"true\" />\r\n : ctrlIsPlaying\r\n ? <Pause size={38} fill=\"currentColor\" aria-hidden=\"true\" />\r\n : <Play size={38} fill=\"currentColor\" style={{ marginLeft: '4px' }} aria-hidden=\"true\" />}\r\n </button>\r\n {!isAdPlaying && (\r\n <button\r\n className=\"sdk-desktop-seek-btn\"\r\n style={{ pointerEvents: 'auto' }}\r\n onClick={(e) => { e.stopPropagation(); performSeek(contentTimeToVideoTime(Math.min(displayDuration, videoTimeToContentTime(ctrlCurrentTime) + 10)), { eventType: 'fast_forward', skipSeconds: 10 }); }}\r\n aria-label=\"Skip forward 10 seconds\"\r\n >\r\n <div style={{ position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\r\n <RotateCw size={30} aria-hidden=\"true\" />\r\n <span className=\"sdk-desktop-seek-label\" aria-hidden=\"true\">10</span>\r\n </div>\r\n </button>\r\n )}\r\n </div>\r\n\r\n {/* Panel-style chapter list (absolute overlay, right side) */}\r\n {effectiveFeatures.enableKeyMoments && showChaptersSidebar && localKeyMoments.length > 0 && chaptersDisplayMode === 'panel' && (\r\n <div className=\"sdk-chapters-panel\" onClick={(e) => e.stopPropagation()}>\r\n <div className=\"sdk-chapters-panel-header\">\r\n <span className=\"sdk-chapters-panel-title\">Chapters</span>\r\n </div>\r\n <div className=\"sdk-chapters-panel-list\">\r\n {localKeyMoments.map((ch, idx) => {\r\n const isActive = activeChapter.index === idx;\r\n return (\r\n <div\r\n key={idx}\r\n role=\"button\"\r\n tabIndex={0}\r\n className={`sdk-chapters-panel-item${isActive ? ' active' : ''}`}\r\n aria-label={`${ch.title}, ${formatTime(ch.startTime)}${isActive ? ', current chapter' : ''}`}\r\n aria-current={isActive ? 'true' : undefined}\r\n onClick={() => handleChapterClick(ch.startTime, ch)}\r\n onKeyDown={cardKeyDown(() => handleChapterClick(ch.startTime, ch))}\r\n >\r\n <div className=\"sdk-chapters-panel-thumb\">\r\n {(chapterThumbnails[idx] || ch.thumbnail) ? (\r\n <img\r\n src={chapterThumbnails[idx] || ch.thumbnail}\r\n alt=\"\"\r\n aria-hidden=\"true\"\r\n style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}\r\n />\r\n ) : (\r\n <ListVideo size={18} aria-hidden=\"true\" style={{ color: 'rgba(255,255,255,0.3)' }} />\r\n )}\r\n </div>\r\n <div className=\"sdk-chapters-panel-info\">\r\n <div className=\"sdk-chapters-panel-item-title\">{ch.title}</div>\r\n {ch.description && (\r\n <div className=\"sdk-chapters-panel-item-desc\">{ch.description}</div>\r\n )}\r\n <div className=\"sdk-chapters-panel-item-time\" aria-hidden=\"true\">{formatTime(ch.startTime)}</div>\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Bottom area: chapters strip + seekbar + controls row */}\r\n <div className=\"sdk-desktop-bottom\">\r\n {/* Chapter thumbnail strip (strip mode only) */}\r\n {effectiveFeatures.enableKeyMoments && showChaptersSidebar && localKeyMoments.length > 0 && chaptersDisplayMode === 'strip' && (\r\n <div className=\"sdk-desktop-chapters-strip\">\r\n {localKeyMoments.map((ch, idx) => {\r\n const isActive = activeChapter.index === idx;\r\n return (\r\n <div\r\n key={idx}\r\n role=\"button\"\r\n tabIndex={0}\r\n className={`sdk-desktop-chapter-card${isActive ? ' active' : ''}`}\r\n aria-label={`${ch.title}, ${formatTime(ch.startTime)}${isActive ? ', current chapter' : ''}`}\r\n aria-current={isActive ? 'true' : undefined}\r\n onClick={(e) => { e.stopPropagation(); handleChapterClick(ch.startTime, ch); }}\r\n onKeyDown={cardKeyDown(() => handleChapterClick(ch.startTime, ch))}\r\n >\r\n <div className=\"sdk-desktop-chapter-thumb\">\r\n {(chapterThumbnails[idx] || ch.thumbnail) ? (\r\n <img\r\n src={chapterThumbnails[idx] || ch.thumbnail}\r\n alt=\"\"\r\n aria-hidden=\"true\"\r\n style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}\r\n />\r\n ) : (\r\n <ListVideo size={18} aria-hidden=\"true\" style={{ color: 'rgba(255,255,255,0.35)' }} />\r\n )}\r\n </div>\r\n <div className=\"sdk-desktop-chapter-title\">{ch.title}</div>\r\n <div className=\"sdk-desktop-chapter-time\" aria-hidden=\"true\">{formatTime(ch.startTime)}</div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n\r\n {/* Seekbar / Ad Progress Bar */}\r\n <div\r\n ref={seekbarRef}\r\n role={seekLocked ? undefined : 'slider'}\r\n aria-label={seekLocked ? undefined : 'Seek'}\r\n aria-valuemin={seekLocked ? undefined : 0}\r\n aria-valuemax={seekLocked ? undefined : Math.round(displayDuration)}\r\n aria-valuenow={seekLocked ? undefined : Math.round(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))}\r\n aria-valuetext={seekLocked ? undefined : `${formatTime(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))} of ${formatTime(displayDuration)}`}\r\n tabIndex={seekLocked ? undefined : 0}\r\n className={`sdk-seekbar-container sdk-desktop-seekbar${isAdPlaying ? ' sdk-ad-mode' : ''}`}\r\n onMouseDown={seekLocked ? undefined : handleSeekMouseDown}\r\n onMouseMove={seekLocked ? undefined : handleSeekbarHover}\r\n onMouseLeave={seekLocked ? undefined : handleSeekbarLeave}\r\n onTouchStart={seekLocked ? undefined : handleSeekTouchStart}\r\n onKeyDown={seekLocked ? undefined : handleSeekbarKeyDown}\r\n style={{ pointerEvents: seekLocked ? 'none' : undefined }}\r\n >\r\n {isAdPlaying ? (\r\n <div\r\n className=\"sdk-ad-progress-fill\"\r\n style={{\r\n // Baseline width for first paint / re-renders — sourced from the live\r\n // media clock (not the throttled `currentTime` state) so it's always\r\n // accurate. The rAF loop above takes over for smooth per-frame motion.\r\n // Tracks the current individual ad within a pod, not the whole break.\r\n width: (() => {\r\n const t = player ? player.getCurrentTime() : currentTime;\r\n const win = getActiveAdWindow(t);\r\n return win ? `${Math.min(100, Math.max(0, ((t - win.start) / (win.end - win.start)) * 100))}%` : '0%';\r\n })(),\r\n }}\r\n />\r\n ) : (\r\n <>\r\n <div\r\n className=\"sdk-seekbar-buffered\"\r\n style={{ width: `${duration ? (player ? (player.getBufferedRanges()[0]?.end || 0) : 0) / duration * 100 : 0}%` }}\r\n />\r\n {displayDuration > 0 && adBreaks.filter(ab => !watchedAdBreaksSet.has(ab.startTime)).map((ab, idx) => (\r\n <div\r\n key={idx}\r\n className=\"sdk-seekbar-adbreak\"\r\n style={{\r\n left: `${(videoTimeToContentTime(ab.startTime) / displayDuration) * 100}%`,\r\n width: '3px',\r\n }}\r\n />\r\n ))}\r\n <div\r\n className=\"sdk-seekbar-progress\"\r\n style={{\r\n width: castDevice\r\n ? `${ctrlDuration > 0 ? (ctrlCurrentTime / ctrlDuration) * 100 : 0}%`\r\n : `${displayDuration ? (videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime) / displayDuration) * 100 : 0}%`,\r\n background: '#ffffff',\r\n boxShadow: 'none',\r\n }}\r\n >\r\n <div className=\"sdk-seekbar-handle\" />\r\n </div>\r\n {effectiveFeatures.enableKeyMoments && localKeyMoments.map((ch, idx) => {\r\n if (displayDuration === 0) return null;\r\n return (\r\n <div\r\n key={idx}\r\n className=\"sdk-seekbar-chapter-marker sdk-desktop-chapter-dot\"\r\n style={{ left: `${(videoTimeToContentTime(ch.startTime) / displayDuration) * 100}%` }}\r\n />\r\n );\r\n })}\r\n {hoverX !== null && hoverThumbnail && (\r\n <div className=\"sdk-thumbnail-tooltip\" style={{ left: `${tooltipLeft}px` }}>\r\n <div className=\"sdk-thumbnail-preview\">\r\n <img\r\n src={hoverThumbnail}\r\n alt=\"preview\"\r\n style={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: '8px', display: 'block' }}\r\n />\r\n </div>\r\n {hoverChapter && (\r\n <div className=\"sdk-thumbnail-chapter-name\">{hoverChapter.title}</div>\r\n )}\r\n <div className=\"sdk-thumbnail-time-label\">{formatTime(videoTimeToContentTime(hoverTime))}</div>\r\n </div>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n\r\n {/* Controls row */}\r\n <div className=\"sdk-desktop-controls-row\">\r\n {/* Left: time + optional volume slider */}\r\n <div className=\"sdk-desktop-left-controls\">\r\n {!isAdPlaying && (\r\n <div className=\"sdk-time-display\">\r\n {castDevice\r\n ? `${formatTime(ctrlCurrentTime)} / ${formatTime(ctrlDuration)}`\r\n : `${formatTime(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))} / ${formatTime(displayDuration)}`}\r\n </div>\r\n )}\r\n {volumeControlType === 'slider' && (\r\n <div className=\"sdk-volume-control\">\r\n <button\r\n className=\"sdk-desktop-icon-btn\"\r\n onClick={(e) => { e.stopPropagation(); toggleMute(); }}\r\n data-tooltip={ctrlMuted ? 'Unmute' : 'Mute'}\r\n aria-label={ctrlMuted ? 'Unmute' : 'Mute'}\r\n aria-pressed={ctrlMuted}\r\n >\r\n {getVolumeIcon()}\r\n </button>\r\n <input\r\n type=\"range\"\r\n className=\"sdk-volume-slider\"\r\n min={0}\r\n max={1}\r\n step={0.02}\r\n value={ctrlMuted ? 0 : ctrlVolume}\r\n aria-label=\"Volume\"\r\n aria-valuetext={ctrlMuted ? 'Muted' : `${Math.round(ctrlVolume * 100)}%`}\r\n style={{ '--vol-pct': Math.round((ctrlMuted ? 0 : ctrlVolume) * 100) } as React.CSSProperties}\r\n onChange={(e) => {\r\n const val = parseFloat(e.target.value);\r\n if (castDevice) {\r\n player?.casting.remoteSetVolume(val);\r\n if (val > 0) player?.casting.remoteSetMute(false);\r\n } else {\r\n player?.setVolume(val);\r\n if (val > 0 && isMuted) player?.setMute(false);\r\n }\r\n }}\r\n onClick={(e) => e.stopPropagation()}\r\n />\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Right: icon buttons */}\r\n <div className=\"sdk-desktop-right-icons\">\r\n {effectiveFeatures.enableKeyMoments && localKeyMoments.length > 0 && !isAdPlaying && (\r\n <button\r\n className={`sdk-desktop-icon-btn${showChaptersSidebar ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); setShowChaptersSidebar(!showChaptersSidebar); }}\r\n data-tooltip=\"Key Moments\"\r\n aria-label={showChaptersSidebar ? 'Hide chapters' : 'Show chapters'}\r\n aria-pressed={showChaptersSidebar}\r\n >\r\n <ListVideo size={20} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n\r\n {effectiveFeatures.enableSubtitles && !isAdPlaying && availableSubtitles.some(t => !t.isNative) && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-desktop-icon-btn${activeSubtitleTrack !== 'off' ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); menuToggle('subtitles'); }}\r\n data-tooltip=\"Captions\"\r\n aria-label=\"Captions\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'subtitles'}\r\n >\r\n <Subtitles size={20} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'subtitles' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Captions\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Subtitles</div>\r\n <div\r\n role=\"option\"\r\n aria-selected={activeSubtitleTrack === 'off'}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSubtitleTrack === 'off' ? 'active' : ''}`}\r\n onClick={() => handleSubtitleChange('off')}\r\n onKeyDown={menuItemKeyDown(() => handleSubtitleChange('off'))}\r\n >\r\n <span>Off</span>\r\n {activeSubtitleTrack === 'off' && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n {availableSubtitles.map((track) => (\r\n <div\r\n key={track.id}\r\n role=\"option\"\r\n aria-selected={activeSubtitleTrack === track.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSubtitleTrack === track.id ? 'active' : ''}`}\r\n onClick={() => handleSubtitleChange(track.id)}\r\n onKeyDown={menuItemKeyDown(() => handleSubtitleChange(track.id))}\r\n >\r\n <span>{getLanguageLabel(track)}</span>\r\n {activeSubtitleTrack === track.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.allowMultipleAudio && audioTracks.length > 1 && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-desktop-icon-btn${activeMenu === 'audio' ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); menuToggle('audio'); }}\r\n data-tooltip=\"Audio Track\"\r\n aria-label=\"Audio track\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'audio'}\r\n >\r\n <Languages size={20} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'audio' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Audio track\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Audio Track</div>\r\n {audioTracks.map((track) => (\r\n <div\r\n key={track.id}\r\n role=\"option\"\r\n aria-selected={activeAudioTrack === track.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeAudioTrack === track.id ? 'active' : ''}`}\r\n onClick={() => {\r\n player?.setAudioTrack(track.id);\r\n setActiveAudioTrack(track.id);\r\n setActiveMenu('none');\r\n setAnnouncement(`Audio: ${getLanguageLabel(track)}`);\r\n }}\r\n onKeyDown={menuItemKeyDown(() => {\r\n player?.setAudioTrack(track.id);\r\n setActiveAudioTrack(track.id);\r\n setActiveMenu('none');\r\n setAnnouncement(`Audio: ${getLanguageLabel(track)}`);\r\n })}\r\n >\r\n <span>{getLanguageLabel(track)}</span>\r\n {activeAudioTrack === track.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.enablePlaybackSpeed && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-desktop-icon-btn${activeMenu === 'speed' ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); menuToggle('speed'); }}\r\n data-tooltip=\"Playback Speed\"\r\n aria-label=\"Playback speed\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'speed'}\r\n >\r\n <Gauge size={20} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'speed' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Playback speed\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Speed</div>\r\n {[0.5, 0.75, 1, 1.25, 1.5, 2].map((rate) => (\r\n <div\r\n key={rate}\r\n role=\"option\"\r\n aria-selected={playbackRate === rate}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${playbackRate === rate ? 'active' : ''}`}\r\n onClick={() => handleSpeedChange(rate)}\r\n onKeyDown={menuItemKeyDown(() => handleSpeedChange(rate))}\r\n >\r\n <span>{rate === 1 ? 'Normal' : `${rate}x`}</span>\r\n {playbackRate === rate && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.enableQuality && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-desktop-icon-btn${activeQuality.id !== -1 ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); menuToggle('quality'); }}\r\n data-tooltip=\"Quality\"\r\n aria-label=\"Video quality\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'quality'}\r\n >\r\n <HdIcon size={20} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'quality' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu large\" role=\"listbox\" aria-label=\"Video quality\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Quality</div>\r\n {qualities.map((q) => (\r\n <div\r\n key={q.id}\r\n role=\"option\"\r\n aria-selected={activeQuality.id === q.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeQuality.id === q.id ? 'active' : ''}`}\r\n onClick={() => handleQualityChange(q.id)}\r\n onKeyDown={menuItemKeyDown(() => handleQualityChange(q.id))}\r\n >\r\n <span>{q.label}</span>\r\n {activeQuality.id === q.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {sources && sources.length > 1 && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-desktop-icon-btn${activeMenu === 'sources' ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); menuToggle('sources'); }}\r\n data-tooltip=\"Select Source\"\r\n aria-label=\"Select source\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'sources'}\r\n >\r\n <MultiSourceHdIcon size={20} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'sources' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Select source\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Source</div>\r\n {sources.map((s, idx) => (\r\n <div\r\n key={idx}\r\n role=\"option\"\r\n aria-selected={activeSourceIndex === idx}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSourceIndex === idx ? 'active' : ''}`}\r\n onClick={() => handleSourceChange(idx)}\r\n onKeyDown={menuItemKeyDown(() => handleSourceChange(idx))}\r\n >\r\n <span>{s.label}</span>\r\n {activeSourceIndex === idx && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.enablePiP && !isAdPlaying && (\r\n <button\r\n className={`sdk-desktop-icon-btn${isPiP ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); togglePiP(); }}\r\n data-tooltip=\"Picture-in-Picture\"\r\n aria-label={isPiP ? 'Exit picture-in-picture' : 'Enter picture-in-picture'}\r\n aria-pressed={isPiP}\r\n >\r\n <PictureInPicture size={20} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n\r\n {volumeControlType === 'button' && (\r\n <button\r\n className=\"sdk-desktop-icon-btn\"\r\n onClick={(e) => { e.stopPropagation(); toggleMute(); }}\r\n data-tooltip={ctrlMuted ? 'Unmute' : 'Mute'}\r\n aria-label={ctrlMuted ? 'Unmute' : 'Mute'}\r\n aria-pressed={ctrlMuted}\r\n >\r\n {getVolumeIcon()}\r\n </button>\r\n )}\r\n\r\n {enableFullscreen && (\r\n <button\r\n className=\"sdk-desktop-icon-btn\"\r\n onClick={(e) => { e.stopPropagation(); toggleFullscreen(); }}\r\n data-tooltip={isFullscreen || isSimulatedFullscreen ? 'Exit fullscreen' : 'Fullscreen'}\r\n aria-label={isFullscreen || isSimulatedFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\r\n aria-pressed={isFullscreen || isSimulatedFullscreen}\r\n >\r\n {isFullscreen ? <Minimize size={20} aria-hidden=\"true\" /> : <Maximize size={20} aria-hidden=\"true\" />}\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Custom PiP window controls — portaled into the floating window's own document,\r\n driven by the exact same state (currentTime/duration/isPlaying) as the on-page\r\n controls, so it never drifts out of sync with real pause/play the way the\r\n browser's native PiP overlay extrapolation could. */}\r\n {pipWindow && (() => {\r\n // While an ad is playing, show the ad's own position/duration (matching the\r\n // main ad overlay's countdown and the media-session ad branch) instead of the\r\n // content timeline — the content clock is frozen for the whole break, so\r\n // reusing it here would show a duration that never matches what's on screen.\r\n const pipAdBreak = adBreaks.find(ab => ctrlCurrentTime >= ab.startTime && ctrlCurrentTime < ab.endTime);\r\n const pipDuration = pipAdBreak ? pipAdBreak.endTime - pipAdBreak.startTime : displayDuration;\r\n const pipPosition = pipAdBreak\r\n ? Math.max(0, ctrlCurrentTime - pipAdBreak.startTime)\r\n : videoTimeToContentTime(ctrlCurrentTime);\r\n\r\n return createPortal(\r\n <div className={`gb-pip-overlay${ctrlIsPlaying ? '' : ' gb-pip-paused'}`}>\r\n <div />\r\n <div className=\"gb-pip-center\">\r\n <button\r\n className=\"gb-pip-btn\"\r\n onClick={() => performSeek(contentTimeToVideoTime(Math.max(0, videoTimeToContentTime(ctrlCurrentTime) - 10)), { eventType: 'fast_backward', skipSeconds: 10 })}\r\n aria-label=\"Rewind 10 seconds\"\r\n disabled={isAdPlaying}\r\n >\r\n <RotateCcw size={20} aria-hidden=\"true\" />\r\n </button>\r\n <button\r\n className=\"gb-pip-btn gb-pip-play\"\r\n onClick={togglePlay}\r\n aria-label={isEnded ? 'Replay' : ctrlIsPlaying ? 'Pause' : 'Play'}\r\n >\r\n {isEnded\r\n ? <RotateCcw size={24} aria-hidden=\"true\" />\r\n : ctrlIsPlaying\r\n ? <Pause size={24} aria-hidden=\"true\" />\r\n : <Play size={24} aria-hidden=\"true\" style={{ marginLeft: 2 }} />}\r\n </button>\r\n <button\r\n className=\"gb-pip-btn\"\r\n onClick={() => performSeek(contentTimeToVideoTime(Math.min(displayDuration, videoTimeToContentTime(ctrlCurrentTime) + 10)), { eventType: 'fast_forward', skipSeconds: 10 })}\r\n aria-label=\"Skip forward 10 seconds\"\r\n disabled={isAdPlaying}\r\n >\r\n <RotateCw size={20} aria-hidden=\"true\" />\r\n </button>\r\n </div>\r\n <div className=\"gb-pip-bottom\">\r\n <div className=\"gb-pip-progress\" onClick={handlePipSeekClick}>\r\n <div\r\n className=\"gb-pip-progress-fill\"\r\n style={{ width: `${pipDuration ? (pipPosition / pipDuration) * 100 : 0}%` }}\r\n />\r\n </div>\r\n <div className=\"gb-pip-time\">\r\n {formatTime(pipPosition)} / {formatTime(pipDuration)}\r\n </div>\r\n </div>\r\n </div>,\r\n pipWindow.document.body\r\n );\r\n })()}\r\n </div>\r\n );\r\n};\r\n","import type { SdkInitResponse } from './types';\r\n\r\nexport const DEFAULT_API_BASE = 'https://dev-api.go-boss.com';\r\n\r\n/** Fallback heartbeat cadence used when `/init` does not return one. */\r\nexport const DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 30;\r\n\r\nfunction buildHeaders(playerKey: string): HeadersInit {\r\n return {\r\n 'x-player-key': playerKey,\r\n 'Content-Type': 'application/json',\r\n }\r\n}\r\n\r\n/** Optional context accepted by POST /init. `referrer` is intentionally not sent. */\r\nexport interface SdkInitOptions {\r\n videoUrl?: string;\r\n}\r\n\r\nexport async function initSdk(\r\n playerKey: string,\r\n apiBase = DEFAULT_API_BASE,\r\n options: SdkInitOptions = {},\r\n): Promise<SdkInitResponse> {\r\n const res = await fetch(`${apiBase}/api/player/sdk/init`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n body: JSON.stringify({ videoUrl: options.videoUrl }),\r\n });\r\n if (!res.ok) throw new Error(`SDK init failed: ${res.status}`);\r\n return await res.json() as SdkInitResponse;\r\n}\r\n\r\n/**\r\n * Body for POST /session/start. Only `sessionId` is required for HTTP acceptance,\r\n * but `videoId`, `videoTitle` and `contentDurationSec` drive the video-level CMS\r\n * analytics (Top Videos, retention, completion) — send them whenever they're known.\r\n * `platform` and `referrer` are intentionally not sent by this SDK.\r\n */\r\nexport interface StartSessionOptions {\r\n /** Client-generated UUID identifying this play. */\r\n sessionId: string;\r\n /** Stable content ID — the grouping key for video-level analytics. */\r\n videoId?: string;\r\n /** Display title for CMS dashboards. */\r\n videoTitle?: string;\r\n /** Playback URL. */\r\n videoUrl?: string;\r\n /** Full content length in seconds, when known. */\r\n contentDurationSec?: number;\r\n}\r\n\r\nexport async function startSession(\r\n playerKey: string,\r\n options: StartSessionOptions,\r\n apiBase = DEFAULT_API_BASE,\r\n): Promise<void> {\r\n const { sessionId, videoId, videoTitle, videoUrl, contentDurationSec } = options;\r\n const res = await fetch(`${apiBase}/api/player/sdk/session/start`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n // undefined fields are dropped by JSON.stringify, so optional context is\r\n // simply omitted rather than sent as null.\r\n body: JSON.stringify({\r\n sessionId,\r\n videoId,\r\n videoTitle,\r\n videoUrl,\r\n // The API rejects negatives and NaN — only send a usable finite duration.\r\n contentDurationSec:\r\n typeof contentDurationSec === 'number' && Number.isFinite(contentDurationSec) && contentDurationSec > 0\r\n ? contentDurationSec\r\n : undefined,\r\n }),\r\n });\r\n if (!res.ok) throw new Error(`Session start failed: ${res.status}`);\r\n}\r\n\r\nexport async function sendHeartbeat(\r\n playerKey: string,\r\n sessionId: string,\r\n currentTime: number,\r\n duration: number,\r\n apiBase = DEFAULT_API_BASE,\r\n): Promise<void> {\r\n const payload = { sessionId, currentTime, duration };\r\n const res = await fetch(`${apiBase}/api/player/sdk/session/heartbeat`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n body: JSON.stringify(payload),\r\n });\r\n if (!res.ok) throw new Error(`Heartbeat failed: ${res.status}`);\r\n}\r\n\r\n/**\r\n * Ends the session and finalizes billed minutes. `currentTime` and `duration`\r\n * are required by the API — pass the final playback position and content length.\r\n */\r\nexport async function endSession(\r\n playerKey: string,\r\n sessionId: string,\r\n currentTime = 0,\r\n duration = 0,\r\n apiBase = DEFAULT_API_BASE,\r\n): Promise<void> {\r\n const res = await fetch(`${apiBase}/api/player/sdk/session/end`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n body: JSON.stringify({ sessionId, currentTime, duration }),\r\n });\r\n if (!res.ok) throw new Error(`Session end failed: ${res.status}`);\r\n}\r\n\r\n/**\r\n * In-session telemetry payload for POST /api/player/sdk/events.\r\n * `eventType`, `sessionId` and `currentTime` are always required; `videoId`,\r\n * `videoTitle` and any event-specific extras (e.g. `from`/`to`, `speed`,\r\n * `bufferingCount`) are sent at the top level — never wrapped in a client\r\n * `properties` object (the server nests them).\r\n */\r\nexport interface PlayerEventPayload {\r\n eventType: string;\r\n sessionId: string;\r\n currentTime: number;\r\n videoId?: string;\r\n videoTitle?: string;\r\n [key: string]: unknown;\r\n}\r\n\r\n/**\r\n * Records a single in-session analytics event. One event per request.\r\n * The endpoint returns 204 No Content on success, so there is no body to parse.\r\n */\r\nexport async function sendEvent(\r\n playerKey: string,\r\n payload: PlayerEventPayload,\r\n apiBase = DEFAULT_API_BASE,\r\n): Promise<void> {\r\n const res = await fetch(`${apiBase}/api/player/sdk/events`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n body: JSON.stringify(payload),\r\n });\r\n if (!res.ok) throw new Error(`Event ${payload.eventType} failed: ${res.status}`);\r\n}\r\n\r\n/** Fire-and-forget session end that survives tab close (`beforeunload`). */\r\nexport function endSessionKeepalive(\r\n playerKey: string,\r\n sessionId: string,\r\n currentTime = 0,\r\n duration = 0,\r\n apiBase = DEFAULT_API_BASE,\r\n): void {\r\n fetch(`${apiBase}/api/player/sdk/session/end`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n body: JSON.stringify({ sessionId, currentTime, duration }),\r\n keepalive: true,\r\n });\r\n}\r\n","import type { AnalyticsEvent, Chapter, PlayerDelegate } from '@goboss/types';\r\n\r\n// Screen orientation for the fullscreen callbacks; undefined where unsupported (e.g. older Safari).\r\nfunction currentOrientation(): string | undefined {\r\n if (typeof screen === 'undefined') return undefined;\r\n return screen.orientation?.type ?? undefined;\r\n}\r\n\r\n/**\r\n * Translates the internal analytics event stream into typed `PlayerDelegate`\r\n * callbacks, deriving the per-event arguments (previous quality/subtitle/audio\r\n * track/speed, seek bounds, watch duration) that individual events don't carry.\r\n *\r\n * One instance lives for the lifetime of a player mount. Delegate exceptions are\r\n * caught and logged so a tenant callback can never break playback.\r\n */\r\nexport class PlayerDelegateBridge {\r\n private delegate: PlayerDelegate | undefined;\r\n private autoplay = false;\r\n private keyMoments: Chapter[] = [];\r\n\r\n // True from the first 'play' until 'ended' — distinguishes start vs resume,\r\n // and re-arms so a replay after 'ended' reports a fresh start.\r\n private isPlaybackActive = false;\r\n private hasFinished = false;\r\n private hasDismissed = false;\r\n\r\n // Last observed playback state, used for playerDidDismiss.\r\n private lastCurrentTime = 0;\r\n private lastDuration = 0;\r\n private lastWatchDuration = 0;\r\n\r\n // Previous values for the from/to media-selection callbacks.\r\n private prevQuality: string | undefined;\r\n private prevSubtitle: string | undefined;\r\n private prevAudioTrack: string | undefined;\r\n private prevSpeed = 1;\r\n\r\n // Seek in flight: started on the 'seek' analytics event, finished on the next\r\n // timeupdate (the position only updates once the seek has landed).\r\n private pendingSeek: { fromTime: number; toTime: number; startedWallMs: number } | null = null;\r\n\r\n public setDelegate(delegate: PlayerDelegate | undefined): void {\r\n this.delegate = delegate;\r\n }\r\n\r\n /** Whether playback was configured to start automatically (reported on playerDidStartPlaying). */\r\n public setAutoplay(autoplay: boolean): void {\r\n this.autoplay = autoplay;\r\n }\r\n\r\n /** Key moments currently loaded — used to resolve chapter bounds for playerDidSelectKeyMoment. */\r\n public setKeyMoments(keyMoments: Chapter[]): void {\r\n this.keyMoments = keyMoments;\r\n }\r\n\r\n /** Route one analytics event to its delegate callback. Call for every event. */\r\n public handleAnalyticsEvent(event: AnalyticsEvent): void {\r\n if (typeof event.currentTime === 'number' && !Number.isNaN(event.currentTime)) {\r\n this.lastCurrentTime = event.currentTime;\r\n }\r\n if (typeof event.duration === 'number' && !Number.isNaN(event.duration)) this.lastDuration = event.duration;\r\n if (typeof event.totalWatchTime === 'number') this.lastWatchDuration = event.totalWatchTime;\r\n\r\n switch (event.eventType) {\r\n case 'play':\r\n if (!this.isPlaybackActive) {\r\n this.isPlaybackActive = true;\r\n this.hasFinished = false;\r\n this.call('playerDidStartPlaying', event.currentTime, this.autoplay);\r\n } else {\r\n this.call('playerDidResumePlaying', event.currentTime);\r\n }\r\n break;\r\n\r\n case 'pause':\r\n // The media element fires an implicit 'pause' just before 'ended' — that is\r\n // not a user pause, so suppress it and let playerDidFinishPlaying report it.\r\n if (!(event.duration > 0 && event.currentTime >= event.duration - 0.25)) {\r\n this.call('playerDidPause', event.currentTime);\r\n }\r\n break;\r\n\r\n case 'ended':\r\n this.isPlaybackActive = false;\r\n this.hasFinished = true;\r\n this.call('playerDidFinishPlaying', event.currentTime, event.totalWatchTime ?? this.lastWatchDuration);\r\n break;\r\n\r\n case 'seek':\r\n this.beginSeek(event.seekFrom ?? this.lastCurrentTime, event.seekTo ?? event.currentTime);\r\n break;\r\n\r\n // Skip gestures are seeks too; at track time currentTime is already the target,\r\n // so recover the origin from the skip amount.\r\n case 'fast_forward':\r\n this.beginSeek(Math.max(0, event.currentTime - (event.skipSeconds ?? 10)), event.currentTime);\r\n break;\r\n case 'fast_backward':\r\n this.beginSeek(event.currentTime + (event.skipSeconds ?? 10), event.currentTime);\r\n break;\r\n\r\n case 'buffering_start':\r\n this.call('playerDidStartBuffering', event.currentTime);\r\n break;\r\n case 'buffering_end':\r\n this.call('playerDidFinishBuffering', event.currentTime);\r\n break;\r\n\r\n case 'quality_change': {\r\n const toQuality = event.rendition;\r\n if (toQuality && toQuality !== this.prevQuality) {\r\n this.call('playerDidChangeQuality', this.prevQuality, toQuality, event.isAutoSwitch ?? false);\r\n this.prevQuality = toQuality;\r\n }\r\n break;\r\n }\r\n\r\n case 'subtitle_change':\r\n if (event.subtitleEnabled === false) {\r\n this.call('playerDidDisableSubtitle', this.prevSubtitle);\r\n this.prevSubtitle = undefined;\r\n } else {\r\n const toSubtitle = event.subtitleLanguage ?? '';\r\n this.call('playerDidChangeSubtitle', this.prevSubtitle, toSubtitle);\r\n this.prevSubtitle = toSubtitle;\r\n }\r\n break;\r\n\r\n case 'audio_track_change': {\r\n const toTrack = event.audioTrack ?? '';\r\n this.call('playerDidChangeAudioTrack', this.prevAudioTrack, toTrack);\r\n this.prevAudioTrack = toTrack;\r\n break;\r\n }\r\n\r\n case 'speed_change': {\r\n const toSpeed = event.speed ?? 1;\r\n // ratechange also fires for internal rate resets (e.g. ad breaks) — only\r\n // report actual changes.\r\n if (toSpeed !== this.prevSpeed) {\r\n this.call('playerDidChangePlaybackSpeed', this.prevSpeed, toSpeed);\r\n this.prevSpeed = toSpeed;\r\n }\r\n break;\r\n }\r\n\r\n case 'fullscreen_enter':\r\n this.call('playerDidEnterFullscreen', currentOrientation());\r\n break;\r\n case 'fullscreen_exit':\r\n this.call('playerDidExitFullscreen', currentOrientation());\r\n break;\r\n\r\n case 'key_moments': {\r\n const title = event.momentTitle ?? '';\r\n const startTime = event.momentTime ?? event.currentTime;\r\n const moments = this.keyMoments;\r\n const idx = moments.findIndex(\r\n (m) => (title && m.title === title) || m.startTime === startTime,\r\n );\r\n const chapter = idx >= 0 ? moments[idx] : undefined;\r\n this.call(\r\n 'playerDidSelectKeyMoment',\r\n idx >= 0 ? String(idx) : undefined,\r\n chapter?.title ?? title,\r\n chapter?.startTime ?? startTime,\r\n chapter?.endTime ?? startTime,\r\n );\r\n break;\r\n }\r\n\r\n case 'pip_toggle': {\r\n // core-player packs the direction into errorMessage ('entered' | 'exited').\r\n const entered = event.pipState ? event.pipState === 'entered' : event.errorMessage === 'entered';\r\n if (entered) this.call('playerDidEnterPictureInPicture', event.currentTime);\r\n else this.call('playerDidExitPictureInPicture', event.currentTime);\r\n break;\r\n }\r\n\r\n case 'error':\r\n this.call(\r\n 'playerDidEncounterPlaybackError',\r\n event.errorCode != null ? String(event.errorCode) : 'PLAYER_ERROR',\r\n event.errorMessage ?? 'Unknown player error',\r\n event.isRecoverable ?? false,\r\n );\r\n break;\r\n\r\n case 'network_error':\r\n this.call(\r\n 'playerDidEncounterNetworkError',\r\n event.errorCode ?? 0,\r\n event.errorMessage ?? 'Network error',\r\n event.isRecoverable ?? false,\r\n );\r\n break;\r\n }\r\n }\r\n\r\n /** Call on every timeupdate — completes in-flight seeks and reports position. */\r\n public handleTimeUpdate(currentTime: number): void {\r\n this.lastCurrentTime = currentTime;\r\n if (this.pendingSeek) {\r\n const { fromTime, startedWallMs } = this.pendingSeek;\r\n this.pendingSeek = null;\r\n this.call('playerDidFinishSeeking', fromTime, currentTime, (Date.now() - startedWallMs) / 1000);\r\n }\r\n this.call('playerDidUpdateCurrentTime', currentTime, this.lastDuration);\r\n }\r\n\r\n /**\r\n * Call when the player is torn down (unmount or tab close). Reports\r\n * playerDidDismiss once, and only if playback started but never finished —\r\n * a completed session is already reported via playerDidFinishPlaying.\r\n */\r\n public notifyDismissed(): void {\r\n if (this.hasDismissed || !this.isPlaybackActive || this.hasFinished) return;\r\n this.hasDismissed = true;\r\n this.call('playerDidDismiss', this.lastCurrentTime, this.lastWatchDuration);\r\n }\r\n\r\n private beginSeek(fromTime: number, toTime: number): void {\r\n if (this.pendingSeek) {\r\n // Rapid consecutive seeks before the first one lands (scrubbing): keep the\r\n // original origin, only the destination moves.\r\n this.pendingSeek.toTime = toTime;\r\n return;\r\n }\r\n this.pendingSeek = { fromTime, toTime, startedWallMs: Date.now() };\r\n this.call('playerDidStartSeeking', fromTime);\r\n }\r\n\r\n // Invokes a delegate method if implemented; a throwing tenant callback is\r\n // contained here so it can never break playback or telemetry.\r\n private call<K extends keyof PlayerDelegate>(\r\n method: K,\r\n ...args: Parameters<NonNullable<PlayerDelegate[K]>>\r\n ): void {\r\n const delegate = this.delegate;\r\n const fn = delegate?.[method];\r\n if (typeof fn !== 'function') return;\r\n try {\r\n (fn as (...fnArgs: unknown[]) => void).apply(delegate, args);\r\n } catch (err) {\r\n console.error(`[PlayerSDK Delegate] ${String(method)} callback failed:`, err);\r\n }\r\n }\r\n}\r\n","import { useState, useEffect, useRef, useCallback } from 'react';\r\nimport { WebVideoPlayer, ErrorScreen } from '@goboss/ui-controls';\r\nimport type { FirebaseConfig } from '@goboss/analytics';\r\nimport type { Chapter, AnalyticsEvent } from '@goboss/types';\r\nimport {\r\n initSdk,\r\n startSession,\r\n sendHeartbeat,\r\n endSession,\r\n endSessionKeepalive,\r\n sendEvent,\r\n DEFAULT_HEARTBEAT_INTERVAL_SECONDS,\r\n} from './api';\r\nimport type { SdkInitResponse, SdkInitFeatures, SdkPackage, FeatureFlags, SubtitleStyle } from './types';\r\nimport type { DRMConfig, SubtitleTrack, SourceItem, PlayerDelegate } from '@goboss/types';\r\nimport { PlayerDelegateBridge } from './delegate';\r\n\r\n// Maps API feature names → player FeatureFlags keys.\r\n// Add a new entry here whenever the API adds a new feature.\r\nconst API_FEATURE_MAP: Record<string, keyof FeatureFlags> = {\r\n subtitles: 'subtitle_support',\r\n ads: 'ad_support',\r\n chapterMarkers: 'key_moments',\r\n playbackSpeed: 'playback_speed',\r\n airplay: 'airplay',\r\n chromecast: 'chromecast',\r\n pictureInPicture: 'picture_in_picture',\r\n multipleResolution: 'multiple_resolution',\r\n multipleAudio: 'multiple_audio',\r\n drmProtection: 'drm_protection',\r\n screenRecordingPrevention: 'screen_recording_prevention',\r\n signalStrength: 'signal_strength',\r\n};\r\n\r\nfunction mapApiFeatures(apiFeatures: SdkInitFeatures): FeatureFlags {\r\n const flags: FeatureFlags = {\r\n m3u8_playback: true,\r\n subtitle_support: false,\r\n playback_speed: false,\r\n airplay: false,\r\n chromecast: false,\r\n picture_in_picture: false,\r\n multiple_resolution: false,\r\n multiple_audio: false,\r\n key_moments: false,\r\n drm_protection: false,\r\n screen_recording_prevention: false,\r\n signal_strength: false,\r\n ad_support: false,\r\n };\r\n for (const [apiKey, playerKey] of Object.entries(API_FEATURE_MAP)) {\r\n if (apiKey in apiFeatures) {\r\n flags[playerKey] = apiFeatures[apiKey];\r\n }\r\n }\r\n return flags;\r\n}\r\n\r\n// Builds a URL transformer from a \"hostname=/proxyPath,hostname2=/proxyPath2\" string.\r\nfunction buildUrlTransformer(proxyDomains?: string): (url: string) => string {\r\n if (!proxyDomains) return (url) => url;\r\n const map: Array<{ domain: string; proxyPath: string }> = [];\r\n for (const entry of proxyDomains.split(',')) {\r\n const [domain, proxyPath] = entry.split('=');\r\n if (domain?.trim() && proxyPath?.trim()) {\r\n map.push({ domain: domain.trim(), proxyPath: proxyPath.trim() });\r\n }\r\n }\r\n return (url: string) => {\r\n for (const { domain, proxyPath } of map) {\r\n if (url.includes(domain)) {\r\n try {\r\n const urlObj = new URL(url);\r\n return `${proxyPath}${urlObj.pathname}${urlObj.search}`;\r\n } catch {\r\n return url.replace(`https://${domain}`, proxyPath);\r\n }\r\n }\r\n }\r\n return url;\r\n };\r\n}\r\n\r\nfunction hexToRgb(hex: string): string {\r\n const cleanHex = hex.replace('#', '');\r\n if (cleanHex.length !== 6) return '99, 102, 241';\r\n const r = parseInt(cleanHex.substring(0, 2), 16);\r\n const g = parseInt(cleanHex.substring(2, 4), 16);\r\n const b = parseInt(cleanHex.substring(4, 6), 16);\r\n return `${r}, ${g}, ${b}`;\r\n}\r\n\r\nconst DEFAULT_HEARTBEAT_WINDOW_MS = DEFAULT_HEARTBEAT_INTERVAL_SECONDS * 1000;\r\n\r\n// Maps a player analytics event → an Events API telemetry payload.\r\n// Returns null for events that are NOT part of the Events API contract (play, pause,\r\n// seek, ended, session_start, heartbeat, …) so those are never forwarded.\r\n// Only the event-specific extras are returned here; the caller adds the common\r\n// `sessionId`/`currentTime`/`videoId` fields. Field names follow the API docs\r\n// (top-level, not wrapped in `properties`).\r\nfunction buildTelemetryEvent(\r\n event: AnalyticsEvent,\r\n prevRendition: string | undefined,\r\n): { eventType: string; extras: Record<string, unknown> } | null {\r\n switch (event.eventType) {\r\n case 'pause':\r\n return { eventType: 'pause', extras: {} };\r\n case 'seek':\r\n return {\r\n eventType: 'seek',\r\n extras: {\r\n seekFrom: event.seekFrom ?? 0,\r\n seekTo: event.seekTo ?? event.currentTime ?? 0,\r\n },\r\n };\r\n case 'mute':\r\n return { eventType: 'mute', extras: { isMuted: event.isMuted ?? true } };\r\n case 'fast_forward':\r\n return { eventType: 'fast_forward', extras: { skipSeconds: event.skipSeconds ?? 10 } };\r\n case 'fast_backward':\r\n return { eventType: 'fast_backward', extras: { skipSeconds: event.skipSeconds ?? 10 } };\r\n case 'key_moments':\r\n return {\r\n eventType: 'key_moments',\r\n extras: {\r\n momentTitle: event.momentTitle ?? '',\r\n momentTime: event.momentTime ?? 0,\r\n },\r\n };\r\n case 'buffering_start':\r\n return { eventType: 'buffering_start', extras: { bufferingCount: event.bufferingCount ?? 0 } };\r\n case 'buffering_end':\r\n return { eventType: 'buffering_end', extras: {} };\r\n case 'quality_change':\r\n // The API requires both `from` and `to`. The player only carries the new\r\n // rendition; `from` is the last one we saw. The first LEVEL_SWITCHED of a\r\n // session is the initial rendition pick — there is no previous quality, so\r\n // it is not a \"change\" and is not forwarded (the API would reject it anyway).\r\n if (!prevRendition || !event.rendition || prevRendition === event.rendition) return null;\r\n return {\r\n eventType: 'quality_change',\r\n extras: { from: prevRendition, to: event.rendition },\r\n };\r\n case 'speed_change':\r\n return { eventType: 'speed_change', extras: { speed: event.speed ?? 1 } };\r\n case 'subtitle_change':\r\n // Turning subtitles off is its own API event type.\r\n if (event.subtitleEnabled === false) {\r\n return { eventType: 'subtitle_disable', extras: { enabled: false } };\r\n }\r\n return {\r\n eventType: 'subtitle_change',\r\n extras: {\r\n language: event.subtitleLanguage ?? '',\r\n enabled: true,\r\n },\r\n };\r\n case 'audio_track_change':\r\n return {\r\n eventType: 'audio_track_change',\r\n extras: { track: event.audioTrack ?? '' },\r\n };\r\n case 'pip_toggle':\r\n // core-player packs the direction into errorMessage ('entered' | 'exited').\r\n return { eventType: 'pip_toggle', extras: { enabled: event.errorMessage === 'entered' } };\r\n case 'fullscreen_enter':\r\n return { eventType: 'fullscreen_toggle', extras: { enabled: true } };\r\n case 'fullscreen_exit':\r\n return { eventType: 'fullscreen_toggle', extras: { enabled: false } };\r\n case 'error':\r\n // The API's `player_error`. HTML5 media errors carry a numeric code + message string.\r\n return {\r\n eventType: 'player_error',\r\n extras: {\r\n code: event.errorCode != null ? String(event.errorCode) : 'PLAYER_ERROR',\r\n message: event.errorMessage ?? 'Unknown player error',\r\n },\r\n };\r\n case 'network_error':\r\n return {\r\n eventType: 'network_error',\r\n extras: {\r\n code: event.errorCode ? String(event.errorCode) : 'NETWORK_ERROR',\r\n message: event.errorMessage ?? 'Network error',\r\n },\r\n };\r\n default:\r\n return null;\r\n }\r\n}\r\n\r\nexport interface BossVideoPlayerProps {\r\n /** Player key from the GoBOSS dashboard — identifies your tenant. */\r\n playerKey: string;\r\n /**\r\n * Stream source(s) to play.\r\n * - Pass a single URL string for standard single-stream playback.\r\n * - Pass an array of `{ label, url }` objects to let the user switch between\r\n * multiple streams at runtime (e.g. 4K, Dolby, Stereo). The first item plays by default.\r\n */\r\n src: string | SourceItem[];\r\n /**\r\n * External subtitle tracks (WebVTT/SRT) to load alongside any tracks\r\n * auto-detected from the M3U8 manifest. Merged into a single subtitle menu.\r\n */\r\n subtitles?: SubtitleTrack[];\r\n /** License key for playback authorization. */\r\n licenseKey?: string;\r\n /** Accent color for the player UI (hex). Defaults to '#6366f1'. */\r\n themeColor?: string;\r\n /** Firebase config for analytics. */\r\n firebaseConfig?: FirebaseConfig;\r\n /**\r\n * CDN proxy domain mappings as a comma-separated string.\r\n * Format: \"cdn.example.com=/my-proxy,other.cdn.com=/other-proxy\"\r\n * Any request URL matching a domain is rewritten to the proxy path.\r\n */\r\n proxyDomains?: string;\r\n /**\r\n * Base URL for the GoBOSS API. Defaults to 'https://dev-api.go-boss.com'.\r\n * Override this if you need to route through a reverse proxy.\r\n */\r\n apiBaseUrl?: string;\r\n /** AWS MediaTailor ad tracking URL for skip-ad flow. */\r\n adTrackingUrl?: string;\r\n /** AWS MediaTailor overlay ad tracking URL. */\r\n overlayAdUrl?: string;\r\n videoId?: string;\r\n /** Video title shown in the player UI. */\r\n title?: string;\r\n /** Watermark text stamped on the video. */\r\n watermark?: string;\r\n autoplay?: boolean;\r\n muted?: boolean;\r\n autoFullscreen?: boolean;\r\n enableControls?: boolean;\r\n enableSkipAd?: boolean;\r\n skipAdAfterSeconds?: number;\r\n chaptersDisplayMode?: 'strip' | 'panel';\r\n volumeControlType?: 'slider' | 'button';\r\n subtitleStyle?: SubtitleStyle;\r\n drmConfig?: DRMConfig;\r\n /** Called for every analytics event — lets consumers add custom tracking. */\r\n onAnalyticsEvent?: (event: AnalyticsEvent) => void;\r\n /**\r\n * Typed per-event callbacks (playerDidStartPlaying, playerDidPause, …) that\r\n * receive only the arguments relevant to each event. Prefer this over\r\n * `onAnalyticsEvent` for analytics, logging, and business logic; implement\r\n * only the methods you need.\r\n */\r\n delegate?: PlayerDelegate;\r\n /** Called when chapter/key-moment data is parsed from the stream. */\r\n onKeyMomentsLoaded?: (keyMoments: Chapter[]) => void;\r\n /** Seek to this position (in seconds) when the video is ready. Use for resume-playback. */\r\n initialTime?: number;\r\n /** Called on every timeupdate with the current playback position in seconds. Use to persist watch progress. */\r\n onTimeUpdate?: (currentTime: number) => void;\r\n}\r\n\r\nexport function BossVideoPlayer({\r\n playerKey,\r\n src,\r\n subtitles,\r\n licenseKey,\r\n themeColor = '#6366f1',\r\n firebaseConfig,\r\n proxyDomains,\r\n apiBaseUrl,\r\n adTrackingUrl,\r\n overlayAdUrl,\r\n videoId: videoIdProp,\r\n title,\r\n watermark,\r\n autoplay = false,\r\n muted = false,\r\n autoFullscreen = false,\r\n enableControls = true,\r\n enableSkipAd,\r\n skipAdAfterSeconds,\r\n chaptersDisplayMode = 'strip',\r\n volumeControlType = 'slider',\r\n subtitleStyle,\r\n drmConfig,\r\n onAnalyticsEvent,\r\n delegate,\r\n onKeyMomentsLoaded,\r\n initialTime,\r\n onTimeUpdate,\r\n}: BossVideoPlayerProps) {\r\n const [keyMoments, setKeyMoments] = useState<Chapter[]>([]);\r\n const [activeSourceIdx, setActiveSourceIdx] = useState(0);\r\n\r\n // Delegate bridge — translates the analytics event stream into typed PlayerDelegate\r\n // callbacks. The bridge is stateful (tracks previous quality/subtitle/speed, seek\r\n // bounds, start-vs-resume) so one instance lives for the component lifetime and is\r\n // kept in sync with the latest props via its setters.\r\n const [bridge] = useState(() => new PlayerDelegateBridge());\r\n useEffect(() => {\r\n bridge.setDelegate(delegate);\r\n bridge.setAutoplay(autoplay);\r\n }, [bridge, delegate, autoplay]);\r\n\r\n // Report playerDidDismiss when the player unmounts mid-session (tab close is\r\n // covered by the beforeunload handler below; the bridge fires at most once).\r\n useEffect(() => () => bridge.notifyDismissed(), [bridge]);\r\n\r\n // Normalize src into a SourceItem array so the rest of the component is uniform\r\n const normalizedSources: SourceItem[] = Array.isArray(src)\r\n ? src\r\n : [{ label: 'Default', url: src }];\r\n const activeSrc: string = normalizedSources[activeSourceIdx]?.url ?? normalizedSources[0]?.url ?? '';\r\n\r\n // ── Player key validation state ─────────────────────────────────────────────\r\n // 'loading': waiting for /sdk/init API response\r\n // 'ready': API confirmed — resolvedPackage holds the feature set\r\n // 'error': API rejected the key or network failed\r\n const [sdkStatus, setSdkStatus] = useState<'loading' | 'ready' | 'error'>('loading');\r\n const [resolvedPackage, setResolvedPackage] = useState<SdkPackage | null>(null);\r\n const [retryCount, setRetryCount] = useState(0);\r\n const [errorKind, setErrorKind] = useState<'auth' | 'network'>('network');\r\n\r\n const sdkDataRef = useRef<SdkInitResponse | null>(null);\r\n const isInAdBreakRef = useRef(false);\r\n const sessionIdRef = useRef('');\r\n const sessionStartedRef = useRef(false);\r\n // Blocks event telemetry once the session has ended (via `ended` or tab close),\r\n // since the Events API rejects events for an inactive session.\r\n const sessionEndedRef = useRef(false);\r\n // Last rendition seen, used to fill `from` on the next quality_change.\r\n const prevRenditionRef = useRef<string | undefined>(undefined);\r\n // True from the moment the user switches source until real content actually plays again.\r\n // While true, the heartbeat window stays paused so the blank detection/fallback period of\r\n // an unsupported source (which fires no buffering events, only a hollow `play`) is never\r\n // counted as watched content.\r\n const awaitingSourcePlaybackRef = useRef(false);\r\n\r\n // Tracks real content time toward the heartbeat window (excludes ads + pauses).\r\n // The window length comes from `heartbeatIntervalSeconds` on the /init response.\r\n const heartbeatWindowMsRef = useRef(DEFAULT_HEARTBEAT_WINDOW_MS);\r\n const heartbeatTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const elapsedInWindowRef = useRef(0);\r\n const windowStartRef = useRef<number | null>(null);\r\n const latestPlaybackRef = useRef({ currentTime: 0, duration: 0 });\r\n const startHeartbeatTimerRef = useRef<() => void>(() => {});\r\n\r\n const srcSegments = activeSrc.split('/').filter(Boolean);\r\n const videoId =\r\n videoIdProp ||\r\n (srcSegments[srcSegments.length - 1] ?? '').replace(/\\.m3u8.*$/, '') ||\r\n 'video';\r\n\r\n // Latest playback URL, read from effects/callbacks that must not re-run on a source\r\n // switch (SDK init, session end on tab close).\r\n const activeSrcRef = useRef(activeSrc);\r\n useEffect(() => {\r\n activeSrcRef.current = activeSrc;\r\n }, [activeSrc]);\r\n\r\n // Build URL transformer once when proxyDomains changes\r\n const urlTransformer = useCallback(\r\n () => buildUrlTransformer(proxyDomains),\r\n [proxyDomains],\r\n )();\r\n\r\n const themeRgb = hexToRgb(themeColor);\r\n\r\n // Apply themeColor CSS variables\r\n useEffect(() => {\r\n document.documentElement.style.setProperty('--accent-indigo', themeColor);\r\n document.documentElement.style.setProperty('--accent-indigo-rgb', themeRgb);\r\n }, [themeColor, themeRgb]);\r\n\r\n // Validate player key — blocks video until the API confirms the key and returns features.\r\n // Cleanup resets to 'loading' so playerKey/retryCount changes show a fresh loading state.\r\n useEffect(() => {\r\n let cancelled = false;\r\n\r\n initSdk(playerKey, apiBaseUrl, { videoUrl: activeSrcRef.current })\r\n .then(data => {\r\n if (cancelled) return;\r\n sdkDataRef.current = data;\r\n const { features, planTier, heartbeatIntervalSeconds } = data.result.data;\r\n // Honour the server-provided heartbeat cadence; keep the 30s default otherwise.\r\n heartbeatWindowMsRef.current =\r\n typeof heartbeatIntervalSeconds === 'number' && heartbeatIntervalSeconds > 0\r\n ? heartbeatIntervalSeconds * 1000\r\n : DEFAULT_HEARTBEAT_WINDOW_MS;\r\n const pkg: SdkPackage = {\r\n package: planTier,\r\n is_active: true,\r\n features: mapApiFeatures(features),\r\n limits: { streaming_minutes: 0 },\r\n };\r\n console.log('[SDK] Player key validated. Features:', pkg.features);\r\n setResolvedPackage(pkg);\r\n setSdkStatus('ready');\r\n })\r\n .catch(err => {\r\n if (cancelled) return;\r\n console.error('[SDK] Player key validation failed:', err);\r\n const msg = (err as Error).message ?? '';\r\n setErrorKind(/init failed: 4\\d\\d/.test(msg) ? 'auth' : 'network');\r\n setSdkStatus('error');\r\n });\r\n\r\n return () => {\r\n cancelled = true;\r\n if (heartbeatTimerRef.current) {\r\n clearTimeout(heartbeatTimerRef.current);\r\n heartbeatTimerRef.current = null;\r\n }\r\n elapsedInWindowRef.current = 0;\r\n windowStartRef.current = null;\r\n setSdkStatus('loading');\r\n setResolvedPackage(null);\r\n sessionStartedRef.current = false;\r\n sessionIdRef.current = '';\r\n sessionEndedRef.current = false;\r\n prevRenditionRef.current = undefined;\r\n };\r\n }, [playerKey, apiBaseUrl, retryCount]);\r\n\r\n // Pauses the timer, saving accumulated time in current window\r\n const pauseHeartbeatTimer = useCallback(() => {\r\n if (!heartbeatTimerRef.current) return;\r\n clearTimeout(heartbeatTimerRef.current);\r\n heartbeatTimerRef.current = null;\r\n if (windowStartRef.current !== null) {\r\n elapsedInWindowRef.current += Date.now() - windowStartRef.current;\r\n windowStartRef.current = null;\r\n }\r\n }, []);\r\n\r\n // Stops the timer and resets the window\r\n const stopHeartbeatTimer = useCallback(() => {\r\n if (heartbeatTimerRef.current) {\r\n clearTimeout(heartbeatTimerRef.current);\r\n heartbeatTimerRef.current = null;\r\n }\r\n elapsedInWindowRef.current = 0;\r\n windowStartRef.current = null;\r\n }, []);\r\n\r\n // Starts (or resumes) the 30s real-content-time timer\r\n const startHeartbeatTimer = useCallback(() => {\r\n if (heartbeatTimerRef.current || !sessionIdRef.current) return;\r\n windowStartRef.current = Date.now();\r\n const remaining = heartbeatWindowMsRef.current - elapsedInWindowRef.current;\r\n heartbeatTimerRef.current = setTimeout(() => {\r\n heartbeatTimerRef.current = null;\r\n elapsedInWindowRef.current = 0;\r\n windowStartRef.current = null;\r\n const { currentTime, duration } = latestPlaybackRef.current;\r\n console.log(`[SDK] Heartbeat timer fired — ${heartbeatWindowMsRef.current / 1000}s of real content elapsed`);\r\n sendHeartbeat(playerKey, sessionIdRef.current, currentTime, duration, apiBaseUrl).catch(err =>\r\n console.warn('[SDK] Heartbeat failed:', (err as Error).message),\r\n );\r\n startHeartbeatTimerRef.current();\r\n }, remaining);\r\n }, [playerKey, apiBaseUrl]);\r\n\r\n useEffect(() => {\r\n startHeartbeatTimerRef.current = startHeartbeatTimer;\r\n }, [startHeartbeatTimer]);\r\n\r\n // Deterministic source-switch handling: the moment the user picks a different source,\r\n // pause the heartbeat window and mark that we're awaiting real playback. The new stream\r\n // (especially an unsupported one like Dolby/Atmos) emits no buffering events during its\r\n // blank detection period — only a hollow `play` — so this is the only reliable way to\r\n // stop counting that time. The window resumes when `buffering_end` confirms real frames\r\n // are rendering (in handleAnalytics). The first run (initial mount) is skipped.\r\n const isInitialSourceRef = useRef(true);\r\n useEffect(() => {\r\n if (isInitialSourceRef.current) {\r\n isInitialSourceRef.current = false;\r\n return;\r\n }\r\n awaitingSourcePlaybackRef.current = true;\r\n // The new source's first LEVEL_SWITCHED is its initial pick, not a change from\r\n // the previous source's rendition — clear so no cross-source quality_change is sent.\r\n prevRenditionRef.current = undefined;\r\n pauseHeartbeatTimer();\r\n }, [activeSourceIdx, pauseHeartbeatTimer]);\r\n\r\n // beforeunload: fire session end with keepalive so the request survives tab close\r\n useEffect(() => {\r\n const handleUnload = () => {\r\n bridge.notifyDismissed();\r\n if (!sessionIdRef.current) return;\r\n sessionEndedRef.current = true;\r\n stopHeartbeatTimer();\r\n const { currentTime, duration } = latestPlaybackRef.current;\r\n endSessionKeepalive(playerKey, sessionIdRef.current, currentTime, duration, apiBaseUrl);\r\n };\r\n window.addEventListener('beforeunload', handleUnload);\r\n return () => {\r\n window.removeEventListener('beforeunload', handleUnload);\r\n stopHeartbeatTimer();\r\n };\r\n }, [playerKey, apiBaseUrl, stopHeartbeatTimer, bridge]);\r\n\r\n const handleAnalytics = useCallback((event: AnalyticsEvent) => {\r\n const e = event as unknown as Record<string, unknown>;\r\n\r\n if (event.currentTime !== undefined) {\r\n latestPlaybackRef.current = { currentTime: event.currentTime, duration: event.duration };\r\n }\r\n\r\n if (event.eventType === 'session_start' && !sessionStartedRef.current) {\r\n sessionStartedRef.current = true;\r\n sessionEndedRef.current = false;\r\n sessionIdRef.current = typeof e.sessionId === 'string' ? e.sessionId : '';\r\n // Video identity is sent here — without it the play is excluded from Top Videos,\r\n // retention and completion analytics. `session_start` fires just before the first\r\n // `play`, so the media duration is already known unless the stream is live.\r\n startSession(\r\n playerKey,\r\n {\r\n sessionId: sessionIdRef.current,\r\n videoId,\r\n videoTitle: title,\r\n videoUrl: activeSrcRef.current,\r\n contentDurationSec: event.duration,\r\n },\r\n apiBaseUrl,\r\n ).catch(err =>\r\n console.warn('[SDK] Session start failed:', (err as Error).message),\r\n );\r\n if (!isInAdBreakRef.current) {\r\n startHeartbeatTimer();\r\n }\r\n }\r\n\r\n // `play` is also fired by an unsupported source's hollow autoplay attempt, so while a\r\n // source switch is awaiting real playback we must NOT let it resume the window.\r\n if (\r\n event.eventType === 'play' &&\r\n sessionStartedRef.current &&\r\n !isInAdBreakRef.current &&\r\n !awaitingSourcePlaybackRef.current\r\n ) {\r\n startHeartbeatTimer();\r\n }\r\n\r\n if (event.eventType === 'pause') {\r\n pauseHeartbeatTimer();\r\n }\r\n\r\n // Normal buffering is not \"real content played\" — pause during it.\r\n if (event.eventType === 'buffering_start') {\r\n pauseHeartbeatTimer();\r\n }\r\n\r\n // `buffering_end` only fires when frames actually render (it's gated by a real\r\n // buffering→playing transition), so it's the reliable \"content is truly playing again\"\r\n // signal. Clear the source-switch wait and resume the window here.\r\n if (event.eventType === 'buffering_end' && sessionStartedRef.current && !isInAdBreakRef.current) {\r\n awaitingSourcePlaybackRef.current = false;\r\n startHeartbeatTimer();\r\n }\r\n\r\n if (event.eventType === 'ended') {\r\n stopHeartbeatTimer();\r\n if (sessionIdRef.current) {\r\n sessionEndedRef.current = true;\r\n const { currentTime, duration } = latestPlaybackRef.current;\r\n endSession(playerKey, sessionIdRef.current, currentTime, duration, apiBaseUrl).catch(err =>\r\n console.warn('[SDK] Session end failed:', (err as Error).message),\r\n );\r\n }\r\n }\r\n\r\n // Forward in-session telemetry to the Events API. Guarded so events are only sent\r\n // while a session is active (started, not yet ended) — the API rejects events for\r\n // an inactive session.\r\n if (sessionStartedRef.current && sessionIdRef.current && !sessionEndedRef.current) {\r\n const telemetry = buildTelemetryEvent(event, prevRenditionRef.current);\r\n if (telemetry) {\r\n sendEvent(\r\n playerKey,\r\n {\r\n eventType: telemetry.eventType,\r\n sessionId: sessionIdRef.current,\r\n currentTime: event.currentTime ?? 0,\r\n videoId,\r\n videoTitle: title,\r\n ...telemetry.extras,\r\n },\r\n apiBaseUrl,\r\n ).catch(err =>\r\n console.warn(`[SDK] Event ${telemetry.eventType} failed:`, (err as Error).message),\r\n );\r\n }\r\n }\r\n\r\n // Track the active rendition even when nothing was forwarded (initial pick,\r\n // session not yet started) so the next quality_change carries the right `from`.\r\n if (event.eventType === 'quality_change' && event.rendition) {\r\n prevRenditionRef.current = event.rendition;\r\n }\r\n\r\n console.log('[SDK Analytics]', event);\r\n bridge.handleAnalyticsEvent(event);\r\n onAnalyticsEvent?.(event);\r\n }, [playerKey, apiBaseUrl, videoId, title, startHeartbeatTimer, pauseHeartbeatTimer, stopHeartbeatTimer, onAnalyticsEvent, bridge]);\r\n\r\n const handleAdBreakChange = useCallback((active: boolean) => {\r\n isInAdBreakRef.current = active;\r\n if (active) {\r\n pauseHeartbeatTimer();\r\n console.log('[SDK] Ad break started — heartbeat timer paused');\r\n } else {\r\n if (sessionStartedRef.current) startHeartbeatTimer();\r\n console.log('[SDK] Ad break ended — heartbeat timer resumed');\r\n }\r\n }, [pauseHeartbeatTimer, startHeartbeatTimer]);\r\n\r\n const handleKeyMomentsLoaded = useCallback((parsed: Chapter[]) => {\r\n bridge.setKeyMoments(parsed);\r\n setKeyMoments(parsed);\r\n onKeyMomentsLoaded?.(parsed);\r\n }, [onKeyMomentsLoaded, bridge]);\r\n\r\n const handleTimeUpdate = useCallback((currentTime: number) => {\r\n bridge.handleTimeUpdate(currentTime);\r\n onTimeUpdate?.(currentTime);\r\n }, [onTimeUpdate, bridge]);\r\n\r\n if (sdkStatus === 'loading') {\r\n return (\r\n <div style={{\r\n width: '100%', aspectRatio: '16 / 9', background: '#000', borderRadius: 8,\r\n display: 'flex', alignItems: 'center', justifyContent: 'center',\r\n }}>\r\n <style>{`@keyframes boss-sdk-spin{to{transform:rotate(360deg)}}`}</style>\r\n <div style={{\r\n width: 60, height: 60, borderRadius: '50%',\r\n border: `4px solid rgba(${themeRgb}, 0.1)`,\r\n borderLeftColor: themeColor,\r\n borderRightColor: `rgba(${themeRgb}, 0.2)`,\r\n filter: `drop-shadow(0 0 10px rgba(${themeRgb}, 0.5))`,\r\n animation: 'boss-sdk-spin 1s linear infinite',\r\n }} />\r\n </div>\r\n );\r\n }\r\n\r\n if (sdkStatus === 'error') {\r\n const isAuth = errorKind === 'auth';\r\n return (\r\n <div style={{\r\n width: '100%', aspectRatio: '16 / 9', background: '#09090e', borderRadius: 8,\r\n display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 20,\r\n }}>\r\n <ErrorScreen\r\n title={isAuth ? 'Invalid Player Key' : 'Something Went Wrong'}\r\n subtitle={\r\n isAuth\r\n ? 'Your player key is invalid or expired.\\nPlease check your configuration.'\r\n : 'We encountered an error while connecting.\\nPlease try again.'\r\n }\r\n onRetry={() => setRetryCount(c => c + 1)}\r\n />\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <WebVideoPlayer\r\n src={activeSrc}\r\n sources={normalizedSources}\r\n activeSourceIndex={activeSourceIdx}\r\n onSourceChange={(idx: number) => setActiveSourceIdx(idx)}\r\n subtitles={subtitles}\r\n keyMoments={keyMoments}\r\n themeColor={themeColor}\r\n firebaseConfig={firebaseConfig}\r\n resolvedEntitlement={resolvedPackage}\r\n licenseKey={licenseKey}\r\n title={title}\r\n watermark={watermark}\r\n autoplay={autoplay}\r\n muted={muted}\r\n autoFullscreen={autoFullscreen}\r\n enableControls={enableControls}\r\n enableSkipAd={enableSkipAd}\r\n skipAdAfterSeconds={skipAdAfterSeconds}\r\n chaptersDisplayMode={chaptersDisplayMode}\r\n volumeControlType={volumeControlType}\r\n subtitleStyle={subtitleStyle}\r\n drmConfig={drmConfig}\r\n overlayAdUrl={overlayAdUrl}\r\n adTrackingUrl={adTrackingUrl}\r\n videoId={videoId}\r\n urlTransformer={urlTransformer}\r\n onAnalyticsEvent={handleAnalytics}\r\n onAdBreakChange={handleAdBreakChange}\r\n onKeyMomentsLoaded={handleKeyMomentsLoaded}\r\n initialTime={initialTime}\r\n onTimeUpdate={handleTimeUpdate}\r\n />\r\n );\r\n}\r\n","import React, { createElement } from 'react';\r\nimport { createRoot } from 'react-dom/client';\r\nimport { BossVideoPlayer } from './BossVideoPlayer';\r\nimport type { BossVideoPlayerProps } from './BossVideoPlayer';\r\n\r\nexport interface PlayerProps extends BossVideoPlayerProps {}\r\n\r\nexport interface PlayerInstance {\r\n /** Re-render the player with updated props (src change, theme, etc.) */\r\n update(props: Partial<PlayerProps>): void;\r\n /** Unmounts the player and releases all resources */\r\n destroy(): void;\r\n}\r\n\r\n/**\r\n * Mounts the full video player into any DOM element.\r\n * Identical to <BossVideoPlayer> — handles player key validation, feature gating,\r\n * session tracking, and heartbeat automatically.\r\n *\r\n * Use this in Vue, Angular, Vanilla JS, or any environment where JSX is unavailable.\r\n *\r\n * @example Vanilla JS\r\n * const player = createPlayer(document.getElementById('player'), {\r\n * playerKey: 'bsp_abc123',\r\n * src: 'https://my-cdn.com/video.m3u8',\r\n * });\r\n * player.destroy(); // cleanup\r\n *\r\n * @example Vue 3\r\n * const player = createPlayer(containerRef.value, { playerKey: 'bsp_abc123', src: props.src });\r\n * onUnmounted(() => player.destroy());\r\n *\r\n * @example Angular\r\n * const player = createPlayer(this.containerRef.nativeElement, { playerKey: 'bsp_abc123', src: this.src });\r\n * ngOnDestroy() { this.player.destroy(); }\r\n */\r\nexport function createPlayer(container: HTMLElement, props: PlayerProps): PlayerInstance {\r\n const root = createRoot(container);\r\n let currentProps = props;\r\n\r\n root.render(createElement(BossVideoPlayer as React.FC<PlayerProps>, currentProps));\r\n\r\n return {\r\n update(newProps: Partial<PlayerProps>) {\r\n currentProps = { ...currentProps, ...newProps };\r\n root.render(createElement(BossVideoPlayer as React.FC<PlayerProps>, currentProps));\r\n },\r\n destroy() {\r\n root.unmount();\r\n },\r\n };\r\n}\r\n"],"x_google_ignoreList":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],"mappings":"gsBAMA,SAAgB,GAAY,CAC1B,QAAQ,uBACR,WAAW;mBACX,WACmB,CACnB,OACE,EAAA,EAAA,MAAA,EAAA,SAAA,CAAA,SAAA,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,MAAO,CACV,MAAO,GAAI,OAAQ,GAAI,aAAc,MACrC,WAAY,yBACZ,QAAS,OAAQ,WAAY,SAAU,eAAgB,SAAU,WAAY,CAC/E,YACE,EAAA,EAAA,MAAC,MAAD,CAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,OAAO,yBAAyB,YAAY,IAAI,cAAc,QAAQ,eAAe,iBAAjJ,EACE,EAAA,EAAA,KAAC,OAAD,CAAM,EAAE,0FAA2F,CAAA,GACnG,EAAA,EAAA,KAAC,OAAD,CAAM,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAK,CAAA,GAAC,EAAA,EAAA,KAAC,OAAD,CAAM,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAK,CAAA,CAC5E,GACF,CAAA,GACL,EAAA,EAAA,MAAC,MAAD,CAAK,MAAO,CAAE,UAAW,SAAU,QAAS,QAAS,WAArD,EACE,EAAA,EAAA,KAAC,IAAD,CAAG,MAAO,CAAE,MAAO,UAAW,SAAU,GAAI,WAAY,IAAK,OAAQ,UAAW,cAAe,QAAS,WACrG,CACA,CAAA,GACH,EAAA,EAAA,KAAC,IAAD,CAAG,MAAO,CAAE,MAAO,wBAAyB,SAAU,GAAI,OAAQ,EAAG,WAAY,IAAK,WAAY,UAAW,WAC1G,CACA,CAAA,CACA,KACL,EAAA,EAAA,MAAC,SAAD,CACE,QAAS,EACT,MAAO,CACL,QAAS,OAAQ,WAAY,SAAU,IAAK,GAC5C,WAAY,UAAW,MAAO,UAC9B,OAAQ,OAAQ,aAAc,GAAI,QAAS,YAC3C,SAAU,GAAI,WAAY,IAAK,OAAQ,UAAW,cAAe,QACnE,WAPF,EASE,EAAA,EAAA,MAAC,MAAD,CAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OAAO,OAAO,eAAe,YAAY,MAAM,cAAc,QAAQ,eAAe,iBAAzI,EACE,EAAA,EAAA,KAAC,WAAD,CAAU,OAAO,eAAgB,CAAA,GAAC,EAAA,EAAA,KAAC,OAAD,CAAM,EAAE,6BAA8B,CAAA,CACrE,IAAC,WAEA,GACR,CAAA,CAAA,CAEN,CCxCA,IAAM,IAAgB,GAAG,IAAY,EAAQ,QAAQ,EAAW,EAAO,IAC9D,EAAQ,GAAc,EAAU,KAAK,IAAM,IAAM,EAAM,QAAQ,CAAS,IAAM,CACtF,EAAE,KAAK,GAAG,EAAE,KAAK,ECFZ,GAAe,GAAW,EAAO,QAAQ,qBAAsB,OAAO,EAAE,YAAY,ECApF,GAAe,GAAW,EAAO,QACrC,yBACC,EAAO,EAAI,IAAO,EAAK,EAAG,YAAY,EAAI,EAAG,YAAY,CAC5D,ECDM,GAAgB,GAAW,CAC/B,IAAM,EAAY,GAAY,CAAM,EACpC,OAAO,EAAU,OAAO,CAAC,EAAE,YAAY,EAAI,EAAU,MAAM,CAAC,CAC9D,ECLI,GAAoB,CACtB,MAAO,6BACP,MAAO,GACP,OAAQ,GACR,QAAS,YACT,KAAM,OACN,OAAQ,eACR,YAAa,EACb,cAAe,QACf,eAAgB,OAClB,ECVM,GAAe,GAAU,CAC7B,IAAK,IAAM,KAAQ,EACjB,GAAI,EAAK,WAAW,OAAO,GAAK,IAAS,QAAU,IAAS,QAC1D,MAAO,GAGX,MAAO,EACT,ECHM,IAAA,EAAA,EAAA,eAA8B,CAAC,CAAC,EAqBhC,OAAA,EAAA,EAAA,YAAoC,EAAa,ECjBjD,IAAA,EAAA,EAAA,aACH,CAAE,QAAO,OAAM,cAAa,sBAAqB,YAAY,GAAI,WAAU,WAAU,GAAG,GAAQ,IAAQ,CACvG,GAAM,CACJ,KAAM,EAAc,GACpB,YAAa,EAAqB,EAClC,oBAAqB,GAA6B,GAClD,MAAO,GAAe,eACtB,UAAW,GAAe,IACxB,EAAiB,GAAK,CAAC,EACrB,GAAwB,GAAuB,GAA6B,OAAO,GAAe,CAAkB,EAAI,GAAK,OAAO,GAAQ,CAAW,EAAI,GAAe,EAChL,OAAA,EAAA,EAAA,eACE,MACA,CACE,MACA,GAAG,GACH,MAAO,GAAQ,GAAe,GAAkB,MAChD,OAAQ,GAAQ,GAAe,GAAkB,OACjD,OAAQ,GAAS,GACjB,YAAa,GACb,UAAW,GAAa,SAAU,GAAc,CAAS,EACzD,GAAG,CAAC,GAAY,CAAC,GAAY,CAAI,GAAK,CAAE,cAAe,MAAO,EAC9D,GAAG,CACL,EACA,CACE,GAAG,EAAS,KAAK,CAAC,EAAK,MAAA,EAAA,EAAA,eAAyB,EAAK,CAAK,CAAC,EAC3D,GAAG,MAAM,QAAQ,CAAQ,EAAI,EAAW,CAAC,CAAQ,CACnD,CACF,CACF,CACF,EC/BM,GAAoB,EAAU,IAAa,CAC/C,IAAM,GAAA,EAAA,EAAA,aACH,CAAE,YAAW,GAAG,GAAS,KAAA,EAAA,EAAA,eAAsB,GAAM,CACpD,MACA,WACA,UAAW,GACT,UAAU,GAAY,GAAa,CAAQ,CAAC,IAC5C,UAAU,IACV,CACF,EACA,GAAG,CACL,CAAC,CACH,EAEA,MADA,GAAU,YAAc,GAAa,CAAQ,EACtC,CACT,ECTM,GAAU,EAAiB,UAAWA,CAT1C,CACE,OACA,CACE,EAAG,6EACH,IAAK,QACP,CACF,EACA,CAAC,OAAQ,CAAE,EAAG,gBAAiB,IAAK,QAAS,CAAC,CAEJA,CAAU,ECNhD,GAAW,EAAiB,WAAYC,CAH5C,CAAC,OAAQ,CAAE,MAAO,KAAM,OAAQ,KAAM,EAAG,IAAK,EAAG,IAAK,GAAI,IAAK,GAAI,IAAK,IAAK,QAAS,CAAC,EACvF,CAAC,OAAQ,CAAE,EAAG,iCAAkC,IAAK,QAAS,CAAC,CAEnBA,CAAU,ECElD,GAAO,EAAiB,OAAQC,CALpC,CAAC,OAAQ,CAAE,EAAG,4DAA6D,IAAK,QAAS,CAAC,EAC1F,CAAC,OAAQ,CAAE,EAAG,sBAAuB,IAAK,QAAS,CAAC,EACpD,CAAC,OAAQ,CAAE,EAAG,sBAAuB,IAAK,QAAS,CAAC,EACpD,CAAC,OAAQ,CAAE,GAAI,IAAK,GAAI,OAAQ,GAAI,KAAM,GAAI,KAAM,IAAK,QAAS,CAAC,CAE/BA,CAAU,ECL1C,EAAQ,EAAiB,QAASC,CADpB,CAAC,OAAQ,CAAE,EAAG,kBAAmB,IAAK,QAAS,CAAC,CAC5BA,CAAU,ECA5C,GAAc,EAAiB,eAAgBC,CADjC,CAAC,OAAQ,CAAE,EAAG,iBAAkB,IAAK,QAAS,CAAC,CACdA,CAAU,ECIzD,GAAe,EAAiB,gBAAiBC,CAJrD,CAAC,OAAQ,CAAE,EAAG,YAAa,IAAK,QAAS,CAAC,EAC1C,CAAC,OAAQ,CAAE,EAAG,cAAe,IAAK,QAAS,CAAC,EAC5C,CAAC,OAAQ,CAAE,EAAG,2DAA4D,IAAK,QAAS,CAAC,CAEpCA,CAAU,ECD3D,GAAQ,EAAiB,QAASC,CAHtC,CAAC,OAAQ,CAAE,EAAG,aAAc,IAAK,QAAS,CAAC,EAC3C,CAAC,OAAQ,CAAE,EAAG,+BAAgC,IAAK,QAAS,CAAC,CAEvBA,CAAU,ECI5C,GAAY,EAAiB,YAAaC,CAP9C,CAAC,OAAQ,CAAE,EAAG,WAAY,IAAK,QAAS,CAAC,EACzC,CAAC,OAAQ,CAAE,EAAG,gBAAiB,IAAK,QAAS,CAAC,EAC9C,CAAC,OAAQ,CAAE,EAAG,UAAW,IAAK,QAAS,CAAC,EACxC,CAAC,OAAQ,CAAE,EAAG,SAAU,IAAK,QAAS,CAAC,EACvC,CAAC,OAAQ,CAAE,EAAG,mBAAoB,IAAK,QAAS,CAAC,EACjD,CAAC,OAAQ,CAAE,EAAG,WAAY,IAAK,QAAS,CAAC,CAEKA,CAAU,ECIpD,GAAY,EAAiB,aAAcC,CAX/C,CAAC,OAAQ,CAAE,EAAG,UAAW,IAAK,QAAS,CAAC,EACxC,CAAC,OAAQ,CAAE,EAAG,WAAY,IAAK,QAAS,CAAC,EACzC,CAAC,OAAQ,CAAE,EAAG,WAAY,IAAK,QAAS,CAAC,EACzC,CACE,OACA,CACE,EAAG,kGACH,IAAK,QACP,CACF,CAE+CA,CAAU,ECNrD,GAAW,EAAiB,WAAYC,CAL5C,CAAC,OAAQ,CAAE,EAAG,yBAA0B,IAAK,QAAS,CAAC,EACvD,CAAC,OAAQ,CAAE,EAAG,2BAA4B,IAAK,QAAS,CAAC,EACzD,CAAC,OAAQ,CAAE,EAAG,0BAA2B,IAAK,QAAS,CAAC,EACxD,CAAC,OAAQ,CAAE,EAAG,4BAA6B,IAAK,QAAS,CAAC,CAEdA,CAAU,ECAlD,GAAW,EAAiB,WAAYC,CAL5C,CAAC,OAAQ,CAAE,EAAG,yBAA0B,IAAK,QAAS,CAAC,EACvD,CAAC,OAAQ,CAAE,EAAG,2BAA4B,IAAK,QAAS,CAAC,EACzD,CAAC,OAAQ,CAAE,EAAG,0BAA2B,IAAK,QAAS,CAAC,EACxD,CAAC,OAAQ,CAAE,EAAG,4BAA6B,IAAK,QAAS,CAAC,CAEdA,CAAU,ECFlD,EAAQ,EAAiB,QAASC,CAHtC,CAAC,OAAQ,CAAE,EAAG,KAAM,EAAG,IAAK,MAAO,IAAK,OAAQ,KAAM,GAAI,IAAK,IAAK,QAAS,CAAC,EAC9E,CAAC,OAAQ,CAAE,EAAG,IAAK,EAAG,IAAK,MAAO,IAAK,OAAQ,KAAM,GAAI,IAAK,IAAK,QAAS,CAAC,CAEvCA,CAAU,ECG5C,GAAmB,EAAiB,qBAAsBC,CAN9D,CAAC,OAAQ,CAAE,EAAG,YAAa,IAAK,OAAQ,CAAC,EACzC,CAAC,OAAQ,CAAE,EAAG,WAAY,IAAK,QAAS,CAAC,EACzC,CAAC,OAAQ,CAAE,EAAG,4BAA6B,IAAK,QAAS,CAAC,EAC1D,CAAC,OAAQ,CAAE,EAAG,0BAA2B,IAAK,QAAS,CAAC,EACxD,CAAC,OAAQ,CAAE,EAAG,KAAM,EAAG,KAAM,MAAO,KAAM,OAAQ,IAAK,GAAI,IAAK,IAAK,QAAS,CAAC,CAEjBA,CAAU,ECEpE,GAAO,EAAiB,OAAQC,CARpC,CACE,OACA,CACE,EAAG,qFACH,IAAK,QACP,CACF,CAEoCA,CAAU,ECL1C,GAAY,EAAiB,aAAcC,CAH/C,CAAC,OAAQ,CAAE,EAAG,oDAAqD,IAAK,QAAS,CAAC,EAClF,CAAC,OAAQ,CAAE,EAAG,WAAY,IAAK,QAAS,CAAC,CAEMA,CAAU,ECArD,GAAW,EAAiB,YAAaC,CAH7C,CAAC,OAAQ,CAAE,EAAG,oDAAqD,IAAK,QAAS,CAAC,EAClF,CAAC,OAAQ,CAAE,EAAG,aAAc,IAAK,QAAS,CAAC,CAEEA,CAAU,ECMnD,GAAU,EAAiB,WAAYC,CAT3C,CACE,OACA,CACE,EAAG,2KACH,IAAK,QACP,CACF,EACA,CAAC,OAAQ,CAAE,EAAG,sBAAuB,IAAK,QAAS,CAAC,CAETA,CAAU,ECCjD,GAAU,EAAiB,WAAYC,CAV3C,CACE,OACA,CACE,EAAG,2KACH,IAAK,QACP,CACF,EACA,CAAC,OAAQ,CAAE,EAAG,sBAAuB,IAAK,QAAS,CAAC,EACpD,CAAC,OAAQ,CAAE,EAAG,oCAAqC,IAAK,QAAS,CAAC,CAEvBA,CAAU,ECAjD,GAAU,EAAiB,WAAYC,CAV3C,CACE,OACA,CACE,EAAG,2KACH,IAAK,QACP,CACF,EACA,CAAC,OAAQ,CAAE,GAAI,KAAM,GAAI,KAAM,GAAI,IAAK,GAAI,KAAM,IAAK,QAAS,CAAC,EACjE,CAAC,OAAQ,CAAE,GAAI,KAAM,GAAI,KAAM,GAAI,IAAK,GAAI,KAAM,IAAK,QAAS,CAAC,CAEtBA,CAAU,ECFjD,GAAS,EAAiB,SAAU,CARxC,CACE,OACA,CACE,EAAG,2KACH,IAAK,QACP,CACF,CAEwC,CAAU,ECgB9C,GAA6B,IAI/B,GAAkB,GAKhB,GAAa;;;;;;;;;;;;;;;;;;;;;;;;;;EA4Bb,GAA4B,GAAgC,CAChE,IAAM,EAAW,EAAK,MAAM,mBAAmB,EAC/C,GAAI,IAAW,IAAI,KAAK,EAAG,OAAO,EAAS,GAAG,KAAK,EACnD,IAAM,EAAc,EAAK,MAAM,2EAA2E,EAE1G,OADI,IAAc,IAAI,KAAK,EAAU,EAAY,GAAG,KAAK,EAClD,IACT,EAEM,EAAiB,GAAoC,CAEzD,GADI,CAAC,GAAQ,EAAK,OAAS,IACvB,EAAK,KAAO,IAAQ,EAAK,KAAO,IAAQ,EAAK,KAAO,GAAM,OAAO,KACrE,IAAM,EAAU,EAAK,GACf,GAAc,EAAK,GAAK,MAAS,IAAQ,EAAK,GAAK,MAAS,IAAQ,EAAK,GAAK,MAAS,EAAM,EAAK,GAAK,IACvG,EAAM,KAAK,IAAI,GAAK,EAAW,EAAK,MAAM,EAC5C,EAAS,GACb,KAAO,EAAS,IAAM,GAAK,CACzB,IAAM,EAAK,OAAO,aAAa,EAAK,GAAS,EAAK,EAAO,GAAI,EAAK,EAAO,GAAI,EAAK,EAAO,EAAE,EACrF,EAAY,GAAW,GACvB,EAAK,EAAO,GAAK,MAAS,IAAQ,EAAK,EAAO,GAAK,MAAS,IAAQ,EAAK,EAAO,GAAK,MAAS,EAAM,EAAK,EAAO,GAAK,IACtH,EAAK,EAAO,IAAM,GAAO,EAAK,EAAO,IAAM,GAAO,EAAK,EAAO,IAAM,EAAK,EAAK,EAAO,GAC1F,GAAI,GAAa,GAAK,EAAS,GAAK,EAAY,EAAK,MACrD,GAAI,IAAO,OAAQ,CACjB,IAAM,EAAM,EAAK,EAAS,IACpB,EAAM,EAAK,MAAM,EAAS,GAAI,EAAS,GAAK,CAAS,EAIrD,GAHU,IAAQ,GAAK,IAAQ,EACjC,IAAI,YAAY,QAAQ,EAAE,OAAO,CAAG,EACpC,IAAI,YAAY,IAAQ,EAAI,QAAU,QAAQ,EAAE,OAAO,CAAG,GACvC,QAAQ,MAAO,EAAE,EAAE,KAAK,EAC/C,GAAI,EAAQ,OAAO,CACrB,CACA,GAAU,GAAK,CACjB,CACA,OAAO,IACT,EAGM,IAAU,CAAE,OAAO,OACvB,EAAA,EAAA,MAAC,MAAD,CAAK,MAAO,EAAM,OAAQ,EAAM,QAAQ,YAAY,KAAK,OAAO,OAAO,eAAe,YAAY,IAAI,cAAc,QAAQ,eAAe,iBAA3I,EACE,EAAA,EAAA,KAAC,OAAD,CAAM,EAAE,IAAI,EAAE,IAAI,MAAM,KAAK,OAAO,KAAK,GAAG,GAAK,CAAA,GACjD,EAAA,EAAA,KAAC,OAAD,CAAM,EAAE,sBAAwB,CAAA,GAChC,EAAA,EAAA,KAAC,OAAD,CAAM,EAAE,6BAA+B,CAAA,CACpC,IAID,IAAqB,CAAE,OAAO,OAClC,EAAA,EAAA,MAAC,MAAD,CAAK,MAAO,EAAM,OAAQ,EAAM,QAAQ,YAAY,KAAK,OAAO,OAAO,eAAe,YAAY,IAAI,cAAc,QAAQ,eAAe,iBAA3I,EACE,EAAA,EAAA,KAAC,OAAD,CAAM,EAAE,IAAI,EAAE,IAAI,MAAM,KAAK,OAAO,KAAK,GAAG,GAAK,CAAA,GACjD,EAAA,EAAA,KAAC,OAAD,CAAM,EAAE,sBAAwB,CAAA,GAChC,EAAA,EAAA,KAAC,OAAD,CAAM,EAAE,6BAA+B,CAAA,CACpC,IAGD,GAAY,GAAwB,CACxC,IAAM,EAAW,EAAI,QAAQ,IAAK,EAAE,EAYpC,OAXI,EAAS,SAAW,EAIf,GAHG,SAAS,EAAS,UAAU,EAAG,CAAC,EAAG,EAGnC,EAAE,IAFF,SAAS,EAAS,UAAU,EAAG,CAAC,EAAG,EAE7B,EAAE,IADR,SAAS,EAAS,UAAU,EAAG,CAAC,EAAG,EACvB,IACb,EAAS,SAAW,EAItB,GAHG,SAAS,EAAS,GAAK,EAAS,GAAI,EAGpC,EAAE,IAFF,SAAS,EAAS,GAAK,EAAS,GAAI,EAE9B,EAAE,IADR,SAAS,EAAS,GAAK,EAAS,GAAI,EACxB,IAEjB,cACT,EAEM,GAA0C,CAC9C,GAAI,UACJ,IAAK,UACL,GAAI,SACJ,IAAK,SACL,IAAK,SACL,GAAI,UACJ,IAAK,UACL,GAAI,SACJ,IAAK,SACL,IAAK,SACL,GAAI,UACJ,IAAK,UACL,GAAI,aACJ,IAAK,aACL,GAAI,WACJ,GAAI,UACJ,GAAI,SACJ,GAAI,UACJ,GAAI,SACJ,GAAI,QACJ,GAAI,QACJ,IAAK,OACP,EAEM,GAAsB,GAAiC,CAC3D,GAAI,CAAC,EAAO,OAAO,KACnB,IAAM,EAAa,EAAM,KAAK,EAAE,YAAY,EACtC,EAAc,EAAW,MAAM,+EAA+E,EACpH,GAAI,GAAe,GAAgB,EAAY,IAC7C,OAAO,GAAgB,EAAY,IAErC,IAAK,GAAM,CAAC,EAAK,KAAS,OAAO,QAAQ,EAAe,EACtD,GAAI,EAAW,SAAS,CAAG,EACzB,OAAO,EAgBX,MAbI,0BAA0B,KAAK,CAAU,EAAU,UACnD,mBAAmB,KAAK,CAAU,EAAU,SAC5C,mBAAmB,KAAK,CAAU,EAAU,UAC5C,kBAAkB,KAAK,CAAU,EAAU,SAC3C,oBAAoB,KAAK,CAAU,EAAU,UAC7C,wBAAwB,KAAK,CAAU,EAAU,aACjD,oBAAoB,KAAK,CAAU,EAAU,WAC7C,cAAc,KAAK,CAAU,EAAU,UACvC,cAAc,KAAK,CAAU,EAAU,SACvC,eAAe,KAAK,CAAU,EAAU,QACxC,mBAAmB,KAAK,CAAU,EAAU,UAC5C,kBAAkB,KAAK,CAAU,EAAU,SAC3C,gBAAgB,KAAK,CAAU,EAAU,QACtC,IACT,EAEM,EAAoB,GAAmD,CAC3E,GAAI,EAAM,KAAM,CACd,IAAM,EAAa,GAAmB,EAAM,IAAI,EAChD,GAAI,EAAY,OAAO,CACzB,CAEA,GAAI,EAAM,MAAO,CACf,IAAM,EAAQ,EAAM,MAAM,KAAK,EACzB,EAAQ,EAAM,MAAM,kBAAkB,EAAE,OAAO,OAAO,EAC5D,IAAK,IAAI,EAAI,EAAM,OAAS,EAAG,GAAK,EAAG,IAAQ,CAC7C,IAAM,EAAY,GAAmB,EAAM,EAAE,EAC7C,GAAI,EAAW,OAAO,CACxB,CACA,IAAM,EAAU,EAAM,QAAQ,cAAe,EAAE,EAAE,KAAK,EACtD,OAAO,GAAmB,CAAO,GAAK,CACxC,CAEA,MAAO,SACT,EAEM,GAAwB,GAA6C,CACzE,IAAM,EAAO,IAAI,IACjB,OAAO,EAAO,OAAQ,GAAU,CAG9B,IAAM,EAAM,GAFE,EAAiB,CAAK,EAAE,YAEvB,EAAM,IADP,EAAM,MAAQ,IAAI,KAAK,EAAE,YACf,IAKxB,OAJI,EAAK,IAAI,CAAG,EACP,IAET,EAAK,IAAI,CAAG,EACL,GACT,CAAC,CACH,EAuBM,GAAe,GAAsB,CACzC,IAAM,GAAS,GAAK,IAAI,MAAM,GAAG,EAAE,IAAI,MAAM,EAC7C,OAAQ,EAAM,IAAM,GAAK,MAAQ,EAAM,IAAM,GAAK,IAAM,EAAM,IAAM,EACtE,EAEM,GAAY,GAAkD,CAClE,GAAI,CAAC,EAAK,MAAO,GACjB,GAAI,CACF,GAAM,CAAE,YAAa,IAAI,IAAI,EAAK,OAAO,SAAS,IAAI,EACtD,OAAO,IAAa,SAAW,IAAa,QAC9C,MAAQ,CACN,MAAO,EACT,CACF,EAEM,GAA0B,GAAkC,CAChE,GAAI,OAAO,GAAU,SAAU,OAAO,OAAO,SAAS,CAAK,GAAK,GAAS,EAAI,EAAQ,KACrF,GAAI,OAAO,GAAU,SAAU,OAAO,KACtC,IAAM,EAAU,EAAM,KAAK,EAC3B,GAAI,CAAC,EAAS,OAAO,KACrB,GAAI,gCAAgC,KAAK,CAAO,EAAG,OAAO,GAAY,CAAO,EAC7E,IAAM,EAAU,OAAO,CAAO,EAC9B,OAAO,OAAO,SAAS,CAAO,GAAK,GAAW,EAAI,EAAU,IAC9D,EAEM,GAA6B,CAAC,EAAG,IAAM,IAAM,IAAM,IAAM,GAAI,EAEnE,SAAS,GACP,EACA,EACA,EACY,CACZ,IAAI,EAAY,GACZ,EAEE,EAAW,GAAkB,CACjC,EAAQ,eAAiB,CACnB,GACJ,MAAM,CAAG,EACN,KAAK,GAAK,EAAE,KAAK,CAAC,EAClB,KAAK,GAAQ,CACZ,GAAI,EAAW,OACf,IAAM,EAAQ,EAAQ,CAAI,EAC1B,GAAI,IAAU,KAAM,CAClB,EAAW,CAAK,EAChB,MACF,CACI,EAAQ,EAAI,GAA2B,QAAQ,EAAQ,EAAQ,CAAC,CACtE,CAAC,EACA,UAAY,CACP,GACA,EAAQ,EAAI,GAA2B,QAAQ,EAAQ,EAAQ,CAAC,CACtE,CAAC,CACL,EAAG,GAA2B,EAAM,CACtC,EAGA,OADA,EAAQ,CAAC,MACI,CACX,EAAY,GACR,GAAO,aAAa,CAAK,CAC/B,CACF,CA8EA,IAAa,IAAiD,CAC5D,MACA,WAAW,GACX,QAAQ,GACR,WAAW,CAAC,EACZ,aACA,YAAY,CAAC,EACb,YACA,aAAa,UACb,aACA,kBACA,WACA,oBACA,mBACA,sBACA,kBAAiB,GACjB,oBACA,mBACA,mBACA,gBACA,uBACA,aACA,oBAAmB,GACnB,kBAAiB,GACjB,yBAAwB,GACxB,gBAAgB,CAAC,EACjB,uBACA,cACA,SACA,uBAAsB,QACtB,qBAAoB,SACpB,kBACA,sBAAqB,GACrB,gBAAe,GACf,sBAAqB,EACrB,gBACA,iBACA,UACA,qBAAoB,EACpB,kBACA,cACA,mBACI,CACJ,IAAM,GAAe,KAAoB,GAAgB,GACnD,GAAA,EAAA,EAAA,QAAsC,IAAI,EAC1C,IAAA,EAAA,EAAA,QAA+D,IAAI,EACnE,IAAA,EAAA,EAAA,QAAgC,EAAK,EACrC,GAAA,EAAA,EAAA,QAAoC,IAAI,EACxC,IAAA,EAAA,EAAA,QAAyB,CAAC,EAC1B,IAAA,EAAA,EAAA,QAAsC,IAAI,EAC1C,GAAA,EAAA,EAAA,QAAiD,IAAI,EACrD,IAAA,EAAA,EAAA,QAAkC,IAAI,EACtC,IAAA,EAAA,EAAA,QAAuC,IAAI,EAC3C,IAAA,EAAA,EAAA,QAAqB,EAAK,EAC1B,GAAA,EAAA,EAAA,QAAmB,EAAK,EACxB,GAAA,EAAA,EAAA,QAAuC,IAAI,EAC3C,GAAA,EAAA,EAAA,QAA2C,IAAI,EAC/C,GAAA,EAAA,EAAA,QAAyC,IAAI,EAC7C,IAAA,EAAA,EAAA,QAAuC,IAAI,EAC3C,IAAA,EAAA,EAAA,YAA+C,CAAC,CAAC,EACjD,GAAA,EAAA,EAAA,QAA+C,IAAI,EACnD,IAAA,EAAA,EAAA,QAA0B,EAAK,EAI/B,IAAA,EAAA,EAAA,QAAsC,IAAI,EAC1C,GAAkB,GAA6C,CAC/D,CAAC,GAAO,GAAa,UAAY,OACrC,GAAa,QAAU,EAAI,MAC3B,EAAI,MAAQ,GACd,EACM,GAAqB,GAA6C,CAClE,GAAa,UAAY,OACzB,IAAK,EAAI,MAAQ,GAAa,SAClC,GAAa,QAAU,KACzB,EACM,IAAA,EAAA,EAAA,QAAmF,IAAI,EAEvF,IAAA,EAAA,EAAA,QAA4B,EAAK,EACjC,IAAA,EAAA,EAAA,QAA6C,IAAI,EACjD,IAAA,EAAA,EAAA,QAAwF,IAAI,EAC5F,IAAA,EAAA,EAAA,QAA4C,IAAI,EAChD,GAAA,EAAA,EAAA,QAA4E,IAAI,EAGhF,IAAA,EAAA,EAAA,QAA8D,IAAI,EAGlE,IAAA,EAAA,EAAA,QAAoB,CAAO,EAC3B,IAAA,EAAA,EAAA,QAA2B,EAAc,EACzC,IAAA,EAAA,EAAA,QAA+B,EAAK,EAIpC,GAAA,EAAA,EAAA,QAA+C,IAAI,EACnD,GAAA,EAAA,EAAA,QAA4C,IAAI,EAChD,GAAA,EAAA,EAAA,QAA2C,IAAI,EAC/C,IAAA,EAAA,EAAA,QAA2C,IAAI,EAI/C,IAAA,EAAA,EAAA,QAA2B,CAAC,EAI5B,IAAA,EAAA,EAAA,QAAqC,IAAI,EAEzC,CAAC,GAAU,KAAA,EAAA,EAAA,UAAwB,EAAK,EACxC,IAAA,EAAA,EAAA,QAAqB,EAAK,EAC1B,IAAA,EAAA,EAAA,QAAwD,IAAI,EAC5D,CAAC,GAAe,KAAA,EAAA,EAAA,UAAsD,IAAI,GAOhF,EAAA,EAAA,eAAgB,CACd,GAAI,OAAO,OAAW,KAAe,GAAiB,OACtD,IAAM,EAAM,OAAO,0BAA4B,CAC7C,IAAM,EAAK,EAAa,QACxB,GAAI,CAAC,EAAI,OACT,IAAM,EAAK,OAAO,iBAAiB,CAAE,EACX,EAAG,WAAa,UAAY,EAAG,sBAAwB,QAE/E,GAAkB,GAClB,QAAQ,KACN;;sCAGF,EAEJ,CAAC,EACD,UAAa,OAAO,qBAAqB,CAAG,CAC9C,EAAG,CAAC,CAAC,GAEL,EAAA,EAAA,eAAgB,CACd,IAAM,EAAK,EAAa,QACxB,GAAI,CAAC,EAAI,OACT,IAAM,EAAK,IAAI,eAAe,GAAW,CACvC,IAAK,IAAM,KAAS,EAClB,GAAY,EAAM,YAAY,OAAS,GAAG,CAE9C,CAAC,EAGD,OAFA,EAAG,QAAQ,CAAE,EACb,GAAY,EAAG,sBAAsB,EAAE,OAAS,GAAG,MACtC,EAAG,WAAW,CAC7B,EAAG,CAAC,CAAC,EAEL,GAAM,CAAC,EAAQ,KAAA,EAAA,EAAA,UAA6C,IAAI,EAC1D,CAAC,GAAe,KAAA,EAAA,EAAA,UAA6B,EAAK,EAKlD,GAAqB,IAAuB,KAC5C,GAAiB,IAAsB,KACvC,EAAK,IAAoB,UAAY,CAAC,EAGtC,GAAuB,GACzB,GAAQ,EAAG,gBAAkB,CAAC,IAC9B,GAIE,EAAoB,CACxB,gBAAqB,GAAiB,EAAQ,EAAG,iBAAwB,OAAO,IAAuB,UAAY,GAAqB,GACxI,iBAAqB,GAAiB,EAAQ,EAAG,WAAyB,OAAO,IAAuB,UAAY,GAAqB,GACzI,iBAAqB,GAAiB,EAAQ,EAAG,YAAyB,OAAO,GAAuB,UAAY,EAAqB,GACzI,cAAqB,GAAiB,EAAQ,EAAG,oBAAyB,OAAO,GAAuB,UAAY,EAAqB,GACzI,oBAAqB,GAAiB,EAAQ,EAAG,eAAyB,OAAO,IAAwB,UAAY,GAAsB,GAC3I,UAAqB,GAAiB,EAAQ,EAAG,mBAAyB,OAAO,IAAuB,UAAY,GAAqB,GACzI,aAAqB,GAAiB,EAAQ,EAAG,QAAwB,GACzE,mBAAqB,GAAiB,EAAQ,EAAG,eAAwB,GACzE,cAAqB,GAAiB,EAAQ,EAAG,cAAwB,GACzE,eAAqB,GAAiB,EAAQ,EAAG,eAAwB,EAC3E,EACM,CAAC,GAAa,KAAA,EAAA,EAAA,UAA2B,EAAK,EAC9C,CAAC,GAAU,KAAA,EAAA,EAAA,UAAwB,CAAC,EACpC,CAAC,GAAW,KAAA,EAAA,EAAA,UAAyB,EAAK,EAC1C,CAAC,EAAa,KAAA,EAAA,EAAA,UAA2B,CAAC,EAC1C,CAAC,GAAU,KAAA,EAAA,EAAA,UAAwB,CAAC,EACpC,CAAC,GAAQ,KAAA,EAAA,EAAA,UAAsB,CAAC,EAChC,CAAC,GAAS,KAAA,EAAA,EAAA,UAAuB,EAAK,EACtC,CAAC,GAAa,KAAA,EAAA,EAAA,UAA2B,EAAK,EAC9C,CAAC,GAAc,KAAA,EAAA,EAAA,UAA4B,EAAK,EAChD,CAAC,GAAuB,KAAA,EAAA,EAAA,UAAqC,EAAK,EAGlE,CAAC,GAA4B,KAAA,EAAA,EAAA,UAA0C,EAAK,EAC5E,GAAkB,KAAc,IAAgB,KAA0B,GAC1E,CAAC,GAAO,KAAA,EAAA,EAAA,UAAqB,EAAK,EAIlC,CAAC,GAAW,KAAA,EAAA,EAAA,UAAwC,IAAI,EACxD,CAAC,GAAS,KAAA,EAAA,EAAA,UAAuB,EAAK,EACtC,CAAC,EAAU,KAAA,EAAA,EAAA,UAAuE,CAAC,CAAC,EACpF,GAAA,EAAA,EAAA,QAAoE,CAAC,CAAC,EACtE,IAAA,EAAA,EAAA,QAAyC,IAAI,GAAK,EAOlD,GAAa,GAAW,EAAQ,OAAS,EAAI,EAAQ,IAAI,GAAK,EAAE,GAAG,EAAE,KAAK,GAAG,EAAI,EAKjF,CAAC,GAAoB,KAAA,EAAA,EAAA,UAA+C,IAAI,GAAK,EAC7E,CAAC,GAA2B,KAAA,EAAA,EAAA,UAAyC,EAAU,EAS/E,IAAA,EAAA,EAAA,QAA8C,IAAI,EAClD,IAAA,EAAA,EAAA,QAAuC,EAAK,EAQ5C,IAAA,EAAA,EAAA,QAAsF,IAAI,EAC5F,KAA8B,KAChC,GAA6B,EAAU,EACvC,GAAsB,IAAI,GAAK,EAC/B,GAAmB,QAAU,IAAI,IAEjC,GAAsB,QAAU,KAChC,GAA8B,QAAU,IAE1C,IAAM,IAAA,EAAA,EAAA,QAA8C,IAAI,EAClD,IAAA,EAAA,EAAA,QAAoC,EAAK,EAGzC,CAAC,GAAY,KAAA,EAAA,EAAA,UAA2C,CAAC,CAAC,EAC1D,CAAC,GAAe,KAAA,EAAA,EAAA,UAAmD,IAAI,EACvE,IAAA,EAAA,EAAA,QAAwC,CAAC,CAAC,EAC1C,IAAA,EAAA,EAAA,QAAgD,IAAI,EACpD,IAAA,EAAA,EAAA,QAA6C,IAAI,GAAK,EAGtD,CAAC,GAAY,KAAA,EAAA,EAAA,UAAuC,CAAC,CAAC,EACtD,IAAA,EAAA,EAAA,QAAoC,CAAC,CAAC,EAEtC,CAAC,GAAa,KAAA,EAAA,EAAA,UAA0E,CAAC,CAAC,EAM1F,GAAqB,GAAqD,CAC9E,IAAM,EAAM,GAAc,QAAQ,KAAK,GAAK,GAAK,EAAE,kBAAoB,EAAI,EAAE,cAAc,EAC3F,GAAI,EAAK,MAAO,CAAE,MAAO,EAAI,iBAAkB,IAAK,EAAI,cAAe,EACvE,IAAM,EAAK,EAAY,QAAQ,KAAK,GAAK,GAAK,EAAE,WAAa,EAAI,EAAE,OAAO,EAC1E,OAAO,EAAK,CAAE,MAAO,EAAG,UAAW,IAAK,EAAG,OAAQ,EAAI,IACzD,EAGM,IAAA,EAAA,EAAA,aACE,EAAS,QAAQ,EAAK,IAAO,GAAO,EAAG,QAAU,EAAG,WAAY,CAAC,EACvE,CAAC,CAAQ,CACX,EAIM,GAAkB,GAAW,EAAI,KAAK,IAAI,EAAG,GAAW,EAAe,EAAI,EAC3E,EAAkB,EAAS,OAAS,GAAK,GAAkB,EAAI,GAAkB,GAIjF,GAAA,EAAA,EAAA,aAAsC,GAAsB,CAChE,IAAI,EAAK,EACT,IAAK,IAAM,KAAM,EACX,GAAK,EAAG,QACV,GAAO,EAAG,QAAU,EAAG,UACd,GAAK,EAAG,YACjB,GAAO,EAAI,EAAG,WAGlB,OAAO,KAAK,IAAI,EAAG,CAAE,CACvB,EAAG,CAAC,CAAQ,CAAC,EAIP,GAAA,EAAA,EAAA,aAAsC,GAAuB,CACjE,IAAI,EAAK,EACT,IAAK,IAAM,KAAM,EACX,GAAM,EAAG,YACX,GAAO,EAAG,QAAU,EAAG,WAG3B,OAAO,CACT,EAAG,CAAC,CAAQ,CAAC,EAGP,CAAC,GAAW,KAAA,EAAA,EAAA,UAAyC,CAAC,CAAC,EACvD,CAAC,GAAe,KAAA,EAAA,EAAA,UAA2C,CAC/D,GAAI,GACJ,OAAQ,EACR,MAAO,EACP,QAAS,EACT,MAAO,MACT,CAAC,EACK,CAAC,GAAc,KAAA,EAAA,EAAA,UAA4B,EAAE,EAC7C,CAAC,EAAqB,KAAA,EAAA,EAAA,UAAmC,KAAK,EAC9D,CAAC,GAAoB,KAAA,EAAA,EAAA,UAAmD,GAAqB,CAAS,CAAC,EACvG,CAAC,GAAa,KAAA,EAAA,EAAA,UAA+E,CAAC,CAAC,EAC/F,CAAC,GAAkB,KAAA,EAAA,EAAA,UAAwC,CAAC,GAMlE,EAAA,EAAA,eAAgB,CACd,GAAI,EAAQ,CACV,IAAM,EAAe,EAAO,UAAU,UAAU,EAEhD,GAAsB,GAAqB,CAD3B,GAAG,EAAW,GAAG,EAAa,OAAO,GAAK,CAAC,EAAU,KAAK,GAAK,EAAE,KAAO,EAAE,EAAE,CAAC,CAClD,CAAM,CAAC,CACpD,MACE,GAAsB,GAAqB,CAAS,CAAC,CAGzD,EAAG,CAZkB,EAAU,IAAI,GAAK,EAAE,EAAE,EAAE,KAAK,GAY/C,EAAc,CAAM,CAAC,EAEzB,GAAM,CAAC,GAAe,KAAA,EAAA,EAAA,UAA2E,CAC/F,QAAS,KACT,MAAO,EACT,CAAC,EAEK,CAAC,EAAiB,KAAA,EAAA,EAAA,UAA4C,GAAc,GAAY,CAAC,CAAC,EAG1F,CAAC,EAAY,IAAA,EAAA,EAAA,UAA6H,MAAM,EAChJ,CAAC,GAAc,KAAA,EAAA,EAAA,UAA4B,CAAC,EAC5C,CAAC,GAAqB,KAAA,EAAA,EAAA,UAAmC,EAAK,EAC9D,CAAC,GAAmB,KAAA,EAAA,EAAA,UAAyD,CAAC,CAAC,EAG/E,CAAC,GAAc,KAAA,EAAA,EAAA,UAA4B,EAAE,EAC7C,CAAC,GAAsB,KAAA,EAAA,EAAA,UAAmD,IAAI,EAC9E,IAAA,EAAA,EAAA,QAA4B,EAAK,EAEjC,GAAgB,KAAK,UAAU,EAAgB,IAAI,IAAM,CAAE,MAAO,EAAE,MAAO,UAAW,EAAE,SAAU,EAAE,CAAC,GAM3G,EAAA,EAAA,eAAgB,CACd,GAAmB,GAAc,GAAY,CAAC,CAAC,CAEjD,EAAG,EANyB,GAAc,GAAY,CAAC,GAAG,IAAI,GAAK,GAAG,EAAE,UAAU,GAAG,EAAE,OAAO,EAAE,KAAK,GAMjG,CAAkB,CAAC,GAGvB,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,GAAO,CAAC,GAAmB,EAAgB,SAAW,EAAG,OAE9D,GAAqB,CAAC,CAAC,EAEvB,IAAM,EAAY,SAAS,cAAc,OAAO,EAChD,EAAU,MAAQ,GAClB,EAAU,QAAU,WACpB,EAAU,YAAc,YAExB,EAAU,MAAM,QAAU,qGAC1B,SAAS,KAAK,YAAY,CAAS,EAEnC,IAAI,EAAsB,KACpB,EAAQ,EAAI,SAAS,OAAO,EAE5B,EAAa,SAAS,cAAc,QAAQ,EAClD,EAAW,MAAQ,IACnB,EAAW,OAAS,GACpB,IAAM,EAAM,EAAW,WAAW,IAAI,EAElC,EAAe,EACf,EAAY,GAEV,MAAoB,CACxB,GAAI,GAAgB,EAAgB,OAAQ,CAC1C,GAAQ,EACR,MACF,CACA,IAAM,EAAa,EAAgB,GAAc,UAEjD,EAAU,YAAc,IAAe,EAAI,IAAO,CACpD,EAEM,MAAiB,CACrB,GAAI,GAAO,EAAU,YAAc,EAAG,CACpC,EAAI,UAAU,EAAW,EAAG,EAAG,EAAW,MAAO,EAAW,MAAM,EAClE,IAAM,EAAU,EAAW,UAAU,aAAc,EAAG,EACtD,GAAsB,IAAU,CAAE,GAAG,GAAO,GAAe,CAAQ,EAAE,EACrE,IACA,EAAY,CACd,MAEE,WAAW,EAAU,GAAG,CAE5B,EAEM,MAAqB,CACrB,IACJ,EAAY,GACZ,EAAY,EACd,EAEA,EAAU,iBAAiB,SAAU,CAAQ,EAC7C,EAAU,iBAAiB,UAAW,CAAY,EAE9C,GAAS,EAAA,EAAI,YAAY,GAC3B,EAAU,IAAI,EAAA,EAAI,CAChB,aAAc,GACd,gBAAiB,EACjB,UAAW,EAAK,IAAQ,CAGtB,IAAM,EAAQ,IAAW,UACzB,GAAI,CACF,IAAM,EAAa,GAAa,CAAG,EAC7B,EAAS,IAAI,IAAI,EAAY,OAAO,SAAS,MAAM,EACrD,GACF,EAAO,aAAa,IAAI,QAAS,CAAK,EAExC,EAAI,KAAK,MAAO,EAAO,SAAS,EAAG,EAAI,CACzC,OAAS,EAAG,CACV,QAAQ,MAAM,2CAA4C,CAAC,CAC7D,CACF,CACF,CAAC,EACD,EAAQ,WAAW,GAAa,CAAG,CAAC,EACpC,EAAQ,YAAY,CAAS,GAE7B,EAAU,IAAM,EAId,EAAU,YAAc,GAC1B,EAAa,EAGf,IAAM,OAAgB,CACpB,EAAU,oBAAoB,SAAU,CAAQ,EAChD,EAAU,oBAAoB,UAAW,CAAY,EACjD,GACF,EAAQ,QAAQ,EAEd,SAAS,KAAK,SAAS,CAAS,GAClC,SAAS,KAAK,YAAY,CAAS,CAEvC,EAEA,OAAO,EACT,EAAG,CAAC,EAAK,EAAa,CAAC,EAGvB,GAAM,CAAC,EAAY,KAAA,EAAA,EAAA,UAAyC,IAAI,EAC1D,CAAC,GAAa,KAAA,EAAA,EAAA,UAAuI,IAAI,EACzJ,IAAA,EAAA,EAAA,QAAkC,EAAK,EACvC,CAAC,GAAoB,KAAA,EAAA,EAAA,UAAkC,EAAK,EAC5D,CAAC,GAAoB,KAAA,EAAA,EAAA,UAAkC,EAAK,EAC5D,CAAC,GAAmB,KAAA,EAAA,EAAA,UAAgD,IAAI,EAGxE,CAAC,GAAQ,KAAA,EAAA,EAAA,UAAqC,IAAI,EAClD,CAAC,GAAa,KAAA,EAAA,EAAA,UAA2B,CAAC,EAC1C,CAAC,GAAW,KAAA,EAAA,EAAA,UAAiC,CAAC,EAC9C,CAAC,GAAgB,KAAA,EAAA,EAAA,UAA6C,IAAI,EAClE,CAAC,GAAc,KAAA,EAAA,EAAA,UAA8C,IAAI,EACjE,CAAC,EAAe,KAAA,EAAA,EAAA,UAA4C,IAAI,GAGtE,EAAA,EAAA,eAAgB,CACd,GAAI,GAAc,SAAW,CAAC,GAAc,QAAQ,cAAc,sBAAsB,EAAG,CACzF,IAAM,EAAW,SAAS,cAAc,sBAAsB,EAC9D,EAAS,MAAM,OAAS,UACxB,EAAS,MAAM,MAAQ,OACvB,EAAS,MAAM,OAAS,OACxB,EAAS,MAAM,QAAU,QACzB,GAAc,QAAQ,YAAY,CAAQ,CAC5C,CACF,EAAG,CAAC,CAAC,GAEL,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,EAAQ,CACX,GAAsB,EAAK,EAC3B,GAAsB,EAAK,EAC3B,GAAqB,IAAI,EACzB,MACF,CAEA,IAAM,EAAsB,GAAuB,GAAsB,CAAS,EAC5E,MAAyB,CAE7B,IAAM,EADQ,EAAO,gBACJ,EACX,EAAY,EAAQ,GAAU,sCACpC,GAAsB,CAAS,EAC/B,GACE,EACI,EAAS,iCAAmC,iBAC5C,IACN,CACF,EAEA,EAAmB,EAAO,QAAQ,mBAAmB,CAAC,EACtD,EAAiB,EAEjB,IAAM,EAAQ,EAAO,gBAAgB,EAC/B,EAAuB,GAAe,CAC1C,EAAmB,EAAM,eAAiB,WAAW,CACvD,EACM,MAA0B,CAC9B,EAAiB,CACnB,EAOA,MALI,mCAAoC,IACtC,EAAM,iBAAiB,0CAA2C,CAAoC,EACtG,EAAM,iBAAiB,+CAAgD,CAAkC,OAG9F,CACP,mCAAoC,IACtC,EAAM,oBAAoB,0CAA2C,CAAoC,EACzG,EAAM,oBAAoB,+CAAgD,CAAkC,EAEhH,CACF,EAAG,CAAC,CAAM,CAAC,EAGX,GAAM,CAAC,GAAgB,KAAA,EAAA,EAAA,UAA8B,EAAE,EACjD,CAAC,GAAiB,KAAA,EAAA,EAAA,UAA+B,EAAI,EACrD,CAAC,EAAa,KAAA,EAAA,EAAA,UAA2B,EAAK,EAQ9C,CAAC,GAAqB,KAAA,EAAA,EAAA,UAAmC,EAAK,EAC9D,IAAA,EAAA,EAAA,QAAgC,EAAK,EACrC,IAAA,EAAA,EAAA,QAA2C,IAAI,EAE/C,IAAA,EAAA,EAAA,QAAsB,EAAK,EAC3B,IAAA,EAAA,EAAA,QAAwB,EAAK,EAC7B,IAAA,EAAA,EAAA,QAA0C,MAAM,EAChD,IAAA,EAAA,EAAA,QAAgD,CAAS,EAEzD,OAA2B,CAC/B,GAAmB,EAAI,EACnB,GAAmB,SACrB,OAAO,aAAa,GAAmB,OAAO,EAEhD,IAAM,EAAQ,GAAY,QAAU,KAAO,IAC3C,GAAmB,QAAU,OAAO,eAAiB,CAC/C,GAAa,SAAW,GAAc,UAAY,QACpD,GAAmB,EAAK,CAE5B,EAAG,CAAK,CACV,EASM,IAAA,EAAA,EAAA,aAAiC,GAAsB,CAC3D,AAEE,EAA6B,WAD7B,aAAa,EAA6B,OAAO,EACV,MAEzC,IAAM,EAAO,GAAW,QAClB,EAAc,IAAO,IAAY,OAAS,cAC1C,EAAgB,IAAO,IAAI,OAAS,UAC1C,GAAuB,QAAU,KACjC,GAAwB,GAAG,EAAY,+CAA+C,EAAc,EAAE,EACtG,OAAO,eAAiB,GAAwB,IAAI,EAAG,GAAI,EAC3D,GAAkB,UAAU,CAAC,CAC/B,EAAG,CAAC,CAAC,GAEL,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,EAAa,QAAS,OAG3B,IAAM,EAAc,SAAS,cAAc,OAAO,EAClD,EAAY,MAAQ,GACpB,EAAY,QAAU,WACtB,EAAY,YAAc,YAC1B,EAAY,MAAM,QAAU,sDAC5B,SAAS,KAAK,YAAY,CAAW,EACrC,EAAe,QAAU,EAEzB,IAAM,EAAQ,EAAI,SAAS,OAAO,EAClC,GAAI,GAAS,EAAA,EAAI,YAAY,EAAG,CAC9B,IAAM,EAAM,IAAI,EAAA,EAAI,CAClB,aAAc,GACd,gBAAiB,EACjB,UAAW,EAAK,IAAQ,CACtB,IAAM,EAAQ,IAAW,UACzB,GAAI,CACF,IAAM,EAAa,GAAa,CAAG,EAC7B,EAAS,IAAI,IAAI,EAAY,OAAO,SAAS,MAAM,EACrD,GACF,EAAO,aAAa,IAAI,QAAS,CAAK,EAExC,EAAI,KAAK,MAAO,EAAO,SAAS,EAAG,EAAI,CACzC,OAAS,EAAG,CACV,QAAQ,MAAM,6CAA8C,CAAC,CAC/D,CACF,CACF,CAAC,EACD,EAAI,WAAW,GAAa,CAAG,CAAC,EAChC,EAAI,YAAY,CAAW,EAC3B,GAAa,QAAU,EAEvB,EAAI,GAAG,EAAA,EAAI,OAAO,iBAAkB,EAAY,IAAyD,CACvG,GAAI,GAAO,OACX,IAAM,EAAQ,GAAyB,GAAM,gBAAgB,cAAgB,EAAE,EAC3E,GAAO,GAAkB,CAAK,CACpC,CAAC,EAED,EAAI,GAAG,EAAA,EAAI,OAAO,uBAAwB,EAAY,IAAoD,CACpG,SAAS,IACb,IAAK,IAAM,KAAU,GAAM,SAAW,CAAC,EAAG,CACxC,IAAM,EAAQ,EAAc,EAAO,IAAI,EACvC,GAAI,EAAO,CAAE,GAAkB,CAAK,EAAG,KAAO,CAChD,CACF,CAAC,CACH,MAAW,GAAS,EAAY,YAAY,+BAA+B,EACzE,EAAY,IAAM,EAepB,EAAY,iBAAiB,aAVN,CACrB,IAAM,EAAS,GAAU,QACzB,GAAI,CAAC,EAAQ,OACb,IAAM,EAAM,EAAO,WAAW,IAAI,EAC9B,GAAO,EAAY,YAAc,IACnC,EAAI,UAAU,EAAa,EAAG,EAAG,EAAO,MAAO,EAAO,MAAM,EAE5D,GADgB,EAAO,UAAU,aAAc,GAC7B,CAAO,EAE7B,CAC+C,EAG/C,IAAI,EACJ,GAAI,GAAgB,CAClB,IAAM,EAAW,IAAI,EAAA,EAA0B,EAAc,EAC7D,GAAoB,QAAU,EAC9B,EAAmB,EAAS,eAAe,CAC7C,CAGA,GAAI,GACF,OAGF,IAAM,EAAc,IAAI,EAAA,EAAe,CACrC,UAAW,EAAa,QACxB,MACA,WACA,WACA,QACA,WAAY,EACZ,YACA,IAAK,CACH,cAAe,EACf,eAAgB,CAAC,YAAa,YAAa,OAAO,SAAS,QAAQ,EACnE,GAAG,EACL,EACA,kBAAoB,GAAU,CAY5B,GAPI,EAAM,YACJ,GAAsB,UAAY,KACpC,GAAsB,QAAU,EAAM,UAC7B,EAAM,YAAc,GAAsB,UACnD,EAAQ,CAAE,GAAG,EAAO,UAAW,GAAsB,OAAQ,IAG7D,EAAM,YAAc,gBAAiB,CACvC,GAAI,GAA8B,QAAS,OAC3C,GAA8B,QAAU,EAC1C,CACA,IAAmB,CAAK,EACpB,IAAkB,GAAiB,CAAK,CAC9C,EAGA,qBACE,GAAiB,SAAS,MAAQ,GAC9B,GAAiB,QAAQ,SACzB,IAAA,GACN,sBAAuB,GAAwB,IAC/C,aACA,iBACF,CAAC,EAED,EAAY,GAAG,mBAAqB,GAAkC,CACpE,GAAmB,CAAgB,EAC/B,IACF,GAAmB,CAAgB,CAEvC,CAAC,EAED,EAAY,GAAG,iBAAmB,GAA8B,CAC9D,GAAmB,CAAc,EAC7B,IACF,GAAmB,CAAc,CAErC,CAAC,EAGD,EAAY,GAAG,WAAc,CAC3B,GAAa,EAAI,EACjB,GAAW,EAAK,EAChB,GAAyB,EAAY,eAAe,EAAG,EAAK,CAC9D,CAAC,EACD,EAAY,GAAG,YAAe,CAC5B,GAAa,EAAK,EAClB,GAAyB,EAAY,eAAe,EAAG,EAAI,CAC7D,CAAC,EACD,EAAY,GAAG,YAAe,CAC5B,GAAa,EAAK,EAClB,GAAW,EAAI,CACjB,CAAC,EAID,IAAM,MAAwB,CAG5B,GAAiB,QAAU,GAC3B,GAAe,EAAK,EAChB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,KAEnE,GAAe,EAAY,gBAAgB,CAAC,CAC9C,EAEM,OAAwB,CAG5B,0BAA4B,CAC1B,0BAA4B,CAC1B,GAAiB,QAAU,GACvB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,KACnE,GAAkB,EAAY,gBAAgB,CAAC,EAC/C,IAAM,EAAM,EAAY,gBAAgB,EACpC,GAAO,CAAC,EAAI,QAAU,EAAI,WAAa,GAAG,GAAe,EAAI,CACnE,CAAC,CACH,CAAC,CACH,EAIM,IAAiB,EAAiB,EAAY,KAAU,CAC5D,EAAU,QAAU,GAWpB,IAAM,EAAa,EAAY,EAAU,GAAO,GAAqB,SAAW,EAAU,GACrF,IAAW,GAAqB,QAAU,MAC/C,GAAmB,QAAU,EAC7B,EAAe,QAAU,EACzB,EAAmB,QAAU,EAAY,eAAe,EACxD,EAAgB,EAChB,EAAY,KAAK,CAAU,EACvB,EAAiB,SAAS,OAAO,aAAa,EAAiB,OAAO,EAC1E,EAAiB,QAAU,OAAO,eAAiB,CACjD,EAAU,QAAU,GACpB,EAAe,QAAU,KACzB,EAAmB,QAAU,KAC7B,EAAiB,QAAU,KAC3B,GAAmB,QAAU,GAC7B,GAAgB,CAClB,EAAG,GAAI,CACT,EAEA,GAAiB,QAAU,GAO3B,IAAM,IAA4B,EAAc,IAAsB,CACpE,GAAI,EAAE,iBAAkB,YAAc,CAAC,UAAU,aAAa,iBAAkB,OAChF,UAAU,aAAa,cAAgB,EAAW,SAAW,UAC7D,IAAM,EAAc,EAAY,YAAY,EACtC,EAAgB,EAAY,QAAQ,KAAK,GAAM,GAAQ,EAAG,WAAa,EAAO,EAAG,OAAO,EAC9F,GAAI,EAAe,CACjB,IAAM,EAAQ,EAAc,QAAU,EAAc,UAC9C,EAAQ,KAAK,IAAI,EAAG,EAAO,EAAc,SAAS,EACxD,GAAI,EAAQ,EACV,GAAI,CACF,UAAU,aAAa,iBAAiB,CACtC,SAAU,EACV,aAAc,EACd,SAAU,KAAK,IAAI,EAAO,CAAK,CACjC,CAAC,CACH,MAAY,CAA4B,CAE5C,KAAO,CACL,IAAM,EAAa,EAAY,QAAQ,QAAQ,EAAG,IAAO,GAAK,EAAG,QAAU,EAAG,WAAY,CAAC,EACrF,EAAa,EAAc,EAAI,KAAK,IAAI,EAAG,EAAc,CAAU,EAAI,EACzE,EAAK,EACT,IAAK,IAAM,KAAM,EAAY,QACvB,GAAQ,EAAG,QAAS,GAAO,EAAG,QAAU,EAAG,UACtC,GAAQ,EAAG,YAAW,GAAO,EAAO,EAAG,WAElD,EAAK,KAAK,IAAI,EAAG,CAAE,EACnB,IAAM,EAAU,EAAY,QAAQ,OAAS,GAAK,EAAa,EAAI,EAAa,EAChF,GAAI,EAAU,EACZ,GAAI,CACF,UAAU,aAAa,iBAAiB,CACtC,SAAU,EACV,aAAc,EACd,SAAU,KAAK,IAAI,EAAI,CAAO,CAChC,CAAC,CACH,MAAY,CAA4B,CAE5C,CACF,EA+YA,GA7YA,EAAY,GAAG,aAAe,GAAiB,CAC7C,GAAI,CAAC,EAAU,SAAW,EAAoB,UAAY,KAAM,CAC9D,IAAM,EAAe,EAAY,QAAQ,KACvC,GAAK,GAAmB,QAAQ,IAAI,EAAE,SAAS,GAC1C,GAAQ,EAAE,UAAY,IACtB,EAAO,EAAE,OAChB,EACA,GAAI,EAAc,CAChB,GAAc,EAAa,OAAO,EAClC,MACF,CAEI,GAAqB,UAAY,MACf,EAAY,QAAQ,KACtC,GAAK,GAAQ,EAAE,QAAU,IAAO,EAAO,EAAE,OAEvC,GAAa,EAAgB,CAErC,CAEA,GAAI,EAAU,SAAW,EAAe,UAAY,KAAM,CACxD,IAAM,EAAS,EAAe,QAK9B,GAJuB,EAAmB,UAAY,MAAQ,EAAS,EAAmB,QAErF,GAAQ,EAAS,GAAO,GAAQ,EAAS,EAC1C,GAAQ,EAAS,EACT,CACV,EAAU,QAAU,GACpB,EAAe,QAAU,KACzB,EAAmB,QAAU,KAC7B,AAEE,EAAiB,WADjB,OAAO,aAAa,EAAiB,OAAO,EACjB,MAE7B,IAAM,EAAa,EAAY,QAAQ,KACrC,GAAK,GAAQ,EAAE,WAAa,EAAO,EAAE,OACvC,EAKM,EAAmB,GAAmB,QAC5C,GAAmB,QAAU,IACzB,GAAqB,CAAC,GAAc,GAAqB,UAAY,OACvE,GAAgB,CAEpB,MAEE,MAEJ,CAEA,GAAe,CAAI,EAGnB,GAAkB,QAAU,EAC5B,IAAM,EAAM,EAAY,YAAY,EAOpC,GANA,GAAY,CAAG,EAMX,EAAoB,UAAY,MAAQ,GAAQ,EAAoB,QAAU,EAAK,CACrF,EAAoB,QAAU,KAC9B,AAEE,EAAmB,WADnB,OAAO,aAAa,EAAmB,OAAO,EACjB,MAM/B,IAAM,EAAqB,EAAY,QAAQ,KAC7C,GAAK,GAAmB,QAAQ,IAAI,EAAE,SAAS,GAAK,GAAQ,EAAE,WAAa,EAAO,EAAE,OACtF,EACI,GAAoB,GAAc,EAAmB,OAAO,EAEhE,0BAA4B,CAC1B,0BAA4B,CACtB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,IACrE,CAAC,CACH,CAAC,CACH,CASA,GAAI,CAAC,GAAsB,SAAW,GAAoB,UAAY,MAAQ,GAAe,EAAc,GAAK,EAAM,GAAK,EAAc,EAAK,CAC5I,GAAsB,QAAU,GAChC,IAAM,EAAe,EAAY,QAAQ,KACvC,GAAK,GAAe,EAAE,WAAa,EAAc,EAAE,OACrD,EACM,EAAe,EAAe,EAAa,UAAY,EAC7D,GAAmB,QAAU,EAAe,KAAO,EACnD,EAAoB,QAAU,EAC9B,EAAY,KAAK,CAAY,EAGzB,EAAmB,SAAS,OAAO,aAAa,EAAmB,OAAO,EAC9E,EAAmB,QAAU,OAAO,eAAiB,CACnD,EAAoB,QAAU,KAC9B,EAAmB,QAAU,KACzB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,IACrE,EAAG,GAAI,CACT,CAKA,GAHA,KAAe,CAAI,EAGf,GAAc,QAAQ,OAAS,EACjC,GAAI,GAAiB,QAAS,CAC5B,IAAM,EAAI,GAAiB,SAEvB,GADY,EAAE,iBAAmB,EAAE,iBAChB,EAAO,EAAE,oBAC9B,GAAiB,IAAI,EACrB,GAAiB,QAAU,KAE/B,KAAO,CACL,IAAM,EAAM,GAAc,QAAQ,KAChC,GAAK,GAAQ,EAAE,kBACV,EAAO,EAAE,iBAAmB,EAAE,iBAC9B,CAAC,GAAuB,QAAQ,IAAI,EAAE,OAAO,CACpD,EACI,IACF,GAAiB,CAAG,EACpB,GAAiB,QAAU,EAC3B,EAAI,kBAAkB,QAAQ,GAAO,CAAE,MAAM,EAAK,CAAE,KAAM,SAAU,CAAC,EAAE,UAAY,CAAC,CAAC,CAAG,CAAC,EAE7F,CAIF,IAAM,EAAW,EAAY,WAAW,iBAAiB,CAAI,EAC7D,GAAiB,CAAE,QAAS,EAAS,UAAW,MAAO,EAAS,KAAM,CAAC,EASvE,GAAyB,EAAM,EAAY,gBAAgB,EAAE,MAAM,CACrE,CAAC,EAED,EAAY,GAAG,gBAAkB,GAA2B,CAC1D,GAAa,CAAM,CACrB,CAAC,EAED,EAAY,GAAG,gBAAkB,GAA0B,CACzD,GAAiB,CAAO,CAC1B,CAAC,EAED,EAAY,GAAG,YAAc,GAAuB,CAIlD,GAHA,GAAe,GAAa,CAAC,GAAiB,OAAO,EAGjD,CAAC,IAEH,AAEE,EAA6B,WAD7B,aAAa,EAA6B,OAAO,EACV,MAEzC,GAAuB,QAAU,KAK7B,GAAoB,UAAY,MAAM,CACxC,IAAM,EAAa,GAAoB,QACvC,GAAoB,QAAU,KAK9B,GAAsB,QAAU,GAChC,EAAoB,QAAU,EAC1B,EAAmB,SAAS,OAAO,aAAa,EAAmB,OAAO,EAC9E,EAAmB,QAAU,OAAO,eAAiB,CACnD,EAAoB,QAAU,KAC9B,EAAmB,QAAU,KACzB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,IACrE,EAAG,GAAI,EACP,EAAY,KAAK,CAAU,CAC7B,CAEJ,CAAC,EAED,EAAY,GAAG,gBAAkB,GAAoB,CAGnD,GAFA,EAAY,UAAU,WAAW,EAAQ,EAAY,gBAAgB,CAAC,EAClE,IAAiB,GAAgB,CAAM,EACvC,EAAQ,CACV,IAAM,EAAW,EAAY,eAAe,EACtC,EAAe,EAAY,QAAQ,KAAK,GAAK,GAAY,EAAE,WAAa,EAAW,EAAE,OAAO,EAMlG,GAFA,GAAuB,QAAU,GACjC,GAAuB,EAAK,EACxB,GAAgB,GAAmB,QAAQ,IAAI,EAAa,SAAS,EAAG,CAI1E,EAAgB,EACX,EAAU,SACb,GAAc,EAAa,OAAO,EAEpC,MACF,CACI,IACF,GAAsB,QAAU,EAAa,WAE/C,GAAe,EAAI,EACnB,EAAc,MAAM,EAIpB,GAAgB,CAClB,MAWE,GATI,GAAsB,UAAY,OACpC,GAAmB,QAAQ,IAAI,GAAsB,OAAO,EAC5D,GAAsB,IAAI,IAAI,GAAmB,OAAO,CAAC,EACzD,GAAsB,QAAU,MAElC,GAAe,EAAK,EAIhB,GAAqB,UAAY,KAAM,CACzC,IAAM,EAAS,GAAqB,QACpC,GAAqB,QAAU,KAC/B,EAAU,QAAU,GACpB,EAAe,QAAU,EACzB,EAAmB,QAAU,EAAY,eAAe,EACxD,EAAgB,EAChB,EAAY,KAAK,CAAM,EACnB,EAAiB,SAAS,OAAO,aAAa,EAAiB,OAAO,EAC1E,EAAiB,QAAU,OAAO,eAAiB,CACjD,EAAU,QAAU,GACpB,EAAe,QAAU,KACzB,EAAmB,QAAU,KAC7B,EAAiB,QAAU,KAC3B,GAAgB,CAClB,EAAG,GAAI,CACT,CAEJ,CAAC,EAED,EAAY,GAAG,iBAAmB,GAA0D,CAG1F,EAAY,QAAU,EACtB,GAAY,CAAM,EAElB,IAAM,EAAgB,GAAmB,QACzC,GAAI,IAAkB,KAAM,OAC5B,GAAmB,QAAU,KAE7B,IAAM,EAAe,EAAO,KAC1B,GAAK,GAAiB,EAAE,WAAa,EAAgB,EAAE,OACzD,EACA,GAAI,CAAC,GAAgB,GAAmB,QAAQ,IAAI,EAAa,SAAS,EAAG,OAE7E,IAAM,EAAM,EAAY,eAAe,EACnC,EAAM,EAAa,WAAa,GAAO,EAAa,UAEpD,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,KACnE,EAAoB,QAAU,EAAa,UACvC,EAAmB,SAAS,OAAO,aAAa,EAAmB,OAAO,EAC9E,EAAmB,QAAU,OAAO,eAAiB,CACnD,EAAoB,QAAU,KAC9B,EAAmB,QAAU,KACzB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,IACrE,EAAG,GAAI,EACP,EAAY,KAAK,EAAa,SAAS,EACzC,CAAC,EAED,EAAY,GAAG,eAAiB,GAA6C,CAC3E,GAAU,EAAK,MAAM,EAGjB,GAAa,UAAY,MAAM,GAAW,EAAK,KAAK,CAC1D,CAAC,EAED,EAAY,GAAG,YAAc,GAAoB,CAC/C,GAAS,CAAM,CACjB,CAAC,EAED,EAAY,GAAG,YAAe,CAC5B,GAAe,EAAI,EACnB,GAAe,EAAK,EAKpB,EAAoB,QAAU,KAC9B,AAEE,EAAmB,WADnB,OAAO,aAAa,EAAmB,OAAO,EACjB,MAE3B,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,IACrE,CAAC,EAMD,EAAY,GAAG,uBAA0B,CACvC,IAAM,EAAU,GAAuB,QACnC,GAAW,EAAQ,YAAc,IAAM,GAAW,SAAS,QAAU,GAAK,GAC5E,GAAkB,EAAQ,SAAS,CAEvC,CAAC,EAGD,EAAY,UAAU,iBAAkB,GAAiB,CACvD,GAAgB,CAAI,CACtB,CAAC,EAED,EAAY,GAAG,kBAAoB,GAA4B,CAE7D,GAAI,CAAC,EAAkB,gBAAiB,OAExC,GAAsB,GAAqB,CAAC,GAAG,CAAM,CAAC,CAAC,EAGvD,IAAM,EAAe,EAAO,KAAM,GAAM,EAAE,SAAS,EAC/C,IACF,EAAY,UAAU,eAAe,EAAa,EAAE,EACpD,GAAuB,EAAa,EAAE,EAE1C,CAAC,EAGD,EAAY,GAAG,oBAAsB,GAA+D,CAClG,GAAe,CAAM,EACrB,GAAoB,EAAO,IAAI,IAAM,CAAC,CACxC,CAAC,EAED,EAAY,GAAG,mBAAqB,GAAe,CACjD,GAAoB,CAAE,CACxB,CAAC,EAGD,EAAY,QAAQ,mBAAmB,EAAoB,IAAuB,CAChF,GAAI,EAAW,CACb,GAAgB,QAAU,EAAY,QAAQ,EAC9C,EAAY,MAAM,EAElB,IAAM,EAAM,EAAY,gBAAgB,EACxC,EAAI,MAAQ,GACZ,GAAc,CAAU,CAC1B,KAAO,CAKL,IAAM,EAAa,EAAY,QAAQ,eAAe,EAClD,GAAc,EAAW,YAAc,IACzC,EAAU,QAAU,GACpB,EAAe,QAAU,EAAW,YACpC,EAAmB,QAAU,EAAY,eAAe,EACxD,EAAY,KAAK,EAAW,WAAW,EACnC,EAAiB,SAAS,OAAO,aAAa,EAAiB,OAAO,EAC1E,EAAiB,QAAU,OAAO,eAAiB,CACjD,EAAU,QAAU,GACpB,EAAe,QAAU,KACzB,EAAmB,QAAU,KAC7B,EAAiB,QAAU,IAC7B,EAAG,GAAI,GAET,IAAM,EAAM,EAAY,gBAAgB,EACxC,EAAI,MAAQ,GAAgB,QAC5B,GAAc,IAAI,EAClB,GAAe,IAAI,CACrB,CACF,CAAC,EAED,EAAY,QAAQ,oBAAqB,GAAU,CACjD,GAAe,CAAK,CACtB,CAAC,EAGD,GAAU,EAAY,UAAU,CAAC,EACjC,GAAW,EAAY,QAAQ,CAAC,EAO5B,iBAAkB,UACpB,GAAI,CACF,UAAU,aAAa,iBAAiB,SAAW,GAAY,CAC7D,GAAI,EAAQ,UAAY,MAAQ,GAAe,SAAW,GAAuB,QAAS,OAC1F,IAAI,EAAO,EAAQ,SACnB,IAAK,IAAM,KAAM,EAAY,QACvB,GAAQ,EAAG,YAAW,GAAS,EAAG,QAAU,EAAG,WAErD,GAAe,UAAU,CAAI,CAC/B,CAAC,EAYD,IAAM,GAAW,EAAkB,IAAuB,CACxD,GAAI,GAAe,SAAW,GAAuB,QAAS,OAC9D,IAAM,EAAS,GAAa,EAAY,EAAI,EAAY,GAClD,EAAO,EAAY,eAAe,EACpC,EAAK,EACT,IAAK,IAAM,KAAM,EAAY,QACvB,GAAQ,EAAG,QAAS,GAAO,EAAG,QAAU,EAAG,UACtC,GAAQ,EAAG,YAAW,GAAO,EAAO,EAAG,WAElD,GAAI,EAAS,CACX,IAAM,EAAa,EAAY,QAAQ,QAAQ,EAAG,IAAO,GAAK,EAAG,QAAU,EAAG,WAAY,CAAC,EACrF,EAAa,KAAK,IAAI,EAAG,EAAY,YAAY,EAAI,CAAU,EAEjE,EADU,KAAK,IAAI,EAAY,EAAK,CAC3B,EACb,IAAK,IAAM,KAAM,EAAY,QACvB,GAAU,EAAG,YAAW,GAAW,EAAG,QAAU,EAAG,WAEzD,GAAe,UAAU,EAAQ,CAAE,UAAW,eAAgB,YAAa,CAAO,CAAC,CACrF,KAAO,CAEL,IAAI,EADU,KAAK,IAAI,EAAG,EAAK,CAClB,EACb,IAAK,IAAM,KAAM,EAAY,QACvB,GAAU,EAAG,YAAW,GAAW,EAAG,QAAU,EAAG,WAEzD,GAAe,UAAU,EAAQ,CAAE,UAAW,gBAAiB,YAAa,CAAO,CAAC,CACtF,CACF,EACA,UAAU,aAAa,iBAAiB,cAAgB,GAAY,EAAQ,GAAM,EAAQ,YAAc,IAAA,EAAS,CAAC,EAClH,UAAU,aAAa,iBAAiB,eAAiB,GAAY,EAAQ,GAAO,EAAQ,YAAc,IAAA,EAAS,CAAC,CACtH,MAAY,CAA4B,CAiB1C,IAAM,EAAiB,EAAY,gBAAgB,EAC7C,OAAwB,CAS5B,GADI,EAFF,SAAS,0BAA4B,GACpC,EAAuB,yBAA2B,uBAGnD,EAAU,SACV,EAAoB,UAAY,MAChC,GAAoB,UAAY,KAChC,OACF,IAAM,EAAS,EAAe,YACxB,EAAO,GAAkB,QAC3B,UAAK,IAAI,EAAS,CAAI,EAAI,IAC9B,IAAI,GAAe,QAAS,CAC1B,EAAe,YAAc,EAC7B,MACF,CAGA,EAAe,YAAc,EAC7B,GAAe,UAAU,CAAM,CAJ/B,CAKF,EACA,EAAe,iBAAiB,UAAW,EAAe,EAE1D,GAAU,CAAW,EAWrB,IAAM,EAAgB,GAAuB,QACzC,GAAiB,EAAc,YAAc,IAC3C,EAA6B,SAC/B,aAAa,EAA6B,OAAO,EAEnD,EAA6B,QAAU,eAAiB,CACtD,EAA6B,QAAU,KACtB,GAAY,kBACL,EAAS,KAAM,GAAM,EAAE,IAAM,EAAE,KAEnD,GACJ,GAAkB,EAAc,SAAS,CAC3C,EAAG,EAA0B,GAS3B,GAAoB,UAAY,OAC9B,EAAmB,SAAS,OAAO,aAAa,EAAmB,OAAO,EAC9E,EAAmB,QAAU,OAAO,eAAiB,CACnD,EAAmB,QAAU,KAC7B,EAAoB,QAAU,KAC1B,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,KAGnE,GAAe,EAAK,CACtB,EAAG,IAAiC,GAKlC,IAAkB,EAAa,UACjC,GAAuB,QAAU,GACjC,EAAa,QAAQ,kBAAkB,EAAE,UAAY,CAEnD,GAAuB,QAAU,EACnC,CAAC,GAIH,IAAM,OAA+B,CACnC,IAAM,EAAS,SACT,EAAO,CAAC,EAAE,SAAS,mBAAqB,EAAO,yBACrD,GAAgB,CAAI,EAEf,GAAM,GAAyB,EAAK,EACzC,IAAM,EAAU,EAAY,gBAAgB,EAC5C,EAAY,UAAU,MACpB,EAAO,mBAAqB,kBAC5B,CACF,CACF,EACA,SAAS,iBAAiB,mBAAoB,EAAsB,EACpE,SAAS,iBAAiB,yBAA0B,EAAsB,EAG1E,IAAM,GAAiB,GAAqB,CAC1C,IAAM,EAAW,SAAS,cACtB,SACF,EAAS,UAAY,SACrB,EAAS,UAAY,YACrB,EAAS,aAAa,iBAAiB,IAAM,QAC7C,EAAS,aAAa,MAAM,IAAM,WAKpC,QAAQ,EAAE,IAAI,YAAY,EAA1B,CACE,IAAK,IACL,IAAK,IAEH,GADA,EAAE,eAAe,EACb,EAAY,gBAAgB,EAAE,OAChC,EAAY,KAAK,MACZ,CACL,EAAY,MAAM,EAClB,GAAI,CACF,IAAM,EAAI,EAAY,gBAAgB,EAClC,GAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,CAC9B,MAAY,CAAC,CACb,GAAI,EAAe,SAAW,CAAC,EAAe,QAAQ,OACpD,GAAI,CAAE,EAAe,QAAQ,MAAM,CAAG,MAAY,CAAC,CAEvD,CACA,MACF,IAAK,YACL,IAAK,IAEH,GADA,EAAE,eAAe,EACb,CAAC,GAAe,QAAS,CAC3B,IAAM,EAAO,EAAY,eAAe,EACpC,EAAK,EACT,IAAK,IAAM,KAAM,EAAY,QACvB,GAAQ,EAAG,QAAS,GAAO,EAAG,QAAU,EAAG,UACtC,GAAQ,EAAG,YAAW,GAAO,EAAO,EAAG,WAGlD,IAAI,EADU,KAAK,IAAI,EAAG,EAAK,EAClB,EACb,IAAK,IAAM,KAAM,EAAY,QACvB,GAAU,EAAG,YAAW,GAAW,EAAG,QAAU,EAAG,WAKzD,GAAe,UAAU,EAAQ,CAAE,UAAW,gBAAiB,YAAa,EAAG,CAAC,CAClF,CACA,MAEF,IAAK,aACL,IAAK,IAEH,GADA,EAAE,eAAe,EACb,CAAC,GAAe,QAAS,CAC3B,IAAM,EAAO,EAAY,eAAe,EAClC,EAAa,EAAY,QAAQ,QAAQ,EAAG,IAAO,GAAK,EAAG,QAAU,EAAG,WAAY,CAAC,EACrF,EAAa,KAAK,IAAI,EAAG,EAAY,YAAY,EAAI,CAAU,EACjE,EAAK,EACT,IAAK,IAAM,KAAM,EAAY,QACvB,GAAQ,EAAG,QAAS,GAAO,EAAG,QAAU,EAAG,UACtC,GAAQ,EAAG,YAAW,GAAO,EAAO,EAAG,WAGlD,IAAI,EADU,KAAK,IAAI,EAAY,EAAK,EAC3B,EACb,IAAK,IAAM,KAAM,EAAY,QACvB,GAAU,EAAG,YAAW,GAAW,EAAG,QAAU,EAAG,WAIzD,GAAe,UAAU,EAAQ,CAAE,UAAW,eAAgB,YAAa,EAAG,CAAC,CACjF,CACA,MAEF,IAAK,UACH,EAAE,eAAe,EACjB,EAAY,UAAU,EAAY,UAAU,EAAI,EAAG,EACnD,MACF,IAAK,YACH,EAAE,eAAe,EACjB,EAAY,UAAU,EAAY,UAAU,EAAI,EAAG,EACnD,MACF,IAAK,IACH,EAAE,eAAe,EACjB,EAAY,QAAQ,CAAC,EAAY,QAAQ,CAAC,EAC1C,MACF,IAAK,IACH,EAAE,eAAe,EACjB,GAAoB,QAAQ,EAC5B,MACF,IAAK,IACH,EAAE,eAAe,EACZ,GAAe,SAAS,EAAY,uBAAuB,EAChE,MACF,IAAK,IAEH,GADA,EAAE,eAAe,EACb,EAAkB,iBAAmB,CAAC,GAAe,QAEvD,GADsB,EAAY,UAAU,iBACxC,IAAkB,MACpB,EAAY,UAAU,eAAe,KAAK,EAC1C,GAAuB,KAAK,MACvB,CACL,IAAM,EAAS,GAAsB,QACjC,GAAU,EAAO,OAAS,IAC5B,EAAY,UAAU,eAAe,EAAO,GAAG,EAAE,EACjD,GAAuB,EAAO,GAAG,EAAE,EAEvC,CAEF,MACF,QACE,MACJ,CACA,GAAmB,CADnB,CAEF,EAOA,OALA,SAAS,iBAAiB,UAAW,EAAa,EAGlD,GAAmB,MAEN,CAUX,GANA,GAAiB,QAAU,CAAE,IAAK,GAAY,SAAU,EAAY,UAAU,mBAAmB,CAAE,EAM/F,GAAa,QAAS,CACxB,IAAM,EAAS,GAAa,QACtB,EAAU,EAAY,gBAAgB,EACxC,EAAa,SAAW,EAAQ,gBAAkB,WACpD,EAAQ,UAAU,OAAO,cAAc,EACvC,EAAa,QAAQ,aAAa,EAAS,EAAa,QAAQ,UAAU,GAE5E,GAAa,QAAU,KACvB,GAAa,IAAI,EACjB,GAAS,EAAK,EACd,EAAO,MAAM,CACf,CA0BA,GAzBA,EAAY,QAAQ,EACpB,GAAoB,SAAS,QAAQ,EACrC,GAAoB,QAAU,KAE9B,SAAS,KAAK,MAAM,SAAW,GAC/B,SAAS,KAAK,MAAM,YAAc,GAClC,SAAS,oBAAoB,mBAAoB,EAAsB,EACvE,SAAS,oBAAoB,yBAA0B,EAAsB,EAC7E,SAAS,oBAAoB,UAAW,EAAa,EACjD,GAAmB,SACrB,OAAO,aAAa,GAAmB,OAAO,EAIhD,AAEE,EAAiB,WADjB,OAAO,aAAa,EAAiB,OAAO,EACjB,MAE7B,AAEE,EAA6B,WAD7B,aAAa,EAA6B,OAAO,EACV,MAEzC,GAAiB,QAAU,GACvB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,KACnE,EAAe,oBAAoB,UAAW,EAAe,EACzD,iBAAkB,UAAW,CAC/B,GAAI,CAAE,UAAU,aAAa,iBAAiB,SAAU,IAAI,CAAG,MAAY,CAAC,CAC5E,GAAI,CAAE,UAAU,aAAa,iBAAiB,cAAe,IAAI,CAAG,MAAY,CAAC,CACjF,GAAI,CAAE,UAAU,aAAa,iBAAiB,eAAgB,IAAI,CAAG,MAAY,CAAC,CACpF,CACA,AAEE,GAAa,WADb,GAAa,QAAQ,QAAQ,EACN,MAEzB,AAEE,EAAe,WADf,SAAS,KAAK,YAAY,EAAe,OAAO,EACvB,MAE3B,GAAsB,QAAU,GAChC,EAAoB,QAAU,KAC9B,AAEE,EAAmB,WADnB,OAAO,aAAa,EAAmB,OAAO,EACjB,KAEjC,CACF,EAAG,CAAC,EAAK,EAAW,EAAQ,CAAC,GAG7B,EAAA,EAAA,eAAgB,CACd,GAAI,EAAQ,CACV,EAAO,WAAW,cAAc,CAAe,EAE/C,IAAM,EAAQ,EAAa,SAAS,cAAc,OAAO,EACzD,GAAI,EAAO,CACT,IAAM,EAAW,EAAO,WAAW,iBAAiB,EAAM,WAAW,EACrE,GAAiB,CAAE,QAAS,EAAS,UAAW,MAAO,EAAS,KAAM,CAAC,CACzE,CACF,CACF,EAAG,CAAC,GAAe,CAAM,CAAC,GAG1B,EAAA,EAAA,eAAgB,CACd,IAAM,EAAuB,GAAkB,CACzC,IAAe,SAEJ,EAAE,OACN,QAAQ,qBAAqB,GAIxC,EAAc,MAAM,EACtB,EAGA,OADA,SAAS,iBAAiB,QAAS,CAAmB,MACzC,CACX,SAAS,oBAAoB,QAAS,CAAmB,CAC3D,CACF,EAAG,CAAC,CAAU,CAAC,GAGf,EAAA,EAAA,eAAgB,CAAE,GAAa,QAAU,EAAW,EAAG,CAAC,EAAS,CAAC,GAClE,EAAA,EAAA,eAAgB,CAAE,GAAe,QAAU,CAAa,EAAG,CAAC,CAAW,CAAC,GACxE,EAAA,EAAA,eAAgB,CAAE,GAAuB,QAAU,EAAqB,EAAG,CAAC,EAAmB,CAAC,EAIhG,IAAM,EAAa,GAAe,IAClC,EAAA,EAAA,eAAgB,CAAE,GAAa,QAAU,EAAW,EAAG,CAAC,EAAS,CAAC,GAClE,EAAA,EAAA,eAAgB,CAAE,GAAc,QAAU,CAAY,EAAG,CAAC,CAAU,CAAC,GAErE,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,GAAe,CAAC,EAAQ,OAE7B,IAAI,EAAM,EACN,EAAa,EAAO,eAAe,EACnC,EAAa,YAAY,IAAI,EAE3B,MAAa,CACjB,IAAM,EAAQ,EAAO,kBAAkB,EACjC,EAAO,EAAO,eAAe,EAC7B,EAAM,YAAY,IAAI,EAExB,EACJ,GAAI,IAAS,EAEX,EAAa,EACb,EAAa,EACb,EAAM,OACD,GAAI,GAAS,CAAC,EAAM,QAAU,CAAC,EAAM,MAAO,CAEjD,IAAM,EAAO,EAAM,cAAgB,EACnC,EAAM,GAAe,EAAM,GAAc,IAAQ,CACnD,MACE,EAAM,EAGR,IAAM,EAAM,GAAkB,CAAI,EAClC,GAAI,EAAK,CAGP,IAAM,EAAS,KAAK,IAAI,EAAK,EAAO,GAAK,EAAI,GAAG,EAC1C,EAAM,KAAK,IAAI,IAAK,KAAK,IAAI,GAAK,EAAS,EAAI,QAAU,EAAI,IAAM,EAAI,OAAU,GAAG,CAAC,GAC7E,EAAa,SAAS,iBAA8B,uBAAuB,IAClF,QAAQ,GAAM,CAAE,EAAG,MAAM,MAAQ,GAAG,EAAI,EAAI,CAAC,CACtD,CAEA,EAAM,sBAAsB,CAAI,CAClC,EAGA,MADA,GAAM,sBAAsB,CAAI,MACnB,qBAAqB,CAAG,CACvC,EAAG,CAAC,EAAa,CAAM,CAAC,GACxB,EAAA,EAAA,eAAgB,CAAE,GAAsB,QAAU,EAAoB,EAAG,CAAC,EAAkB,CAAC,GAC7F,EAAA,EAAA,eAAgB,CAAE,GAAY,QAAU,EAAU,EAAG,CAAC,EAAQ,CAAC,GAC/D,EAAA,EAAA,eAAgB,CAAE,EAAY,QAAU,CAAU,EAAG,CAAC,CAAQ,CAAC,GAC/D,EAAA,EAAA,eAAgB,CAAE,GAAc,QAAU,EAAY,EAAG,CAAC,EAAU,CAAC,GACrE,EAAA,EAAA,eAAgB,CAAE,GAAiB,QAAU,EAAe,EAAG,CAAC,EAAa,CAAC,GAC9E,EAAA,EAAA,eAAgB,CAAE,GAAc,QAAU,EAAY,EAAG,CAAC,EAAU,CAAC,GACrE,EAAA,EAAA,eAAgB,CAAE,GAAW,QAAU,CAAS,EAAG,CAAC,CAAO,CAAC,GAC5D,EAAA,EAAA,eAAgB,CAAE,GAAkB,QAAU,EAAgB,EAAG,CAAC,EAAc,CAAC,GACjF,EAAA,EAAA,eAAgB,CACd,GAAsB,QAAU,KAChC,GAA2B,QAAU,GACrC,GAAqB,QAAU,KAC/B,GAAmB,QAAU,KAE7B,GAAiB,IAAI,EACrB,GAAiB,QAAU,KAC3B,GAAuB,QAAU,IAAI,IACrC,GAAe,EAAK,CACtB,EAAG,CAAC,CAAG,CAAC,GAER,EAAA,EAAA,eAAgB,CAAE,GAAiB,EAAK,CAAG,EAAG,CAAC,CAAG,CAAC,GAEnD,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,EAAQ,OACb,IAAM,EAAQ,EAAO,gBAAgB,EACrC,GAAI,GAAS,EAAM,YAAc,EAAG,CAClC,GAAiB,EAAI,EACrB,MACF,CACA,IAAM,MAAgB,GAAiB,EAAI,EAG3C,OAFA,EAAO,GAAG,gBAAiB,CAAO,EAClC,GAAO,iBAAiB,iBAAkB,CAAO,MACpC,CACX,EAAO,IAAI,gBAAiB,CAAO,EACnC,GAAO,oBAAoB,iBAAkB,CAAO,CACtD,CACF,EAAG,CAAC,CAAM,CAAC,GAGX,EAAA,EAAA,eAAgB,CACd,MAAc,CAAC,CAAC,EACZ,GAAC,IAAgB,CAAC,IACtB,OAAO,GACL,GACC,GAAc,CACb,IAAM,EAAO,IAAI,IACX,EAA0B,CAAC,EACjC,IAAK,IAAM,KAAU,EAAK,iBAAmB,CAAC,EAAI,CAChD,GAAI,EAAK,IAAI,EAAM,OAAO,EAAG,SAC7B,EAAK,IAAI,EAAM,OAAO,EACtB,IAAM,EAAU,EAAM,mBAAmB,GACnC,EAAK,GAAS,kBAAkB,GACtC,GAAI,CAAC,GAAI,eAAgB,SACzB,IAAM,GAA+B,GAAS,gBAAkB,CAAC,GAC9D,OAAQ,GAAW,EAAE,YAAc,YAAY,EAC/C,QAAS,GAAW,EAAE,YAAc,CAAC,CAAC,EACrC,EAAyD,CAAC,EAC9D,GAAI,CAAE,EAAW,KAAK,MAAM,EAAG,cAAgB,IAAI,CAAG,MAAY,CAAC,CACnE,IAAM,EAAW,EAAS,UAAY,gBACtC,EAAO,KAAK,CACV,QAAS,EAAM,QACf,iBAAkB,GAAY,EAAM,WAAa,EAAE,EACnD,gBAAiB,GAAY,EAAG,sBAAwB,UAAU,EAClE,SAAU,EAAG,eACb,WAAY,SAAS,EAAG,OAAS,MAAO,EAAE,EAC1C,YAAa,SAAS,EAAG,QAAU,KAAM,EAAE,EAC3C,MAAO,EAAG,SAAW,GACrB,aAAc,GAAS,EAAG,YAAY,EAAI,EAAG,aAAe,GAC5D,iBAAkB,EAAG,eAAiB,GACtC,oBACA,WACA,YAAa,EAAS,cAAgB,EACxC,CAAC,CACH,CACA,OAAO,EAAO,OAAS,EAAI,EAAS,IACtC,EACA,EACF,CACF,EAAG,CAAC,GAAc,EAAK,EAAa,CAAC,GAKrC,EAAA,EAAA,eAAgB,CACd,MAAc,CAAC,CAAC,EAChB,GAAe,CAAC,CAAC,EACb,GAAC,IAAiB,CAAC,IACvB,OAAO,GACL,GACC,GAAc,CACb,IAAM,EAAoB,CAAC,EACrB,EAAwD,CAAC,EAC/D,IAAK,IAAM,KAAU,EAAK,QAAU,CAAC,EAAI,CACvC,IAAM,EAAM,EAAM,KAAO,CAAC,EAC1B,GAAI,EAAI,SAAW,EAAG,SACtB,IAAM,EAAa,EAAM,oBAAsB,EAAI,GAAG,oBAAsB,EACtE,EAAS,EAAI,EAAI,OAAS,GAC1B,GAAU,GAAQ,oBAAsB,IAAe,GAAQ,mBAAqB,GAC1F,EAAO,KAAK,CACV,UAAW,EACX,QAAS,EAAM,mBAAqB,KAEhC,KAAK,IAAI,EAAQ,CAAU,EAD3B,EAAa,EAAM,iBAEzB,CAAC,EACD,EAAI,SAAS,EAAS,IAAqB,CACzC,IAAM,EAAoB,GAAuB,GAAI,UAAU,EACzD,EAAY,IAAsB,KACpC,EAA6F,CAAC,EAClG,GAAI,CAAE,EAAW,KAAK,MAAM,GAAI,cAAgB,IAAI,CAAG,MAAY,CAAC,CACpE,IAAM,EAAwB,EAAS,SAAW,GAAS,EAAS,WAAW,EAC3E,CACE,KAAM,EAAS,QACf,IAAK,EAAS,YACd,MAAO,EAAS,OAAS,IAAA,GACzB,YAAa,EAAS,aAAe,IAAA,EACvC,EACA,KACE,EAAmB,EAAG,oBAAsB,EAC5C,EAAkB,EAAG,mBAAqB,EAChD,EAAK,KAAK,CACR,mBACA,eAAgB,EAAmB,EACnC,kBACA,YACA,kBAAmB,GAAqB,EACxC,MACA,WACA,SAAU,EAAI,MAChB,CAAC,CACH,CAAC,CACH,CACA,OAAO,EAAK,OAAS,EAAI,CAAE,OAAM,QAAO,EAAI,IAC9C,GACC,CAAE,OAAM,YAAa,CACpB,GAAc,CAAI,EAClB,GAAe,CAAM,CACvB,CACF,CACF,EAAG,CAAC,GAAe,EAAK,EAAa,CAAC,GAKtC,EAAA,EAAA,eAAgB,CACV,CAAC,GAAU,GAAY,SAAW,GACtC,EAAO,YAAY,EAAW,CAChC,EAAG,CAAC,EAAQ,EAAW,CAAC,GAExB,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,EAAQ,OAEb,IAAM,MAA+B,CAC/B,SAAS,OACP,IAAsB,GAAe,SAAW,GAAa,UAC/D,EAAO,MAAM,EACb,GAA2B,QAAU,IAGvC,AAEE,GAA2B,WAD3B,EAAO,KAAK,EACyB,GAG3C,EAGA,OADA,SAAS,iBAAiB,mBAAoB,CAAsB,MACvD,CACX,SAAS,oBAAoB,mBAAoB,CAAsB,CACzE,CACF,EAAG,CAAC,EAAQ,EAAkB,CAAC,EAE/B,IAAM,OAAwB,CAC5B,GAAmB,CACrB,EAEM,OAA8B,CAC9B,IAAkB,CAAC,GAAuB,SAAW,EAAa,UACpE,GAAuB,QAAU,GACjC,EAAa,QAAQ,kBAAkB,EAAE,UAAY,CAAC,CAAC,EAE3D,EAGM,OAAmB,CAClB,KACL,IAAI,EAAY,CACd,EAAO,QAAQ,gBAAgB,EAC/B,GAAmB,EACnB,MACF,CACA,GAAI,GACF,EAAY,CAAC,EACb,GAAsB,EACtB,EAAO,KAAK,EACZ,GAAW,EAAK,OACX,GAAI,GAAW,CACpB,EAAO,MAAM,EAEb,GAAI,CACF,IAAM,EAAU,EAAO,kBAAkB,EACrC,GAAW,CAAC,EAAQ,QAAQ,EAAQ,MAAM,CAChD,MAAY,CAAC,CAKb,GAJI,EAAa,SAEf,EAD0B,QAAQ,iBAAiB,OACnD,EAAK,QAAQ,GAAK,CAAE,GAAI,CAAE,EAAwB,MAAM,CAAG,MAAY,CAAC,CAAE,CAAC,EAEzE,EAAe,SAAW,CAAC,EAAe,QAAQ,OACpD,GAAI,CAAE,EAAe,QAAQ,MAAM,CAAG,MAAY,CAAC,CAEvD,MACE,GAAsB,EACtB,EAAO,KAAK,EAEd,GAAmB,CAxBnB,CAyBF,EAEM,GAAiB,GAAqD,CAC1E,GAAI,CAAC,EAAW,SAAW,CAAC,GAAU,IAAoB,EAAG,MAAO,GACpE,IAAM,EAAO,EAAW,QAAQ,sBAAsB,EAEtD,OAAO,EADS,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAE,QAAU,EAAK,MAAQ,EAAK,KAAK,CAC9C,EAAU,CAAe,CACzD,EAEM,GAAuB,GAAwC,CACnE,GAAI,CAAC,GAAU,GAAe,GAAqB,OACnD,GAAY,QAAU,GAEtB,GADoB,GAAc,CACjB,CAAW,EAE5B,IAAM,EAAe,GAAmB,CACtC,GAAI,CAAC,GAAY,SAAW,CAAC,EAAW,QAAS,OACjD,IAAM,EAAO,EAAW,QAAQ,sBAAsB,EAEhD,EAAa,EADH,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAG,QAAU,EAAK,MAAQ,EAAK,KAAK,CACnC,EAAU,CAAe,EAEnE,GAAiB,CAAU,EAC3B,GAAU,EAAG,QAAU,EAAK,IAAI,EAChC,GAAa,CAAU,EAEnB,EAAe,UACjB,EAAe,QAAQ,YAAc,GAEvC,GAAmB,CACrB,EAEM,EAAa,GAAmB,CAEpC,GADA,GAAY,QAAU,GAClB,EAAW,QAAS,CACtB,IAAM,EAAO,EAAW,QAAQ,sBAAsB,EAEtD,EAAY,EADI,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAG,QAAU,EAAK,MAAQ,EAAK,KAAK,CAC1C,EAAU,CAAe,CAAC,CAC/D,CACA,GAAiB,IAAI,EACrB,GAAU,IAAI,EACd,GAAkB,IAAI,EACtB,GAAgB,IAAI,EACpB,SAAS,oBAAoB,YAAa,CAAW,EACrD,SAAS,oBAAoB,UAAW,CAAS,CACnD,EAEA,SAAS,iBAAiB,YAAa,CAAW,EAClD,SAAS,iBAAiB,UAAW,CAAS,CAChD,EAEM,GAAe,EAAc,IAAkC,CACnE,GAAI,CAAC,EAAQ,OAEb,GADA,GAAmB,QAAU,KACzB,EAAY,CACd,EAAO,QAAQ,WAAW,CAAI,EAC9B,MACF,CAEA,IAAM,EAAc,KAAK,IAAI,EAAG,GAAW,EAAI,KAAK,IAAI,EAAM,EAAQ,EAAI,CAAI,EAI9E,GAAI,EAAa,CACf,GAAM,CAAE,YAAW,GAAG,GAAW,EACjC,EAAO,kBAAkB,EAAW,CAAM,CAC5C,CAUA,IAAM,EAAiB,EAAO,eAAe,EAC7C,GAAI,EAAc,GAAkB,EAAY,QAAQ,OAAS,EAAG,CAClE,IAAM,EAAS,EAAY,QACxB,OAAO,GACN,EAAE,UAAY,GACd,EAAE,QAAU,GACZ,CAAC,GAAmB,QAAQ,IAAI,EAAE,SAAS,CAC7C,EACC,MAAM,EAAG,IAAM,EAAE,UAAY,EAAE,SAAS,EAE3C,GAAI,EAAO,OAAS,EAAG,CAIrB,GAAqB,QAAU,EAC/B,IAAM,EAAgB,EAAO,EAAO,OAAS,GAGvC,EAAiB,KAAK,IAAI,EAAc,UAAW,CAAc,EACvE,EAAU,QAAU,GACpB,EAAe,QAAU,EACzB,EAAmB,QAAU,EAC7B,GAAe,CAAc,EAI7B,GAAuB,QAAU,GACjC,GAAuB,EAAI,EAKvB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,KACnE,GAAe,EAAO,gBAAgB,CAAC,EACvC,EAAO,KAAK,CAAc,EACtB,EAAiB,SAAS,OAAO,aAAa,EAAiB,OAAO,EAC1E,EAAiB,QAAU,OAAO,eAAiB,CACjD,EAAU,QAAU,GACpB,GAAuB,QAAU,GACjC,GAAuB,EAAK,EAC5B,EAAe,QAAU,KACzB,EAAmB,QAAU,KAC7B,EAAiB,QAAU,KAC3B,GAAiB,QAAU,GACvB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,KACnE,GAAkB,EAAO,gBAAgB,CAAC,CAC5C,EAAG,GAAI,EACP,MACF,CACF,CAIA,GAAqB,QAAU,KAC/B,IAAM,EAAqB,EAAY,QAAQ,KAC7C,GAAK,GAAmB,QAAQ,IAAI,EAAE,SAAS,GAAK,GAAe,EAAE,UAAY,IAAO,EAAc,EAAE,QAAU,CACpH,EACM,EAAe,EAAqB,KAAK,IAAI,EAAa,EAAmB,QAAU,EAAG,EAAI,EACpG,EAAU,QAAU,GACpB,EAAe,QAAU,EACzB,EAAmB,QAAU,EAC7B,GAAe,CAAY,EAC3B,GAAiB,QAAU,GACvB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,KAGnE,GAAkB,EAAO,gBAAgB,CAAC,EAC1C,EAAO,KAAK,CAAY,EACpB,EAAiB,SAAS,OAAO,aAAa,EAAiB,OAAO,EAC1E,EAAiB,QAAU,OAAO,eAAiB,CACjD,EAAU,QAAU,GACpB,EAAe,QAAU,KACzB,EAAmB,QAAU,KAC7B,EAAiB,QAAU,KAC3B,GAAiB,QAAU,GACvB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,IACrE,EAAG,GAAI,CACT,EAIA,GAAe,QAAU,EAEzB,IAAM,OAAmB,CACvB,GAAI,CAAC,EAAQ,OACb,GAAI,EAAY,CACd,EAAO,QAAQ,cAAc,EAAE,IAAa,SAAW,GAAM,EAC7D,MACF,CACA,IAAM,EAAW,CAAC,GAClB,EAAO,QAAQ,CAAQ,EACvB,GAAW,CAAQ,CACrB,EAEM,GAAqB,GAAiB,CACrC,IACL,EAAO,gBAAgB,CAAI,EAC3B,GAAgB,CAAI,EACpB,EAAc,MAAM,EACpB,GAAgB,mBAAmB,IAAS,EAAI,SAAW,GAAG,EAAK,IAAI,EACzE,EAEM,GAAuB,GAAsB,CACjD,GAAI,CAAC,EAAQ,OACb,EAAO,WAAW,CAAS,EAE3B,IAAM,EAAW,GAAU,KAAM,GAAM,EAAE,KAAO,CAAS,EACrD,IACF,GAAiB,CAAQ,EACzB,GAAgB,YAAY,EAAS,OAAO,GAE9C,EAAc,MAAM,CACtB,EAEM,GAAwB,GAAoB,CAC3C,KAIL,GAHA,EAAO,UAAU,eAAe,CAAO,EACvC,GAAuB,CAAO,EAC9B,EAAc,MAAM,EAChB,IAAY,MACd,GAAgB,cAAc,MACzB,CACL,IAAM,EAAQ,GAAmB,KAAM,GAAM,EAAE,KAAO,CAAO,EAC7D,GAAgB,aAAa,EAAQ,EAAiB,CAAK,EAAI,MAAM,CACvE,CACF,EAEM,GAAsB,GAAgB,CACrC,IAEL,AAEE,EAA6B,WAD7B,aAAa,EAA6B,OAAO,EACV,MAEzC,GAAwB,IAAI,EAMxB,EAAe,UAAS,EAAe,QAAQ,MAAM,QAAU,KAOnE,GAAe,EAAI,EAKf,GAAuB,UAAY,OACrC,GAAoB,QAAU,EAAO,eAAe,GAGtD,GAAuB,QAAU,CAAE,UAAW,CAAI,EAClD,EAAc,MAAM,EACpB,GAAgB,WAAW,IAAU,IAAM,OAAS,IAAI,EACxD,KAAiB,CAAG,EACtB,EAEM,IAAsB,EAAmB,IAAuB,CACpE,EAAY,EAAW,CACrB,UAAW,cACX,YAAa,GAAQ,OAAS,GAC9B,WAAY,CACd,CAAC,EACD,EAAc,MAAM,CACtB,EAMM,IAAA,EAAA,EAAA,iBAAmC,CAEvC,GAAI,CADW,GAAa,QACf,OACb,IAAM,EAAU,GAAQ,kBAAkB,EACtC,GAAW,EAAa,SAAW,EAAQ,gBAAkB,WAC/D,EAAQ,UAAU,OAAO,cAAc,EACvC,EAAQ,MAAM,UAAY,GAC1B,EAAa,QAAQ,aAAa,EAAS,EAAa,QAAQ,UAAU,GAE5E,GAAa,QAAU,KACvB,GAAa,IAAI,EACjB,GAAS,EAAK,CAChB,EAAG,CAAC,CAAM,CAAC,EAEL,GAAY,SAAY,CAC5B,GAAI,CAAC,EAAQ,OACb,IAAM,EAAU,OAAe,yBAO/B,GAAI,CAAC,EAAQ,CACX,EAAO,uBAAuB,EAC9B,MACF,CAEA,GAAI,GAAa,QAAS,CACxB,GAAa,QAAQ,MAAM,EAC3B,MACF,CAEA,GAAI,CACF,IAAM,EAAU,EAAO,gBAAgB,EACjC,EAAS,EAAQ,YAAc,EAAQ,YACzC,EAAQ,WAAa,EAAQ,YAC7B,GAAK,EAEH,EAAiB,MAAM,EAAO,cAAc,CAAE,UAAO,OAAQ,KAAK,MAAM,IAAQ,CAAM,CAAE,CAAC,EAEzF,EAAQ,EAAO,SAAS,cAAc,OAAO,EACnD,EAAM,YAAc,GACpB,EAAO,SAAS,KAAK,YAAY,CAAK,EAEtC,EAAQ,UAAU,IAAI,cAAc,EACpC,EAAO,SAAS,KAAK,YAAY,CAAO,EAIxC,EAAO,iBAAiB,eAAkB,GAAe,EAAG,CAAE,KAAM,EAAK,CAAC,EAE1E,GAAa,QAAU,EACvB,GAAa,CAAM,EACnB,GAAS,EAAI,CACf,OAAS,EAAK,CACZ,QAAQ,MAAM,mEAAoE,CAAG,CACvF,CACF,EAKM,GAAsB,GAAwC,CAClE,GAAI,GAAe,GAAqB,OACxC,IAAM,EAAO,EAAE,cAAc,sBAAsB,EAEnD,EAAY,EADI,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAE,QAAU,EAAK,MAAQ,EAAK,KAAK,CACzC,EAAU,CAAe,CAAC,CAC/D,EAEM,OAAyB,CAC7B,GAAI,CAAC,EAAa,QAAS,OAC3B,IAAM,EAAY,EAAa,QACzB,EAAS,SACT,EAAe,EACf,EAAa,CAAC,EAAE,SAAS,mBAAqB,EAAO,yBAErD,MAAuB,CAC3B,GAAyB,EAAI,EAC7B,GAAgB,EAAI,EACpB,SAAS,KAAK,MAAM,SAAW,SAC/B,SAAS,KAAK,MAAM,YAAc,OAClC,GAAQ,UAAU,MAAM,mBAAoB,EAAO,gBAAgB,CAAC,CACtE,EAEM,MAAsB,CAC1B,GAAyB,EAAK,EAC9B,GAAgB,EAAK,EACrB,SAAS,KAAK,MAAM,SAAW,GAC/B,SAAS,KAAK,MAAM,YAAc,GAClC,GAAQ,UAAU,MAAM,kBAAmB,EAAO,gBAAgB,CAAC,CACrE,EAEA,GAAI,GAAc,GAChB,GAA8B,EAAK,EAC/B,SAAS,kBACX,SAAS,eAAe,EACf,EAAO,wBAChB,EAAO,uBAAuB,EAE9B,EAAc,MAEX,CACL,GAA8B,GAAY,OAAO,EACjD,IAAM,EACJ,EAAU,mBAAmB,KAAK,CAAS,GAC3C,EAAa,yBAAyB,KAAK,CAAY,EACrD,EACF,EAAU,EAAE,MAAM,CAAc,EAGhC,EAAe,CAEnB,CACF,GAGA,EAAA,EAAA,eAAgB,CACd,GAAoB,QAAU,EAChC,CAAC,EAED,IAAM,OAAqB,CACzB,GAAI,CAAC,EAAa,OAClB,IAAM,EAAiB,EAAS,KAAK,GAAM,GAAe,EAAG,WAAa,EAAc,EAAG,OAAO,EAK5F,EAAY,GAAW,KAAK,GAAK,GAAe,EAAE,kBAAoB,EAAc,EAAE,cAAc,EAC1G,GAAI,EAAW,CACb,IAAM,EAAc,EAAU,UAAY,EAAU,SAAW,EAC/D,GAAiB,UACf,EAAe,GAAgB,SAAW,EAAU,eAAkB,EAAU,eAChF,CAAC,CACH,EACA,MACF,CAIK,GACL,GAAiB,UAAU,EAAe,OAAO,CACnD,EAEM,OAAuB,CACvB,GAAiB,SACnB,GAAuB,QAAQ,IAAI,GAAiB,QAAQ,OAAO,EAErE,GAAiB,IAAI,EACrB,GAAiB,QAAU,IAC7B,EAEM,GAAsB,GAA2B,CACjD,EAAQ,kBAAkB,MAAM,EAAQ,iBAAkB,CAAE,KAAM,SAAU,CAAC,EAAE,UAAY,CAAC,CAAC,EAC7F,EAAQ,cAAc,OAAO,KAAK,EAAQ,aAAc,SAAU,qBAAqB,CAC7F,EAEM,GAAoB,GAAmB,CAC3C,OAAO,KAAK,EAAI,IAAK,SAAU,qBAAqB,CACtD,EAEM,OAAyB,CACxB,IACD,EACF,EAAO,QAAQ,YAAY,EAE3B,EAAO,QAAQ,eAAe,EAAK,CAAW,EAElD,EAEM,OAA4B,CAChC,GAAI,CAAC,EAAQ,OACb,IAAM,EAAQ,EAAO,gBAAgB,EAErC,GAAI,GAAoB,CACtB,EAAO,QAAQ,YAAY,CAAK,EAChC,MACF,CAEI,OAAQ,EAAc,gCAAmC,YAElD,EAAO,QAAQ,mBAAmB,EAD3C,EAAO,QAAQ,sBAAsB,CAAK,EAI1C,QAAQ,KAAK,8DAA8D,CAE/E,EAEM,GAAsB,GAAwC,CAClE,GAAI,CAAC,EAAW,SAAW,IAAoB,GAAK,CAAC,GAAU,GAAe,GAAqB,OACnG,IAAM,EAAO,EAAW,QAAQ,sBAAsB,EACtD,GAAgB,QAAU,EAAK,MAE/B,GAAe,KAAK,IAAI,IAAW,EAAG,KAAK,IAAI,EAAE,QAAU,EAAK,KAAM,EAAK,MAAQ,IAAW,CAAC,CAAC,CAAC,EAEjG,IAAM,EAAc,EADJ,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAE,QAAU,EAAK,MAAQ,EAAK,KAAK,CACjC,EAAU,CAAe,EAEpE,GAAU,EAAE,QAAU,EAAK,IAAI,EAC/B,GAAa,CAAW,EAEpB,GAAe,SAAS,OAAO,aAAa,GAAe,OAAO,EACtE,GAAe,QAAU,OAAO,eAAiB,CAC3C,EAAe,UACjB,EAAe,QAAQ,YAAc,EAEzC,EAAG,GAAG,EAGN,GADgB,EAAO,WAAW,iBAAiB,CACnC,EAAQ,SAAS,CACnC,EAEM,GAAqB,GAA+B,CACxD,GAAI,CAAC,EAAW,SAAW,IAAoB,EAAG,MAAO,GACzD,IAAM,EAAO,EAAW,QAAQ,sBAAsB,EAEtD,OAAO,EADS,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAM,QAAU,EAAK,MAAQ,EAAK,KAAK,CAClD,EAAU,CAAe,CACzD,EAEM,GAAwB,GAAwC,CACpE,GAAI,CAAC,GAAU,GAAe,IAAuB,IAAoB,EAAG,OAC5E,EAAE,eAAe,EACjB,GAAY,QAAU,GAEtB,GADoB,GAAkB,EAAE,QAAQ,EAC/B,CAAW,EAE5B,IAAM,EAAe,GAAmB,CACtC,GAAI,CAAC,GAAY,SAAW,CAAC,EAAW,QAAS,OACjD,IAAM,EAAI,EAAG,QAAQ,GACf,EAAO,EAAW,QAAQ,sBAAsB,EAEhD,EAAa,EADH,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAE,QAAU,EAAK,MAAQ,EAAK,KAAK,CAClC,EAAU,CAAe,EACnE,GAAiB,CAAU,EAC3B,GAAa,CAAU,EACvB,GAAmB,CACrB,EAEM,EAAc,GAAmB,CAErC,GADA,GAAY,QAAU,GAClB,EAAW,SAAW,EAAG,eAAe,GAAI,CAC9C,IAAM,EAAI,EAAG,eAAe,GACtB,EAAO,EAAW,QAAQ,sBAAsB,EAEtD,EAAY,EADI,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAE,QAAU,EAAK,MAAQ,EAAK,KAAK,CACzC,EAAU,CAAe,CAAC,CAC/D,CACA,GAAiB,IAAI,EACrB,SAAS,oBAAoB,YAAa,CAAW,EACrD,SAAS,oBAAoB,WAAY,CAAU,CACrD,EAEA,SAAS,iBAAiB,YAAa,EAAa,CAAE,QAAS,EAAM,CAAC,EACtE,SAAS,iBAAiB,WAAY,CAAU,CAClD,EAEM,OAA2B,CAC/B,GAAU,IAAI,EACd,GAAkB,IAAI,EACtB,GAAgB,IAAI,EAChB,GAAe,SACjB,OAAO,aAAa,GAAe,OAAO,CAE9C,EAEM,EAAc,GAAoB,CACtC,GAAI,MAAM,CAAO,GAAK,EAAU,EAAG,MAAO,QAC1C,IAAM,EAAI,KAAK,MAAM,CAAO,EACtB,EAAM,KAAK,MAAM,EAAI,IAAI,EACzB,EAAO,KAAK,MAAO,EAAI,KAAQ,EAAE,EACjC,EAAO,EAAI,GAEX,EAAO,GAAgB,OAAO,CAAG,EAAE,SAAS,EAAG,GAAG,EAKxD,OAHI,EAAM,EACD,GAAG,EAAI,CAAG,EAAE,GAAG,EAAI,CAAI,EAAE,GAAG,EAAI,CAAI,IAEtC,GAAG,EAAI,CAAI,EAAE,GAAG,EAAI,CAAI,GACjC,GAIA,EAAA,EAAA,eAAgB,CACT,GAAmB,SACxB,GAAgB,GAAY,UAAY,QAAQ,CAClD,EAAG,CAAC,EAAS,CAAC,GAEd,EAAA,EAAA,eAAgB,CACT,GAAmB,SACxB,GAAgB,GAAU,QAAU,SAAS,CAC/C,EAAG,CAAC,EAAO,CAAC,GAEZ,EAAA,EAAA,eAAgB,CAAE,GAAmB,QAAU,EAAM,EAAG,CAAC,CAAC,GAG1D,EAAA,EAAA,eAAgB,CACd,IAAM,EAAgB,GAAqB,CACrC,EAAE,MAAQ,UAAY,GAAc,UAAY,SAClD,EAAE,eAAe,EACjB,EAAc,MAAM,EAExB,EAEA,OADA,SAAS,iBAAiB,UAAW,CAAY,MACpC,SAAS,oBAAoB,UAAW,CAAY,CACnE,EAAG,CAAC,CAAC,EAGL,IAAM,GAAgB,EAAa,EAAE,IAAa,UAAY,IAAQ,GAChE,EAAkB,EAAc,IAAa,aAAe,EAAK,EACjE,GAAe,EAAc,IAAa,UAAY,EAAK,EAC3D,GAAa,EAAc,IAAa,aAAe,EAAK,GAC5D,GAAY,EAAc,IAAa,SAAW,GAAS,GAE3D,IAAA,EAAA,EAAA,aAAwC,GAAwB,CAEpE,GADI,GAAe,IACd,EAAE,OAAuB,QAAQ,kBAAkB,EAAG,OAC3D,IAAM,EAAM,KAAK,IAAI,EACf,EAAO,EAAa,SAAS,sBAAsB,EACnD,EAAO,EAAE,SAAW,GAAM,MAAQ,GAClC,EAAO,GAAW,QACxB,GAAI,GAAQ,EAAM,EAAK,KAAO,IAAK,CACjC,GAAW,QAAU,KACrB,IAAM,EAAS,GAAQ,GAAM,OAAS,GAAK,EACrC,EAAQ,EAAS,IAAM,GAE7B,EAAY,EADW,KAAK,IAAI,EAAG,KAAK,IAAI,EAAiB,EAAuB,CAAe,EAAI,CAAK,CACzE,CAAc,EAAG,CAClD,UAAW,EAAS,gBAAkB,eACtC,YAAa,EACf,CAAC,EACD,GAAiB,EAAS,OAAS,OAAO,EAC1C,OAAO,eAAiB,GAAiB,IAAI,EAAG,GAAG,EACnD,GAAmB,CACrB,MACE,GAAW,QAAU,CAAE,KAAM,EAAK,EAAG,CAAK,EACtC,IACF,GAAmB,EAAK,EACpB,GAAmB,SAAS,OAAO,aAAa,GAAmB,OAAO,GAE9E,GAAmB,CAGzB,EAAG,CAAC,EAAa,GAAqB,EAAiB,EAAwB,EAAiB,EAAwB,EAAa,EAAe,CAAC,EAE/I,OACA,IAAa,KAAe,GAAU,EAAA,EAAA,KAAC,GAAD,CAAS,KAAM,EAAK,CAAA,EAC1D,GAAa,IAAY,EAAA,EAAA,KAAC,GAAD,CAAQ,KAAM,EAAK,CAAA,EAC5C,GAAa,IAAY,EAAA,EAAA,KAAC,GAAD,CAAS,KAAM,EAAK,CAAA,GAC1C,EAAA,EAAA,KAAC,GAAD,CAAS,KAAM,EAAK,CAAA,EAIvB,EAAmB,GAAwB,GAA2B,CACtE,EAAE,MAAQ,SAAW,EAAE,MAAQ,KACjC,EAAE,eAAe,EACjB,EAAO,GACE,EAAE,MAAQ,WACnB,EAAE,eAAe,EACjB,EAAc,MAAM,EAExB,EAGM,GAAe,GAAwB,GAA2B,EAClE,EAAE,MAAQ,SAAW,EAAE,MAAQ,OACjC,EAAE,eAAe,EACjB,EAAO,EAEX,EAGM,GAAwB,GAA2C,CACnE,QAAe,IACnB,GAAI,EAAE,MAAQ,aAAe,EAAE,MAAQ,aAAc,CACnD,EAAE,eAAe,EACjB,IAAM,EAAQ,EAAE,MAAQ,YAAc,GAAK,EAE3C,EAAY,EADE,KAAK,IAAI,EAAG,KAAK,IAAI,EAAiB,EAAuB,CAAe,EAAI,CAAK,CAChE,CAAK,CAAC,CAC3C,MAAW,EAAE,MAAQ,QACnB,EAAE,eAAe,EACjB,EAAY,EAAuB,CAAC,CAAC,GAC5B,EAAE,MAAQ,QACnB,EAAE,eAAe,EACjB,EAAY,EAAuB,CAAe,CAAC,EAEvD,EAEM,GAAc,GAA4B,CAC9C,EAAc,IAAe,EAAO,OAAS,CAAI,CACnD,EAQM,IAAA,EAAA,EAAA,aAA+B,GAA8B,CACjE,GAAI,CAAC,EAAI,OACT,EAAG,MAAM,KAAO,GAChB,IAAM,EAAY,EAAa,QAC/B,GAAI,CAAC,EAAW,OAChB,IAAM,EAAW,EAAG,sBAAsB,EACpC,EAAW,EAAU,sBAAsB,EAE3C,EAAgB,EAAS,OAAS,EAAS,MAAQ,GACnD,EAAgB,EAAS,KAAO,EAAU,EAAS,KACrD,EAAgB,EAClB,EAAG,MAAM,KAAO,cAAc,KAAK,KAAK,CAAa,EAAE,KAC9C,EAAe,IACxB,EAAG,MAAM,KAAO,cAAc,KAAK,KAAK,CAAY,EAAE,KAE1D,EAAG,CAAC,CAAC,EAEL,OACE,EAAA,EAAA,MAAC,MAAD,CACE,IAAK,EACL,KAAK,SACL,aAAY,IAAS,GAAiB,GAAG,IAAS,GAAe,eAAiB,eAClF,UAAW,wBAAwB,GAAkB,kBAAoB,GAAG,GAAG,GAAwB,mBAAqB,KAC5H,YAAa,GACb,aAAc,GAAkB,IAAA,GAAY,GAC5C,iBAAoB,CAGpB,EACA,MAAO,CACL,OAAQ,GAAwB,OAAS,sCACzC,oBAAqB,EACrB,wBAAyB,GAAS,CAAU,CAC9C,WAfF,EAiBE,EAAA,EAAA,KAAC,SAAD,CACE,IAAK,GACL,MAAO,IACP,OAAQ,GACR,MAAO,CAAE,QAAS,OAAQ,SAAU,WAAY,cAAe,MAAO,EACtE,cAAY,MACb,CAAA,GAGD,EAAA,EAAA,KAAC,MAAD,CACE,KAAK,SACL,YAAU,SACV,cAAY,OACZ,UAAU,uBAET,EACE,CAAA,GAGL,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,EACL,MAAO,CAAE,QAAS,EAAG,SAAU,WAAY,MAAO,EAAG,WAAY,OAAQ,OAAQ,IAAK,cAAe,OAAQ,WAAY,SAAU,EACnI,cAAY,MACb,CAAA,EAKA,KACC,EAAA,EAAA,MAAC,MAAD,CACE,UAAU,sBACV,cAAY,gBAFd,EAIE,EAAA,EAAA,KAAC,GAAD,CAAkB,KAAM,GAAI,cAAY,MAAQ,CAAA,GAChD,EAAA,EAAA,KAAC,OAAD,CAAA,SAAM,+BAAmC,CAAA,CACtC,KAKP,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,EACL,MAAO,CAAE,QAAS,GAAe,EAAc,EAAI,EAAI,EAAG,SAAU,WAAY,MAAO,EAAG,WAAY,OAAQ,OAAQ,IAAK,cAAe,OAAQ,WAAY,SAAU,EACxK,cAAY,MACb,CAAA,GAED,EAAA,EAAA,KAAC,MAAD,CAAK,UAAW,uBAAuB,GAAc,SAAW,KAAM,cAAY,iBAChF,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,aAAmB,CAAA,CAC/B,CAAA,EAEJ,KACC,EAAA,EAAA,KAAC,MAAD,CAAK,MAAO,CACV,SAAU,WAAY,MAAO,EAAG,OAAQ,GACxC,WAAY,oBACZ,QAAS,OAAQ,cAAe,SAAU,WAAY,SAAU,eAAgB,SAChF,IAAK,GAAI,QAAS,QACpB,YACE,EAAA,EAAA,KAAC,GAAD,CACE,YAAe,CACb,GAAe,EAAK,EACpB,GAAY,GAAK,EAAI,CAAC,CACxB,CACD,CAAA,CACE,CAAA,EAGN,KACC,EAAA,EAAA,KAAC,MAAD,CAAK,MAAO,CAMV,SAAU,WAAY,IAAK,MAAO,KAAM,MACxC,UAAW,qCACX,WAAY,mBAAoB,MAAO,OAAQ,SAAU,GAAI,WAAY,IACzE,QAAS,WAAY,aAAc,EAAG,OAAQ,IAAK,cAAe,OAClE,SAAU,MAAO,UAAW,SAAU,eAAgB,YACtD,OAAQ,kCACV,WACG,EACE,CAAA,EAIN,EAAkB,iBAAmB,CAAC,GAAe,IAAgB,IAAwB,QAC5F,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,kCACb,EAAA,EAAA,KAAC,OAAD,CACE,UAAU,qBACV,MAAO,CACL,SAAU,EAAc,OAAS,QAAU,OAAS,EAAc,OAAS,QAAU,OAAS,OAC9F,MAAO,EAAc,OAAS,UAC9B,WAAY,EAAc,aAAe,OACrC,cACA,EAAc,aAAe,QAC3B,sBACA,yBACN,OAAQ,EAAc,aAAe,OAAS,OAAS,mCACvD,eAAgB,EAAc,aAAe,OAAS,OAAS,WACjE,EACA,wBAAyB,CAAE,OAAQ,EAAa,CACjD,CAAA,CACE,CAAA,EAIN,KACC,EAAA,EAAA,MAAC,MAAD,CAAK,UAAW,kCAAkC,GAAc,WAAY,KAAK,gBAAgB,aAAW,yBAA5G,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,iCAAf,EACE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,gCAAuB,IAAQ,CAAA,EAC9C,GAAc,cACb,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,uBACV,QAAS,GACT,aAAW,+BACZ,GAEO,CAAA,CAEP,KACL,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,GAAc,SACnB,MAAO,GAAc,WACrB,OAAQ,GAAc,YACtB,IAAK,GAAc,OAAS,gBAC5B,UAAU,qBACV,YAAe,GAAmB,EAAa,CAChD,CAAA,CACE,IAGN,QAAsB,CAGrB,IAAM,EAAY,GAAW,KAAK,GAAK,GAAe,EAAE,kBAAoB,EAAc,EAAE,cAAc,EACpG,EAAiB,EAAS,KAAK,GAAM,GAAe,EAAG,WAAa,EAAc,EAAG,OAAO,EAC5F,EAAkB,EACpB,KAAK,IAAI,EAAG,EAAU,eAAiB,CAAW,EACjD,EAAiB,KAAK,IAAI,EAAG,EAAe,QAAU,CAAW,EAAI,EACpE,EAAkB,EACpB,EAAc,EAAU,iBACvB,EAAiB,EAAc,EAAe,UAAY,EACzD,EAAQ,CAAC,CAAC,GAAa,EAAU,SAAW,EAC5C,EAAqB,EAAY,EAAU,UAAY,GACvD,EAAqB,EAAY,EAAU,kBAAoB,GAC/D,EAAU,GAAsB,GAAmB,EACnD,EAAgB,KAAK,KAAK,EAAqB,CAAe,EACpE,OACE,EAAA,EAAA,MAAA,EAAA,SAAA,CAAA,SAAA,EAEE,EAAA,EAAA,MAAC,MAAD,CACE,UAAU,mBACV,KAAK,QACL,aAAY,EACR,iBAAiB,EAAW,SAAW,EAAE,MAAM,EAAW,SAAS,KAAK,EAAW,CAAe,EAAE,YACpG,mBAAmB,EAAW,CAAe,EAAE,YACnD,YAAU,eANZ,EAQE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,yBAAyB,cAAY,gBAClD,EAAQ,MAAM,EAAW,SAAW,EAAE,MAAM,EAAW,WAAa,IACjE,CAAA,EACL,CAAC,IAAS,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,uBAAuB,cAAY,gBAAO,GAAO,CAAA,GAC5E,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,wBAAwB,cAAY,gBAAQ,EAAW,CAAe,CAAQ,CAAA,CAC3F,IAGJ,IACC,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,mBAAmB,EAAU,yBAA2B,KACnE,QAAS,EAAU,GAAe,IAAA,GAClC,SAAU,CAAC,EACX,aAAY,EAAU,qBAAuB,mCAAmC,EAAc,UAC9F,gBAAe,CAAC,WAEf,EAAU,YAAc,WAAW,EAAc,EAC5C,CAAA,EAOT,GAAW,UAAc,CACxB,IAAM,EAAM,EAAU,IAChB,GACJ,EAAA,EAAA,MAAC,SAAD,CACE,UAAU,iBACV,YAAe,GAAiB,CAAG,EACnC,aAAY,GAAG,EAAI,KAAK,gDAH1B,EAKE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,+BAAuB,EAAI,IAAW,CAAA,GACtD,EAAA,EAAA,KAAC,GAAD,CAAc,KAAM,GAAI,cAAY,MAAQ,CAAA,CACtC,IAEV,OACE,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,aACV,KAAK,gBACL,aAAW,wCAEV,EAAI,OACH,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,2BAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,2BAAf,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,4BAAoB,EAAI,KAAW,CAAA,EACjD,EAAI,cACH,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,2BAAmB,EAAI,WAAiB,CAAA,CAEtD,IACJ,CACE,IACH,CACD,CAAA,CAET,GAAG,CACH,CAAA,CAAA,CAEN,GAAG,EAEF,KACC,EAAA,EAAA,KAAC,MAAD,CAAK,MAAO,CAAC,SAAS,WAAW,MAAM,EAAE,QAAQ,OAAO,WAAW,SAAS,eAAe,SAAS,WAAW,mBAAmB,MAAM,OAAO,OAAO,EAAE,YACtJ,EAAA,EAAA,MAAC,MAAD,CAAK,MAAO,CAAC,SAAS,IAAI,UAAU,SAAS,QAAQ,EAAE,WAAvD,EACE,EAAA,EAAA,KAAC,KAAD,CAAI,MAAO,CAAC,OAAO,SAAS,WAAG,qBAAuB,CAAA,GACtD,EAAA,EAAA,KAAC,IAAD,CAAG,MAAO,CAAC,OAAO,CAAC,WAAG,oFAAqF,CAAA,CACxG,GACF,CAAA,GAIL,GAAc,MACd,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,aAAe,CAAA,EAI/B,KACC,EAAA,EAAA,MAAC,MAAD,CACE,UAAW,sBAAsB,GAAkB,SAAW,KAC9D,QAAS,YAFX,CAKG,KACC,EAAA,EAAA,KAAC,MAAD,CAAK,UAAW,wBAAwB,eACtC,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,qCAAf,CACG,KAAkB,QACf,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,EAAK,CAAA,GACtB,EAAA,EAAA,KAAC,GAAD,CAAU,KAAM,EAAK,CAAA,GACzB,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,sCAA6B,KAAS,CAAA,CACnD,GACF,CAAA,EAGN,KACC,EAAA,EAAA,MAAA,EAAA,SAAA,CAAA,SAAA,EAEE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,6CAAf,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,kCAAf,EACI,IAAgB,MAChB,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,sBACV,QAAS,GACT,aAAW,4BAEX,EAAA,EAAA,KAAC,GAAD,CAAa,KAAM,GAAI,cAAY,MAAQ,CAAA,CACrC,CAAA,GAER,IAAS,MACT,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,4BAAoB,IAAS,EAAoB,CAAA,CAE/D,KACL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,qCAAf,CACG,EAAkB,YACjB,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,qBACV,YAAe,CAAO,GAAa,GAAU,CAAG,EAChD,MAAO,CAAE,MAAO,GAAQ,EAAa,EAAG,EACxC,aAAY,GAAQ,0BAA4B,2BAChD,eAAc,aAEd,EAAA,EAAA,KAAC,GAAD,CAAkB,KAAM,GAAI,cAAY,MAAQ,CAAA,CAC1C,CAAA,EAET,EAAkB,kBAAoB,CAAC,IACtC,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,qBACV,QAAS,GACT,eAAc,EAAa,cAAc,EAAW,sBAAwB,iBAC5E,aAAY,EAAa,mBAAmB,IAAe,iBAC3D,eAAc,CAAC,CAAC,EAChB,MAAO,CAAE,MAAO,EAAa,UAAY,EAAG,YAE5C,EAAA,EAAA,KAAC,GAAD,CAAM,KAAM,GAAI,KAAM,EAAa,eAAiB,OAAQ,cAAY,MAAQ,CAAA,CAC1E,CAAA,EAET,EAAkB,cAAgB,KACjC,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,qBACV,QAAS,GACT,aAAY,GAAqB,mBAAmB,IAAqB,WAAa,oBACtF,eAAc,GACd,MAAO,CAAE,MAAO,GAAqB,EAAa,EAAG,YAErD,EAAA,EAAA,KAAC,GAAD,CAAS,KAAM,GAAI,cAAY,MAAQ,CAAA,CACjC,CAAA,CAEP,GACF,KAGL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,sDAAf,CACG,CAAC,IACA,EAAA,EAAA,MAAC,SAAD,CACE,UAAU,sBACV,YAAe,EAAY,EAAuB,KAAK,IAAI,EAAG,EAAuB,CAAe,EAAI,EAAE,CAAC,EAAG,CAAE,UAAW,gBAAiB,YAAa,EAAG,CAAC,EAC7J,aAAW,6BAHb,EAKE,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,GACzC,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,wBAAwB,cAAY,gBAAO,IAAQ,CAAA,CAC7D,KAEV,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,sBACV,QAAS,GACT,aAAY,GAAU,SAAW,GAAgB,QAAU,gBAE1D,IACC,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,EACvC,IACF,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,KAAK,eAAe,cAAY,MAAQ,CAAA,GAEzD,EAAA,EAAA,KAAC,GAAD,CAAM,KAAM,GAAI,KAAK,eAAe,MAAO,CAAE,WAAY,KAAM,EAAG,cAAY,MAAQ,CAAA,CAElF,CAAA,EACP,CAAC,IACA,EAAA,EAAA,MAAC,SAAD,CACE,UAAU,sBACV,YAAe,EAAY,EAAuB,KAAK,IAAI,EAAiB,EAAuB,CAAe,EAAI,EAAE,CAAC,EAAG,CAAE,UAAW,eAAgB,YAAa,EAAG,CAAC,EAC1K,aAAW,mCAHb,EAKE,EAAA,EAAA,KAAC,GAAD,CAAU,KAAM,GAAI,cAAY,MAAQ,CAAA,GACxC,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,wBAAwB,cAAY,gBAAO,IAAQ,CAAA,CAC7D,GAEP,KAGL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,iDAAf,CAEG,EAAkB,kBAAoB,IAAuB,EAAgB,OAAS,IACrF,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,sCACZ,EAAgB,KAAK,EAAI,IAAQ,CAChC,IAAM,EAAW,GAAc,QAAU,EACzC,OACE,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,SAAU,EACV,UAAW,0BAA0B,EAAW,UAAY,KAC5D,MAAO,CAAE,YAAa,EAAW,EAAa,IAAA,EAAU,EACxD,aAAY,GAAG,EAAG,MAAM,IAAI,EAAW,EAAG,SAAS,IAAI,EAAW,oBAAsB,KACxF,eAAc,EAAW,OAAS,IAAA,GAClC,YAAe,CAAE,GAAmB,EAAG,UAAW,CAAE,CAAG,EACvD,UAAW,OAAkB,GAAmB,EAAG,UAAW,CAAE,CAAC,WATnE,EAWE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,oCAAf,CACI,GAAkB,IAAQ,EAAG,WAC7B,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,GAAkB,IAAQ,EAAG,UAClC,IAAI,GACJ,cAAY,OACZ,MAAO,CAAE,MAAO,OAAQ,OAAQ,OAAQ,UAAW,QAAS,QAAS,OAAQ,CAC9E,CAAA,GAED,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,OAAO,MAAO,CAAE,MAAO,EAAW,EAAa,uBAAwB,CAAI,CAAA,GAE9G,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,gCAAgC,cAAY,gBAAQ,EAAW,EAAG,SAAS,CAAQ,CAAA,CAChG,KACL,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,2BAA2B,MAAO,CAAE,MAAO,EAAW,EAAa,IAAA,EAAU,WACzF,EAAG,KACD,CAAA,CACF,GA1BE,CA0BF,CAET,CAAC,CACE,CAAA,GAIP,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,EACL,KAAM,EAAa,IAAA,GAAY,SAC/B,aAAY,EAAa,IAAA,GAAY,OACrC,gBAAe,EAAa,IAAA,GAAY,EACxC,gBAAe,EAAa,IAAA,GAAY,KAAK,MAAM,CAAe,EAClE,gBAAe,EAAa,IAAA,GAAY,KAAK,MAAM,EAAuB,IAAkB,KAAuB,EAAhB,CAA2B,CAAC,EAC/H,iBAAgB,EAAa,IAAA,GAAY,GAAG,EAAW,EAAuB,IAAkB,KAAuB,EAAhB,CAA2B,CAAC,EAAE,MAAM,EAAW,CAAe,IACrK,SAAU,EAAa,IAAA,GAAY,EACnC,UAAW,2CAA2C,EAAc,eAAiB,KACrF,YAAa,EAAa,IAAA,GAAY,GACtC,YAAa,EAAa,IAAA,GAAY,GACtC,aAAc,EAAa,IAAA,GAAY,GACvC,aAAc,EAAa,IAAA,GAAY,GACvC,UAAW,EAAa,IAAA,GAAY,GACpC,MAAO,CAAE,cAAe,EAAa,OAAS,IAAA,GAAW,YAAa,MAAO,WAE5E,GACC,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,uBACV,MAAO,CAKL,WAAc,CACZ,IAAM,EAAI,EAAS,EAAO,eAAe,EAAI,EACvC,EAAM,GAAkB,CAAC,EAC/B,OAAO,EAAM,GAAG,KAAK,IAAI,IAAK,KAAK,IAAI,GAAK,EAAI,EAAI,QAAU,EAAI,IAAM,EAAI,OAAU,GAAG,CAAC,EAAE,GAAK,IACnG,GAAG,CACL,CACD,CAAA,GAED,EAAA,EAAA,MAAA,EAAA,SAAA,CAAA,SAAA,EACE,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,uBACV,MAAO,CAAE,MAAO,GAAG,IAAY,GAAU,EAAO,kBAAkB,EAAE,IAAI,KAAY,GAAK,GAAW,IAAM,EAAE,EAAG,CAChH,CAAA,EACA,EAAkB,GAAK,EAAS,OAAO,GAAM,CAAC,GAAmB,IAAI,EAAG,SAAS,CAAC,EAAE,KAAK,EAAI,KAC5F,EAAA,EAAA,KAAC,MAAD,CAEE,UAAU,sBACV,MAAO,CACL,KAAM,GAAI,EAAuB,EAAG,SAAS,EAAI,EAAmB,IAAI,GACxE,MAAO,KACT,CACD,EANM,CAMN,CACF,GACD,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,uBACV,MAAO,CACL,MAAO,EACH,GAAG,GAAe,EAAK,EAAkB,GAAgB,IAAM,EAAE,GACjE,GAAG,EAAmB,EAAuB,IAAkB,KAAuB,EAAhB,CAA2B,EAAI,EAAmB,IAAM,EAAE,GACpI,WAAY,UACZ,UAAW,MACb,YAEA,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,qBAAqB,MAAO,CAAE,QAAS,EAAG,UAAW,UAAW,CAAI,CAAA,CAChF,CAAA,EACJ,EAAkB,kBAAoB,EAAgB,KAAK,EAAI,IAC1D,IAAoB,EAAU,MAC3B,EAAA,EAAA,KAAC,MAAD,CAAe,UAAU,qDAAqD,MAAO,CAAE,KAAM,GAAI,EAAuB,EAAG,SAAS,EAAI,EAAmB,IAAI,EAAG,CAAI,EAA5J,CAA4J,CAC9K,CACD,CAAA,CAAA,CAED,CAAA,GAGL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,CACG,CAAC,IACA,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,4BACb,EACG,GAAG,EAAW,CAAe,EAAE,KAAK,EAAW,EAAY,IAC3D,GAAG,EAAW,EAAuB,IAAkB,KAAuB,EAAhB,CAA2B,CAAC,EAAE,KAAK,EAAW,CAAe,GAC3H,CAAA,GAER,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,CACG,EAAkB,kBAAoB,EAAgB,OAAS,GAAK,CAAC,IACpE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,sBAAsB,GAAsB,UAAY,KACnE,YAAe,CAAE,GAAuB,CAAC,EAAmB,CAAG,EAC/D,aAAY,GAAsB,gBAAkB,gBACpD,eAAc,aAEd,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,CACnC,CAAA,EAGT,EAAkB,iBAAmB,CAAC,GAAe,GAAmB,KAAK,GAAK,CAAC,EAAE,QAAQ,IAC5F,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,sBAAsB,IAAwB,MAAoB,GAAZ,YACjE,YAAe,CAAE,GAAW,WAAW,CAAG,EAC1C,aAAW,WACX,gBAAc,UACd,gBAAe,IAAe,sBAE9B,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,CACnC,CAAA,EACP,IAAe,cACd,EAAA,EAAA,MAAC,MAAD,CAAK,IAAK,GAAiB,UAAU,kBAAkB,KAAK,UAAU,aAAW,oBAAjF,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBAAoB,cAAY,gBAAO,WAAc,CAAA,GACpE,EAAA,EAAA,MAAC,MAAD,CACE,KAAK,SACL,gBAAe,IAAwB,MACvC,SAAU,EACV,UAAW,mBAAmB,IAAwB,MAAQ,SAAW,KACzE,YAAe,GAAqB,KAAK,EACzC,UAAW,MAAsB,GAAqB,KAAK,CAAC,WAN9D,EAQE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAM,KAAS,CAAA,EACd,IAAwB,QAAS,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CACpE,IACJ,GAAmB,IAAK,IACvB,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,gBAAe,IAAwB,EAAM,GAC7C,SAAU,EACV,UAAW,mBAAmB,IAAwB,EAAM,GAAK,SAAW,KAC5E,YAAe,GAAqB,EAAM,EAAE,EAC5C,UAAW,MAAsB,GAAqB,EAAM,EAAE,CAAC,WAPjE,EASE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,EAAiB,CAAK,CAAQ,CAAA,EACpC,IAAwB,EAAM,KAAM,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CACvE,GAVE,EAAM,EAUR,CACN,CACE,GAEJ,IAGN,EAAkB,oBAAsB,GAAY,OAAS,GAAK,CAAC,IAClE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,sBAAsB,IAAe,QAAU,UAAY,KACtE,YAAe,CAAE,GAAW,OAAO,CAAG,EACtC,aAAW,cACX,gBAAc,UACd,gBAAe,IAAe,kBAE9B,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,CACnC,CAAA,EACP,IAAe,UACd,EAAA,EAAA,MAAC,MAAD,CAAK,IAAK,GAAiB,UAAU,kBAAkB,KAAK,UAAU,aAAW,uBAAjF,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBAAoB,cAAY,gBAAO,aAAgB,CAAA,EACrE,GAAY,IAAK,IAChB,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,gBAAe,KAAqB,EAAM,GAC1C,SAAU,EACV,UAAW,mBAAmB,KAAqB,EAAM,GAAK,SAAW,KACzE,YAAe,CAAE,GAAQ,cAAc,EAAM,EAAE,EAAG,GAAoB,EAAM,EAAE,EAAG,EAAc,MAAM,EAAG,GAAgB,UAAU,EAAiB,CAAK,GAAG,CAAG,EAC9J,UAAW,MAAsB,CAAE,GAAQ,cAAc,EAAM,EAAE,EAAG,GAAoB,EAAM,EAAE,EAAG,EAAc,MAAM,EAAG,GAAgB,UAAU,EAAiB,CAAK,GAAG,CAAG,CAAC,WAPnL,EASE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,EAAiB,CAAK,CAAQ,CAAA,EACpC,KAAqB,EAAM,KAAM,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CACpE,GAVE,EAAM,EAUR,CACN,CACE,GAEJ,IAGN,EAAkB,qBAAuB,CAAC,IACzC,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,sBAAsB,IAAe,QAAU,UAAY,KACtE,YAAe,CAAE,GAAW,OAAO,CAAG,EACtC,aAAW,iBACX,gBAAc,UACd,gBAAe,IAAe,kBAE9B,EAAA,EAAA,KAAC,GAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CAC/B,CAAA,EACP,IAAe,UACd,EAAA,EAAA,MAAC,MAAD,CAAK,IAAK,GAAiB,UAAU,kBAAkB,KAAK,UAAU,aAAW,0BAAjF,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBAAoB,cAAY,gBAAO,OAAU,CAAA,EAC/D,CAAC,GAAK,IAAM,EAAG,KAAM,IAAK,CAAC,EAAE,IAAK,IACjC,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,gBAAe,KAAiB,EAChC,SAAU,EACV,UAAW,mBAAmB,KAAiB,EAAO,SAAW,KACjE,YAAe,GAAkB,CAAI,EACrC,UAAW,MAAsB,GAAkB,CAAI,CAAC,WAP1D,EASE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,IAAS,EAAI,SAAW,GAAG,EAAK,EAAS,CAAA,EAC/C,KAAiB,IAAQ,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CAC5D,GAVE,CAUF,CACN,CACE,GAEJ,IAGN,EAAkB,eAAiB,CAAC,IACnC,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,sBAAsB,GAAc,KAAO,GAAiB,GAAZ,YAC3D,YAAe,CAAE,GAAW,SAAS,CAAG,EACxC,aAAW,gBACX,gBAAc,UACd,gBAAe,IAAe,oBAE9B,EAAA,EAAA,KAAC,GAAD,CAAQ,KAAM,GAAI,cAAY,MAAQ,CAAA,CAChC,CAAA,EACP,IAAe,YACd,EAAA,EAAA,MAAC,MAAD,CAAK,IAAK,GAAiB,UAAU,wBAAwB,KAAK,UAAU,aAAW,yBAAvF,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBAAoB,cAAY,gBAAO,SAAY,CAAA,EACjE,GAAU,IAAK,IACd,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,gBAAe,GAAc,KAAO,EAAE,GACtC,SAAU,EACV,UAAW,mBAAmB,GAAc,KAAO,EAAE,GAAK,SAAW,KACrE,YAAe,GAAoB,EAAE,EAAE,EACvC,UAAW,MAAsB,GAAoB,EAAE,EAAE,CAAC,WAP5D,EASE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,EAAE,KAAY,CAAA,EACpB,GAAc,KAAO,EAAE,KAAM,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CAChE,GAVE,EAAE,EAUJ,CACN,CACE,GAEJ,IAGN,GAAW,EAAQ,OAAS,GAAK,CAAC,IACjC,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,sBAAsB,IAAe,UAAY,UAAY,KACxE,YAAe,CAAE,GAAW,SAAS,CAAG,EACxC,aAAW,gBACX,gBAAc,UACd,gBAAe,IAAe,oBAE9B,EAAA,EAAA,KAAC,GAAD,CAAmB,KAAM,GAAI,cAAY,MAAQ,CAAA,CAC3C,CAAA,EACP,IAAe,YACd,EAAA,EAAA,MAAC,MAAD,CAAK,IAAK,GAAiB,UAAU,kBAAkB,KAAK,UAAU,aAAW,yBAAjF,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBAAoB,cAAY,gBAAO,QAAW,CAAA,EAChE,EAAQ,KAAK,EAAG,KACf,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,gBAAe,KAAsB,EACrC,SAAU,EACV,UAAW,mBAAmB,KAAsB,EAAM,SAAW,KACrE,YAAe,GAAmB,CAAG,EACrC,UAAW,MAAsB,GAAmB,CAAG,CAAC,WAP1D,EASE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,EAAE,KAAY,CAAA,EACpB,KAAsB,IAAO,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CAChE,GAVE,CAUF,CACN,CACE,GAEJ,KAGP,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,sBACV,QAAS,GACT,aAAY,IAAgB,GAAwB,kBAAoB,mBACxE,eAAc,IAAgB,YAE7B,IAAe,EAAA,EAAA,KAAC,GAAD,CAAU,KAAM,GAAI,cAAY,MAAQ,CAAA,GAAI,EAAA,EAAA,KAAC,GAAD,CAAU,KAAM,GAAI,cAAY,MAAQ,CAAA,CAC9F,CAAA,CACL,GACF,GACF,GACL,CAAA,CAAA,CAED,IAIN,CAAC,KACA,EAAA,EAAA,MAAC,MAAD,CACE,UAAU,uBACV,QAAU,GAAM,CACV,GACC,EAAE,OAAuB,QAAQ,qBAAqB,GACtD,EAAE,OAAuB,QAAQ,8BAA8B,GAC/D,EAAE,OAAuB,QAAQ,qBAAqB,GAC3D,GAAW,CACb,WARF,EAWE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACI,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,EACI,IAAgB,MAChB,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,uBACV,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAiB,CAAG,EAC3D,aAAW,4BAEX,EAAA,EAAA,KAAC,GAAD,CAAa,KAAM,GAAI,cAAY,MAAQ,CAAA,CACrC,CAAA,GAER,IAAS,MAAmB,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,6BAAqB,IAAS,EAAqB,CAAA,CAC9F,KACL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,oCAAf,CACG,EAAkB,kBAAoB,CAAC,IACtC,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,sBACV,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAiB,CAAG,EAC3D,eAAc,EAAa,cAAc,EAAW,wBAA0B,iBAC9E,aAAY,EAAa,mBAAmB,IAAe,iBAC3D,eAAc,CAAC,CAAC,EAChB,MAAO,CAAE,MAAO,EAAa,UAAY,EAAG,YAE5C,EAAA,EAAA,KAAC,GAAD,CAAM,KAAM,GAAI,KAAM,EAAa,eAAiB,OAAQ,cAAY,MAAQ,CAAA,CAC1E,CAAA,EAET,IACC,EAAA,EAAA,MAAC,OAAD,CAAM,UAAU,qBAAqB,cAAY,gBAAjD,EACE,EAAA,EAAA,KAAC,GAAD,CAAM,KAAM,GAAI,KAAK,eAAe,cAAY,MAAQ,CAAA,EACvD,CACG,IAEP,EAAkB,cAAgB,KACjC,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,sBACV,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAoB,CAAG,EAC9D,eAAc,GAAqB,cAAc,IAAqB,SAAS,wBAA0B,oBACzG,aAAY,GAAqB,mBAAmB,IAAqB,WAAa,oBACtF,eAAc,GACd,MAAO,CAAE,MAAO,GAAqB,EAAa,EAAG,YAErD,EAAA,EAAA,KAAC,GAAD,CAAS,KAAM,GAAI,cAAY,MAAQ,CAAA,CACjC,CAAA,CAEP,GACJ,IAEJ,KACC,EAAA,EAAA,MAAA,EAAA,SAAA,CAAA,SAAA,EAEE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAA8B,MAAO,CAAE,cAAe,MAAO,WAA5E,CACG,CAAC,IACA,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,uBACV,MAAO,CAAE,cAAe,MAAO,EAC/B,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,EAAY,EAAuB,KAAK,IAAI,EAAG,EAAuB,CAAe,EAAI,EAAE,CAAC,EAAG,CAAE,UAAW,gBAAiB,YAAa,EAAG,CAAC,CAAG,EACxL,aAAW,8BAEX,EAAA,EAAA,MAAC,MAAD,CAAK,MAAO,CAAE,SAAU,WAAY,QAAS,OAAQ,WAAY,SAAU,eAAgB,QAAS,WAApG,EACE,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,GACzC,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,yBAAyB,cAAY,gBAAO,IAAQ,CAAA,CACjE,GACC,CAAA,GAEV,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,uBACV,MAAO,CAAE,cAAe,MAAO,EAC/B,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAW,CAAG,EACrD,aAAY,GAAU,SAAW,GAAgB,QAAU,gBAE1D,IACG,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,EACzC,IACE,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,KAAK,eAAe,cAAY,MAAQ,CAAA,GACzD,EAAA,EAAA,KAAC,GAAD,CAAM,KAAM,GAAI,KAAK,eAAe,MAAO,CAAE,WAAY,KAAM,EAAG,cAAY,MAAQ,CAAA,CACtF,CAAA,EACP,CAAC,IACA,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,uBACV,MAAO,CAAE,cAAe,MAAO,EAC/B,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,EAAY,EAAuB,KAAK,IAAI,EAAiB,EAAuB,CAAe,EAAI,EAAE,CAAC,EAAG,CAAE,UAAW,eAAgB,YAAa,EAAG,CAAC,CAAG,EACrM,aAAW,oCAEX,EAAA,EAAA,MAAC,MAAD,CAAK,MAAO,CAAE,SAAU,WAAY,QAAS,OAAQ,WAAY,SAAU,eAAgB,QAAS,WAApG,EACE,EAAA,EAAA,KAAC,GAAD,CAAU,KAAM,GAAI,cAAY,MAAQ,CAAA,GACxC,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,yBAAyB,cAAY,gBAAO,IAAQ,CAAA,CACjE,GACC,CAAA,CAEP,IAGJ,EAAkB,kBAAoB,IAAuB,EAAgB,OAAS,GAAK,KAAwB,UAClH,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,qBAAqB,QAAU,GAAM,EAAE,gBAAgB,WAAtE,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,sCACb,EAAA,EAAA,KAAC,OAAD,CAAM,UAAU,oCAA2B,UAAc,CAAA,CACtD,CAAA,GACL,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,mCACZ,EAAgB,KAAK,EAAI,IAAQ,CAChC,IAAM,EAAW,GAAc,QAAU,EACzC,OACE,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,SAAU,EACV,UAAW,0BAA0B,EAAW,UAAY,KAC5D,aAAY,GAAG,EAAG,MAAM,IAAI,EAAW,EAAG,SAAS,IAAI,EAAW,oBAAsB,KACxF,eAAc,EAAW,OAAS,IAAA,GAClC,YAAe,GAAmB,EAAG,UAAW,CAAE,EAClD,UAAW,OAAkB,GAAmB,EAAG,UAAW,CAAE,CAAC,WARnE,EAUE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oCACX,GAAkB,IAAQ,EAAG,WAC7B,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,GAAkB,IAAQ,EAAG,UAClC,IAAI,GACJ,cAAY,OACZ,MAAO,CAAE,MAAO,OAAQ,OAAQ,OAAQ,UAAW,QAAS,QAAS,OAAQ,CAC9E,CAAA,GAED,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,OAAO,MAAO,CAAE,MAAO,uBAAwB,CAAI,CAAA,CAEnF,CAAA,GACL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,yCAAiC,EAAG,KAAW,CAAA,EAC7D,EAAG,cACF,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,wCAAgC,EAAG,WAAiB,CAAA,GAErE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,+BAA+B,cAAY,gBAAQ,EAAW,EAAG,SAAS,CAAO,CAAA,CAC7F,GACF,GA5BE,CA4BF,CAET,CAAC,CACE,CAAA,CACF,KAIP,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,CAEG,EAAkB,kBAAoB,IAAuB,EAAgB,OAAS,GAAK,KAAwB,UAClH,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,sCACZ,EAAgB,KAAK,EAAI,IAAQ,CAChC,IAAM,EAAW,GAAc,QAAU,EACzC,OACE,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,SAAU,EACV,UAAW,2BAA2B,EAAW,UAAY,KAC7D,aAAY,GAAG,EAAG,MAAM,IAAI,EAAW,EAAG,SAAS,IAAI,EAAW,oBAAsB,KACxF,eAAc,EAAW,OAAS,IAAA,GAClC,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAmB,EAAG,UAAW,CAAE,CAAG,EAC7E,UAAW,OAAkB,GAAmB,EAAG,UAAW,CAAE,CAAC,WARnE,EAUE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,qCACX,GAAkB,IAAQ,EAAG,WAC7B,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,GAAkB,IAAQ,EAAG,UAClC,IAAI,GACJ,cAAY,OACZ,MAAO,CAAE,MAAO,OAAQ,OAAQ,OAAQ,UAAW,QAAS,QAAS,OAAQ,CAC9E,CAAA,GAED,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,OAAO,MAAO,CAAE,MAAO,wBAAyB,CAAI,CAAA,CAEpF,CAAA,GACL,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,qCAA6B,EAAG,KAAW,CAAA,GAC1D,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,2BAA2B,cAAY,gBAAQ,EAAW,EAAG,SAAS,CAAO,CAAA,CACzF,GAvBE,CAuBF,CAET,CAAC,CACE,CAAA,GAIP,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,EACL,KAAM,EAAa,IAAA,GAAY,SAC/B,aAAY,EAAa,IAAA,GAAY,OACrC,gBAAe,EAAa,IAAA,GAAY,EACxC,gBAAe,EAAa,IAAA,GAAY,KAAK,MAAM,CAAe,EAClE,gBAAe,EAAa,IAAA,GAAY,KAAK,MAAM,EAAuB,IAAkB,KAAuB,EAAhB,CAA2B,CAAC,EAC/H,iBAAgB,EAAa,IAAA,GAAY,GAAG,EAAW,EAAuB,IAAkB,KAAuB,EAAhB,CAA2B,CAAC,EAAE,MAAM,EAAW,CAAe,IACrK,SAAU,EAAa,IAAA,GAAY,EACnC,UAAW,4CAA4C,EAAc,eAAiB,KACtF,YAAa,EAAa,IAAA,GAAY,GACtC,YAAa,EAAa,IAAA,GAAY,GACtC,aAAc,EAAa,IAAA,GAAY,GACvC,aAAc,EAAa,IAAA,GAAY,GACvC,UAAW,EAAa,IAAA,GAAY,GACpC,MAAO,CAAE,cAAe,EAAa,OAAS,IAAA,EAAU,WAEvD,GACC,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,uBACV,MAAO,CAKL,WAAc,CACZ,IAAM,EAAI,EAAS,EAAO,eAAe,EAAI,EACvC,EAAM,GAAkB,CAAC,EAC/B,OAAO,EAAM,GAAG,KAAK,IAAI,IAAK,KAAK,IAAI,GAAK,EAAI,EAAI,QAAU,EAAI,IAAM,EAAI,OAAU,GAAG,CAAC,EAAE,GAAK,IACnG,GAAG,CACL,CACD,CAAA,GAED,EAAA,EAAA,MAAA,EAAA,SAAA,CAAA,SAAA,EACE,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,uBACV,MAAO,CAAE,MAAO,GAAG,IAAY,GAAU,EAAO,kBAAkB,EAAE,IAAI,KAAY,GAAK,GAAW,IAAM,EAAE,EAAG,CAChH,CAAA,EACA,EAAkB,GAAK,EAAS,OAAO,GAAM,CAAC,GAAmB,IAAI,EAAG,SAAS,CAAC,EAAE,KAAK,EAAI,KAC5F,EAAA,EAAA,KAAC,MAAD,CAEE,UAAU,sBACV,MAAO,CACL,KAAM,GAAI,EAAuB,EAAG,SAAS,EAAI,EAAmB,IAAI,GACxE,MAAO,KACT,CACD,EANM,CAMN,CACF,GACD,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,uBACV,MAAO,CACL,MAAO,EACH,GAAG,GAAe,EAAK,EAAkB,GAAgB,IAAM,EAAE,GACjE,GAAG,EAAmB,EAAuB,IAAkB,KAAuB,EAAhB,CAA2B,EAAI,EAAmB,IAAM,EAAE,GACpI,WAAY,UACZ,UAAW,MACb,YAEA,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBAAsB,CAAA,CAClC,CAAA,EACJ,EAAkB,kBAAoB,EAAgB,KAAK,EAAI,IAC1D,IAAoB,EAAU,MAEhC,EAAA,EAAA,KAAC,MAAD,CAEE,UAAU,qDACV,MAAO,CAAE,KAAM,GAAI,EAAuB,EAAG,SAAS,EAAI,EAAmB,IAAI,EAAG,CACrF,EAHM,CAGN,CAEJ,EACA,KAAW,MAAQ,KAClB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,wBAAwB,MAAO,CAAE,KAAM,GAAG,GAAY,GAAI,WAAzE,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,kCACb,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,GACL,IAAI,UACJ,MAAO,CAAE,MAAO,OAAQ,OAAQ,OAAQ,UAAW,QAAS,aAAc,MAAO,QAAS,OAAQ,CACnG,CAAA,CACE,CAAA,EACJ,KACC,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,sCAA8B,GAAa,KAAW,CAAA,GAEvE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oCAA4B,EAAW,EAAuB,EAAS,CAAC,CAAO,CAAA,CAC3F,GAEP,CAAA,CAAA,CAED,CAAA,GAGL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,oCAAf,EAEE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,qCAAf,CACG,CAAC,IACA,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,4BACZ,EACG,GAAG,EAAW,CAAe,EAAE,KAAK,EAAW,EAAY,IAC3D,GAAG,EAAW,EAAuB,IAAkB,KAAuB,EAAhB,CAA2B,CAAC,EAAE,KAAK,EAAW,CAAe,GAC5H,CAAA,EAEN,KAAsB,WACrB,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,uBACV,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAW,CAAG,EACrD,eAAc,GAAY,SAAW,OACrC,aAAY,GAAY,SAAW,OACnC,eAAc,YAEb,GAAc,CACT,CAAA,GACR,EAAA,EAAA,KAAC,QAAD,CACE,KAAK,QACL,UAAU,oBACV,IAAK,EACL,IAAK,EACL,KAAM,IACN,MAAO,GAAY,EAAI,GACvB,aAAW,SACX,iBAAgB,GAAY,QAAU,GAAG,KAAK,MAAM,GAAa,GAAG,EAAE,GACtE,MAAO,CAAE,YAAa,KAAK,OAAO,GAAY,EAAI,IAAc,GAAG,CAAE,EACrE,SAAW,GAAM,CACf,IAAM,EAAM,WAAW,EAAE,OAAO,KAAK,EACjC,GACF,GAAQ,QAAQ,gBAAgB,CAAG,EAC/B,EAAM,GAAG,GAAQ,QAAQ,cAAc,EAAK,IAEhD,GAAQ,UAAU,CAAG,EACjB,EAAM,GAAK,IAAS,GAAQ,QAAQ,EAAK,EAEjD,EACA,QAAU,GAAM,EAAE,gBAAgB,CACnC,CAAA,CACE,GAEJ,KAGL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,mCAAf,CACG,EAAkB,kBAAoB,EAAgB,OAAS,GAAK,CAAC,IACpE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,uBAAuB,GAAsB,UAAY,KACpE,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAuB,CAAC,EAAmB,CAAG,EACrF,eAAa,cACb,aAAY,GAAsB,gBAAkB,gBACpD,eAAc,aAEd,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,CACnC,CAAA,EAGT,EAAkB,iBAAmB,CAAC,GAAe,GAAmB,KAAK,GAAK,CAAC,EAAE,QAAQ,IAC5F,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,uBAAuB,IAAwB,MAAoB,GAAZ,YAClE,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAW,WAAW,CAAG,EAChE,eAAa,WACb,aAAW,WACX,gBAAc,UACd,gBAAe,IAAe,sBAE9B,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,CACnC,CAAA,EACP,IAAe,cACd,EAAA,EAAA,MAAC,MAAD,CAAK,IAAK,GAAiB,UAAU,kBAAkB,KAAK,UAAU,aAAW,oBAAjF,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBAAoB,cAAY,gBAAO,WAAc,CAAA,GACpE,EAAA,EAAA,MAAC,MAAD,CACE,KAAK,SACL,gBAAe,IAAwB,MACvC,SAAU,EACV,UAAW,mBAAmB,IAAwB,MAAQ,SAAW,KACzE,YAAe,GAAqB,KAAK,EACzC,UAAW,MAAsB,GAAqB,KAAK,CAAC,WAN9D,EAQE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAM,KAAS,CAAA,EACd,IAAwB,QAAS,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CACpE,IACJ,GAAmB,IAAK,IACvB,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,gBAAe,IAAwB,EAAM,GAC7C,SAAU,EACV,UAAW,mBAAmB,IAAwB,EAAM,GAAK,SAAW,KAC5E,YAAe,GAAqB,EAAM,EAAE,EAC5C,UAAW,MAAsB,GAAqB,EAAM,EAAE,CAAC,WAPjE,EASE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,EAAiB,CAAK,CAAQ,CAAA,EACpC,IAAwB,EAAM,KAAM,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CACvE,GAVE,EAAM,EAUR,CACN,CACE,GAEJ,IAGN,EAAkB,oBAAsB,GAAY,OAAS,GAAK,CAAC,IAClE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,uBAAuB,IAAe,QAAU,UAAY,KACvE,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAW,OAAO,CAAG,EAC5D,eAAa,cACb,aAAW,cACX,gBAAc,UACd,gBAAe,IAAe,kBAE9B,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,CACnC,CAAA,EACP,IAAe,UACd,EAAA,EAAA,MAAC,MAAD,CAAK,IAAK,GAAiB,UAAU,kBAAkB,KAAK,UAAU,aAAW,uBAAjF,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBAAoB,cAAY,gBAAO,aAAgB,CAAA,EACrE,GAAY,IAAK,IAChB,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,gBAAe,KAAqB,EAAM,GAC1C,SAAU,EACV,UAAW,mBAAmB,KAAqB,EAAM,GAAK,SAAW,KACzE,YAAe,CACb,GAAQ,cAAc,EAAM,EAAE,EAC9B,GAAoB,EAAM,EAAE,EAC5B,EAAc,MAAM,EACpB,GAAgB,UAAU,EAAiB,CAAK,GAAG,CACrD,EACA,UAAW,MAAsB,CAC/B,GAAQ,cAAc,EAAM,EAAE,EAC9B,GAAoB,EAAM,EAAE,EAC5B,EAAc,MAAM,EACpB,GAAgB,UAAU,EAAiB,CAAK,GAAG,CACrD,CAAC,WAjBH,EAmBE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,EAAiB,CAAK,CAAQ,CAAA,EACpC,KAAqB,EAAM,KAAM,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CACpE,GApBE,EAAM,EAoBR,CACN,CACE,GAEJ,IAGN,EAAkB,qBAAuB,CAAC,IACzC,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,uBAAuB,IAAe,QAAU,UAAY,KACvE,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAW,OAAO,CAAG,EAC5D,eAAa,iBACb,aAAW,iBACX,gBAAc,UACd,gBAAe,IAAe,kBAE9B,EAAA,EAAA,KAAC,GAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CAC/B,CAAA,EACP,IAAe,UACd,EAAA,EAAA,MAAC,MAAD,CAAK,IAAK,GAAiB,UAAU,kBAAkB,KAAK,UAAU,aAAW,0BAAjF,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBAAoB,cAAY,gBAAO,OAAU,CAAA,EAC/D,CAAC,GAAK,IAAM,EAAG,KAAM,IAAK,CAAC,EAAE,IAAK,IACjC,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,gBAAe,KAAiB,EAChC,SAAU,EACV,UAAW,mBAAmB,KAAiB,EAAO,SAAW,KACjE,YAAe,GAAkB,CAAI,EACrC,UAAW,MAAsB,GAAkB,CAAI,CAAC,WAP1D,EASE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,IAAS,EAAI,SAAW,GAAG,EAAK,EAAS,CAAA,EAC/C,KAAiB,IAAQ,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CAC5D,GAVE,CAUF,CACN,CACE,GAEJ,IAGN,EAAkB,eAAiB,CAAC,IACnC,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,uBAAuB,GAAc,KAAO,GAAiB,GAAZ,YAC5D,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAW,SAAS,CAAG,EAC9D,eAAa,UACb,aAAW,gBACX,gBAAc,UACd,gBAAe,IAAe,oBAE9B,EAAA,EAAA,KAAC,GAAD,CAAQ,KAAM,GAAI,cAAY,MAAQ,CAAA,CAChC,CAAA,EACP,IAAe,YACd,EAAA,EAAA,MAAC,MAAD,CAAK,IAAK,GAAiB,UAAU,wBAAwB,KAAK,UAAU,aAAW,yBAAvF,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBAAoB,cAAY,gBAAO,SAAY,CAAA,EACjE,GAAU,IAAK,IACd,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,gBAAe,GAAc,KAAO,EAAE,GACtC,SAAU,EACV,UAAW,mBAAmB,GAAc,KAAO,EAAE,GAAK,SAAW,KACrE,YAAe,GAAoB,EAAE,EAAE,EACvC,UAAW,MAAsB,GAAoB,EAAE,EAAE,CAAC,WAP5D,EASE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,EAAE,KAAY,CAAA,EACpB,GAAc,KAAO,EAAE,KAAM,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CAChE,GAVE,EAAE,EAUJ,CACN,CACE,GAEJ,IAGN,GAAW,EAAQ,OAAS,GAAK,CAAC,IACjC,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,8BAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,uBAAuB,IAAe,UAAY,UAAY,KACzE,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAW,SAAS,CAAG,EAC9D,eAAa,gBACb,aAAW,gBACX,gBAAc,UACd,gBAAe,IAAe,oBAE9B,EAAA,EAAA,KAAC,GAAD,CAAmB,KAAM,GAAI,cAAY,MAAQ,CAAA,CAC3C,CAAA,EACP,IAAe,YACd,EAAA,EAAA,MAAC,MAAD,CAAK,IAAK,GAAiB,UAAU,kBAAkB,KAAK,UAAU,aAAW,yBAAjF,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,oBAAoB,cAAY,gBAAO,QAAW,CAAA,EAChE,EAAQ,KAAK,EAAG,KACf,EAAA,EAAA,MAAC,MAAD,CAEE,KAAK,SACL,gBAAe,KAAsB,EACrC,SAAU,EACV,UAAW,mBAAmB,KAAsB,EAAM,SAAW,KACrE,YAAe,GAAmB,CAAG,EACrC,UAAW,MAAsB,GAAmB,CAAG,CAAC,WAP1D,EASE,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,EAAE,KAAY,CAAA,EACpB,KAAsB,IAAO,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,CAChE,GAVE,CAUF,CACN,CACE,GAEJ,IAGN,EAAkB,WAAa,CAAC,IAC/B,EAAA,EAAA,KAAC,SAAD,CACE,UAAW,uBAAuB,GAAQ,UAAY,KACtD,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAU,CAAG,EACpD,eAAa,qBACb,aAAY,GAAQ,0BAA4B,2BAChD,eAAc,aAEd,EAAA,EAAA,KAAC,GAAD,CAAkB,KAAM,GAAI,cAAY,MAAQ,CAAA,CAC1C,CAAA,EAGT,KAAsB,WACrB,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,uBACV,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAW,CAAG,EACrD,eAAc,GAAY,SAAW,OACrC,aAAY,GAAY,SAAW,OACnC,eAAc,YAEb,GAAc,CACT,CAAA,EAGT,KACC,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,uBACV,QAAU,GAAM,CAAE,EAAE,gBAAgB,EAAG,GAAiB,CAAG,EAC3D,eAAc,IAAgB,GAAwB,kBAAoB,aAC1E,aAAY,IAAgB,GAAwB,kBAAoB,mBACxE,eAAc,IAAgB,YAE7B,IAAe,EAAA,EAAA,KAAC,GAAD,CAAU,KAAM,GAAI,cAAY,MAAQ,CAAA,GAAI,EAAA,EAAA,KAAC,GAAD,CAAU,KAAM,GAAI,cAAY,MAAQ,CAAA,CAC9F,CAAA,CAEP,GACF,GACF,GACL,CAAA,CAAA,CAED,IAON,SAAoB,CAKnB,IAAM,EAAa,EAAS,KAAK,GAAM,GAAmB,EAAG,WAAa,EAAkB,EAAG,OAAO,EAChG,EAAc,EAAa,EAAW,QAAU,EAAW,UAAY,EACvE,EAAc,EAChB,KAAK,IAAI,EAAG,EAAkB,EAAW,SAAS,EAClD,EAAuB,CAAe,EAE1C,OAAA,EAAA,EAAA,eACA,EAAA,EAAA,MAAC,MAAD,CAAK,UAAW,iBAAiB,GAAgB,GAAK,4BAAtD,EACE,EAAA,EAAA,KAAC,MAAD,CAAM,CAAA,GACN,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,yBAAf,EACE,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,aACV,YAAe,EAAY,EAAuB,KAAK,IAAI,EAAG,EAAuB,CAAe,EAAI,EAAE,CAAC,EAAG,CAAE,UAAW,gBAAiB,YAAa,EAAG,CAAC,EAC7J,aAAW,oBACX,SAAU,YAEV,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,CACnC,CAAA,GACR,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,yBACV,QAAS,GACT,aAAY,GAAU,SAAW,GAAgB,QAAU,gBAE1D,IACG,EAAA,EAAA,KAAC,GAAD,CAAW,KAAM,GAAI,cAAY,MAAQ,CAAA,EACzC,IACE,EAAA,EAAA,KAAC,EAAD,CAAO,KAAM,GAAI,cAAY,MAAQ,CAAA,GACrC,EAAA,EAAA,KAAC,GAAD,CAAM,KAAM,GAAI,cAAY,OAAO,MAAO,CAAE,WAAY,CAAE,CAAI,CAAA,CAC9D,CAAA,GACR,EAAA,EAAA,KAAC,SAAD,CACE,UAAU,aACV,YAAe,EAAY,EAAuB,KAAK,IAAI,EAAiB,EAAuB,CAAe,EAAI,EAAE,CAAC,EAAG,CAAE,UAAW,eAAgB,YAAa,EAAG,CAAC,EAC1K,aAAW,0BACX,SAAU,YAEV,EAAA,EAAA,KAAC,GAAD,CAAU,KAAM,GAAI,cAAY,MAAQ,CAAA,CAClC,CAAA,CACL,KACL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,yBAAf,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,kBAAkB,QAAS,aACxC,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,uBACV,MAAO,CAAE,MAAO,GAAG,EAAe,EAAc,EAAe,IAAM,EAAE,EAAG,CAC3E,CAAA,CACE,CAAA,GACL,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,uBAAf,CACG,EAAW,CAAW,EAAE,MAAI,EAAW,CAAW,CAChD,GACF,GACF,IACL,GAAU,SAAS,IACnB,CACF,GAAG,CACA,GAET,EC1qIa,GAAmB,8BAKhC,SAAS,EAAa,EAAgC,CACpD,MAAO,CACL,eAAgB,EAChB,eAAgB,kBAClB,CACF,CAOA,eAAsB,GACpB,EACA,EAAU,GACV,EAA0B,CAAC,EACD,CAC1B,IAAM,EAAM,MAAM,MAAM,GAAG,EAAQ,sBAAuB,CACxD,OAAQ,OACR,QAAS,EAAa,CAAS,EAC/B,KAAM,KAAK,UAAU,CAAE,SAAU,EAAQ,QAAS,CAAC,CACrD,CAAC,EACD,GAAI,CAAC,EAAI,GAAI,MAAU,MAAM,oBAAoB,EAAI,QAAQ,EAC7D,OAAO,MAAM,EAAI,KAAK,CACxB,CAqBA,eAAsB,GACpB,EACA,EACA,EAAU,GACK,CACf,GAAM,CAAE,YAAW,UAAS,aAAY,WAAU,sBAAuB,EACnE,EAAM,MAAM,MAAM,GAAG,EAAQ,+BAAgC,CACjE,OAAQ,OACR,QAAS,EAAa,CAAS,EAG/B,KAAM,KAAK,UAAU,CACnB,YACA,UACA,aACA,WAEA,mBACE,OAAO,GAAuB,UAAY,OAAO,SAAS,CAAkB,GAAK,EAAqB,EAClG,EACA,IAAA,EACR,CAAC,CACH,CAAC,EACD,GAAI,CAAC,EAAI,GAAI,MAAU,MAAM,yBAAyB,EAAI,QAAQ,CACpE,CAEA,eAAsB,GACpB,EACA,EACA,EACA,EACA,EAAU,GACK,CACf,IAAM,EAAU,CAAE,YAAW,cAAa,UAAS,EAC7C,EAAM,MAAM,MAAM,GAAG,EAAQ,mCAAoC,CACrE,OAAQ,OACR,QAAS,EAAa,CAAS,EAC/B,KAAM,KAAK,UAAU,CAAO,CAC9B,CAAC,EACD,GAAI,CAAC,EAAI,GAAI,MAAU,MAAM,qBAAqB,EAAI,QAAQ,CAChE,CAMA,eAAsB,GACpB,EACA,EACA,EAAc,EACd,EAAW,EACX,EAAU,GACK,CACf,IAAM,EAAM,MAAM,MAAM,GAAG,EAAQ,6BAA8B,CAC/D,OAAQ,OACR,QAAS,EAAa,CAAS,EAC/B,KAAM,KAAK,UAAU,CAAE,YAAW,cAAa,UAAS,CAAC,CAC3D,CAAC,EACD,GAAI,CAAC,EAAI,GAAI,MAAU,MAAM,uBAAuB,EAAI,QAAQ,CAClE,CAsBA,eAAsB,GACpB,EACA,EACA,EAAU,GACK,CACf,IAAM,EAAM,MAAM,MAAM,GAAG,EAAQ,wBAAyB,CAC1D,OAAQ,OACR,QAAS,EAAa,CAAS,EAC/B,KAAM,KAAK,UAAU,CAAO,CAC9B,CAAC,EACD,GAAI,CAAC,EAAI,GAAI,MAAU,MAAM,SAAS,EAAQ,UAAU,WAAW,EAAI,QAAQ,CACjF,CAGA,SAAgB,GACd,EACA,EACA,EAAc,EACd,EAAW,EACX,EAAU,GACJ,CACN,MAAM,GAAG,EAAQ,6BAA8B,CAC7C,OAAQ,OACR,QAAS,EAAa,CAAS,EAC/B,KAAM,KAAK,UAAU,CAAE,YAAW,cAAa,UAAS,CAAC,EACzD,UAAW,EACb,CAAC,CACH,CC7JA,SAAS,IAAyC,CAC5C,YAAO,OAAW,KACtB,OAAO,OAAO,aAAa,MAAQ,IAAA,EACrC,CAUA,IAAa,GAAb,KAAkC,6BAEb,mBACa,CAAC,wBAIN,oBACL,qBACC,wBAGG,oBACH,yBACK,iBAMR,mBAIsE,KAE1F,YAAmB,EAA4C,CAC7D,KAAK,SAAW,CAClB,CAGA,YAAmB,EAAyB,CAC1C,KAAK,SAAW,CAClB,CAGA,cAAqB,EAA6B,CAChD,KAAK,WAAa,CACpB,CAGA,qBAA4B,EAA6B,CAOvD,OANI,OAAO,EAAM,aAAgB,UAAY,CAAC,OAAO,MAAM,EAAM,WAAW,IAC1E,KAAK,gBAAkB,EAAM,aAE3B,OAAO,EAAM,UAAa,UAAY,CAAC,OAAO,MAAM,EAAM,QAAQ,IAAG,KAAK,aAAe,EAAM,UAC/F,OAAO,EAAM,gBAAmB,WAAU,KAAK,kBAAoB,EAAM,gBAErE,EAAM,UAAd,CACE,IAAK,OACE,KAAK,iBAKR,KAAK,KAAK,yBAA0B,EAAM,WAAW,GAJrD,KAAK,iBAAmB,GACxB,KAAK,YAAc,GACnB,KAAK,KAAK,wBAAyB,EAAM,YAAa,KAAK,QAAQ,GAIrE,MAEF,IAAK,QAGG,EAAM,SAAW,GAAK,EAAM,aAAe,EAAM,SAAW,KAChE,KAAK,KAAK,iBAAkB,EAAM,WAAW,EAE/C,MAEF,IAAK,QACH,KAAK,iBAAmB,GACxB,KAAK,YAAc,GACnB,KAAK,KAAK,yBAA0B,EAAM,YAAa,EAAM,gBAAkB,KAAK,iBAAiB,EACrG,MAEF,IAAK,OACH,KAAK,UAAU,EAAM,UAAY,KAAK,gBAAiB,EAAM,QAAU,EAAM,WAAW,EACxF,MAIF,IAAK,eACH,KAAK,UAAU,KAAK,IAAI,EAAG,EAAM,aAAe,EAAM,aAAe,GAAG,EAAG,EAAM,WAAW,EAC5F,MACF,IAAK,gBACH,KAAK,UAAU,EAAM,aAAe,EAAM,aAAe,IAAK,EAAM,WAAW,EAC/E,MAEF,IAAK,kBACH,KAAK,KAAK,0BAA2B,EAAM,WAAW,EACtD,MACF,IAAK,gBACH,KAAK,KAAK,2BAA4B,EAAM,WAAW,EACvD,MAEF,IAAK,iBAAkB,CACrB,IAAM,EAAY,EAAM,UACpB,GAAa,IAAc,KAAK,cAClC,KAAK,KAAK,yBAA0B,KAAK,YAAa,EAAW,EAAM,cAAgB,EAAK,EAC5F,KAAK,YAAc,GAErB,KACF,CAEA,IAAK,kBACH,GAAI,EAAM,kBAAoB,GAC5B,KAAK,KAAK,2BAA4B,KAAK,YAAY,EACvD,KAAK,aAAe,IAAA,OACf,CACL,IAAM,EAAa,EAAM,kBAAoB,GAC7C,KAAK,KAAK,0BAA2B,KAAK,aAAc,CAAU,EAClE,KAAK,aAAe,CACtB,CACA,MAEF,IAAK,qBAAsB,CACzB,IAAM,EAAU,EAAM,YAAc,GACpC,KAAK,KAAK,4BAA6B,KAAK,eAAgB,CAAO,EACnE,KAAK,eAAiB,EACtB,KACF,CAEA,IAAK,eAAgB,CACnB,IAAM,EAAU,EAAM,OAAS,EAG3B,IAAY,KAAK,YACnB,KAAK,KAAK,+BAAgC,KAAK,UAAW,CAAO,EACjE,KAAK,UAAY,GAEnB,KACF,CAEA,IAAK,mBACH,KAAK,KAAK,2BAA4B,GAAmB,CAAC,EAC1D,MACF,IAAK,kBACH,KAAK,KAAK,0BAA2B,GAAmB,CAAC,EACzD,MAEF,IAAK,cAAe,CAClB,IAAM,EAAQ,EAAM,aAAe,GAC7B,EAAY,EAAM,YAAc,EAAM,YACtC,EAAU,KAAK,WACf,EAAM,EAAQ,UACjB,GAAO,GAAS,EAAE,QAAU,GAAU,EAAE,YAAc,CACzD,EACM,EAAU,GAAO,EAAI,EAAQ,GAAO,IAAA,GAC1C,KAAK,KACH,2BACA,GAAO,EAAI,OAAO,CAAG,EAAI,IAAA,GACzB,GAAS,OAAS,EAClB,GAAS,WAAa,EACtB,GAAS,SAAW,CACtB,EACA,KACF,CAEA,IAAK,cAEa,EAAM,SAAW,EAAM,WAAa,UAAY,EAAM,eAAiB,WAC1E,KAAK,KAAK,iCAAkC,EAAM,WAAW,EACrE,KAAK,KAAK,gCAAiC,EAAM,WAAW,EACjE,MAGF,IAAK,QACH,KAAK,KACH,kCACA,EAAM,WAAa,KAAiC,eAA1B,OAAO,EAAM,SAAS,EAChD,EAAM,cAAgB,uBACtB,EAAM,eAAiB,EACzB,EACA,MAEF,IAAK,gBACH,KAAK,KACH,iCACA,EAAM,WAAa,EACnB,EAAM,cAAgB,gBACtB,EAAM,eAAiB,EACzB,EACA,KACJ,CACF,CAGA,iBAAwB,EAA2B,CAEjD,GADA,KAAK,gBAAkB,EACnB,KAAK,YAAa,CACpB,GAAM,CAAE,WAAU,iBAAkB,KAAK,YACzC,KAAK,YAAc,KACnB,KAAK,KAAK,yBAA0B,EAAU,GAAc,KAAK,IAAI,EAAI,GAAiB,GAAI,CAChG,CACA,KAAK,KAAK,6BAA8B,EAAa,KAAK,YAAY,CACxE,CAOA,iBAA+B,CACzB,KAAK,cAAgB,CAAC,KAAK,kBAAoB,KAAK,cACxD,KAAK,aAAe,GACpB,KAAK,KAAK,mBAAoB,KAAK,gBAAiB,KAAK,iBAAiB,EAC5E,CAEA,UAAkB,EAAkB,EAAsB,CACxD,GAAI,KAAK,YAAa,CAGpB,KAAK,YAAY,OAAS,EAC1B,MACF,CACA,KAAK,YAAc,CAAE,WAAU,SAAQ,cAAe,KAAK,IAAI,CAAE,EACjE,KAAK,KAAK,wBAAyB,CAAQ,CAC7C,CAIA,KACE,EACA,GAAG,EACG,CACN,IAAM,EAAW,KAAK,SAChB,EAAK,IAAW,GAClB,UAAO,GAAO,WAClB,GAAI,CACF,EAAuC,MAAM,EAAU,CAAI,CAC7D,OAAS,EAAK,CACZ,QAAQ,MAAM,wBAAwB,OAAO,CAAM,EAAE,mBAAoB,CAAG,CAC9E,CACF,CACF,ECrOM,GAAsD,CAC1D,UAA2B,mBAC3B,IAA2B,aAC3B,eAA2B,cAC3B,cAA2B,iBAC3B,QAA2B,UAC3B,WAA2B,aAC3B,iBAA2B,qBAC3B,mBAA2B,sBAC3B,cAA2B,iBAC3B,cAA2B,iBAC3B,0BAA2B,8BAC3B,eAA2B,iBAC7B,EAEA,SAAS,GAAe,EAA4C,CAClE,IAAM,EAAsB,CAC1B,cAA6B,GAC7B,iBAA6B,GAC7B,eAA6B,GAC7B,QAA6B,GAC7B,WAA6B,GAC7B,mBAA6B,GAC7B,oBAA6B,GAC7B,eAA6B,GAC7B,YAA6B,GAC7B,eAA6B,GAC7B,4BAA6B,GAC7B,gBAA6B,GAC7B,WAA6B,EAC/B,EACA,IAAK,GAAM,CAAC,EAAQ,KAAc,OAAO,QAAQ,EAAe,EAC1D,KAAU,IACZ,EAAM,GAAa,EAAY,IAGnC,OAAO,CACT,CAGA,SAAS,GAAoB,EAAgD,CAC3E,GAAI,CAAC,EAAc,MAAQ,IAAQ,EACnC,IAAM,EAAoD,CAAC,EAC3D,IAAK,IAAM,KAAS,EAAa,MAAM,GAAG,EAAG,CAC3C,GAAM,CAAC,EAAQ,GAAa,EAAM,MAAM,GAAG,EACvC,GAAQ,KAAK,GAAK,GAAW,KAAK,GACpC,EAAI,KAAK,CAAE,OAAQ,EAAO,KAAK,EAAG,UAAW,EAAU,KAAK,CAAE,CAAC,CAEnE,CACA,MAAQ,IAAgB,CACtB,IAAK,GAAM,CAAE,SAAQ,eAAe,EAClC,GAAI,EAAI,SAAS,CAAM,EACrB,GAAI,CACF,IAAM,EAAS,IAAI,IAAI,CAAG,EAC1B,MAAO,GAAG,IAAY,EAAO,WAAW,EAAO,QACjD,MAAQ,CACN,OAAO,EAAI,QAAQ,WAAW,IAAU,CAAS,CACnD,CAGJ,OAAO,CACT,CACF,CAEA,SAAS,EAAS,EAAqB,CACrC,IAAM,EAAW,EAAI,QAAQ,IAAK,EAAE,EAKpC,OAJI,EAAS,SAAW,EAIjB,GAHG,SAAS,EAAS,UAAU,EAAG,CAAC,EAAG,EAGnC,EAAE,IAFF,SAAS,EAAS,UAAU,EAAG,CAAC,EAAG,EAE7B,EAAE,IADR,SAAS,EAAS,UAAU,EAAG,CAAC,EAAG,EACvB,IAJY,cAKpC,CAEA,IAAM,GAAA,GAAmE,IAQzE,SAAS,GACP,EACA,EAC+D,CAC/D,OAAQ,EAAM,UAAd,CACE,IAAK,QACH,MAAO,CAAE,UAAW,QAAS,OAAQ,CAAC,CAAE,EAC1C,IAAK,OACH,MAAO,CACL,UAAW,OACX,OAAQ,CACN,SAAU,EAAM,UAAY,EAC5B,OAAQ,EAAM,QAAU,EAAM,aAAe,CAC/C,CACF,EACF,IAAK,OACH,MAAO,CAAE,UAAW,OAAQ,OAAQ,CAAE,QAAS,EAAM,SAAW,EAAK,CAAE,EACzE,IAAK,eACH,MAAO,CAAE,UAAW,eAAgB,OAAQ,CAAE,YAAa,EAAM,aAAe,EAAG,CAAE,EACvF,IAAK,gBACH,MAAO,CAAE,UAAW,gBAAiB,OAAQ,CAAE,YAAa,EAAM,aAAe,EAAG,CAAE,EACxF,IAAK,cACH,MAAO,CACL,UAAW,cACX,OAAQ,CACN,YAAa,EAAM,aAAe,GAClC,WAAY,EAAM,YAAc,CAClC,CACF,EACF,IAAK,kBACH,MAAO,CAAE,UAAW,kBAAmB,OAAQ,CAAE,eAAgB,EAAM,gBAAkB,CAAE,CAAE,EAC/F,IAAK,gBACH,MAAO,CAAE,UAAW,gBAAiB,OAAQ,CAAC,CAAE,EAClD,IAAK,iBAMH,MADI,CAAC,GAAiB,CAAC,EAAM,WAAa,IAAkB,EAAM,UAAkB,KAC7E,CACL,UAAW,iBACX,OAAQ,CAAE,KAAM,EAAe,GAAI,EAAM,SAAU,CACrD,EACF,IAAK,eACH,MAAO,CAAE,UAAW,eAAgB,OAAQ,CAAE,MAAO,EAAM,OAAS,CAAE,CAAE,EAC1E,IAAK,kBAKH,OAHI,EAAM,kBAAoB,GACrB,CAAE,UAAW,mBAAoB,OAAQ,CAAE,QAAS,EAAM,CAAE,EAE9D,CACL,UAAW,kBACX,OAAQ,CACN,SAAU,EAAM,kBAAoB,GACpC,QAAS,EACX,CACF,EACF,IAAK,qBACH,MAAO,CACL,UAAW,qBACX,OAAQ,CAAE,MAAO,EAAM,YAAc,EAAG,CAC1C,EACF,IAAK,aAEH,MAAO,CAAE,UAAW,aAAc,OAAQ,CAAE,QAAS,EAAM,eAAiB,SAAU,CAAE,EAC1F,IAAK,mBACH,MAAO,CAAE,UAAW,oBAAqB,OAAQ,CAAE,QAAS,EAAK,CAAE,EACrE,IAAK,kBACH,MAAO,CAAE,UAAW,oBAAqB,OAAQ,CAAE,QAAS,EAAM,CAAE,EACtE,IAAK,QAEH,MAAO,CACL,UAAW,eACX,OAAQ,CACN,KAAM,EAAM,WAAa,KAAiC,eAA1B,OAAO,EAAM,SAAS,EACtD,QAAS,EAAM,cAAgB,sBACjC,CACF,EACF,IAAK,gBACH,MAAO,CACL,UAAW,gBACX,OAAQ,CACN,KAAM,EAAM,UAAY,OAAO,EAAM,SAAS,EAAI,gBAClD,QAAS,EAAM,cAAgB,eACjC,CACF,EACF,QACE,OAAO,IACX,CACF,CAsEA,SAAgB,EAAgB,CAC9B,YACA,MACA,YACA,aACA,aAAa,UACb,iBACA,eACA,aACA,gBACA,eACA,QAAS,GACT,SACA,aACA,YAAW,GACX,SAAQ,GACR,kBAAiB,GACjB,kBAAiB,GACjB,gBACA,qBACA,uBAAsB,QACtB,oBAAoB,SACpB,iBACA,aACA,oBACA,WACA,sBACA,eACA,iBACuB,CACvB,GAAM,CAAC,GAAY,KAAA,EAAA,EAAA,UAAqC,CAAC,CAAC,EACpD,CAAC,GAAiB,KAAA,EAAA,EAAA,UAA+B,CAAC,EAMlD,CAAC,IAAA,EAAA,EAAA,cAAyB,IAAI,EAAsB,GAC1D,EAAA,EAAA,eAAgB,CACd,EAAO,YAAY,CAAQ,EAC3B,EAAO,YAAY,EAAQ,CAC7B,EAAG,CAAC,EAAQ,EAAU,EAAQ,CAAC,GAI/B,EAAA,EAAA,mBAAsB,EAAO,gBAAgB,EAAG,CAAC,CAAM,CAAC,EAGxD,IAAM,GAAkC,MAAM,QAAQ,CAAG,EACrD,EACA,CAAC,CAAE,MAAO,UAAW,IAAK,CAAI,CAAC,EAC7B,GAAoB,GAAkB,KAAkB,KAAO,GAAkB,IAAI,KAAO,GAM5F,CAAC,GAAW,KAAA,EAAA,EAAA,UAAwD,SAAS,EAC7E,CAAC,GAAiB,KAAA,EAAA,EAAA,UAAkD,IAAI,EACxE,CAAC,GAAY,KAAA,EAAA,EAAA,UAA0B,CAAC,EACxC,CAAC,GAAW,KAAA,EAAA,EAAA,UAA6C,SAAS,EAElE,IAAA,EAAA,EAAA,QAA4C,IAAI,EAChD,IAAA,EAAA,EAAA,QAAwB,EAAK,EAC7B,GAAA,EAAA,EAAA,QAAsB,EAAE,EACxB,IAAA,EAAA,EAAA,QAA2B,EAAK,EAGhC,IAAA,EAAA,EAAA,QAAyB,EAAK,EAE9B,IAAA,EAAA,EAAA,QAA8C,IAAA,EAAS,EAKvD,IAAA,EAAA,EAAA,QAAmC,EAAK,EAIxC,IAAA,EAAA,EAAA,QAA8B,EAA2B,EACzD,GAAA,EAAA,EAAA,QAAiE,IAAI,EACrE,IAAA,EAAA,EAAA,QAA4B,CAAC,EAC7B,IAAA,EAAA,EAAA,QAAuC,IAAI,EAC3C,IAAA,EAAA,EAAA,QAA2B,CAAE,YAAa,EAAG,SAAU,CAAE,CAAC,EAC1D,IAAA,EAAA,EAAA,YAAkD,CAAC,CAAC,EAEpD,GAAc,GAAU,MAAM,GAAG,EAAE,OAAO,OAAO,EACjD,GACJ,KACC,GAAY,GAAY,OAAS,IAAM,IAAI,QAAQ,YAAa,EAAE,GACnE,QAII,IAAA,EAAA,EAAA,QAAsB,EAAS,GACrC,EAAA,EAAA,eAAgB,CACd,GAAa,QAAU,EACzB,EAAG,CAAC,EAAS,CAAC,EAGd,IAAM,GAAA,EAAA,EAAA,iBACE,GAAoB,CAAY,EACtC,CAAC,CAAY,CACf,EAAE,EAEI,GAAW,EAAS,CAAU,GAGpC,EAAA,EAAA,eAAgB,CACd,SAAS,gBAAgB,MAAM,YAAY,kBAAmB,CAAU,EACxE,SAAS,gBAAgB,MAAM,YAAY,sBAAuB,EAAQ,CAC5E,EAAG,CAAC,EAAY,EAAQ,CAAC,GAIzB,EAAA,EAAA,eAAgB,CACd,IAAI,EAAY,GA8BhB,OA5BA,GAAQ,EAAW,EAAY,CAAE,SAAU,GAAa,OAAQ,CAAC,EAC9D,KAAK,GAAQ,CACZ,GAAI,EAAW,OACf,GAAW,QAAU,EACrB,GAAM,CAAE,WAAU,WAAU,4BAA6B,EAAK,OAAO,KAErE,GAAqB,QACnB,OAAO,GAA6B,UAAY,EAA2B,EACvE,EAA2B,IAC3B,GACN,IAAM,EAAkB,CACtB,QAAS,EACT,UAAW,GACX,SAAU,GAAe,CAAQ,EACjC,OAAQ,CAAE,kBAAmB,CAAE,CACjC,EACA,QAAQ,IAAI,wCAAyC,EAAI,QAAQ,EACjE,GAAmB,CAAG,EACtB,GAAa,OAAO,CACtB,CAAC,EACA,MAAM,GAAO,CACZ,GAAI,EAAW,OACf,QAAQ,MAAM,sCAAuC,CAAG,EACxD,IAAM,EAAO,EAAc,SAAW,GACtC,GAAa,qBAAqB,KAAK,CAAG,EAAI,OAAS,SAAS,EAChE,GAAa,OAAO,CACtB,CAAC,MAEU,CACX,EAAY,GACZ,AAEE,EAAkB,WADlB,aAAa,EAAkB,OAAO,EACV,MAE9B,GAAmB,QAAU,EAC7B,GAAe,QAAU,KACzB,GAAa,SAAS,EACtB,GAAmB,IAAI,EACvB,GAAkB,QAAU,GAC5B,EAAa,QAAU,GACvB,GAAgB,QAAU,GAC1B,GAAiB,QAAU,IAAA,EAC7B,CACF,EAAG,CAAC,EAAW,EAAY,EAAU,CAAC,EAGtC,IAAM,IAAA,EAAA,EAAA,iBAAwC,CACvC,EAAkB,UACvB,aAAa,EAAkB,OAAO,EACtC,EAAkB,QAAU,KACxB,GAAe,UAAY,OAC7B,GAAmB,SAAW,KAAK,IAAI,EAAI,GAAe,QAC1D,GAAe,QAAU,MAE7B,EAAG,CAAC,CAAC,EAGC,GAAA,EAAA,EAAA,iBAAuC,CAC3C,AAEE,EAAkB,WADlB,aAAa,EAAkB,OAAO,EACV,MAE9B,GAAmB,QAAU,EAC7B,GAAe,QAAU,IAC3B,EAAG,CAAC,CAAC,EAGC,IAAA,EAAA,EAAA,iBAAwC,CAC5C,GAAI,EAAkB,SAAW,CAAC,EAAa,QAAS,OACxD,GAAe,QAAU,KAAK,IAAI,EAClC,IAAM,EAAY,GAAqB,QAAU,GAAmB,QACpE,EAAkB,QAAU,eAAiB,CAC3C,EAAkB,QAAU,KAC5B,GAAmB,QAAU,EAC7B,GAAe,QAAU,KACzB,GAAM,CAAE,cAAa,YAAa,GAAkB,QACpD,QAAQ,IAAI,iCAAiC,GAAqB,QAAU,IAAK,0BAA0B,EAC3G,GAAc,EAAW,EAAa,QAAS,EAAa,EAAU,CAAU,EAAE,MAAM,GACtF,QAAQ,KAAK,0BAA4B,EAAc,OAAO,CAChE,EACA,GAAuB,QAAQ,CACjC,EAAG,CAAS,CACd,EAAG,CAAC,EAAW,CAAU,CAAC,GAE1B,EAAA,EAAA,eAAgB,CACd,GAAuB,QAAU,EACnC,EAAG,CAAC,EAAmB,CAAC,EAQxB,IAAM,IAAA,EAAA,EAAA,QAA4B,EAAI,GACtC,EAAA,EAAA,eAAgB,CACd,GAAI,GAAmB,QAAS,CAC9B,GAAmB,QAAU,GAC7B,MACF,CACA,GAA0B,QAAU,GAGpC,GAAiB,QAAU,IAAA,GAC3B,GAAoB,CACtB,EAAG,CAAC,GAAiB,EAAmB,CAAC,GAGzC,EAAA,EAAA,eAAgB,CACd,IAAM,MAAqB,CAEzB,GADA,EAAO,gBAAgB,EACnB,CAAC,EAAa,QAAS,OAC3B,GAAgB,QAAU,GAC1B,EAAmB,EACnB,GAAM,CAAE,cAAa,YAAa,GAAkB,QACpD,GAAoB,EAAW,EAAa,QAAS,EAAa,EAAU,CAAU,CACxF,EAEA,OADA,OAAO,iBAAiB,eAAgB,CAAY,MACvC,CACX,OAAO,oBAAoB,eAAgB,CAAY,EACvD,EAAmB,CACrB,CACF,EAAG,CAAC,EAAW,EAAY,EAAoB,CAAM,CAAC,EAEtD,IAAM,GAAA,EAAA,EAAA,aAA+B,GAA0B,CAC7D,IAAM,EAAI,EA2DV,GAzDI,EAAM,cAAgB,IAAA,KACxB,GAAkB,QAAU,CAAE,YAAa,EAAM,YAAa,SAAU,EAAM,QAAS,GAGrF,EAAM,YAAc,iBAAmB,CAAC,GAAkB,UAC5D,GAAkB,QAAU,GAC5B,GAAgB,QAAU,GAC1B,EAAa,QAAU,OAAO,EAAE,WAAc,SAAW,EAAE,UAAY,GAIvE,GACE,EACA,CACE,UAAW,EAAa,QACxB,WACA,WAAY,GACZ,SAAU,GAAa,QACvB,mBAAoB,EAAM,QAC5B,EACA,CACF,EAAE,MAAM,GACN,QAAQ,KAAK,8BAAgC,EAAc,OAAO,CACpE,EACK,GAAe,SAClB,GAAoB,GAOtB,EAAM,YAAc,QACpB,GAAkB,SAClB,CAAC,GAAe,SAChB,CAAC,GAA0B,SAE3B,GAAoB,EAGlB,EAAM,YAAc,SACtB,GAAoB,EAIlB,EAAM,YAAc,mBACtB,GAAoB,EAMlB,EAAM,YAAc,iBAAmB,GAAkB,SAAW,CAAC,GAAe,UACtF,GAA0B,QAAU,GACpC,GAAoB,GAGlB,EAAM,YAAc,UACtB,EAAmB,EACf,EAAa,SAAS,CACxB,GAAgB,QAAU,GAC1B,GAAM,CAAE,cAAa,YAAa,GAAkB,QACpD,GAAW,EAAW,EAAa,QAAS,EAAa,EAAU,CAAU,EAAE,MAAM,GACnF,QAAQ,KAAK,4BAA8B,EAAc,OAAO,CAClE,CACF,CAMF,GAAI,GAAkB,SAAW,EAAa,SAAW,CAAC,GAAgB,QAAS,CACjF,IAAM,EAAY,GAAoB,EAAO,GAAiB,OAAO,EACjE,GACF,GACE,EACA,CACE,UAAW,EAAU,UACrB,UAAW,EAAa,QACxB,YAAa,EAAM,aAAe,EAClC,WACA,WAAY,GACZ,GAAG,EAAU,MACf,EACA,CACF,EAAE,MAAM,GACN,QAAQ,KAAK,eAAe,EAAU,UAAU,UAAY,EAAc,OAAO,CACnF,CAEJ,CAII,EAAM,YAAc,kBAAoB,EAAM,YAChD,GAAiB,QAAU,EAAM,WAGnC,QAAQ,IAAI,kBAAmB,CAAK,EACpC,EAAO,qBAAqB,CAAK,EACjC,KAAmB,CAAK,CAC1B,EAAG,CAAC,EAAW,EAAY,GAAS,GAAO,GAAqB,GAAqB,EAAoB,GAAkB,CAAM,CAAC,EAE5H,IAAA,EAAA,EAAA,aAAmC,GAAoB,CAC3D,GAAe,QAAU,EACrB,GACF,GAAoB,EACpB,QAAQ,IAAI,iDAAiD,IAEzD,GAAkB,SAAS,GAAoB,EACnD,QAAQ,IAAI,gDAAgD,EAEhE,EAAG,CAAC,GAAqB,EAAmB,CAAC,EAEvC,IAAA,EAAA,EAAA,aAAsC,GAAsB,CAChE,EAAO,cAAc,CAAM,EAC3B,GAAc,CAAM,EACpB,KAAqB,CAAM,CAC7B,EAAG,CAAC,GAAoB,CAAM,CAAC,EAEzB,GAAA,EAAA,EAAA,aAAgC,GAAwB,CAC5D,EAAO,iBAAiB,CAAW,EACnC,KAAe,CAAW,CAC5B,EAAG,CAAC,GAAc,CAAM,CAAC,EAEzB,GAAI,KAAc,UAChB,OACE,EAAA,EAAA,MAAC,MAAD,CAAK,MAAO,CACV,MAAO,OAAQ,YAAa,SAAU,WAAY,OAAQ,aAAc,EACxE,QAAS,OAAQ,WAAY,SAAU,eAAgB,QACzD,WAHA,EAIE,EAAA,EAAA,KAAC,QAAD,CAAA,SAAQ,wDAAgE,CAAA,GACxE,EAAA,EAAA,KAAC,MAAD,CAAK,MAAO,CACV,MAAO,GAAI,OAAQ,GAAI,aAAc,MACrC,OAAQ,kBAAkB,GAAS,QACnC,gBAAiB,EACjB,iBAAkB,QAAQ,GAAS,QACnC,OAAQ,6BAA6B,GAAS,SAC9C,UAAW,kCACb,CAAI,CAAA,CACD,IAIT,GAAI,KAAc,QAAS,CACzB,IAAM,EAAS,KAAc,OAC7B,OACE,EAAA,EAAA,KAAC,MAAD,CAAK,MAAO,CACV,MAAO,OAAQ,YAAa,SAAU,WAAY,UAAW,aAAc,EAC3E,QAAS,OAAQ,WAAY,SAAU,eAAgB,SAAU,cAAe,SAAU,IAAK,EACjG,YACE,EAAA,EAAA,KAAC,GAAD,CACE,MAAO,EAAS,qBAAuB,uBACvC,SACE,EACI;kCACA;mBAEN,YAAe,GAAc,GAAK,EAAI,CAAC,CACxC,CAAA,CACE,CAAA,CAET,CAEA,OACE,EAAA,EAAA,KAAC,GAAD,CACE,IAAK,GACL,QAAS,GACT,kBAAmB,GACnB,eAAiB,GAAgB,GAAmB,CAAG,EAC5C,YACC,cACA,aACI,iBAChB,oBAAqB,GACT,aACL,SACI,aACD,YACH,SACS,kBACA,kBACF,gBACM,qBACC,uBACF,oBACJ,iBACJ,aACG,eACC,gBACN,WACO,iBAChB,iBAAkB,EAClB,gBAAiB,GACjB,mBAAoB,GACP,eACb,aAAc,CACf,CAAA,CAEL,CC1pBA,SAAgB,GAAa,EAAwB,EAAoC,CACvF,IAAM,GAAA,EAAA,GAAA,YAAkB,CAAS,EAC7B,EAAe,EAInB,OAFA,EAAK,QAAA,EAAA,EAAA,eAAqB,EAA0C,CAAY,CAAC,EAE1E,CACL,OAAO,EAAgC,CACrC,EAAe,CAAE,GAAG,EAAc,GAAG,CAAS,EAC9C,EAAK,QAAA,EAAA,EAAA,eAAqB,EAA0C,CAAY,CAAC,CACnF,EACA,SAAU,CACR,EAAK,QAAQ,CACf,CACF,CACF"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode"],"sources":["../../ui-controls/src/ErrorScreen.tsx","../../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.mjs","../../../node_modules/lucide-react/dist/esm/defaultAttributes.mjs","../../../node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.mjs","../../../node_modules/lucide-react/dist/esm/context.mjs","../../../node_modules/lucide-react/dist/esm/Icon.mjs","../../../node_modules/lucide-react/dist/esm/createLucideIcon.mjs","../../../node_modules/lucide-react/dist/esm/icons/airplay.mjs","../../../node_modules/lucide-react/dist/esm/icons/captions.mjs","../../../node_modules/lucide-react/dist/esm/icons/cast.mjs","../../../node_modules/lucide-react/dist/esm/icons/check.mjs","../../../node_modules/lucide-react/dist/esm/icons/chevron-left.mjs","../../../node_modules/lucide-react/dist/esm/icons/external-link.mjs","../../../node_modules/lucide-react/dist/esm/icons/gauge.mjs","../../../node_modules/lucide-react/dist/esm/icons/languages.mjs","../../../node_modules/lucide-react/dist/esm/icons/list-video.mjs","../../../node_modules/lucide-react/dist/esm/icons/maximize.mjs","../../../node_modules/lucide-react/dist/esm/icons/minimize.mjs","../../../node_modules/lucide-react/dist/esm/icons/pause.mjs","../../../node_modules/lucide-react/dist/esm/icons/picture-in-picture.mjs","../../../node_modules/lucide-react/dist/esm/icons/play.mjs","../../../node_modules/lucide-react/dist/esm/icons/rotate-ccw.mjs","../../../node_modules/lucide-react/dist/esm/icons/rotate-cw.mjs","../../../node_modules/lucide-react/dist/esm/icons/volume-1.mjs","../../../node_modules/lucide-react/dist/esm/icons/volume-2.mjs","../../../node_modules/lucide-react/dist/esm/icons/volume-x.mjs","../../../node_modules/lucide-react/dist/esm/icons/volume.mjs","../../ui-controls/src/WebVideoPlayer.tsx","../src/api.ts","../src/delegate.ts","../src/BossVideoPlayer.tsx","../src/create-player.ts"],"sourcesContent":["interface ErrorScreenProps {\r\n title?: string;\r\n subtitle?: string;\r\n onRetry: () => void;\r\n}\r\n\r\nexport function ErrorScreen({\r\n title = 'Something Went Wrong',\r\n subtitle = 'We encountered an error while playing this video.\\nPlease try again.',\r\n onRetry,\r\n}: ErrorScreenProps) {\r\n return (\r\n <>\r\n <div style={{\r\n width: 80, height: 80, borderRadius: '50%',\r\n background: 'rgba(255,255,255,0.08)',\r\n display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,\r\n }}>\r\n <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"rgba(255,255,255,0.85)\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/>\r\n <line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\r\n </svg>\r\n </div>\r\n <div style={{ textAlign: 'center', padding: '0 24px' }}>\r\n <p style={{ color: '#ffffff', fontSize: 22, fontWeight: 700, margin: '0 0 8px', letterSpacing: '-0.2px' }}>\r\n {title}\r\n </p>\r\n <p style={{ color: 'rgba(255,255,255,0.5)', fontSize: 14, margin: 0, lineHeight: 1.6, whiteSpace: 'pre-line' }}>\r\n {subtitle}\r\n </p>\r\n </div>\r\n <button\r\n onClick={onRetry}\r\n style={{\r\n display: 'flex', alignItems: 'center', gap: 10,\r\n background: '#ffffff', color: '#09090e',\r\n border: 'none', borderRadius: 10, padding: '14px 32px',\r\n fontSize: 16, fontWeight: 700, cursor: 'pointer', letterSpacing: '-0.1px',\r\n }}\r\n >\r\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 .49-3.51\"/>\r\n </svg>\r\n Retry Now\r\n </button>\r\n </>\r\n );\r\n}\r\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst mergeClasses = (...classes) => classes.filter((className, index, array) => {\n return Boolean(className) && className.trim() !== \"\" && array.indexOf(className) === index;\n}).join(\" \").trim();\n\nexport { mergeClasses };\n//# sourceMappingURL=mergeClasses.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n\nexport { toKebabCase };\n//# sourceMappingURL=toKebabCase.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst toCamelCase = (string) => string.replace(\n /^([A-Z])|[\\s-_]+(\\w)/g,\n (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()\n);\n\nexport { toCamelCase };\n//# sourceMappingURL=toCamelCase.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { toCamelCase } from './toCamelCase.mjs';\n\nconst toPascalCase = (string) => {\n const camelCase = toCamelCase(string);\n return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);\n};\n\nexport { toPascalCase };\n//# sourceMappingURL=toPascalCase.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nvar defaultAttributes = {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: 24,\n height: 24,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 2,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\"\n};\n\nexport { defaultAttributes as default };\n//# sourceMappingURL=defaultAttributes.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst hasA11yProp = (props) => {\n for (const prop in props) {\n if (prop.startsWith(\"aria-\") || prop === \"role\" || prop === \"title\") {\n return true;\n }\n }\n return false;\n};\n\nexport { hasA11yProp };\n//# sourceMappingURL=hasA11yProp.mjs.map\n","\"use strict\";\n\"use client\";\n/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { createContext, useContext, useMemo, createElement } from 'react';\n\nconst LucideContext = createContext({});\nfunction LucideProvider({\n children,\n size,\n color,\n strokeWidth,\n absoluteStrokeWidth,\n className\n}) {\n const value = useMemo(\n () => ({\n size,\n color,\n strokeWidth,\n absoluteStrokeWidth,\n className\n }),\n [size, color, strokeWidth, absoluteStrokeWidth, className]\n );\n return createElement(LucideContext.Provider, { value }, children);\n}\nconst useLucideContext = () => useContext(LucideContext);\n\nexport { LucideProvider, useLucideContext };\n//# sourceMappingURL=context.mjs.map\n","\"use strict\";\n\"use client\";\n/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { forwardRef, createElement } from 'react';\nimport defaultAttributes from './defaultAttributes.mjs';\nimport { hasA11yProp } from './shared/src/utils/hasA11yProp.mjs';\nimport { mergeClasses } from './shared/src/utils/mergeClasses.mjs';\nimport { useLucideContext } from './context.mjs';\n\nconst Icon = forwardRef(\n ({ color, size, strokeWidth, absoluteStrokeWidth, className = \"\", children, iconNode, ...rest }, ref) => {\n const {\n size: contextSize = 24,\n strokeWidth: contextStrokeWidth = 2,\n absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,\n color: contextColor = \"currentColor\",\n className: contextClass = \"\"\n } = useLucideContext() ?? {};\n const calculatedStrokeWidth = absoluteStrokeWidth ?? contextAbsoluteStrokeWidth ? Number(strokeWidth ?? contextStrokeWidth) * 24 / Number(size ?? contextSize) : strokeWidth ?? contextStrokeWidth;\n return createElement(\n \"svg\",\n {\n ref,\n ...defaultAttributes,\n width: size ?? contextSize ?? defaultAttributes.width,\n height: size ?? contextSize ?? defaultAttributes.height,\n stroke: color ?? contextColor,\n strokeWidth: calculatedStrokeWidth,\n className: mergeClasses(\"lucide\", contextClass, className),\n ...!children && !hasA11yProp(rest) && { \"aria-hidden\": \"true\" },\n ...rest\n },\n [\n ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),\n ...Array.isArray(children) ? children : [children]\n ]\n );\n }\n);\n\nexport { Icon as default };\n//# sourceMappingURL=Icon.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { forwardRef, createElement } from 'react';\nimport { mergeClasses } from './shared/src/utils/mergeClasses.mjs';\nimport { toKebabCase } from './shared/src/utils/toKebabCase.mjs';\nimport { toPascalCase } from './shared/src/utils/toPascalCase.mjs';\nimport Icon from './Icon.mjs';\n\nconst createLucideIcon = (iconName, iconNode) => {\n const Component = forwardRef(\n ({ className, ...props }, ref) => createElement(Icon, {\n ref,\n iconNode,\n className: mergeClasses(\n `lucide-${toKebabCase(toPascalCase(iconName))}`,\n `lucide-${iconName}`,\n className\n ),\n ...props\n })\n );\n Component.displayName = toPascalCase(iconName);\n return Component;\n};\n\nexport { createLucideIcon as default };\n//# sourceMappingURL=createLucideIcon.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1\",\n key: \"ns4c3b\"\n }\n ],\n [\"path\", { d: \"m12 15 5 6H7Z\", key: \"14qnn2\" }]\n];\nconst Airplay = createLucideIcon(\"airplay\", __iconNode);\n\nexport { __iconNode, Airplay as default };\n//# sourceMappingURL=airplay.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"rect\", { width: \"18\", height: \"14\", x: \"3\", y: \"5\", rx: \"2\", ry: \"2\", key: \"12ruh7\" }],\n [\"path\", { d: \"M7 15h4M15 15h2M7 11h2M13 11h4\", key: \"1ueiar\" }]\n];\nconst Captions = createLucideIcon(\"captions\", __iconNode);\n\nexport { __iconNode, Captions as default };\n//# sourceMappingURL=captions.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6\", key: \"3zrzxg\" }],\n [\"path\", { d: \"M2 12a9 9 0 0 1 8 8\", key: \"g6cvee\" }],\n [\"path\", { d: \"M2 16a5 5 0 0 1 4 4\", key: \"1y1dii\" }],\n [\"line\", { x1: \"2\", x2: \"2.01\", y1: \"20\", y2: \"20\", key: \"xu2jvo\" }]\n];\nconst Cast = createLucideIcon(\"cast\", __iconNode);\n\nexport { __iconNode, Cast as default };\n//# sourceMappingURL=cast.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [[\"path\", { d: \"M20 6 9 17l-5-5\", key: \"1gmf2c\" }]];\nconst Check = createLucideIcon(\"check\", __iconNode);\n\nexport { __iconNode, Check as default };\n//# sourceMappingURL=check.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [[\"path\", { d: \"m15 18-6-6 6-6\", key: \"1wnfg3\" }]];\nconst ChevronLeft = createLucideIcon(\"chevron-left\", __iconNode);\n\nexport { __iconNode, ChevronLeft as default };\n//# sourceMappingURL=chevron-left.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M15 3h6v6\", key: \"1q9fwt\" }],\n [\"path\", { d: \"M10 14 21 3\", key: \"gplh6r\" }],\n [\"path\", { d: \"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\", key: \"a6xqqp\" }]\n];\nconst ExternalLink = createLucideIcon(\"external-link\", __iconNode);\n\nexport { __iconNode, ExternalLink as default };\n//# sourceMappingURL=external-link.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"m12 14 4-4\", key: \"9kzdfg\" }],\n [\"path\", { d: \"M3.34 19a10 10 0 1 1 17.32 0\", key: \"19p75a\" }]\n];\nconst Gauge = createLucideIcon(\"gauge\", __iconNode);\n\nexport { __iconNode, Gauge as default };\n//# sourceMappingURL=gauge.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"m5 8 6 6\", key: \"1wu5hv\" }],\n [\"path\", { d: \"m4 14 6-6 2-3\", key: \"1k1g8d\" }],\n [\"path\", { d: \"M2 5h12\", key: \"or177f\" }],\n [\"path\", { d: \"M7 2h1\", key: \"1t2jsx\" }],\n [\"path\", { d: \"m22 22-5-10-5 10\", key: \"don7ne\" }],\n [\"path\", { d: \"M14 18h6\", key: \"1m8k6r\" }]\n];\nconst Languages = createLucideIcon(\"languages\", __iconNode);\n\nexport { __iconNode, Languages as default };\n//# sourceMappingURL=languages.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M21 5H3\", key: \"1fi0y6\" }],\n [\"path\", { d: \"M10 12H3\", key: \"1ulcyk\" }],\n [\"path\", { d: \"M10 19H3\", key: \"108z41\" }],\n [\n \"path\",\n {\n d: \"M15 12.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997a1 1 0 0 1-1.517-.86z\",\n key: \"ms4nik\"\n }\n ]\n];\nconst ListVideo = createLucideIcon(\"list-video\", __iconNode);\n\nexport { __iconNode, ListVideo as default };\n//# sourceMappingURL=list-video.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M8 3H5a2 2 0 0 0-2 2v3\", key: \"1dcmit\" }],\n [\"path\", { d: \"M21 8V5a2 2 0 0 0-2-2h-3\", key: \"1e4gt3\" }],\n [\"path\", { d: \"M3 16v3a2 2 0 0 0 2 2h3\", key: \"wsl5sc\" }],\n [\"path\", { d: \"M16 21h3a2 2 0 0 0 2-2v-3\", key: \"18trek\" }]\n];\nconst Maximize = createLucideIcon(\"maximize\", __iconNode);\n\nexport { __iconNode, Maximize as default };\n//# sourceMappingURL=maximize.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M8 3v3a2 2 0 0 1-2 2H3\", key: \"hohbtr\" }],\n [\"path\", { d: \"M21 8h-3a2 2 0 0 1-2-2V3\", key: \"5jw1f3\" }],\n [\"path\", { d: \"M3 16h3a2 2 0 0 1 2 2v3\", key: \"198tvr\" }],\n [\"path\", { d: \"M16 21v-3a2 2 0 0 1 2-2h3\", key: \"ph8mxp\" }]\n];\nconst Minimize = createLucideIcon(\"minimize\", __iconNode);\n\nexport { __iconNode, Minimize as default };\n//# sourceMappingURL=minimize.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"rect\", { x: \"14\", y: \"3\", width: \"5\", height: \"18\", rx: \"1\", key: \"kaeet6\" }],\n [\"rect\", { x: \"5\", y: \"3\", width: \"5\", height: \"18\", rx: \"1\", key: \"1wsw3u\" }]\n];\nconst Pause = createLucideIcon(\"pause\", __iconNode);\n\nexport { __iconNode, Pause as default };\n//# sourceMappingURL=pause.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M2 10h6V4\", key: \"zwrco\" }],\n [\"path\", { d: \"m2 4 6 6\", key: \"ug085t\" }],\n [\"path\", { d: \"M21 10V7a2 2 0 0 0-2-2h-7\", key: \"git5jr\" }],\n [\"path\", { d: \"M3 14v2a2 2 0 0 0 2 2h3\", key: \"1f7fh3\" }],\n [\"rect\", { x: \"12\", y: \"14\", width: \"10\", height: \"7\", rx: \"1\", key: \"1wjs3o\" }]\n];\nconst PictureInPicture = createLucideIcon(\"picture-in-picture\", __iconNode);\n\nexport { __iconNode, PictureInPicture as default };\n//# sourceMappingURL=picture-in-picture.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\",\n key: \"10ikf1\"\n }\n ]\n];\nconst Play = createLucideIcon(\"play\", __iconNode);\n\nexport { __iconNode, Play as default };\n//# sourceMappingURL=play.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\", key: \"1357e3\" }],\n [\"path\", { d: \"M3 3v5h5\", key: \"1xhq8a\" }]\n];\nconst RotateCcw = createLucideIcon(\"rotate-ccw\", __iconNode);\n\nexport { __iconNode, RotateCcw as default };\n//# sourceMappingURL=rotate-ccw.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\"path\", { d: \"M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8\", key: \"1p45f6\" }],\n [\"path\", { d: \"M21 3v5h-5\", key: \"1q7to0\" }]\n];\nconst RotateCw = createLucideIcon(\"rotate-cw\", __iconNode);\n\nexport { __iconNode, RotateCw as default };\n//# sourceMappingURL=rotate-cw.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z\",\n key: \"uqj9uw\"\n }\n ],\n [\"path\", { d: \"M16 9a5 5 0 0 1 0 6\", key: \"1q6k2b\" }]\n];\nconst Volume1 = createLucideIcon(\"volume-1\", __iconNode);\n\nexport { __iconNode, Volume1 as default };\n//# sourceMappingURL=volume-1.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z\",\n key: \"uqj9uw\"\n }\n ],\n [\"path\", { d: \"M16 9a5 5 0 0 1 0 6\", key: \"1q6k2b\" }],\n [\"path\", { d: \"M19.364 18.364a9 9 0 0 0 0-12.728\", key: \"ijwkga\" }]\n];\nconst Volume2 = createLucideIcon(\"volume-2\", __iconNode);\n\nexport { __iconNode, Volume2 as default };\n//# sourceMappingURL=volume-2.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z\",\n key: \"uqj9uw\"\n }\n ],\n [\"line\", { x1: \"22\", x2: \"16\", y1: \"9\", y2: \"15\", key: \"1ewh16\" }],\n [\"line\", { x1: \"16\", x2: \"22\", y1: \"9\", y2: \"15\", key: \"5ykzw1\" }]\n];\nconst VolumeX = createLucideIcon(\"volume-x\", __iconNode);\n\nexport { __iconNode, VolumeX as default };\n//# sourceMappingURL=volume-x.mjs.map\n","/**\n * @license lucide-react v1.16.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.mjs';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z\",\n key: \"uqj9uw\"\n }\n ]\n];\nconst Volume = createLucideIcon(\"volume\", __iconNode);\n\nexport { __iconNode, Volume as default };\n//# sourceMappingURL=volume.mjs.map\n","import React, { useRef, useState, useEffect, useMemo, useCallback } from 'react';\r\nimport { createPortal } from 'react-dom';\r\nimport { ErrorScreen } from './ErrorScreen';\r\nimport Hls from 'hls.js';\r\nimport {\r\n Play,\r\n Pause,\r\n RotateCcw,\r\n RotateCw,\r\n Volume2,\r\n VolumeX,\r\n Volume1,\r\n Volume,\r\n Subtitles,\r\n Maximize,\r\n Minimize,\r\n Cast,\r\n Airplay,\r\n ListVideo,\r\n Check,\r\n ChevronLeft,\r\n PictureInPicture,\r\n Languages,\r\n Gauge,\r\n ExternalLink,\r\n} from 'lucide-react';\r\nimport { VideoPlayerSDK } from '@goboss/core-player';\r\nimport { FirebaseAnalyticsProvider } from '@goboss/analytics';\r\nimport type { FirebaseConfig } from '@goboss/analytics';\r\nimport type { VideoQuality, Chapter, KeyMoment, SubtitleTrack, AnalyticsEvent, AnalyticsSessionSnapshot, SourceItem } from '@goboss/types';\r\nimport './player.css';\r\n\r\n// How long to wait for a switched-to source to actually start playing before\r\n// assuming the format is unsupported and falling back to the first source.\r\nconst SOURCE_FALLBACK_TIMEOUT_MS = 10000;\r\n\r\n// Guard so the \"missing stylesheet\" hint is logged at most once per session,\r\n// even if several players mount.\r\nlet cssWarningShown = false;\r\n\r\n// Self-contained stylesheet for the custom Document Picture-in-Picture window.\r\n// The PiP window is a separate `document` with no access to the host page's\r\n// stylesheets, so its controls need their own rules injected directly.\r\nconst PIP_STYLES = `\r\n html, body { margin: 0; padding: 0; height: 100%; background: #000; overflow: hidden; }\r\n * { box-sizing: border-box; }\r\n .gb-pip-video { width: 100%; height: 100%; object-fit: contain; display: block; background: #000; }\r\n .gb-pip-overlay {\r\n position: absolute; inset: 0; z-index: 2;\r\n display: flex; flex-direction: column; justify-content: space-between;\r\n font-family: system-ui, -apple-system, sans-serif; color: #fff;\r\n opacity: 0; transition: opacity 0.15s ease;\r\n background: linear-gradient(to bottom, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0) 25%, rgba(0,0,0,0) 65%, rgba(0,0,0,0.55) 100%);\r\n }\r\n .gb-pip-overlay:hover, .gb-pip-overlay.gb-pip-paused { opacity: 1; }\r\n .gb-pip-center {\r\n flex: 1; display: flex; align-items: center; justify-content: center; gap: 28px;\r\n }\r\n .gb-pip-btn {\r\n background: rgba(0,0,0,0.35); border: none; color: #fff; border-radius: 50%;\r\n width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;\r\n cursor: pointer; padding: 0;\r\n }\r\n .gb-pip-btn:hover { background: rgba(0,0,0,0.55); }\r\n .gb-pip-btn.gb-pip-play { width: 56px; height: 56px; }\r\n .gb-pip-bottom { padding: 6px 10px 8px; display: flex; flex-direction: column; gap: 4px; }\r\n .gb-pip-progress { position: relative; height: 4px; border-radius: 2px; background: rgba(255,255,255,0.3); cursor: pointer; }\r\n .gb-pip-progress-fill { position: absolute; inset: 0; width: 0%; border-radius: 2px; background: #fff; }\r\n .gb-pip-time { font-size: 11px; font-variant-numeric: tabular-nums; opacity: 0.9; }\r\n`;\r\n\r\nconst extractTitleFromManifest = (text: string): string | null => {\r\n const titleTag = text.match(/#EXT-X-TITLE:(.+)/);\r\n if (titleTag?.[1]?.trim()) return titleTag[1].trim();\r\n const sessionData = text.match(/#EXT-X-SESSION-DATA:[^\\n]*DATA-ID=\"[^\"]*title[^\"]*\"[^\\n]*VALUE=\"([^\"]+)\"/i);\r\n if (sessionData?.[1]?.trim()) return sessionData[1].trim();\r\n return null;\r\n};\r\n\r\nconst parseID3Title = (data: Uint8Array): string | null => {\r\n if (!data || data.length < 10) return null;\r\n if (data[0] !== 0x49 || data[1] !== 0x44 || data[2] !== 0x33) return null;\r\n const version = data[3];\r\n const totalSize = ((data[6] & 0x7f) << 21) | ((data[7] & 0x7f) << 14) | ((data[8] & 0x7f) << 7) | (data[9] & 0x7f);\r\n const end = Math.min(10 + totalSize, data.length);\r\n let offset = 10;\r\n while (offset + 10 <= end) {\r\n const id = String.fromCharCode(data[offset], data[offset+1], data[offset+2], data[offset+3]);\r\n const frameSize = version >= 4\r\n ? ((data[offset+4] & 0x7f) << 21) | ((data[offset+5] & 0x7f) << 14) | ((data[offset+6] & 0x7f) << 7) | (data[offset+7] & 0x7f)\r\n : (data[offset+4] << 24) | (data[offset+5] << 16) | (data[offset+6] << 8) | data[offset+7];\r\n if (frameSize <= 0 || offset + 10 + frameSize > end) break;\r\n if (id === 'TIT2') {\r\n const enc = data[offset + 10];\r\n const raw = data.slice(offset + 11, offset + 10 + frameSize);\r\n const decoded = enc === 1 || enc === 2\r\n ? new TextDecoder('utf-16').decode(raw)\r\n : new TextDecoder(enc === 3 ? 'utf-8' : 'latin1').decode(raw);\r\n const result = decoded.replace(/\\0/g, '').trim();\r\n if (result) return result;\r\n }\r\n offset += 10 + frameSize;\r\n }\r\n return null;\r\n};\r\n\r\n\r\nconst HdIcon = ({ size = 20 }: { size?: number }) => (\r\n <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <rect x=\"2\" y=\"5\" width=\"20\" height=\"14\" rx=\"3\" />\r\n <path d=\"M7 9v6M7 12h3M10 9v6\" />\r\n <path d=\"M14 9h1.5a3 3 0 0 1 0 6H14z\" />\r\n </svg>\r\n);\r\n\r\n// Simple \"HD\" badge — used for the multi-source menu\r\nconst MultiSourceHdIcon = ({ size = 20 }: { size?: number }) => (\r\n <svg width={size} height={size} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\r\n <rect x=\"2\" y=\"5\" width=\"20\" height=\"14\" rx=\"3\" />\r\n <path d=\"M7 9v6M7 12h3M10 9v6\" />\r\n <path d=\"M14 9h1.5a3 3 0 0 1 0 6H14z\" />\r\n </svg>\r\n);\r\n\r\nconst hexToRgb = (hex: string): string => {\r\n const cleanHex = hex.replace('#', '');\r\n if (cleanHex.length === 6) {\r\n const r = parseInt(cleanHex.substring(0, 2), 16);\r\n const g = parseInt(cleanHex.substring(2, 4), 16);\r\n const b = parseInt(cleanHex.substring(4, 6), 16);\r\n return `${r}, ${g}, ${b}`;\r\n } else if (cleanHex.length === 3) {\r\n const r = parseInt(cleanHex[0] + cleanHex[0], 16);\r\n const g = parseInt(cleanHex[1] + cleanHex[1], 16);\r\n const b = parseInt(cleanHex[2] + cleanHex[2], 16);\r\n return `${r}, ${g}, ${b}`;\r\n }\r\n return '99, 102, 241';\r\n};\r\n\r\nconst languageNameMap: Record<string, string> = {\r\n en: 'English',\r\n eng: 'English',\r\n fr: 'French',\r\n fre: 'French',\r\n fra: 'French',\r\n es: 'Spanish',\r\n spa: 'Spanish',\r\n de: 'German',\r\n ger: 'German',\r\n deu: 'German',\r\n it: 'Italian',\r\n ita: 'Italian',\r\n pt: 'Portuguese',\r\n por: 'Portuguese',\r\n ja: 'Japanese',\r\n zh: 'Chinese',\r\n ko: 'Korean',\r\n ru: 'Russian',\r\n ar: 'Arabic',\r\n hi: 'Hindi',\r\n ta: 'Tamil',\r\n tam: 'Tamil',\r\n};\r\n\r\nconst formatLanguageName = (value: string): string | null => {\r\n if (!value) return null;\r\n const normalized = value.trim().toLowerCase();\r\n const prefixMatch = normalized.match(/^(en|eng|fr|fre|fra|es|spa|de|ger|deu|it|ita|pt|por|ja|zh|ko|ru|ar|hi|ta|tam)/);\r\n if (prefixMatch && languageNameMap[prefixMatch[0]]) {\r\n return languageNameMap[prefixMatch[0]];\r\n }\r\n for (const [key, name] of Object.entries(languageNameMap)) {\r\n if (normalized.includes(key)) {\r\n return name;\r\n }\r\n }\r\n if (/english|anglais|ingles/i.test(normalized)) return 'English';\r\n if (/french|français/i.test(normalized)) return 'French';\r\n if (/spanish|español/i.test(normalized)) return 'Spanish';\r\n if (/german|deutsch/i.test(normalized)) return 'German';\r\n if (/italian|italiano/i.test(normalized)) return 'Italian';\r\n if (/portuguese|português/i.test(normalized)) return 'Portuguese';\r\n if (/japanese|nihongo/i.test(normalized)) return 'Japanese';\r\n if (/chinese|中文/i.test(normalized)) return 'Chinese';\r\n if (/korean|한국어/i.test(normalized)) return 'Korean';\r\n if (/tamil|தமிழ்/i.test(normalized)) return 'Tamil';\r\n if (/russian|русский/i.test(normalized)) return 'Russian';\r\n if (/arabic|العربية/i.test(normalized)) return 'Arabic';\r\n if (/hindi|हिन्दी/i.test(normalized)) return 'Hindi';\r\n return null;\r\n};\r\n\r\nconst getLanguageLabel = (track: { label: string; lang: string }): string => {\r\n if (track.lang) {\r\n const normalized = formatLanguageName(track.lang);\r\n if (normalized) return normalized;\r\n }\r\n\r\n if (track.label) {\r\n const label = track.label.trim();\r\n const parts = label.split(/[#\\d\\.\\s\\-_/|:]+/).filter(Boolean);\r\n for (let i = parts.length - 1; i >= 0; i -= 1) {\r\n const candidate = formatLanguageName(parts[i]);\r\n if (candidate) return candidate;\r\n }\r\n const cleaned = label.replace(/^[#\\d\\.\\s]+/, '').trim();\r\n return formatLanguageName(cleaned) || cleaned;\r\n }\r\n\r\n return 'Unknown';\r\n};\r\n\r\nconst dedupeSubtitleTracks = (tracks: SubtitleTrack[]): SubtitleTrack[] => {\r\n const seen = new Set<string>();\r\n return tracks.filter((track) => {\r\n const label = getLanguageLabel(track).toLowerCase();\r\n const lang = (track.lang || '').trim().toLowerCase();\r\n const key = `${label}|${lang}`;\r\n if (seen.has(key)) {\r\n return false;\r\n }\r\n seen.add(key);\r\n return true;\r\n });\r\n};\r\n\r\ninterface SubtitleStyle {\r\n size?: 'small' | 'medium' | 'large';\r\n background?: 'none' | 'dim' | 'solid';\r\n color?: string;\r\n}\r\n\r\ninterface OverlayAdItem {\r\n availId: string;\r\n startTimeSeconds: number;\r\n durationSeconds: number;\r\n imageUrl: string;\r\n imageWidth: number;\r\n imageHeight: number;\r\n title: string;\r\n clickThrough: string;\r\n clickTrackingUrl: string;\r\n impressionBeacons: string[];\r\n position: string;\r\n dismissible: boolean;\r\n}\r\n\r\nconst parseHHMMSS = (t: string): number => {\r\n const parts = (t ?? '').split(':').map(Number);\r\n return (parts[0] || 0) * 3600 + (parts[1] || 0) * 60 + (parts[2] || 0);\r\n};\r\n\r\nconst isWebUrl = (url: string | undefined | null): url is string => {\r\n if (!url) return false;\r\n try {\r\n const { protocol } = new URL(url, window.location.href);\r\n return protocol === 'http:' || protocol === 'https:';\r\n } catch {\r\n return false;\r\n }\r\n};\r\n\r\nconst parseSkipOffsetSeconds = (value: unknown): number | null => {\r\n if (typeof value === 'number') return Number.isFinite(value) && value >= 0 ? value : null;\r\n if (typeof value !== 'string') return null;\r\n const trimmed = value.trim();\r\n if (!trimmed) return null;\r\n if (/^\\d+:\\d{1,2}:\\d{1,2}(\\.\\d+)?$/.test(trimmed)) return parseHHMMSS(trimmed);\r\n const seconds = Number(trimmed);\r\n return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;\r\n};\r\n\r\nconst AD_METADATA_POLL_DELAYS_MS = [0, 1000, 2000, 3000, 5000, 8000];\r\n\r\nfunction pollAdMetadata<T>(\r\n url: string,\r\n extract: (data: any) => T | null,\r\n onResolved: (value: T) => void\r\n): () => void {\r\n let cancelled = false;\r\n let timer: ReturnType<typeof setTimeout> | undefined;\r\n\r\n const attempt = (index: number) => {\r\n timer = setTimeout(() => {\r\n if (cancelled) return;\r\n fetch(url)\r\n .then(r => r.json())\r\n .then(data => {\r\n if (cancelled) return;\r\n const value = extract(data);\r\n if (value !== null) {\r\n onResolved(value);\r\n return;\r\n }\r\n if (index + 1 < AD_METADATA_POLL_DELAYS_MS.length) attempt(index + 1);\r\n })\r\n .catch(() => {\r\n if (cancelled) return;\r\n if (index + 1 < AD_METADATA_POLL_DELAYS_MS.length) attempt(index + 1);\r\n });\r\n }, AD_METADATA_POLL_DELAYS_MS[index]);\r\n };\r\n\r\n attempt(0);\r\n return () => {\r\n cancelled = true;\r\n if (timer) clearTimeout(timer);\r\n };\r\n}\r\n\r\ninterface SeekAttribution {\r\n eventType: 'fast_forward' | 'fast_backward' | 'key_moments';\r\n skipSeconds?: number;\r\n momentTitle?: string;\r\n momentTime?: number;\r\n}\r\n\r\ninterface AdCtaInfo {\r\n text: string;\r\n url: string;\r\n title?: string;\r\n description?: string;\r\n}\r\n\r\ninterface AdPodItem {\r\n startTimeSeconds: number;\r\n endTimeSeconds: number;\r\n durationSeconds: number;\r\n skippable: boolean;\r\n skipOffsetSeconds: number;\r\n cta: AdCtaInfo | null;\r\n podIndex: number;\r\n podTotal: number;\r\n}\r\n\r\ninterface WebVideoPlayerProps {\r\n src: string;\r\n autoplay?: boolean;\r\n muted?: boolean;\r\n chapters?: Chapter[];\r\n keyMoments?: KeyMoment[];\r\n subtitles?: SubtitleTrack[];\r\n watermark?: string;\r\n themeColor?: string;\r\n drmConfig?: any;\r\n firebaseConfig?: FirebaseConfig;\r\n onAnalyticsEvent?: (event: AnalyticsEvent) => void;\r\n onAdBreakChange?: (active: boolean) => void;\r\n onKeyMomentsLoaded?: (keyMoments: KeyMoment[]) => void;\r\n enableControls?: boolean;\r\n enableChromecast?: boolean;\r\n enableKeyMoments?: boolean;\r\n enableSubtitles?: boolean;\r\n enableQuality?: boolean;\r\n enablePlaybackSpeed?: boolean;\r\n enablePiP?: boolean;\r\n enableFullscreen?: boolean;\r\n autoFullscreen?: boolean;\r\n watchProgressInterval?: number; // in seconds, default 30\r\n subtitleStyle?: SubtitleStyle;\r\n /**\r\n * Pre-resolved entitlement package from BossVideoPlayer.\r\n * When provided, features are determined exclusively by this package (API mode).\r\n * When absent, features fall back to explicit props with true defaults (standalone mode).\r\n */\r\n resolvedEntitlement?: { package: string; is_active: boolean; features: Record<string, boolean>; limits: { streaming_minutes: number } } | null;\r\n licenseKey?: string | null;\r\n title?: string;\r\n chaptersDisplayMode?: 'strip' | 'panel';\r\n volumeControlType?: 'slider' | 'button';\r\n urlTransformer?: (url: string) => string;\r\n pauseAdOnTabSwitch?: boolean;\r\n enableSkipAd?: boolean;\r\n skipAdAfterSeconds?: number;\r\n overlayAdUrl?: string;\r\n adTrackingUrl?: string;\r\n videoId?: string;\r\n sources?: SourceItem[];\r\n activeSourceIndex?: number;\r\n onSourceChange?: (index: number) => void;\r\n /** Seek to this position (in seconds) as soon as the video is ready. Use for resume-playback. */\r\n initialTime?: number;\r\n /** Called on every timeupdate with the current playback position in seconds. Use to persist watch progress. */\r\n onTimeUpdate?: (currentTime: number) => void;\r\n}\r\n\r\ntype SourceSwitchPrefs = {\r\n playbackRate: number;\r\n volume: number;\r\n muted: boolean;\r\n /** Subtitle track id, or 'off'. Kept alongside lang so external tracks (stable ids) match first. */\r\n subtitleId: string;\r\n subtitleLang: string | null;\r\n audioLang: string | null;\r\n audioLabel: string | null;\r\n /** Height of a manually pinned rendition; null when the viewer was on Auto. */\r\n qualityHeight: number | null;\r\n};\r\n\r\nexport const WebVideoPlayer: React.FC<WebVideoPlayerProps> = ({\r\n src,\r\n autoplay = false,\r\n muted = false,\r\n chapters = [],\r\n keyMoments,\r\n subtitles = [],\r\n watermark,\r\n themeColor = '#6366f1',\r\n drmConfig,\r\n firebaseConfig,\r\n videoId,\r\n onAnalyticsEvent,\r\n onAdBreakChange,\r\n onKeyMomentsLoaded,\r\n enableControls = true,\r\n enableChromecast,\r\n enableKeyMoments,\r\n enableSubtitles,\r\n enableQuality,\r\n enablePlaybackSpeed,\r\n enablePiP,\r\n enableFullscreen = true,\r\n autoFullscreen = false,\r\n watchProgressInterval = 30,\r\n subtitleStyle = {},\r\n resolvedEntitlement,\r\n licenseKey,\r\n title,\r\n chaptersDisplayMode = 'strip',\r\n volumeControlType = 'slider',\r\n urlTransformer,\r\n pauseAdOnTabSwitch = true,\r\n enableSkipAd = false,\r\n skipAdAfterSeconds = 5,\r\n overlayAdUrl,\r\n adTrackingUrl,\r\n sources,\r\n activeSourceIndex = 0,\r\n onSourceChange,\r\n initialTime,\r\n onTimeUpdate,\r\n}) => {\r\n const transformUrl = urlTransformer ?? ((url: string) => url);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const firebaseProviderRef = useRef<FirebaseAnalyticsProvider | null>(null);\r\n const hasAutoFullscreenedRef = useRef(false);\r\n const seekbarRef = useRef<HTMLDivElement>(null);\r\n const seekbarWidthRef = useRef(0);\r\n const canvasRef = useRef<HTMLCanvasElement>(null);\r\n const hiddenVideoRef = useRef<HTMLVideoElement | null>(null);\r\n const hiddenHlsRef = useRef<Hls | null>(null);\r\n const hoverSeekTimer = useRef<number | null>(null);\r\n const isScrubbing = useRef(false);\r\n const isSeeking = useRef(false);\r\n const seekTargetTime = useRef<number | null>(null);\r\n const preSeekPositionRef = useRef<number | null>(null);\r\n const seekGuardTimeout = useRef<number | null>(null);\r\n const castButtonRef = useRef<HTMLDivElement>(null);\r\n const toggleFullscreenRef = useRef<() => void>(() => {});\r\n const adSkipCoverRef = useRef<HTMLDivElement | null>(null);\r\n const adSkipCoverUpRef = useRef(false);\r\n // Force-mute bookkeeping for the ad-skip cover: the cover only hides pixels, so\r\n // audio from the covered frames would still leak. Holds the user's muted state\r\n // while force-muted; null = not force-muted.\r\n const coverMuteRef = useRef<boolean | null>(null);\r\n const muteCoverAudio = (vid: HTMLVideoElement | null | undefined) => {\r\n if (!vid || coverMuteRef.current !== null) return;\r\n coverMuteRef.current = vid.muted;\r\n vid.muted = true;\r\n };\r\n const restoreCoverAudio = (vid: HTMLVideoElement | null | undefined) => {\r\n if (coverMuteRef.current === null) return;\r\n if (vid) vid.muted = coverMuteRef.current;\r\n coverMuteRef.current = null;\r\n };\r\n const triggerAdSkipRef = useRef<((endTime: number, withinPod?: boolean) => void) | null>(null);\r\n // True while an in-flight ad skip is expected to land on another ad of the same break.\r\n const adSkipWithinPodRef = useRef(false);\r\n const pendingSeekTargetRef = useRef<number | null>(null);\r\n const performSeekRef = useRef<((time: number, attribution?: SeekAttribution) => void) | null>(null);\r\n const sourceChangeSeekRef = useRef<number | null>(null);\r\n const sourceChangePrefsRef = useRef<SourceSwitchPrefs | null>(null);\r\n const sourceChangeFallbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n // Tracks an in-flight source switch so the new player's effect can start a\r\n // \"did playback actually start?\" watchdog. Survives the player re-init because it's a ref.\r\n const pendingSourceSwitchRef = useRef<{ targetIdx: number } | null>(null);\r\n // Latest sources / onSourceChange, readable from inside the player effect closure\r\n // (whose deps are [src, watermark, retryKey], so it would otherwise capture stale values).\r\n const sourcesRef = useRef(sources);\r\n const onSourceChangeRef = useRef(onSourceChange);\r\n const initialSeekAppliedRef = useRef(false);\r\n // Resume-playback cover: isolated from the ad-skip machinery so it never touches\r\n // isSeeking / seekTargetTime / adSkipCoverRef. Raised at mount when initialTime > 0 so\r\n // frame 0 is never shown, then dropped once playback actually lands at the resume point.\r\n const resumeCoverRef = useRef<HTMLDivElement | null>(null);\r\n const resumeSeekTargetRef = useRef<number | null>(null);\r\n const resumeGuardTimeout = useRef<number | null>(null);\r\n const resumeAdRecheckRef = useRef<number | null>(null);\r\n // Last confirmed playback position from timeupdate. Revert point for the native-seek\r\n // guard: seeks that bypass Media Session (e.g. Chrome's PiP window scrubber seeks the\r\n // <video> element directly) are snapped back here before being blocked or re-routed.\r\n const lastStableTimeRef = useRef(0);\r\n // Custom Document Picture-in-Picture window (Chromium only). Mirrors the `pipWindow`\r\n // state into a ref so effect cleanups (which run inside closures captured at a different\r\n // render) can always reach the live window instead of a stale null.\r\n const pipWindowRef = useRef<Window | null>(null);\r\n\r\n const [isMobile, setIsMobile] = useState(false);\r\n const isMobileRef = useRef(false);\r\n const lastTapRef = useRef<{ time: number; x: number } | null>(null);\r\n const [doubleTapSide, setDoubleTapSide] = useState<'left' | 'right' | null>(null);\r\n\r\n // One-time, console-only hint if the SDK stylesheet wasn't imported. The\r\n // `.sdk-player-container` rule sets `overflow: hidden` and `border-radius: 12px`,\r\n // neither of which is applied inline — so if both read as browser defaults after\r\n // paint, the consumer almost certainly forgot `import 'boss-web-player-sdk/css'`.\r\n // Browser-only, runs once, never throws — it cannot affect rendering or playback.\r\n useEffect(() => {\r\n if (typeof window === 'undefined' || cssWarningShown) return;\r\n const raf = window.requestAnimationFrame(() => {\r\n const el = containerRef.current;\r\n if (!el) return;\r\n const cs = window.getComputedStyle(el);\r\n const stylesheetMissing = cs.overflow !== 'hidden' && cs.borderTopLeftRadius === '0px';\r\n if (stylesheetMissing) {\r\n cssWarningShown = true;\r\n console.warn(\r\n \"[boss-web-player-sdk] Player styles appear to be missing — the player will look broken/unstyled.\\n\" +\r\n \"Import the stylesheet once at your app entry point:\\n\" +\r\n \" import 'boss-web-player-sdk/css';\"\r\n );\r\n }\r\n });\r\n return () => window.cancelAnimationFrame(raf);\r\n }, []);\r\n\r\n useEffect(() => {\r\n const el = containerRef.current;\r\n if (!el) return;\r\n const ro = new ResizeObserver(entries => {\r\n for (const entry of entries) {\r\n setIsMobile(entry.contentRect.width <= 768);\r\n }\r\n });\r\n ro.observe(el);\r\n setIsMobile(el.getBoundingClientRect().width <= 768);\r\n return () => ro.disconnect();\r\n }, []);\r\n\r\n const [player, setPlayer] = useState<VideoPlayerSDK | null>(null);\r\n const [manifestReady, setManifestReady] = useState(false);\r\n\r\n // Entitlement is always pre-resolved by BossVideoPlayer before this component mounts.\r\n // When resolvedEntitlement is provided (API mode): features come exclusively from the API.\r\n // When absent (standalone/test mode): features come from explicit props or true defaults.\r\n const entitlementPackage = resolvedEntitlement ?? null;\r\n const hasEntitlement = entitlementPackage != null;\r\n const ef = entitlementPackage?.features ?? {};\r\n\r\n // DRM: block playback if the plan requires a license key but none was provided.\r\n const isEntitlementBlocked = hasEntitlement\r\n ? Boolean(ef.drm_protection && !licenseKey)\r\n : false;\r\n\r\n // API-gated features — controlled by the init API when a playerKey is used.\r\n // In standalone mode (no resolvedEntitlement), fall back to explicit props or true defaults.\r\n const effectiveFeatures = {\r\n enableSubtitles: hasEntitlement ? Boolean(ef.subtitle_support) : (typeof enableSubtitles === 'boolean' ? enableSubtitles : true),\r\n enableChromecast: hasEntitlement ? Boolean(ef.chromecast) : (typeof enableChromecast === 'boolean' ? enableChromecast : true),\r\n enableKeyMoments: hasEntitlement ? Boolean(ef.key_moments) : (typeof enableKeyMoments === 'boolean' ? enableKeyMoments : true),\r\n enableQuality: hasEntitlement ? Boolean(ef.multiple_resolution) : (typeof enableQuality === 'boolean' ? enableQuality : true),\r\n enablePlaybackSpeed: hasEntitlement ? Boolean(ef.playback_speed) : (typeof enablePlaybackSpeed === 'boolean' ? enablePlaybackSpeed : true),\r\n enablePiP: hasEntitlement ? Boolean(ef.picture_in_picture) : (typeof enablePiP === 'boolean' ? enablePiP : true),\r\n allowAirplay: hasEntitlement ? Boolean(ef.airplay) : false,\r\n allowMultipleAudio: hasEntitlement ? Boolean(ef.multiple_audio) : true,\r\n m3u8_playback: hasEntitlement ? Boolean(ef.m3u8_playback) : true,\r\n drm_protection: hasEntitlement ? Boolean(ef.drm_protection) : false,\r\n };\r\n const [playerError, setPlayerError] = useState(false);\r\n const [retryKey, setRetryKey] = useState(0);\r\n const [isPlaying, setIsPlaying] = useState(false);\r\n const [currentTime, setCurrentTime] = useState(0);\r\n const [duration, setDuration] = useState(0);\r\n const [volume, setVolume] = useState(1);\r\n const [isMuted, setIsMuted] = useState(false);\r\n const [isBuffering, setIsBuffering] = useState(false);\r\n const [isFullscreen, setIsFullscreen] = useState(false);\r\n const [isSimulatedFullscreen, setIsSimulatedFullscreen] = useState(false);\r\n // Tracks whether the container was in mobile layout when fullscreen was entered.\r\n // Keeps mobile controls active during landscape rotation without affecting desktop fullscreen.\r\n const [wasMobileOnFullscreenEnter, setWasMobileOnFullscreenEnter] = useState(false);\r\n const useMobileLayout = isMobile || ((isFullscreen || isSimulatedFullscreen) && wasMobileOnFullscreenEnter);\r\n const [isPiP, setIsPiP] = useState(false);\r\n // Non-null while a custom Document Picture-in-Picture window is open. Distinct from\r\n // `isPiP` (which also covers the native video-element PiP fallback) because the custom\r\n // window needs its own React portal target and its own on-page video placeholder.\r\n const [pipWindow, setPipWindow] = useState<Window | null>(null);\r\n const [isEnded, setIsEnded] = useState(false);\r\n const [adBreaks, setAdBreaks] = useState<Array<{ startTime: number; endTime: number }>>([]);\r\n const adBreaksRef = useRef<Array<{ startTime: number; endTime: number }>>([]);\r\n const watchedAdBreaksRef = useRef<Set<number>>(new Set());\r\n // Stable identifier for the underlying content. Every entry in `sources` is the same video\r\n // in different formats sharing one SSAI session (identical ad-break positions), so a format\r\n // switch — which only changes the active `src` — must be treated as a single continuous\r\n // session. Keying watched-ad state on the *set of source URLs* rather than the active `src`\r\n // preserves it across a switch, while still resetting on a genuine content change (a\r\n // different `sources` set, or a new single `src` in standalone mode).\r\n const contentKey = sources && sources.length > 0 ? sources.map(s => s.url).join('|') : src;\r\n // React state mirror of watchedAdBreaksRef — drives the cue-marker filter in render.\r\n // Reset during render (not in an effect) using the \"update during render\" pattern so\r\n // no setState call is ever needed inside a useEffect body. State and ref are reset together\r\n // and only when contentKey changes, so switching formats keeps watched ads skipped/hidden.\r\n const [watchedAdBreaksSet, setWatchedAdBreaksSet] = useState<Set<number>>(new Set());\r\n const [watchedAdBreaksContentKey, setWatchedAdBreaksContentKey] = useState(contentKey);\r\n // Stable raw-analytics session identity, keyed on contentKey like the watched-ad\r\n // state. Every player re-init (source switch, unsupported-format fallback, error\r\n // retry) constructs a fresh AnalyticsModule that generates a new internal sessionId\r\n // and re-fires session_start — but a format switch is the same viewing session, so\r\n // the first module's id is adopted, re-stamped onto later events, and the duplicate\r\n // session_start events are swallowed before reaching Firebase / onAnalyticsEvent\r\n // (see the analyticsCallback wrapper). This mirrors what BossVideoPlayer already\r\n // does for the Events API, so raw-event consumers see the same session continuity.\r\n const analyticsSessionIdRef = useRef<string | null>(null);\r\n const analyticsSessionStartFiredRef = useRef(false);\r\n // Session-scoped analytics counters, snapshotted from the outgoing player at teardown\r\n // and seeded into its successor so raw-event consumers (Firebase / onAnalyticsEvent)\r\n // see continuous totalWatchTime/streamingTime/buffering counters and no re-fired\r\n // quartile milestones across a re-init. Tagged with the contentKey the snapshot came\r\n // from: the player effect's cleanup runs AFTER the render that resets contentKey-scoped\r\n // state, so a plain ref would be repopulated with the old content's counters on a\r\n // genuine content change — the tag lets the next init discard a stale snapshot instead.\r\n const analyticsSeedRef = useRef<{ key: string; snapshot: AnalyticsSessionSnapshot } | null>(null);\r\n if (watchedAdBreaksContentKey !== contentKey) {\r\n setWatchedAdBreaksContentKey(contentKey);\r\n setWatchedAdBreaksSet(new Set());\r\n watchedAdBreaksRef.current = new Set();\r\n // Genuine content change — a new raw session identity is correct here.\r\n analyticsSessionIdRef.current = null;\r\n analyticsSessionStartFiredRef.current = false;\r\n }\r\n const activeAdBreakStartRef = useRef<number | null>(null);\r\n const wasAdPausedByVisibilityRef = useRef(false);\r\n\r\n // Overlay (non-linear) ads\r\n const [overlayAds, setOverlayAds] = useState<OverlayAdItem[]>([]);\r\n const [activeOverlay, setActiveOverlay] = useState<OverlayAdItem | null>(null);\r\n const overlayAdsRef = useRef<OverlayAdItem[]>([]);\r\n const activeOverlayRef = useRef<OverlayAdItem | null>(null);\r\n const dismissedOverlayIdsRef = useRef<Set<string>>(new Set());\r\n\r\n // Per-ad skip/CTA info from avails metadata — one entry per individual ad within a pod\r\n const [adPodItems, setAdPodItems] = useState<AdPodItem[]>([]);\r\n const adPodItemsRef = useRef<AdPodItem[]>([]);\r\n const adChipRef = useRef<HTMLDivElement | null>(null);\r\n const [adChipSize, setAdChipSize] = useState<{ w: number; h: number }>({ w: 0, h: 0 });\r\n // Break-level (avail) start/end ranges derived from the tracking URL, fed into core-player\r\n const [availBreaks, setAvailBreaks] = useState<Array<{ startTime: number; endTime: number }>>([]);\r\n\r\n const getActiveAdWindow = (t: number): { start: number; end: number } | null => {\r\n const pod = adPodItemsRef.current.find(a => t >= a.startTimeSeconds && t < a.endTimeSeconds);\r\n if (pod) return { start: pod.startTimeSeconds, end: pod.endTimeSeconds };\r\n const ab = adBreaksRef.current.find(b => t >= b.startTime && t < b.endTime);\r\n return ab ? { start: ab.startTime, end: ab.endTime } : null;\r\n };\r\n\r\n // Total ad duration summed from all detected breaks\r\n const totalAdDuration = useMemo(\r\n () => adBreaks.reduce((sum, ab) => sum + (ab.endTime - ab.startTime), 0),\r\n [adBreaks]\r\n );\r\n\r\n // Content-only duration (video duration minus all spliced ad time).\r\n // Falls back to raw duration until adBreaks are loaded so the display is never blank.\r\n const contentDuration = duration > 0 ? Math.max(0, duration - totalAdDuration) : 0;\r\n const displayDuration = adBreaks.length > 0 && contentDuration > 0 ? contentDuration : duration;\r\n\r\n // Convert raw video time → content-only time (subtracts elapsed ad time before `t`).\r\n // During an ad the returned value is frozen at the content timestamp where the ad started.\r\n const videoTimeToContentTime = useCallback((t: number): number => {\r\n let ct = t;\r\n for (const ab of adBreaks) {\r\n if (t >= ab.endTime) {\r\n ct -= (ab.endTime - ab.startTime);\r\n } else if (t >= ab.startTime) {\r\n ct -= (t - ab.startTime);\r\n }\r\n }\r\n return Math.max(0, ct);\r\n }, [adBreaks]);\r\n\r\n // Convert content-only time → raw video time (adds back ad durations).\r\n // Used to translate a seekbar click position into the actual seek target.\r\n const contentTimeToVideoTime = useCallback((ct: number): number => {\r\n let vt = ct;\r\n for (const ab of adBreaks) {\r\n if (vt >= ab.startTime) {\r\n vt += (ab.endTime - ab.startTime);\r\n }\r\n }\r\n return vt;\r\n }, [adBreaks]);\r\n\r\n // Lists & Selections\r\n const [qualities, setQualities] = useState<VideoQuality[]>([]);\r\n const [activeQuality, setActiveQuality] = useState<VideoQuality>({\r\n id: -1,\r\n height: 0,\r\n width: 0,\r\n bitrate: 0,\r\n label: 'Auto',\r\n });\r\n const [subtitleText, setSubtitleText] = useState('');\r\n const [activeSubtitleTrack, setActiveSubtitleTrack] = useState('off');\r\n const [availableSubtitles, setAvailableSubtitles] = useState<SubtitleTrack[]>(dedupeSubtitleTracks(subtitles));\r\n const [audioTracks, setAudioTracks] = useState<Array<{ id: number; label: string; lang: string }>>([]);\r\n const [activeAudioTrack, setActiveAudioTrack] = useState<number>(0);\r\n\r\n // Stable key — avoids infinite loops when parent passes inline [] on every render\r\n const subtitlesKey = subtitles.map(s => s.id).join(',');\r\n\r\n // Sync external subtitles prop dynamically and merge with discovered tracks\r\n useEffect(() => {\r\n if (player) {\r\n const playerTracks = player.subtitles.getTracks();\r\n const merged = [...subtitles, ...playerTracks.filter(t => !subtitles.some(s => s.id === t.id))];\r\n setAvailableSubtitles(dedupeSubtitleTracks(merged));\r\n } else {\r\n setAvailableSubtitles(dedupeSubtitleTracks(subtitles));\r\n }\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [subtitlesKey, player]);\r\n\r\n const [activeChapter, setActiveChapter] = useState<{ chapter: KeyMoment | null; index: number }>({\r\n chapter: null,\r\n index: -1,\r\n });\r\n\r\n const [localKeyMoments, setLocalKeyMoments] = useState<KeyMoment[]>(keyMoments || chapters || []);\r\n\r\n // UI Dropdowns\r\n const [activeMenu, setActiveMenu] = useState<'none' | 'quality' | 'speed' | 'subtitles' | 'chapters' | 'audio' | 'sources' | 'mobile-settings'>('none');\r\n const [playbackRate, setPlaybackRate] = useState(1);\r\n const [showChaptersSidebar, setShowChaptersSidebar] = useState(false);\r\n const [chapterThumbnails, setChapterThumbnails] = useState<Record<number, string>>({});\r\n\r\n // Accessibility: live-region announcement text\r\n const [announcement, setAnnouncement] = useState('');\r\n const [sourceFallbackNotice, setSourceFallbackNotice] = useState<string | null>(null);\r\n const announceMountedRef = useRef(false);\r\n\r\n const keyMomentsKey = JSON.stringify(localKeyMoments.map(c => ({ title: c.title, startTime: c.startTime })));\r\n\r\n // Stable key for incoming props — avoids infinite loops when parent passes inline [] on every render\r\n const keyMomentsPropsKey = (keyMoments || chapters || []).map(k => `${k.startTime}:${k.title}`).join(',');\r\n\r\n // Sync props to state dynamically\r\n useEffect(() => {\r\n setLocalKeyMoments(keyMoments || chapters || []);\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [keyMomentsPropsKey]);\r\n\r\n // Dynamically extract actual video frames at chapter start times\r\n useEffect(() => {\r\n if (!src || !localKeyMoments || localKeyMoments.length === 0) return;\r\n\r\n setChapterThumbnails({});\r\n\r\n const tempVideo = document.createElement('video');\r\n tempVideo.muted = true;\r\n tempVideo.preload = 'metadata';\r\n tempVideo.crossOrigin = 'anonymous';\r\n // position:fixed with opacity:0.01 forces the browser compositor to decode frames, bypassing display:none optimizations\r\n tempVideo.style.cssText = 'position:fixed;top:0;left:0;width:10px;height:10px;opacity:0.01;pointer-events:none;z-index:-9999;';\r\n document.body.appendChild(tempVideo);\r\n\r\n let tempHls: Hls | null = null;\r\n const isHls = src.includes('.m3u8');\r\n\r\n const tempCanvas = document.createElement('canvas');\r\n tempCanvas.width = 160;\r\n tempCanvas.height = 90;\r\n const ctx = tempCanvas.getContext('2d');\r\n\r\n let currentIndex = 0;\r\n let isStarted = false;\r\n\r\n const captureNext = () => {\r\n if (currentIndex >= localKeyMoments.length) {\r\n cleanup();\r\n return;\r\n }\r\n const targetTime = localKeyMoments[currentIndex].startTime;\r\n // Seek to target. If 0, seek to 0.01 to ensure the browser registers a currentTime change and triggers 'seeked'\r\n tempVideo.currentTime = targetTime === 0 ? 0.01 : targetTime;\r\n };\r\n\r\n const onSeeked = () => {\r\n if (ctx && tempVideo.readyState >= 2) {\r\n ctx.drawImage(tempVideo, 0, 0, tempCanvas.width, tempCanvas.height);\r\n const dataUrl = tempCanvas.toDataURL('image/jpeg', 0.8);\r\n setChapterThumbnails((prev) => ({ ...prev, [currentIndex]: dataUrl }));\r\n currentIndex++;\r\n captureNext();\r\n } else {\r\n // Fallback retry if video frame buffer wasn't ready\r\n setTimeout(onSeeked, 100);\r\n }\r\n };\r\n\r\n const startCapture = () => {\r\n if (isStarted) return;\r\n isStarted = true;\r\n captureNext();\r\n };\r\n\r\n tempVideo.addEventListener('seeked', onSeeked);\r\n tempVideo.addEventListener('canplay', startCapture);\r\n\r\n if (isHls && Hls.isSupported()) {\r\n tempHls = new Hls({\r\n enableWorker: false,\r\n maxBufferLength: 5,\r\n xhrSetup: (xhr, url) => {\r\n // Only use an explicit auth token passed via `drmConfig`.\r\n // Do NOT infer or parse tokens from the stream URL.\r\n const token = drmConfig?.authToken;\r\n try {\r\n const proxiedUrl = transformUrl(url);\r\n const urlObj = new URL(proxiedUrl, window.location.origin);\r\n if (token) {\r\n urlObj.searchParams.set('token', token);\r\n }\r\n xhr.open('GET', urlObj.toString(), true);\r\n } catch (e) {\r\n console.error('[WebVideoPlayer tempHls] xhrSetup error:', e);\r\n }\r\n }\r\n });\r\n tempHls.loadSource(transformUrl(src));\r\n tempHls.attachMedia(tempVideo);\r\n } else {\r\n tempVideo.src = src;\r\n }\r\n\r\n // If readyState is already satisfied, start capturing immediately\r\n if (tempVideo.readyState >= 2) {\r\n startCapture();\r\n }\r\n\r\n const cleanup = () => {\r\n tempVideo.removeEventListener('seeked', onSeeked);\r\n tempVideo.removeEventListener('canplay', startCapture);\r\n if (tempHls) {\r\n tempHls.destroy();\r\n }\r\n if (document.body.contains(tempVideo)) {\r\n document.body.removeChild(tempVideo);\r\n }\r\n };\r\n\r\n return cleanup;\r\n }, [src, keyMomentsKey]);\r\n\r\n // Cast state\r\n const [castDevice, setCastDevice] = useState<string | null>(null);\r\n const [remoteState, setRemoteState] = useState<{ currentTime: number; duration: number; isPaused: boolean; volumeLevel: number; isMuted: boolean } | null>(null);\r\n const preCastMutedRef = useRef<boolean>(false);\r\n const [isAirPlayAvailable, setIsAirPlayAvailable] = useState(false);\r\n const [isAirPlayConnected, setIsAirPlayConnected] = useState(false);\r\n const [airPlayTargetName, setAirPlayTargetName] = useState<string | null>(null);\r\n\r\n // Scrubber Hover\r\n const [hoverX, setHoverX] = useState<number | null>(null);\r\n const [tooltipLeft, setTooltipLeft] = useState(0);\r\n const [hoverTime, setHoverTime] = useState<number>(0);\r\n const [hoverThumbnail, setHoverThumbnail] = useState<string | null>(null);\r\n const [hoverChapter, setHoverChapter] = useState<KeyMoment | null>(null);\r\n const [scrubbingTime, setScrubbingTime] = useState<number | null>(null);\r\n\r\n // Inject Native Google Cast Button (Invisible Overlay)\r\n useEffect(() => {\r\n if (castButtonRef.current && !castButtonRef.current.querySelector('google-cast-launcher')) {\r\n const launcher = document.createElement('google-cast-launcher');\r\n launcher.style.cursor = 'pointer';\r\n launcher.style.width = '100%';\r\n launcher.style.height = '100%';\r\n launcher.style.display = 'block';\r\n castButtonRef.current.appendChild(launcher);\r\n }\r\n }, []);\r\n\r\n useEffect(() => {\r\n if (!player) {\r\n setIsAirPlayAvailable(false);\r\n setIsAirPlayConnected(false);\r\n setAirPlayTargetName(null);\r\n return;\r\n }\r\n\r\n const updateAvailability = (available: boolean) => setIsAirPlayAvailable(available);\r\n const updateConnection = () => {\r\n const video = player.getVideoElement();\r\n const anyVideo = video as any;\r\n const connected = Boolean(anyVideo?.webkitCurrentPlaybackTargetIsWireless);\r\n setIsAirPlayConnected(connected);\r\n setAirPlayTargetName(\r\n connected\r\n ? anyVideo.webkitCurrentPlaybackTargetName || 'AirPlay device'\r\n : null\r\n );\r\n };\r\n\r\n updateAvailability(player.casting.isAirPlayAvailable());\r\n updateConnection();\r\n\r\n const video = player.getVideoElement();\r\n const availabilityHandler = (event: any) => {\r\n updateAvailability(event.availability === 'available');\r\n };\r\n const connectionHandler = () => {\r\n updateConnection();\r\n };\r\n\r\n if ('webkitShowPlaybackTargetPicker' in video) {\r\n video.addEventListener('webkitplaybacktargetavailabilitychanged', availabilityHandler as EventListener);\r\n video.addEventListener('webkitcurrentplaybacktargetiswirelesschanged', connectionHandler as EventListener);\r\n }\r\n\r\n return () => {\r\n if ('webkitShowPlaybackTargetPicker' in video) {\r\n video.removeEventListener('webkitplaybacktargetavailabilitychanged', availabilityHandler as EventListener);\r\n video.removeEventListener('webkitcurrentplaybacktargetiswirelesschanged', connectionHandler as EventListener);\r\n }\r\n };\r\n }, [player]);\r\n\r\n // Control UI visibility timeout\r\n const [extractedTitle, setExtractedTitle] = useState('');\r\n const [controlsVisible, setControlsVisible] = useState(true);\r\n const [isAdPlaying, setIsAdPlaying] = useState(false);\r\n // True from the instant performSeek redirects a forward seek into an unwatched ad break\r\n // until the ad actually appears (adbreakchange(true), which takes over via isAdPlaying)\r\n // or the redirect's guard timeout fires. Without this, the seekbar/PiP/media-session seek\r\n // entry points stay fully interactive during that gap — since setting video.currentTime\r\n // updates the readback synchronously, any seek landing in that window (e.g. a fast\r\n // double-click, or a drag that ends right after the redirect fires) re-issues a real seek\r\n // that overrides the pending redirect at the browser level and skips the ad outright.\r\n const [isAdRedirectPending, setIsAdRedirectPending] = useState(false);\r\n const isAdRedirectPendingRef = useRef(false);\r\n const controlsTimeoutRef = useRef<number | null>(null);\r\n // Ref so the timeout callback always reads the *current* isPlaying value (avoids stale closure)\r\n const isPlayingRef = useRef(false);\r\n const isAdPlayingRef = useRef(false);\r\n const activeMenuRef = useRef<typeof activeMenu>('none');\r\n const availableSubtitlesRef = useRef<SubtitleTrack[]>(subtitles);\r\n\r\n const resetControlsTimer = () => {\r\n setControlsVisible(true);\r\n if (controlsTimeoutRef.current) {\r\n window.clearTimeout(controlsTimeoutRef.current);\r\n }\r\n const delay = isMobileRef.current ? 3500 : 10000;\r\n controlsTimeoutRef.current = window.setTimeout(() => {\r\n if (isPlayingRef.current && activeMenuRef.current === 'none') {\r\n setControlsVisible(false);\r\n }\r\n }, delay);\r\n };\r\n\r\n // Falls back to the first source when the source the user switched to can't be played.\r\n // Uses refs only, so it's safe to call from inside the player effect closure regardless\r\n // of when it was captured. Keeps sourceChangeSeekRef intact so the fallback source\r\n // resumes at the position the user was watching, and leaves the resume cover and the\r\n // switch-forced spinner up (raised in handleSourceChange) so no frame-0 flash shows\r\n // during the fallback re-init — both drop once the fallback source lands at the\r\n // restored position.\r\n const runSourceFallback = useCallback((failedIdx: number) => {\r\n if (sourceChangeFallbackTimerRef.current) {\r\n clearTimeout(sourceChangeFallbackTimerRef.current);\r\n sourceChangeFallbackTimerRef.current = null;\r\n }\r\n const srcs = sourcesRef.current;\r\n const failedLabel = srcs?.[failedIdx]?.label ?? 'This format';\r\n const fallbackLabel = srcs?.[0]?.label ?? 'default';\r\n pendingSourceSwitchRef.current = null;\r\n setSourceFallbackNotice(`${failedLabel} isn't supported on this device. Switched to ${fallbackLabel}.`);\r\n window.setTimeout(() => setSourceFallbackNotice(null), 4000);\r\n onSourceChangeRef.current?.(0);\r\n }, []);\r\n\r\n useEffect(() => {\r\n if (!containerRef.current) return;\r\n\r\n // ── Hidden video for position-accurate thumbnail capture ──────────────\r\n const hiddenVideo = document.createElement('video');\r\n hiddenVideo.muted = true;\r\n hiddenVideo.preload = 'metadata';\r\n hiddenVideo.crossOrigin = 'anonymous';\r\n hiddenVideo.style.cssText = 'display:none;position:absolute;pointer-events:none;';\r\n document.body.appendChild(hiddenVideo);\r\n hiddenVideoRef.current = hiddenVideo;\r\n\r\n const isHls = src.includes('.m3u8');\r\n if (isHls && Hls.isSupported()) {\r\n const hls = new Hls({\r\n enableWorker: false,\r\n maxBufferLength: 5,\r\n xhrSetup: (xhr, url) => {\r\n const token = drmConfig?.authToken;\r\n try {\r\n const proxiedUrl = transformUrl(url);\r\n const urlObj = new URL(proxiedUrl, window.location.origin);\r\n if (token) {\r\n urlObj.searchParams.set('token', token);\r\n }\r\n xhr.open('GET', urlObj.toString(), true);\r\n } catch (e) {\r\n console.error('[WebVideoPlayer hiddenHls] xhrSetup error:', e);\r\n }\r\n }\r\n });\r\n hls.loadSource(transformUrl(src));\r\n hls.attachMedia(hiddenVideo);\r\n hiddenHlsRef.current = hls;\r\n\r\n hls.on(Hls.Events.MANIFEST_LOADED, (_e: string, data: { networkDetails?: { responseText?: string } }) => {\r\n if (title) return;\r\n const found = extractTitleFromManifest(data?.networkDetails?.responseText ?? '');\r\n if (found) setExtractedTitle(found);\r\n });\r\n\r\n hls.on(Hls.Events.FRAG_PARSING_METADATA, (_e: string, data: { samples?: Array<{ data: Uint8Array }> }) => {\r\n if (title || extractedTitle) return;\r\n for (const sample of data?.samples ?? []) {\r\n const found = parseID3Title(sample.data);\r\n if (found) { setExtractedTitle(found); break; }\r\n }\r\n });\r\n } else if (isHls && hiddenVideo.canPlayType('application/vnd.apple.mpegurl')) {\r\n hiddenVideo.src = src;\r\n } else {\r\n hiddenVideo.src = src;\r\n }\r\n\r\n const onSeeked = () => {\r\n const canvas = canvasRef.current;\r\n if (!canvas) return;\r\n const ctx = canvas.getContext('2d');\r\n if (ctx && hiddenVideo.readyState >= 2) {\r\n ctx.drawImage(hiddenVideo, 0, 0, canvas.width, canvas.height);\r\n const dataUrl = canvas.toDataURL('image/jpeg', 0.82);\r\n setHoverThumbnail(dataUrl);\r\n }\r\n };\r\n hiddenVideo.addEventListener('seeked', onSeeked);\r\n\r\n // ── Firebase Analytics Provider ───────────────────────────────────────\r\n let firebaseCallback: ((event: import('@goboss/types').AnalyticsEvent) => void) | undefined;\r\n if (firebaseConfig) {\r\n const provider = new FirebaseAnalyticsProvider(firebaseConfig);\r\n firebaseProviderRef.current = provider;\r\n firebaseCallback = provider.createCallback();\r\n }\r\n\r\n // ── Main SDK player ───────────────────────────────────────────────────\r\n if (isEntitlementBlocked) {\r\n return;\r\n }\r\n\r\n const sdkInstance = new VideoPlayerSDK({\r\n container: containerRef.current,\r\n src,\r\n videoId,\r\n autoplay,\r\n muted,\r\n keyMoments: localKeyMoments,\r\n subtitles,\r\n drm: {\r\n watermarkText: watermark,\r\n allowedDomains: ['localhost', '127.0.0.1', window.location.hostname],\r\n ...drmConfig,\r\n },\r\n analyticsCallback: (event) => {\r\n // Normalize session identity across player re-inits (see analyticsSessionIdRef):\r\n // adopt the first module's sessionId, re-stamp events from re-created modules\r\n // that carry a regenerated id, and drop the duplicate session_start each new\r\n // module fires on its first play.\r\n if (event.sessionId) {\r\n if (analyticsSessionIdRef.current === null) {\r\n analyticsSessionIdRef.current = event.sessionId;\r\n } else if (event.sessionId !== analyticsSessionIdRef.current) {\r\n event = { ...event, sessionId: analyticsSessionIdRef.current };\r\n }\r\n }\r\n if (event.eventType === 'session_start') {\r\n if (analyticsSessionStartFiredRef.current) return;\r\n analyticsSessionStartFiredRef.current = true;\r\n }\r\n firebaseCallback?.(event);\r\n if (onAnalyticsEvent) onAnalyticsEvent(event);\r\n },\r\n // Seed only a snapshot taken for this same content — a stale snapshot from a\r\n // previous content is discarded by the key check (fresh session, as before).\r\n analyticsSessionSeed:\r\n analyticsSeedRef.current?.key === contentKey\r\n ? analyticsSeedRef.current.snapshot\r\n : undefined,\r\n watchProgressInterval: watchProgressInterval * 1000,\r\n themeColor,\r\n urlTransformer,\r\n });\r\n\r\n sdkInstance.on('keymomentsloaded', (parsedKeyMoments: KeyMoment[]) => {\r\n setLocalKeyMoments(parsedKeyMoments);\r\n if (onKeyMomentsLoaded) {\r\n onKeyMomentsLoaded(parsedKeyMoments);\r\n }\r\n });\r\n\r\n sdkInstance.on('chaptersloaded', (parsedChapters: Chapter[]) => {\r\n setLocalKeyMoments(parsedChapters);\r\n if (onKeyMomentsLoaded) {\r\n onKeyMomentsLoaded(parsedChapters);\r\n }\r\n });\r\n\r\n // Subscriptions to core player events\r\n sdkInstance.on('play', () => {\r\n setIsPlaying(true);\r\n setIsEnded(false);\r\n syncMediaSessionPosition(sdkInstance.getCurrentTime(), false);\r\n });\r\n sdkInstance.on('pause', () => {\r\n setIsPlaying(false);\r\n syncMediaSessionPosition(sdkInstance.getCurrentTime(), true);\r\n });\r\n sdkInstance.on('ended', () => {\r\n setIsPlaying(false);\r\n setIsEnded(true);\r\n });\r\n // Shared helper — hide video, seek to endTime, restore once the seek lands.\r\n // All ad-skip paths (timeupdate pre-skip and adbreakchange fallback) use this\r\n // so opacity management and the guard timeout are always in sync.\r\n const showAdSkipCover = () => {\r\n // opacity change on a will-change:opacity element is a pure compositor operation —\r\n // no layout/paint required, takes effect in the current composite frame.\r\n adSkipCoverUpRef.current = true;\r\n setIsBuffering(false);\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '1';\r\n // Audio from the covered frames would still leak through the black cover.\r\n muteCoverAudio(sdkInstance.getVideoElement());\r\n };\r\n\r\n const hideAdSkipCover = () => {\r\n // Double rAF: wait for the browser to commit a fresh post-seek content frame\r\n // before removing the cover, so no ad frame bleeds through.\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => {\r\n adSkipCoverUpRef.current = false;\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '0';\r\n restoreCoverAudio(sdkInstance.getVideoElement());\r\n const vid = sdkInstance.getVideoElement();\r\n if (vid && !vid.paused && vid.readyState < 3) setIsBuffering(true);\r\n });\r\n });\r\n };\r\n\r\n // `withinPod` marks a skip that lands on the next ad of the same break rather than\r\n // on content, which changes how a queued seek target is handled (see below).\r\n const triggerAdSkip = (endTime: number, withinPod = false) => {\r\n isSeeking.current = true;\r\n // If this skip is part of a seek-forward redirect, jump straight to the user's original\r\n // target in one seek. This avoids a double-seek race: without this, triggerAdSkip would\r\n // seek to adEnd+0.5, then adbreakchange(false) would immediately seek again to the\r\n // pending target — the timeupdate from the first seek would fire between the two seeks\r\n // with seekTargetTime already updated to the second target, triggering a false \"landed\"\r\n // detection and prematurely hiding the cover before the second seek completes.\r\n //\r\n // A mid-pod skip is the exception: it stays inside the break, so the queued target has\r\n // to survive until the remaining ads have played. Consuming it here would let skipping\r\n // a pod's first ad release the viewer to their original target and bypass the rest.\r\n const skipTarget = withinPod ? endTime + 0.5 : (pendingSeekTargetRef.current ?? endTime + 0.5);\r\n if (!withinPod) pendingSeekTargetRef.current = null;\r\n adSkipWithinPodRef.current = withinPod;\r\n seekTargetTime.current = skipTarget;\r\n preSeekPositionRef.current = sdkInstance.getCurrentTime();\r\n showAdSkipCover();\r\n sdkInstance.seek(skipTarget);\r\n const video = sdkInstance.getVideoElement();\r\n if (video) {\r\n const onSkipSeeked = () => {\r\n video.removeEventListener('seeked', onSkipSeeked);\r\n if (seekGuardTimeout.current) {\r\n window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = null;\r\n }\r\n isSeeking.current = false;\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n const wasWithinPod = adSkipWithinPodRef.current;\r\n adSkipWithinPodRef.current = false;\r\n // Mirrors the timeupdate landing rules: a redirect seek still owes the\r\n // viewer their original target, so the cover stays up for that hop.\r\n if (wasWithinPod || pendingSeekTargetRef.current === null) hideAdSkipCover();\r\n };\r\n video.addEventListener('seeked', onSkipSeeked);\r\n }\r\n\r\n if (seekGuardTimeout.current) window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = window.setTimeout(() => {\r\n isSeeking.current = false;\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n seekGuardTimeout.current = null;\r\n adSkipWithinPodRef.current = false;\r\n hideAdSkipCover();\r\n }, 3000);\r\n };\r\n\r\n triggerAdSkipRef.current = triggerAdSkip;\r\n\r\n // `isPaused` drives navigator.mediaSession.playbackState, which is what actually\r\n // freezes Chrome's PiP-window timeline extrapolation. setPositionState's own\r\n // `playbackRate` field must stay a positive number — the spec throws a TypeError\r\n // for 0, which the try/catch below would silently swallow, leaving the browser's\r\n // last-known rate (1) in effect and the PiP timeline drifting forward while paused.\r\n const syncMediaSessionPosition = (time: number, isPaused: boolean) => {\r\n if (!('mediaSession' in navigator) || !navigator.mediaSession.setPositionState) return;\r\n navigator.mediaSession.playbackState = isPaused ? 'paused' : 'playing';\r\n const rawDuration = sdkInstance.getDuration();\r\n const activeAdBreak = adBreaksRef.current.find(ab => time >= ab.startTime && time < ab.endTime);\r\n if (activeAdBreak) {\r\n const adDur = activeAdBreak.endTime - activeAdBreak.startTime;\r\n const adPos = Math.max(0, time - activeAdBreak.startTime);\r\n if (adDur > 0) {\r\n try {\r\n navigator.mediaSession.setPositionState({\r\n duration: adDur,\r\n playbackRate: 1,\r\n position: Math.min(adPos, adDur),\r\n });\r\n } catch (_) { /* unsupported browser */ }\r\n }\r\n } else {\r\n const totalAdDur = adBreaksRef.current.reduce((s, ab) => s + (ab.endTime - ab.startTime), 0);\r\n const contentDur = rawDuration > 0 ? Math.max(0, rawDuration - totalAdDur) : 0;\r\n let ct = time;\r\n for (const ab of adBreaksRef.current) {\r\n if (time >= ab.endTime) ct -= (ab.endTime - ab.startTime);\r\n else if (time >= ab.startTime) ct -= (time - ab.startTime);\r\n }\r\n ct = Math.max(0, ct);\r\n const dispDur = adBreaksRef.current.length > 0 && contentDur > 0 ? contentDur : rawDuration;\r\n if (dispDur > 0) {\r\n try {\r\n navigator.mediaSession.setPositionState({\r\n duration: dispDur,\r\n playbackRate: 1,\r\n position: Math.min(ct, dispDur),\r\n });\r\n } catch (_) { /* unsupported browser */ }\r\n }\r\n }\r\n };\r\n\r\n sdkInstance.on('timeupdate', (time: number) => {\r\n if (!isSeeking.current && resumeSeekTargetRef.current === null) {\r\n const watchedBreak = adBreaksRef.current.find(\r\n b => watchedAdBreaksRef.current.has(b.startTime)\r\n && time >= b.startTime - 0.5\r\n && time < b.endTime\r\n );\r\n if (watchedBreak) {\r\n triggerAdSkip(watchedBreak.endTime);\r\n return;\r\n }\r\n\r\n if (pendingSeekTargetRef.current !== null) {\r\n const endingBreak = adBreaksRef.current.find(\r\n b => time >= b.endTime - 0.5 && time < b.endTime\r\n );\r\n if (endingBreak) showAdSkipCover();\r\n }\r\n }\r\n\r\n if (isSeeking.current && seekTargetTime.current !== null) {\r\n const target = seekTargetTime.current;\r\n const isBackwardSeek = preSeekPositionRef.current !== null && target < preSeekPositionRef.current;\r\n const landed = isBackwardSeek\r\n ? (time <= target + 2.0 && time >= target - 2.0)\r\n : time >= target - 2.0;\r\n if (landed) {\r\n isSeeking.current = false;\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n if (seekGuardTimeout.current) {\r\n window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = null;\r\n }\r\n const landedInAd = adBreaksRef.current.some(\r\n b => time >= b.startTime && time < b.endTime\r\n );\r\n // Landing inside a break normally means a redirect-into-ad seek, where the cover\r\n // must stay up until adbreakchange(true) commits the first ad frame. A mid-pod\r\n // skip lands inside a break by design though, and adbreakchange never fires\r\n // because the break was never left — so this is its only chance to reveal.\r\n const wasWithinPodSkip = adSkipWithinPodRef.current;\r\n adSkipWithinPodRef.current = false;\r\n if (wasWithinPodSkip || (!landedInAd && pendingSeekTargetRef.current === null)) {\r\n hideAdSkipCover();\r\n }\r\n } else {\r\n // Still seeking — skip this stale update\r\n return;\r\n }\r\n }\r\n\r\n setCurrentTime(time);\r\n // Only positions that survived the seek guards above count as \"stable\" — a mid-seek\r\n // timeupdate must never become the native-seek guard's revert point.\r\n lastStableTimeRef.current = time;\r\n const dur = sdkInstance.getDuration();\r\n setDuration(dur);\r\n\r\n // Drop the resume cover once playback has actually reached the resume point.\r\n // Checked BEFORE the arm block below so it never runs on the same tick it was armed\r\n // (resumeSeekTargetRef is still null then). The -2.0 tolerance mirrors the ad-skip\r\n // landing detection to absorb HLS keyframe snapping. Isolated from ad-skip state.\r\n if (resumeSeekTargetRef.current !== null && time >= resumeSeekTargetRef.current - 2.0) {\r\n resumeSeekTargetRef.current = null;\r\n if (resumeGuardTimeout.current) {\r\n window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = null;\r\n }\r\n // Keyframe snapping can land the resume seek inside an already-watched break\r\n // (saved position just past its end). Trigger the skip NOW, before the reveal\r\n // below — the ad-skip cover goes up synchronously inside triggerAdSkip, so the\r\n // reveal exposes that cover's black instead of watched-ad frames.\r\n const landedWatchedBreak = adBreaksRef.current.find(\r\n b => watchedAdBreaksRef.current.has(b.startTime) && time >= b.startTime && time < b.endTime\r\n );\r\n if (landedWatchedBreak) triggerAdSkip(landedWatchedBreak.endTime);\r\n // Double rAF: wait for the browser to commit a fresh post-seek frame before revealing.\r\n requestAnimationFrame(() => {\r\n requestAnimationFrame(() => {\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n });\r\n });\r\n }\r\n\r\n // Apply one-time seek to resume position once the stream has a valid duration.\r\n // The cover was already raised at mount (JSX initial opacity) so frame 0 never showed;\r\n // here we arm the landing detection above so it hides only after the seek lands.\r\n // Skipped while a source-switch restore is pending (sourceChangeSeekRef non-null):\r\n // the effect cleanup resets initialSeekAppliedRef on every re-init, so after a\r\n // switch this block would otherwise re-apply initialTime and fight the restore\r\n // seek for the final position. The saved switch position is authoritative.\r\n if (!initialSeekAppliedRef.current && sourceChangeSeekRef.current === null && initialTime && initialTime > 0 && dur > 0 && initialTime < dur) {\r\n initialSeekAppliedRef.current = true;\r\n const landingBreak = adBreaksRef.current.find(\r\n b => initialTime >= b.startTime && initialTime < b.endTime\r\n );\r\n const resumeTarget = landingBreak ? landingBreak.startTime : initialTime;\r\n resumeAdRecheckRef.current = landingBreak ? null : initialTime;\r\n resumeSeekTargetRef.current = resumeTarget;\r\n sdkInstance.seek(resumeTarget);\r\n // Fallback: if playback never reaches the target (e.g. seek fails), reveal anyway\r\n // so the cover can't get stuck up.\r\n if (resumeGuardTimeout.current) window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = window.setTimeout(() => {\r\n resumeSeekTargetRef.current = null;\r\n resumeGuardTimeout.current = null;\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n }, 4000);\r\n }\r\n\r\n onTimeUpdate?.(time);\r\n\r\n // Overlay (non-linear) ad: driven by content time, not wall-clock\r\n if (overlayAdsRef.current.length > 0) {\r\n if (activeOverlayRef.current) {\r\n const o = activeOverlayRef.current;\r\n const endTime = o.startTimeSeconds + o.durationSeconds;\r\n if (time >= endTime || time < o.startTimeSeconds) {\r\n setActiveOverlay(null);\r\n activeOverlayRef.current = null;\r\n }\r\n } else {\r\n const due = overlayAdsRef.current.find(\r\n o => time >= o.startTimeSeconds\r\n && time < o.startTimeSeconds + o.durationSeconds\r\n && !dismissedOverlayIdsRef.current.has(o.availId)\r\n );\r\n if (due) {\r\n setActiveOverlay(due);\r\n activeOverlayRef.current = due;\r\n due.impressionBeacons.forEach(url => { fetch(url, { mode: 'no-cors' }).catch(() => {}); });\r\n }\r\n }\r\n }\r\n\r\n // Update chapter badge\r\n const activeCh = sdkInstance.keyMoments.getCurrentMoment(time);\r\n setActiveChapter({ chapter: activeCh.keyMoment, index: activeCh.index });\r\n\r\n // Keep the browser's media-session position in sync so native PiP controls\r\n // (iOS Safari, macOS, Chrome's PiP scrub bar, etc.) display the correct timeline.\r\n // Some MSE/HLS.js playback paths fire stray `timeupdate` events while the video\r\n // is genuinely paused (buffer appends nudging currentTime) — trusting the event\r\n // name alone would mark the session 'playing' again and resume the PiP\r\n // extrapolation the pause handler just froze. Read the element's real paused\r\n // state instead.\r\n syncMediaSessionPosition(time, sdkInstance.getVideoElement().paused);\r\n });\r\n\r\n sdkInstance.on('qualitylevels', (levels: VideoQuality[]) => {\r\n setQualities(levels);\r\n\r\n const prefs = sourceChangePrefsRef.current;\r\n if (!prefs) return;\r\n const carried =\r\n prefs.qualityHeight !== null\r\n ? levels.find((q) => q.height === prefs.qualityHeight)\r\n : undefined;\r\n if (carried) {\r\n sdkInstance.setQuality(carried.id);\r\n setActiveQuality(carried);\r\n } else {\r\n setActiveQuality({ id: -1, height: 0, width: 0, bitrate: 0, label: 'Auto' });\r\n }\r\n });\r\n\r\n sdkInstance.on('qualitychange', (quality: VideoQuality) => {\r\n setActiveQuality(quality);\r\n });\r\n\r\n sdkInstance.on('buffering', (buffering: boolean) => {\r\n setIsBuffering(buffering && !adSkipCoverUpRef.current);\r\n // 'buffering: false' maps to the native 'playing' event — proof that frames are\r\n // actually rendering. This is the success signal for a source switch.\r\n if (!buffering) {\r\n // Playback started — cancel the unsupported-format watchdog\r\n if (sourceChangeFallbackTimerRef.current) {\r\n clearTimeout(sourceChangeFallbackTimerRef.current);\r\n sourceChangeFallbackTimerRef.current = null;\r\n }\r\n pendingSourceSwitchRef.current = null;\r\n const prefs = sourceChangePrefsRef.current;\r\n if (prefs) {\r\n sourceChangePrefsRef.current = null;\r\n if (prefs.playbackRate !== 1) sdkInstance.setPlaybackRate(prefs.playbackRate);\r\n sdkInstance.setVolume(prefs.volume);\r\n sdkInstance.setMute(prefs.muted);\r\n }\r\n // Restore the playback position the user was at before the switch.\r\n // The resume cover has been up since handleSourceChange — arm the timeupdate\r\n // landing detection so it drops only after playback actually reaches the\r\n // saved position, plus a guard so it can never get stuck up if the seek fails.\r\n if (sourceChangeSeekRef.current !== null) {\r\n const seekTarget = sourceChangeSeekRef.current;\r\n sourceChangeSeekRef.current = null;\r\n // The switch restore supersedes the initialTime resume: the effect cleanup\r\n // reset initialSeekAppliedRef, so without this the initialTime block below\r\n // would re-apply on this player and yank playback away from the restored\r\n // position.\r\n initialSeekAppliedRef.current = true;\r\n resumeSeekTargetRef.current = seekTarget;\r\n if (resumeGuardTimeout.current) window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = window.setTimeout(() => {\r\n resumeSeekTargetRef.current = null;\r\n resumeGuardTimeout.current = null;\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n }, 4000);\r\n sdkInstance.seek(seekTarget);\r\n }\r\n }\r\n });\r\n\r\n sdkInstance.on('adbreakchange', (active: boolean) => {\r\n sdkInstance.analytics.setAdBreak(active, sdkInstance.getVideoElement());\r\n if (onAdBreakChange) onAdBreakChange(active);\r\n if (active) {\r\n const currentT = sdkInstance.getCurrentTime();\r\n const currentBreak = adBreaksRef.current.find(b => currentT >= b.startTime && currentT < b.endTime);\r\n // The ad is confirmed on screen (or about to be skipped as already-watched) —\r\n // isAdPlaying (or the skip cover) takes over gating seeks from here, so the\r\n // redirect-pending lock has done its job.\r\n isAdRedirectPendingRef.current = false;\r\n setIsAdRedirectPending(false);\r\n if (currentBreak && watchedAdBreaksRef.current.has(currentBreak.startTime)) {\r\n // Already watched — cover immediately (belt-and-suspenders with the timeupdate\r\n // pre-skip: if the 500ms look-ahead somehow missed this frame, adbreakchange is\r\n // the last synchronous chance to block it before the next compositor pass).\r\n showAdSkipCover();\r\n if (!isSeeking.current) {\r\n triggerAdSkip(currentBreak.endTime);\r\n }\r\n return;\r\n }\r\n if (currentBreak) {\r\n activeAdBreakStartRef.current = currentBreak.startTime;\r\n }\r\n setIsAdPlaying(true);\r\n setActiveMenu('none');\r\n // The ad is on screen now — drop the redirect-seek cover (no-op if it was\r\n // never raised). Double rAF inside ensures the first ad frame is committed\r\n // before the reveal.\r\n hideAdSkipCover();\r\n } else {\r\n // Ad finished naturally — mark this break as watched for the rest of the session\r\n if (activeAdBreakStartRef.current !== null) {\r\n watchedAdBreaksRef.current.add(activeAdBreakStartRef.current);\r\n setWatchedAdBreaksSet(new Set(watchedAdBreaksRef.current));\r\n activeAdBreakStartRef.current = null;\r\n }\r\n setIsAdPlaying(false);\r\n\r\n // If this ad was part of a seek-forward redirect, resume at the original target.\r\n // Only one (nearest) ad is ever enforced per seek, so no further queuing needed.\r\n if (pendingSeekTargetRef.current !== null) {\r\n const target = pendingSeekTargetRef.current;\r\n pendingSeekTargetRef.current = null;\r\n isSeeking.current = true;\r\n seekTargetTime.current = target;\r\n preSeekPositionRef.current = sdkInstance.getCurrentTime();\r\n showAdSkipCover();\r\n sdkInstance.seek(target);\r\n if (seekGuardTimeout.current) window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = window.setTimeout(() => {\r\n isSeeking.current = false;\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n seekGuardTimeout.current = null;\r\n hideAdSkipCover();\r\n }, 3000);\r\n }\r\n }\r\n });\r\n\r\n sdkInstance.on('adbreaksloaded', (breaks: Array<{ startTime: number; endTime: number }>) => {\r\n // Sync the ref immediately so timeupdate/adbreakchange handlers don't read a stale empty array\r\n // during the React render cycle that follows setAdBreaks.\r\n adBreaksRef.current = breaks;\r\n setAdBreaks(breaks);\r\n\r\n const pendingResume = resumeAdRecheckRef.current;\r\n if (pendingResume === null) return;\r\n resumeAdRecheckRef.current = null;\r\n\r\n const landingBreak = breaks.find(\r\n b => pendingResume >= b.startTime && pendingResume < b.endTime\r\n );\r\n if (!landingBreak || watchedAdBreaksRef.current.has(landingBreak.startTime)) return;\r\n\r\n const now = sdkInstance.getCurrentTime();\r\n if (now < landingBreak.startTime || now >= landingBreak.endTime) return;\r\n\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '1';\r\n resumeSeekTargetRef.current = landingBreak.startTime;\r\n if (resumeGuardTimeout.current) window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = window.setTimeout(() => {\r\n resumeSeekTargetRef.current = null;\r\n resumeGuardTimeout.current = null;\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n }, 4000);\r\n sdkInstance.seek(landingBreak.startTime);\r\n });\r\n\r\n sdkInstance.on('volumechange', (data: { volume: number; muted: boolean }) => {\r\n setVolume(data.volume);\r\n // Ignore muted-state changes caused by the ad-skip cover's force-mute so the\r\n // UI mute icon doesn't flicker; the user's real state is restored on reveal.\r\n if (coverMuteRef.current === null) setIsMuted(data.muted);\r\n });\r\n\r\n sdkInstance.on('pipchange', (active: boolean) => {\r\n setIsPiP(active);\r\n });\r\n\r\n sdkInstance.on('error', () => {\r\n setPlayerError(true);\r\n setIsBuffering(false);\r\n // The error screen renders at z-index 50, below the covers (100) — drop the\r\n // resume cover and its landing state so a failed switch or resume never leaves\r\n // the user staring at black with the error screen hidden behind it.\r\n // sourceChangeSeekRef is intentionally kept: a retry re-init restores position.\r\n resumeSeekTargetRef.current = null;\r\n if (resumeGuardTimeout.current) {\r\n window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = null;\r\n }\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n });\r\n\r\n // Layer 1 (primary): the browser reported this stream's codecs as undecodable.\r\n // Only auto-fall back when this player was created by a user switch to a non-default\r\n // source and there's a first source to fall back to. The default source loading\r\n // normally never emits this, so default playback is unaffected.\r\n sdkInstance.on('unsupportedcodec', () => {\r\n const pending = pendingSourceSwitchRef.current;\r\n if (pending && pending.targetIdx !== 0 && (sourcesRef.current?.length ?? 0) > 1) {\r\n runSourceFallback(pending.targetIdx);\r\n }\r\n });\r\n\r\n // Subtitles track rendering hook\r\n sdkInstance.subtitles.onSubtitleChange((text: string) => {\r\n setSubtitleText(text);\r\n });\r\n\r\n sdkInstance.on('subtitlesloaded', (tracks: SubtitleTrack[]) => {\r\n // Subtitles are gated by the player-key entitlement — skip entirely when not enabled.\r\n if (!effectiveFeatures.enableSubtitles) return;\r\n\r\n setAvailableSubtitles(dedupeSubtitleTracks([...tracks]));\r\n\r\n const prefs = sourceChangePrefsRef.current;\r\n if (prefs) {\r\n const carried =\r\n prefs.subtitleId === 'off'\r\n ? 'off'\r\n : tracks.find((t) => t.id === prefs.subtitleId)?.id ??\r\n (prefs.subtitleLang\r\n ? tracks.find((t) => t.lang === prefs.subtitleLang)?.id\r\n : undefined);\r\n if (carried) {\r\n sdkInstance.subtitles.setActiveTrack(carried);\r\n setActiveSubtitleTrack(carried);\r\n return;\r\n }\r\n }\r\n\r\n // Auto-select default subtitle track\r\n const defaultTrack = tracks.find((t) => t.isDefault);\r\n if (defaultTrack) {\r\n sdkInstance.subtitles.setActiveTrack(defaultTrack.id);\r\n setActiveSubtitleTrack(defaultTrack.id);\r\n }\r\n });\r\n\r\n // Audio track discovery\r\n sdkInstance.on('audiotracksloaded', (tracks: Array<{ id: number; label: string; lang: string }>) => {\r\n setAudioTracks(tracks);\r\n const prefs = sourceChangePrefsRef.current;\r\n const carried = prefs\r\n ? tracks.find((t) => prefs.audioLang && t.lang === prefs.audioLang) ??\r\n tracks.find((t) => prefs.audioLabel && t.label === prefs.audioLabel)\r\n : undefined;\r\n if (carried) {\r\n sdkInstance.setAudioTrack(carried.id);\r\n setActiveAudioTrack(carried.id);\r\n return;\r\n }\r\n setActiveAudioTrack(tracks[0]?.id ?? 0);\r\n });\r\n\r\n sdkInstance.on('audiotrackchange', (id: number) => {\r\n setActiveAudioTrack(id);\r\n });\r\n\r\n // Cast state syncer — pauses local video on cast start, restores on stop\r\n sdkInstance.casting.onCastStateChange((isCasting: boolean, deviceName: string) => {\r\n if (isCasting) {\r\n preCastMutedRef.current = sdkInstance.isMuted();\r\n sdkInstance.pause();\r\n // Mute local element so audio doesn't double-play\r\n const vid = sdkInstance.getVideoElement();\r\n vid.muted = true;\r\n setCastDevice(deviceName);\r\n } else {\r\n // Restore local video from where remote cast left off. Marked as an internal\r\n // seek (isSeeking + target) so the native-seek guard doesn't intercept it and\r\n // re-route through performSeek — whose closure may still hold the not-yet-cleared\r\n // castDevice and would send the seek to the just-disconnected remote session.\r\n const remoteSnap = sdkInstance.casting.getRemoteState();\r\n if (remoteSnap && remoteSnap.currentTime > 0) {\r\n isSeeking.current = true;\r\n seekTargetTime.current = remoteSnap.currentTime;\r\n preSeekPositionRef.current = sdkInstance.getCurrentTime();\r\n sdkInstance.seek(remoteSnap.currentTime);\r\n if (seekGuardTimeout.current) window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = window.setTimeout(() => {\r\n isSeeking.current = false;\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n seekGuardTimeout.current = null;\r\n }, 3000);\r\n }\r\n const vid = sdkInstance.getVideoElement();\r\n vid.muted = preCastMutedRef.current;\r\n setCastDevice(null);\r\n setRemoteState(null);\r\n }\r\n });\r\n\r\n sdkInstance.casting.onRemoteStateChange((state) => {\r\n setRemoteState(state);\r\n });\r\n\r\n // Sync HTML5 video volume initially\r\n setVolume(sdkInstance.getVolume());\r\n setIsMuted(sdkInstance.isMuted());\r\n\r\n // When the user seeks from the native PiP window the browser fires this action with\r\n // a seek time expressed in the content timeline (because we set setPositionState with\r\n // content duration). Convert it back to raw video time then route through performSeek\r\n // so ad-break enforcement (block during ads, redirect over missed breaks) applies\r\n // identically to PiP seeks and normal seekbar seeks.\r\n if ('mediaSession' in navigator) {\r\n try {\r\n navigator.mediaSession.setActionHandler('seekto', (details) => {\r\n if (details.seekTime == null || isAdPlayingRef.current || isAdRedirectPendingRef.current) return;\r\n let rawT = details.seekTime;\r\n for (const ab of adBreaksRef.current) {\r\n if (rawT >= ab.startTime) rawT += (ab.endTime - ab.startTime);\r\n }\r\n performSeekRef.current?.(rawT);\r\n });\r\n\r\n // The native PiP window / media-hub skip buttons fire seekforward / seekbackward.\r\n // Without explicit handlers the browser performs a DEFAULT seek straight on the\r\n // media element, bypassing ad-break enforcement entirely — that let the user skip\r\n // past (or straight out of) an ad from the PiP window. Registering handlers\r\n // suppresses the default and lets us apply the same rules as the on-screen controls:\r\n // • while an ad is on screen the handler no-ops, so seeking / skipping is disabled\r\n // in PiP during ads (only play/pause remain functional), and\r\n // • otherwise a forward skip over an unwatched break is routed through performSeek,\r\n // which redirects into the ad first before landing at the target.\r\n // Content↔raw conversion mirrors the arrow-key handlers below.\r\n const pipSkip = (forward: boolean, rawOffset?: number) => {\r\n if (isAdPlayingRef.current || isAdRedirectPendingRef.current) return;\r\n const offset = rawOffset && rawOffset > 0 ? rawOffset : 10;\r\n const rawT = sdkInstance.getCurrentTime();\r\n let ct = rawT;\r\n for (const ab of adBreaksRef.current) {\r\n if (rawT >= ab.endTime) ct -= (ab.endTime - ab.startTime);\r\n else if (rawT >= ab.startTime) ct -= (rawT - ab.startTime);\r\n }\r\n if (forward) {\r\n const totalAdDur = adBreaksRef.current.reduce((s, ab) => s + (ab.endTime - ab.startTime), 0);\r\n const contentDur = Math.max(0, sdkInstance.getDuration() - totalAdDur);\r\n const newCt = Math.min(contentDur, ct + offset);\r\n let newRaw = newCt;\r\n for (const ab of adBreaksRef.current) {\r\n if (newRaw >= ab.startTime) newRaw += (ab.endTime - ab.startTime);\r\n }\r\n performSeekRef.current?.(newRaw, { eventType: 'fast_forward', skipSeconds: offset });\r\n } else {\r\n const newCt = Math.max(0, ct - offset);\r\n let newRaw = newCt;\r\n for (const ab of adBreaksRef.current) {\r\n if (newRaw >= ab.startTime) newRaw += (ab.endTime - ab.startTime);\r\n }\r\n performSeekRef.current?.(newRaw, { eventType: 'fast_backward', skipSeconds: offset });\r\n }\r\n };\r\n navigator.mediaSession.setActionHandler('seekforward', (details) => pipSkip(true, details.seekOffset ?? undefined));\r\n navigator.mediaSession.setActionHandler('seekbackward', (details) => pipSkip(false, details.seekOffset ?? undefined));\r\n } catch (_) { /* unsupported browser */ }\r\n }\r\n\r\n // ── Native-seek guard (backstop for PiP / external controls) ──────────\r\n // Chrome's PiP window scrubber (and some media-hub / hardware controls) seek the\r\n // <video> element DIRECTLY — the Media Session handlers above never fire — so ad\r\n // enforcement was bypassed entirely from the PiP window. Every internal seek path\r\n // sets isSeeking / a resume ref BEFORE calling seek, so a 'seeking' event arriving\r\n // without any of those flags can only be a native seek. Handle it like the\r\n // on-screen controls would:\r\n // • ad on screen → snap straight back (seeking fully disabled; only play/pause\r\n // remain usable from the PiP window), or\r\n // • otherwise → snap back to the origin, then re-issue via performSeek so a\r\n // forward jump over unwatched ad breaks plays the nearest missed ad first and\r\n // only then resumes at the target — identical rules to the normal player.\r\n // The revert itself re-fires 'seeking', but lands within 0.5s of the stable\r\n // position and is swallowed by the noise threshold (also covers HLS gap-jumps).\r\n const guardedVideoEl = sdkInstance.getVideoElement();\r\n const guardNativeSeek = () => {\r\n // Only guard while THIS video is in PiP: on-page controls are fully custom and\r\n // the media hub goes through the 'seekto' handler, so outside PiP the only\r\n // unflagged seeks are hls.js's own gap-jumps — reverting those would loop\r\n // playback into a stall at the buffer hole.\r\n const inPiP =\r\n document.pictureInPictureElement === guardedVideoEl ||\r\n (guardedVideoEl as any).webkitPresentationMode === 'picture-in-picture';\r\n if (!inPiP) return;\r\n if (\r\n isSeeking.current ||\r\n resumeSeekTargetRef.current !== null ||\r\n sourceChangeSeekRef.current !== null\r\n ) return; // internal seek — already ad-aware\r\n const target = guardedVideoEl.currentTime;\r\n const from = lastStableTimeRef.current;\r\n if (Math.abs(target - from) < 0.5) return;\r\n if (isAdPlayingRef.current) {\r\n guardedVideoEl.currentTime = from;\r\n return;\r\n }\r\n // Restore the true origin first so performSeek's missed-break scan sees the\r\n // real skipped range (currentTime already holds the native target here).\r\n guardedVideoEl.currentTime = from;\r\n performSeekRef.current?.(target);\r\n };\r\n guardedVideoEl.addEventListener('seeking', guardNativeSeek);\r\n\r\n setPlayer(sdkInstance);\r\n\r\n // Layer 2 (backstop): watchdog for streams whose codecs aren't declared in the manifest,\r\n // so Layer 1 has nothing to inspect. If this player was created by a switch to a\r\n // non-default source and real playback ('buffering: false') hasn't fired by the deadline,\r\n // decide based on the buffer:\r\n // • buffer still empty → segments downloaded but couldn't be decoded → fall back.\r\n // • buffer has grown → it's just a slow network, the format IS loading → keep waiting.\r\n // This buffer check is what prevents a valid-but-slow stream from being misjudged.\r\n // The timer is started here (not in handleSourceChange) so the source-switch re-init's\r\n // own cleanup doesn't clear it before it can fire.\r\n const pendingSwitch = pendingSourceSwitchRef.current;\r\n if (pendingSwitch && pendingSwitch.targetIdx !== 0) {\r\n if (sourceChangeFallbackTimerRef.current) {\r\n clearTimeout(sourceChangeFallbackTimerRef.current);\r\n }\r\n sourceChangeFallbackTimerRef.current = setTimeout(() => {\r\n sourceChangeFallbackTimerRef.current = null;\r\n const buffered = sdkInstance.getBufferedRanges();\r\n const hasBufferedData = buffered.some((r) => r.end > r.start);\r\n // Slow network — the chosen format is genuinely loading; don't interrupt it.\r\n if (hasBufferedData) return;\r\n runSourceFallback(pendingSwitch.targetIdx);\r\n }, SOURCE_FALLBACK_TIMEOUT_MS);\r\n }\r\n\r\n // Stall safety net for the source-switch resume cover: if this player never\r\n // reaches buffering:false (total stall with no error/unsupportedcodec event),\r\n // drop the cover so the user is never left on a black screen. Slightly longer\r\n // than SOURCE_FALLBACK_TIMEOUT_MS so the watchdog above gets to run a fallback\r\n // first (whose re-init re-arms this timer via the effect re-run). The normal\r\n // path clears/re-arms this guard when buffering:false issues the restore seek.\r\n if (sourceChangeSeekRef.current !== null) {\r\n if (resumeGuardTimeout.current) window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = window.setTimeout(() => {\r\n resumeGuardTimeout.current = null;\r\n resumeSeekTargetRef.current = null;\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '0';\r\n // Also clear the switch-forced spinner (see handleSourceChange) so a dead\r\n // stall doesn't leave an eternal spinner after the cover is abandoned.\r\n setIsBuffering(false);\r\n }, SOURCE_FALLBACK_TIMEOUT_MS + 2000);\r\n }\r\n\r\n // Attempt fullscreen immediately on player init when autoFullscreen is requested.\r\n // Browsers allow this when the page was opened by a user action (URL entry, link click).\r\n if (autoFullscreen && containerRef.current) {\r\n hasAutoFullscreenedRef.current = true;\r\n containerRef.current.requestFullscreen().catch(() => {\r\n // Browser denied — video still autoplays in normal mode, no harm done\r\n hasAutoFullscreenedRef.current = false;\r\n });\r\n }\r\n\r\n // Watch for fullscreen change globally to sync state and track analytics\r\n const handleFullscreenChange = () => {\r\n const anyDoc = document as any;\r\n const isFs = !!(document.fullscreenElement || anyDoc.webkitFullscreenElement);\r\n setIsFullscreen(isFs);\r\n // Clear simulated fullscreen flag when native fullscreen exits\r\n if (!isFs) setIsSimulatedFullscreen(false);\r\n const videoEl = sdkInstance.getVideoElement();\r\n sdkInstance.analytics.track(\r\n isFs ? 'fullscreen_enter' : 'fullscreen_exit',\r\n videoEl\r\n );\r\n };\r\n document.addEventListener('fullscreenchange', handleFullscreenChange);\r\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n\r\n // Global keyboard controls handler\r\n const handleKeyDown = (e: KeyboardEvent) => {\r\n const activeEl = document.activeElement;\r\n if (activeEl && (\r\n activeEl.tagName === 'INPUT' ||\r\n activeEl.tagName === 'TEXTAREA' ||\r\n activeEl.getAttribute('contenteditable') === 'true' ||\r\n activeEl.getAttribute('role') === 'slider'\r\n )) {\r\n return;\r\n }\r\n\r\n switch (e.key.toLowerCase()) {\r\n case ' ': // Space bar\r\n case 'k':\r\n e.preventDefault();\r\n if (sdkInstance.getVideoElement().paused) {\r\n sdkInstance.play();\r\n } else {\r\n sdkInstance.pause();\r\n try {\r\n const v = sdkInstance.getVideoElement();\r\n if (v && !v.paused) v.pause();\r\n } catch (e) {}\r\n if (hiddenVideoRef.current && !hiddenVideoRef.current.paused) {\r\n try { hiddenVideoRef.current.pause(); } catch (e) {}\r\n }\r\n }\r\n break;\r\n case 'arrowleft': // Seek back 10s\r\n case 'j': {\r\n e.preventDefault();\r\n if (!isAdPlayingRef.current) {\r\n const rawT = sdkInstance.getCurrentTime();\r\n let ct = rawT;\r\n for (const ab of adBreaksRef.current) {\r\n if (rawT >= ab.endTime) ct -= (ab.endTime - ab.startTime);\r\n else if (rawT >= ab.startTime) ct -= (rawT - ab.startTime);\r\n }\r\n const newCt = Math.max(0, ct - 10);\r\n let newRaw = newCt;\r\n for (const ab of adBreaksRef.current) {\r\n if (newRaw >= ab.startTime) newRaw += (ab.endTime - ab.startTime);\r\n }\r\n // Route through performSeek so isSeeking is set before the seek — a raw\r\n // sdkInstance.seek here would be misread by the native-seek guard as an\r\n // external seek and re-routed (consuming the attribution twice).\r\n performSeekRef.current?.(newRaw, { eventType: 'fast_backward', skipSeconds: 10 });\r\n }\r\n break;\r\n }\r\n case 'arrowright': // Seek forward 10s\r\n case 'l': {\r\n e.preventDefault();\r\n if (!isAdPlayingRef.current) {\r\n const rawT = sdkInstance.getCurrentTime();\r\n const totalAdDur = adBreaksRef.current.reduce((s, ab) => s + (ab.endTime - ab.startTime), 0);\r\n const contentDur = Math.max(0, sdkInstance.getDuration() - totalAdDur);\r\n let ct = rawT;\r\n for (const ab of adBreaksRef.current) {\r\n if (rawT >= ab.endTime) ct -= (ab.endTime - ab.startTime);\r\n else if (rawT >= ab.startTime) ct -= (rawT - ab.startTime);\r\n }\r\n const newCt = Math.min(contentDur, ct + 10);\r\n let newRaw = newCt;\r\n for (const ab of adBreaksRef.current) {\r\n if (newRaw >= ab.startTime) newRaw += (ab.endTime - ab.startTime);\r\n }\r\n // Route through performSeek so any unwatched ad break in the skipped\r\n // range is enforced — same validation as scrubber and mobile buttons.\r\n performSeekRef.current?.(newRaw, { eventType: 'fast_forward', skipSeconds: 10 });\r\n }\r\n break;\r\n }\r\n case 'arrowup': // Volume up 10%\r\n e.preventDefault();\r\n sdkInstance.setVolume(sdkInstance.getVolume() + 0.1);\r\n break;\r\n case 'arrowdown': // Volume down 10%\r\n e.preventDefault();\r\n sdkInstance.setVolume(sdkInstance.getVolume() - 0.1);\r\n break;\r\n case 'm': // Toggle Mute\r\n e.preventDefault();\r\n sdkInstance.setMute(!sdkInstance.isMuted());\r\n break;\r\n case 'f': // Toggle Fullscreen\r\n e.preventDefault();\r\n toggleFullscreenRef.current();\r\n break;\r\n case 'p': // Toggle Picture-in-Picture\r\n e.preventDefault();\r\n if (!isAdPlayingRef.current) sdkInstance.togglePictureInPicture();\r\n break;\r\n case 'c': // Toggle Subtitles\r\n e.preventDefault();\r\n if (effectiveFeatures.enableSubtitles && !isAdPlayingRef.current) {\r\n const activeTrackId = sdkInstance.subtitles.getActiveTrackId();\r\n if (activeTrackId !== 'off') {\r\n sdkInstance.subtitles.setActiveTrack('off');\r\n setActiveSubtitleTrack('off');\r\n } else {\r\n const tracks = availableSubtitlesRef.current;\r\n if (tracks && tracks.length > 0) {\r\n sdkInstance.subtitles.setActiveTrack(tracks[0].id);\r\n setActiveSubtitleTrack(tracks[0].id);\r\n }\r\n }\r\n }\r\n break;\r\n default:\r\n return;\r\n }\r\n resetControlsTimer();\r\n };\r\n\r\n document.addEventListener('keydown', handleKeyDown);\r\n\r\n // Reset controls visibility timer\r\n resetControlsTimer();\r\n\r\n return () => {\r\n // Snapshot session-scoped analytics counters before destroy so the successor\r\n // instance (source switch / fallback / retry) continues the same analytics\r\n // session. Tagged with this player's contentKey — see analyticsSeedRef.\r\n analyticsSeedRef.current = { key: contentKey, snapshot: sdkInstance.analytics.getSessionSnapshot() };\r\n // If a custom PiP window is open, reclaim the <video> element and close the\r\n // floating window BEFORE destroy() runs — otherwise destroy() detaches the video\r\n // from the (still-open) PiP document, leaving a dangling empty window, and the\r\n // next player instance would append a second <video> into containerRef alongside\r\n // whatever this cleanup left behind.\r\n if (pipWindowRef.current) {\r\n const pipWin = pipWindowRef.current;\r\n const videoEl = sdkInstance.getVideoElement();\r\n if (containerRef.current && videoEl.ownerDocument !== document) {\r\n videoEl.classList.remove('gb-pip-video');\r\n containerRef.current.insertBefore(videoEl, containerRef.current.firstChild);\r\n }\r\n pipWindowRef.current = null;\r\n setPipWindow(null);\r\n setIsPiP(false);\r\n pipWin.close();\r\n }\r\n sdkInstance.destroy();\r\n firebaseProviderRef.current?.destroy();\r\n firebaseProviderRef.current = null;\r\n // Restore body scroll in case the component unmounts while simulated fullscreen is active\r\n document.body.style.overflow = '';\r\n document.body.style.touchAction = '';\r\n document.removeEventListener('fullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);\r\n document.removeEventListener('keydown', handleKeyDown);\r\n if (controlsTimeoutRef.current) {\r\n window.clearTimeout(controlsTimeoutRef.current);\r\n }\r\n // Cancel any in-flight ad-skip seek and remove the cover so the player is\r\n // never left blacked-out if the component unmounts mid-skip.\r\n if (seekGuardTimeout.current) {\r\n window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = null;\r\n }\r\n if (sourceChangeFallbackTimerRef.current) {\r\n clearTimeout(sourceChangeFallbackTimerRef.current);\r\n sourceChangeFallbackTimerRef.current = null;\r\n }\r\n adSkipCoverUpRef.current = false;\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '0';\r\n guardedVideoEl.removeEventListener('seeking', guardNativeSeek);\r\n if ('mediaSession' in navigator) {\r\n try { navigator.mediaSession.setActionHandler('seekto', null); } catch (_) {}\r\n try { navigator.mediaSession.setActionHandler('seekforward', null); } catch (_) {}\r\n try { navigator.mediaSession.setActionHandler('seekbackward', null); } catch (_) {}\r\n }\r\n if (hiddenHlsRef.current) {\r\n hiddenHlsRef.current.destroy();\r\n hiddenHlsRef.current = null;\r\n }\r\n if (hiddenVideoRef.current) {\r\n document.body.removeChild(hiddenVideoRef.current);\r\n hiddenVideoRef.current = null;\r\n }\r\n initialSeekAppliedRef.current = false;\r\n resumeSeekTargetRef.current = null;\r\n if (resumeGuardTimeout.current) {\r\n window.clearTimeout(resumeGuardTimeout.current);\r\n resumeGuardTimeout.current = null;\r\n }\r\n };\r\n }, [src, watermark, retryKey]);\r\n\r\n // Synchronize key moments prop dynamically to the running player instance\r\n useEffect(() => {\r\n if (player) {\r\n player.keyMoments.setKeyMoments(localKeyMoments);\r\n \r\n const video = containerRef.current?.querySelector('video');\r\n if (video) {\r\n const activeCh = player.keyMoments.getCurrentMoment(video.currentTime);\r\n setActiveChapter({ chapter: activeCh.keyMoment, index: activeCh.index });\r\n }\r\n }\r\n }, [keyMomentsKey, player]);\r\n\r\n // Close any open control drop-up menu when clicking outside of it\r\n useEffect(() => {\r\n const handleDocumentClick = (e: MouseEvent) => {\r\n if (activeMenu === 'none') return;\r\n\r\n const target = e.target as HTMLElement;\r\n if (target.closest('.sdk-menu-container')) {\r\n return;\r\n }\r\n\r\n setActiveMenu('none');\r\n };\r\n\r\n document.addEventListener('click', handleDocumentClick);\r\n return () => {\r\n document.removeEventListener('click', handleDocumentClick);\r\n };\r\n }, [activeMenu]);\r\n\r\n // Keep refs in sync with latest state so timeout callbacks are never stale\r\n useEffect(() => { isPlayingRef.current = isPlaying; }, [isPlaying]);\r\n useEffect(() => { isAdPlayingRef.current = isAdPlaying; }, [isAdPlaying]);\r\n useEffect(() => { isAdRedirectPendingRef.current = isAdRedirectPending; }, [isAdRedirectPending]);\r\n\r\n // Drives the seekbar's interactive/disabled rendering: locked both while an ad is\r\n // actually on screen and during the brief redirect-in-flight window beforehand.\r\n const seekLocked = isAdPlaying || isAdRedirectPending;\r\n useEffect(() => { pipWindowRef.current = pipWindow; }, [pipWindow]);\r\n useEffect(() => { activeMenuRef.current = activeMenu; }, [activeMenu]);\r\n\r\n useEffect(() => {\r\n if (!isAdPlaying || !player) return;\r\n\r\n let raf = 0;\r\n let anchorTime = player.getCurrentTime();\r\n let anchorWall = performance.now();\r\n\r\n const tick = () => {\r\n const video = player.getVideoElement?.();\r\n const live = player.getCurrentTime();\r\n const now = performance.now();\r\n\r\n let est: number;\r\n if (live !== anchorTime) {\r\n // Media clock advanced (or seeked) — re-anchor to the true position.\r\n anchorTime = live;\r\n anchorWall = now;\r\n est = live;\r\n } else if (video && !video.paused && !video.ended) {\r\n // Media clock hasn't ticked this frame — glide forward with the wall clock.\r\n const rate = video.playbackRate || 1;\r\n est = anchorTime + ((now - anchorWall) / 1000) * rate;\r\n } else {\r\n est = anchorTime; // paused/ended/stalled — hold position\r\n }\r\n\r\n const win = getActiveAdWindow(live);\r\n if (win) {\r\n // Never let interpolation overshoot the true clock (guards against buffering\r\n // stalls where currentTime freezes) or the current ad's boundary.\r\n const capped = Math.min(est, live + 0.4, win.end);\r\n const pct = Math.min(100, Math.max(0, ((capped - win.start) / (win.end - win.start)) * 100));\r\n const fills = containerRef.current?.querySelectorAll<HTMLElement>('.sdk-ad-progress-fill');\r\n fills?.forEach(el => { el.style.width = `${pct}%`; });\r\n }\r\n\r\n raf = requestAnimationFrame(tick);\r\n };\r\n\r\n raf = requestAnimationFrame(tick);\r\n return () => cancelAnimationFrame(raf);\r\n }, [isAdPlaying, player]);\r\n useEffect(() => { availableSubtitlesRef.current = availableSubtitles; }, [availableSubtitles]);\r\n useEffect(() => { isMobileRef.current = isMobile; }, [isMobile]);\r\n useEffect(() => { adBreaksRef.current = adBreaks; }, [adBreaks]);\r\n useEffect(() => { overlayAdsRef.current = overlayAds; }, [overlayAds]);\r\n useEffect(() => { activeOverlayRef.current = activeOverlay; }, [activeOverlay]);\r\n useEffect(() => { adPodItemsRef.current = adPodItems; }, [adPodItems]);\r\n useEffect(() => { sourcesRef.current = sources; }, [sources]);\r\n\r\n useEffect(() => {\r\n const el = adChipRef.current;\r\n if (!isAdPlaying || !el || typeof ResizeObserver === 'undefined') return;\r\n const measure = () => {\r\n const r = el.getBoundingClientRect();\r\n setAdChipSize(prev =>\r\n prev.w === r.width && prev.h === r.height ? prev : { w: r.width, h: r.height }\r\n );\r\n };\r\n measure();\r\n const ro = new ResizeObserver(measure);\r\n ro.observe(el);\r\n return () => ro.disconnect();\r\n }, [isAdPlaying, adPodItems]);\r\n useEffect(() => { onSourceChangeRef.current = onSourceChange; }, [onSourceChange]);\r\n useEffect(() => {\r\n activeAdBreakStartRef.current = null;\r\n wasAdPausedByVisibilityRef.current = false;\r\n pendingSeekTargetRef.current = null;\r\n resumeAdRecheckRef.current = null;\r\n // Reset overlay ad state on source change\r\n setActiveOverlay(null);\r\n activeOverlayRef.current = null;\r\n dismissedOverlayIdsRef.current = new Set();\r\n setPlayerError(false);\r\n }, [src]);\r\n\r\n useEffect(() => { setManifestReady(false); }, [src]);\r\n\r\n useEffect(() => {\r\n if (!player) return;\r\n const video = player.getVideoElement();\r\n if (video && video.readyState >= 1) {\r\n setManifestReady(true);\r\n return;\r\n }\r\n const onReady = () => setManifestReady(true);\r\n player.on('qualitylevels', onReady);\r\n video?.addEventListener('loadedmetadata', onReady);\r\n return () => {\r\n player.off('qualitylevels', onReady);\r\n video?.removeEventListener('loadedmetadata', onReady);\r\n };\r\n }, [player]);\r\n\r\n // Fetch and parse overlay (non-linear) ad metadata\r\n useEffect(() => {\r\n setOverlayAds([]);\r\n if (!overlayAdUrl || !manifestReady) return;\r\n return pollAdMetadata(\r\n overlayAdUrl,\r\n (data: any) => {\r\n const seen = new Set<string>();\r\n const parsed: OverlayAdItem[] = [];\r\n for (const avail of (data.nonLinearAvails ?? [])) {\r\n if (seen.has(avail.availId)) continue;\r\n seen.add(avail.availId);\r\n const adsList = avail.nonLinearAdsList?.[0];\r\n const ad = adsList?.nonLinearAdList?.[0];\r\n if (!ad?.staticResource) continue;\r\n const impressionBeacons: string[] = (adsList?.trackingEvents ?? [])\r\n .filter((e: any) => e.eventType === 'impression')\r\n .flatMap((e: any) => e.beaconUrls ?? []);\r\n let adParams: { position?: string; dismissible?: boolean } = {};\r\n try { adParams = JSON.parse(ad.adParameters ?? '{}'); } catch (_) {}\r\n const position = adParams.position ?? 'bottom_center';\r\n parsed.push({\r\n availId: avail.availId,\r\n startTimeSeconds: parseHHMMSS(avail.startTime ?? ''),\r\n durationSeconds: parseHHMMSS(ad.minSuggestedDuration ?? '00:00:15'),\r\n imageUrl: ad.staticResource,\r\n imageWidth: parseInt(ad.width ?? '320', 10),\r\n imageHeight: parseInt(ad.height ?? '50', 10),\r\n title: ad.adTitle ?? '',\r\n clickThrough: isWebUrl(ad.clickThrough) ? ad.clickThrough : '',\r\n clickTrackingUrl: ad.clickTracking ?? '',\r\n impressionBeacons,\r\n position,\r\n dismissible: adParams.dismissible === true,\r\n });\r\n }\r\n return parsed.length > 0 ? parsed : null;\r\n },\r\n setOverlayAds\r\n );\r\n }, [overlayAdUrl, src, manifestReady]);\r\n\r\n // Fetch and parse avails (linear ad) metadata. Each avail is one ad break and may carry\r\n // a pod of multiple back-to-back ads (avail.ads) — every ad in the pod is parsed, not just\r\n // the first, so skip/CTA/countdown state is correct throughout the whole break.\r\n useEffect(() => {\r\n setAdPodItems([]);\r\n setAvailBreaks([]);\r\n if (!adTrackingUrl || !manifestReady) return;\r\n return pollAdMetadata(\r\n adTrackingUrl,\r\n (data: any) => {\r\n const pods: AdPodItem[] = [];\r\n const breaks: Array<{ startTime: number; endTime: number }> = [];\r\n for (const avail of (data.avails ?? [])) {\r\n const ads = avail.ads ?? [];\r\n if (ads.length === 0) continue;\r\n const breakStart = avail.startTimeInSeconds ?? ads[0].startTimeInSeconds ?? 0;\r\n const lastAd = ads[ads.length - 1];\r\n const podEnd = (lastAd?.startTimeInSeconds ?? breakStart) + (lastAd?.durationInSeconds ?? 0);\r\n breaks.push({\r\n startTime: breakStart,\r\n endTime: avail.durationInSeconds != null\r\n ? breakStart + avail.durationInSeconds\r\n : Math.max(podEnd, breakStart),\r\n });\r\n ads.forEach((ad: any, podIndex: number) => {\r\n const skipOffsetSeconds = parseSkipOffsetSeconds(ad?.skipOffset);\r\n const skippable = skipOffsetSeconds !== null;\r\n let adParams: { ctaText?: string; redirectUrl?: string; title?: string; description?: string } = {};\r\n try { adParams = JSON.parse(ad?.adParameters ?? '{}'); } catch (_) {}\r\n const cta: AdCtaInfo | null = adParams.ctaText && isWebUrl(adParams.redirectUrl)\r\n ? {\r\n text: adParams.ctaText,\r\n url: adParams.redirectUrl,\r\n title: adParams.title || undefined,\r\n description: adParams.description || undefined,\r\n }\r\n : null;\r\n const startTimeSeconds = ad.startTimeInSeconds ?? 0;\r\n const durationSeconds = ad.durationInSeconds ?? 0;\r\n pods.push({\r\n startTimeSeconds,\r\n endTimeSeconds: startTimeSeconds + durationSeconds,\r\n durationSeconds,\r\n skippable,\r\n skipOffsetSeconds: skipOffsetSeconds ?? 0,\r\n cta,\r\n podIndex,\r\n podTotal: ads.length,\r\n });\r\n });\r\n }\r\n return pods.length > 0 ? { pods, breaks } : null;\r\n },\r\n ({ pods, breaks }) => {\r\n setAdPodItems(pods);\r\n setAvailBreaks(breaks);\r\n }\r\n );\r\n }, [adTrackingUrl, src, manifestReady]);\r\n\r\n // Feed avails-derived ad breaks into core-player so adbreakchange, cue markers,\r\n // and ad mode all work even when the HLS manifest parser doesn't detect them.\r\n // Runs whenever the player initialises or avails data arrives (whichever is last).\r\n useEffect(() => {\r\n if (!player || availBreaks.length === 0) return;\r\n player.setAdBreaks(availBreaks);\r\n }, [player, availBreaks]);\r\n\r\n useEffect(() => {\r\n if (!player) return;\r\n\r\n const handleVisibilityChange = () => {\r\n if (document.hidden) {\r\n if (pauseAdOnTabSwitch && isAdPlayingRef.current && isPlayingRef.current) {\r\n player.pause();\r\n wasAdPausedByVisibilityRef.current = true;\r\n }\r\n } else {\r\n if (wasAdPausedByVisibilityRef.current) {\r\n player.play();\r\n wasAdPausedByVisibilityRef.current = false;\r\n }\r\n }\r\n };\r\n\r\n document.addEventListener('visibilitychange', handleVisibilityChange);\r\n return () => {\r\n document.removeEventListener('visibilitychange', handleVisibilityChange);\r\n };\r\n }, [player, pauseAdOnTabSwitch]);\r\n\r\n const handleMouseMove = () => {\r\n resetControlsTimer();\r\n };\r\n\r\n const triggerAutoFullscreen = () => {\r\n if (autoFullscreen && !hasAutoFullscreenedRef.current && containerRef.current) {\r\n hasAutoFullscreenedRef.current = true;\r\n containerRef.current.requestFullscreen().catch(() => {});\r\n }\r\n };\r\n\r\n\r\n const togglePlay = () => {\r\n if (!player) return;\r\n if (castDevice) {\r\n player.casting.remotePlayPause();\r\n resetControlsTimer();\r\n return;\r\n }\r\n if (isEnded) {\r\n performSeek(0);\r\n triggerAutoFullscreen();\r\n player.play();\r\n setIsEnded(false);\r\n } else if (isPlaying) {\r\n player.pause();\r\n // Ensure all underlying HTMLMediaElements are paused as a defensive measure\r\n try {\r\n const videoEl = player.getVideoElement?.();\r\n if (videoEl && !videoEl.paused) videoEl.pause();\r\n } catch (e) {}\r\n if (containerRef.current) {\r\n const vids = containerRef.current.querySelectorAll('video');\r\n vids.forEach(v => { try { (v as HTMLVideoElement).pause(); } catch (e) {} });\r\n }\r\n if (hiddenVideoRef.current && !hiddenVideoRef.current.paused) {\r\n try { hiddenVideoRef.current.pause(); } catch (e) {}\r\n }\r\n } else {\r\n triggerAutoFullscreen();\r\n player.play();\r\n }\r\n resetControlsTimer();\r\n };\r\n\r\n const seekFromEvent = (e: React.MouseEvent<HTMLDivElement> | MouseEvent) => {\r\n if (!seekbarRef.current || !player || displayDuration === 0) return 0;\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\r\n return contentTimeToVideoTime(percent * displayDuration);\r\n };\r\n\r\n const handleSeekMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {\r\n if (!player || isAdPlaying || isAdRedirectPending) return;\r\n isScrubbing.current = true;\r\n const initialTime = seekFromEvent(e);\r\n setScrubbingTime(initialTime);\r\n\r\n const onMouseMove = (ev: MouseEvent) => {\r\n if (!isScrubbing.current || !seekbarRef.current) return;\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (ev.clientX - rect.left) / rect.width));\r\n const targetTime = contentTimeToVideoTime(percent * displayDuration);\r\n\r\n setScrubbingTime(targetTime);\r\n setHoverX(ev.clientX - rect.left);\r\n setHoverTime(targetTime);\r\n\r\n if (hiddenVideoRef.current) {\r\n hiddenVideoRef.current.currentTime = targetTime;\r\n }\r\n resetControlsTimer();\r\n };\r\n\r\n const onMouseUp = (ev: MouseEvent) => {\r\n isScrubbing.current = false;\r\n if (seekbarRef.current) {\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (ev.clientX - rect.left) / rect.width));\r\n performSeek(contentTimeToVideoTime(percent * displayDuration));\r\n }\r\n setScrubbingTime(null);\r\n setHoverX(null);\r\n setHoverThumbnail(null);\r\n setHoverChapter(null);\r\n document.removeEventListener('mousemove', onMouseMove);\r\n document.removeEventListener('mouseup', onMouseUp);\r\n };\r\n\r\n document.addEventListener('mousemove', onMouseMove);\r\n document.addEventListener('mouseup', onMouseUp);\r\n };\r\n\r\n const performSeek = (time: number, attribution?: SeekAttribution) => {\r\n if (!player) return;\r\n resumeAdRecheckRef.current = null;\r\n if (castDevice) {\r\n player.casting.remoteSeek(time);\r\n return;\r\n }\r\n // Clamp to valid range so time never goes negative or past duration\r\n const clampedTime = Math.max(0, duration > 0 ? Math.min(time, duration) : time);\r\n\r\n // Telemetry: report skip-button / key-moment seeks under their own event type.\r\n // Applies to whichever player.seek fires next (direct or ad-redirect).\r\n if (attribution) {\r\n const { eventType, ...extras } = attribution;\r\n player.attributeNextSeek(eventType, extras);\r\n }\r\n\r\n // Forward seek: enforce any unplayed ad breaks in the skipped range.\r\n // Find breaks that overlap [rawCurrentTime, clampedTime) and have not yet been\r\n // watched this session. Uses endTime > rawCurrentTime (not startTime > rawCurrentTime)\r\n // so a break the player is already sitting at/inside still counts as unmissed: setting\r\n // video.currentTime updates the readback synchronously, so a second seek fired quickly\r\n // after a redirect-to-ad seek (e.g. a fast double-click on the seekbar landing while\r\n // isAdPlaying hasn't flipped yet) would otherwise see rawCurrentTime already equal to\r\n // the break's startTime and no longer treat it as \"ahead\", skipping the ad entirely.\r\n const rawCurrentTime = player.getCurrentTime();\r\n if (clampedTime > rawCurrentTime && adBreaksRef.current.length > 0) {\r\n const missed = adBreaksRef.current\r\n .filter(b =>\r\n b.startTime < clampedTime &&\r\n b.endTime > rawCurrentTime &&\r\n !watchedAdBreaksRef.current.has(b.startTime)\r\n )\r\n .sort((a, b) => a.startTime - b.startTime);\r\n\r\n if (missed.length > 0) {\r\n // Only enforce the nearest (last) missed ad before the target — earlier skipped\r\n // breaks are left unwatched. After that single ad plays, adbreakchange(false)\r\n // resumes at the original target directly.\r\n pendingSeekTargetRef.current = clampedTime;\r\n const nearestMissed = missed[missed.length - 1];\r\n // If we're already at/inside the break (the race above), hold position instead\r\n // of seeking backward to its startTime.\r\n const redirectTarget = Math.max(nearestMissed.startTime, rawCurrentTime);\r\n isSeeking.current = true;\r\n seekTargetTime.current = redirectTarget;\r\n preSeekPositionRef.current = rawCurrentTime;\r\n setCurrentTime(redirectTarget);\r\n // Lock out further seeking immediately (ref first — synchronous — so any seek\r\n // handler invoked before the next render still sees it) until the ad is actually\r\n // on screen (isAdPlaying takes over) or the guard timeout below gives up.\r\n isAdRedirectPendingRef.current = true;\r\n setIsAdRedirectPending(true);\r\n // Cover the redirect seek: keyframe snapping can land slightly before the\r\n // break, briefly exposing pre-break content. adbreakchange(true) drops the\r\n // cover once the first ad frame is committed. Mute alongside — the cover\r\n // only hides pixels, not the covered frames' audio.\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '1';\r\n muteCoverAudio(player.getVideoElement());\r\n player.seek(redirectTarget);\r\n if (seekGuardTimeout.current) window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = window.setTimeout(() => {\r\n isSeeking.current = false;\r\n isAdRedirectPendingRef.current = false;\r\n setIsAdRedirectPending(false);\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n seekGuardTimeout.current = null;\r\n adSkipCoverUpRef.current = false;\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '0';\r\n restoreCoverAudio(player.getVideoElement());\r\n }, 3000);\r\n return;\r\n }\r\n }\r\n\r\n // No missed ads (backward seek, or all breaks already watched) — seek normally.\r\n // Also clear any stale pending target so a previous queue doesn't interfere.\r\n pendingSeekTargetRef.current = null;\r\n const nearbyWatchedBreak = adBreaksRef.current.find(\r\n b => watchedAdBreaksRef.current.has(b.startTime) && clampedTime >= b.startTime - 0.5 && clampedTime < b.endTime + 2\r\n );\r\n const actualTarget = nearbyWatchedBreak ? Math.max(clampedTime, nearbyWatchedBreak.endTime + 0.5) : clampedTime;\r\n isSeeking.current = true;\r\n seekTargetTime.current = actualTarget;\r\n preSeekPositionRef.current = rawCurrentTime;\r\n setCurrentTime(actualTarget);\r\n adSkipCoverUpRef.current = false;\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '0';\r\n // Clear any dangling force-mute from an abandoned ad redirect (e.g. the user\r\n // seeked backward while a redirect seek was still in flight).\r\n restoreCoverAudio(player.getVideoElement());\r\n player.seek(actualTarget);\r\n if (seekGuardTimeout.current) window.clearTimeout(seekGuardTimeout.current);\r\n seekGuardTimeout.current = window.setTimeout(() => {\r\n isSeeking.current = false;\r\n seekTargetTime.current = null;\r\n preSeekPositionRef.current = null;\r\n seekGuardTimeout.current = null;\r\n adSkipCoverUpRef.current = false;\r\n if (adSkipCoverRef.current) adSkipCoverRef.current.style.opacity = '0';\r\n }, 3000);\r\n };\r\n\r\n // Keep ref current on every render so the keyboard handler (inside the useEffect\r\n // closure) always calls the latest performSeek with up-to-date state.\r\n performSeekRef.current = performSeek;\r\n\r\n const toggleMute = () => {\r\n if (!player) return;\r\n if (castDevice) {\r\n player.casting.remoteSetMute(!(remoteState?.isMuted ?? false));\r\n return;\r\n }\r\n const nextMute = !isMuted;\r\n player.setMute(nextMute);\r\n setIsMuted(nextMute);\r\n };\r\n\r\n const handleSpeedChange = (rate: number) => {\r\n if (!player) return;\r\n player.setPlaybackRate(rate);\r\n setPlaybackRate(rate);\r\n setActiveMenu('none');\r\n setAnnouncement(`Playback speed: ${rate === 1 ? 'Normal' : `${rate}x`}`);\r\n };\r\n\r\n const handleQualityChange = (qualityId: number) => {\r\n if (!player) return;\r\n player.setQuality(qualityId);\r\n\r\n const selected = qualities.find((q) => q.id === qualityId);\r\n if (selected) {\r\n setActiveQuality(selected);\r\n setAnnouncement(`Quality: ${selected.label}`);\r\n }\r\n setActiveMenu('none');\r\n };\r\n\r\n const handleSubtitleChange = (trackId: string) => {\r\n if (!player) return;\r\n player.subtitles.setActiveTrack(trackId);\r\n setActiveSubtitleTrack(trackId);\r\n setActiveMenu('none');\r\n if (trackId === 'off') {\r\n setAnnouncement('Captions off');\r\n } else {\r\n const track = availableSubtitles.find((t) => t.id === trackId);\r\n setAnnouncement(`Captions: ${track ? getLanguageLabel(track) : 'on'}`);\r\n }\r\n };\r\n\r\n const handleSourceChange = (idx: number) => {\r\n if (!player) return;\r\n // Clear any previous fallback watchdog before starting a new switch\r\n if (sourceChangeFallbackTimerRef.current) {\r\n clearTimeout(sourceChangeFallbackTimerRef.current);\r\n sourceChangeFallbackTimerRef.current = null;\r\n }\r\n setSourceFallbackNotice(null);\r\n // Raise the resume cover for the entire switch. It survives the player re-init\r\n // (the effect cleanup intentionally doesn't touch it) and any unsupported-format\r\n // fallback, and is dropped by the timeupdate landing detection once playback\r\n // reaches the restored position — so neither frame 0 of the new source nor any\r\n // watched-ad frames near the restore point are ever composited visibly.\r\n if (resumeCoverRef.current) resumeCoverRef.current.style.opacity = '1';\r\n // Show the spinner (it renders above the cover) for the whole attempt, not just\r\n // while 'buffering' events fire: an unsupported format may never emit them — it\r\n // stalls silently until the fallback watchdog — which would leave the user staring\r\n // at the bare cover with no feedback. Cleared by the new source's buffering:false\r\n // (native 'playing'), the error handler, or the stall safety net — the same\r\n // signals that resolve the switch itself.\r\n setIsBuffering(true);\r\n // Save current position so the new source resumes where the user left off.\r\n // Only capture a fresh position if the previous switch already resolved — otherwise\r\n // the current player may be a stalled (unsupported) source sitting at 0, and we want\r\n // to keep the original position the user was actually watching.\r\n if (pendingSourceSwitchRef.current === null) {\r\n sourceChangeSeekRef.current = player.getCurrentTime();\r\n const activeSub = availableSubtitles.find((t) => t.id === activeSubtitleTrack);\r\n const activeAudio = audioTracks.find((t) => t.id === activeAudioTrack);\r\n sourceChangePrefsRef.current = {\r\n playbackRate,\r\n volume,\r\n muted: isMuted,\r\n subtitleId: activeSubtitleTrack,\r\n subtitleLang: activeSub?.lang ?? null,\r\n audioLang: activeAudio && activeAudioTrack !== 0 ? activeAudio.lang : null,\r\n audioLabel: activeAudio && activeAudioTrack !== 0 ? activeAudio.label : null,\r\n qualityHeight: activeQuality.id !== -1 ? activeQuality.height : null,\r\n };\r\n }\r\n // Flag the switch so the new player's effect starts the unsupported-format watchdog\r\n pendingSourceSwitchRef.current = { targetIdx: idx };\r\n setActiveMenu('none');\r\n setAnnouncement(`Source: ${sources?.[idx]?.label ?? ''}`);\r\n onSourceChange?.(idx);\r\n };\r\n\r\n const handleChapterClick = (startTime: number, moment?: KeyMoment) => {\r\n performSeek(startTime, {\r\n eventType: 'key_moments',\r\n momentTitle: moment?.title ?? '',\r\n momentTime: startTime,\r\n });\r\n setActiveMenu('none');\r\n };\r\n\r\n // Moves the real <video> element (still owned by core-player, wherever it currently\r\n // lives) back into the on-page container and clears PiP state. Shared by the manual\r\n // toggle-off path, the PiP window's own close button ('pagehide'), and the player\r\n // effect's cleanup (source change / unmount) so a closed window never leaves a stale ref.\r\n const closeCustomPip = useCallback(() => {\r\n const pipWin = pipWindowRef.current;\r\n if (!pipWin) return;\r\n const videoEl = player?.getVideoElement?.();\r\n if (videoEl && containerRef.current && videoEl.ownerDocument !== document) {\r\n videoEl.classList.remove('gb-pip-video');\r\n videoEl.style.objectFit = '';\r\n containerRef.current.insertBefore(videoEl, containerRef.current.firstChild);\r\n }\r\n pipWindowRef.current = null;\r\n setPipWindow(null);\r\n setIsPiP(false);\r\n }, [player]);\r\n\r\n const togglePiP = async () => {\r\n if (!player) return;\r\n const docPip = (window as any).documentPictureInPicture;\r\n\r\n // Chromium's Document Picture-in-Picture API lets us open a real floating window and\r\n // render our own controls inside it — sidestepping the native PiP overlay entirely,\r\n // which (per the countdown-drift bug) reads the real <video> element's currentTime\r\n // directly and isn't reliably frozen on pause. Where it's unsupported (Firefox, Safari)\r\n // fall back to the existing native video.requestPictureInPicture() flow.\r\n if (!docPip) {\r\n player.togglePictureInPicture();\r\n return;\r\n }\r\n\r\n if (pipWindowRef.current) {\r\n pipWindowRef.current.close();\r\n return;\r\n }\r\n\r\n try {\r\n const videoEl = player.getVideoElement();\r\n const aspect = videoEl.videoWidth && videoEl.videoHeight\r\n ? videoEl.videoWidth / videoEl.videoHeight\r\n : 16 / 9;\r\n const width = 360;\r\n const pipWin: Window = await docPip.requestWindow({ width, height: Math.round(width / aspect) });\r\n\r\n const style = pipWin.document.createElement('style');\r\n style.textContent = PIP_STYLES;\r\n pipWin.document.head.appendChild(style);\r\n\r\n videoEl.classList.add('gb-pip-video');\r\n pipWin.document.body.appendChild(videoEl);\r\n\r\n // The window can be dismissed via its own native close button, not just our\r\n // toggle — 'pagehide' fires either way, so it's the single source of truth for cleanup.\r\n pipWin.addEventListener('pagehide', () => closeCustomPip(), { once: true });\r\n\r\n pipWindowRef.current = pipWin;\r\n setPipWindow(pipWin);\r\n setIsPiP(true);\r\n } catch (err) {\r\n console.error('[PlayerSDK PiP] Failed to open custom Picture-in-Picture window:', err);\r\n }\r\n };\r\n\r\n // Simple click-to-seek for the custom PiP window's progress bar — deliberately not\r\n // wired into the main seekbar's drag/scrub state machine (hover thumbnails, chapter\r\n // preview, etc.), since none of that UI exists in the small floating window.\r\n const handlePipSeekClick = (e: React.MouseEvent<HTMLDivElement>) => {\r\n if (isAdPlaying || isAdRedirectPending) return;\r\n const rect = e.currentTarget.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\r\n performSeek(contentTimeToVideoTime(percent * displayDuration));\r\n };\r\n\r\n const toggleFullscreen = () => {\r\n if (!containerRef.current) return;\r\n const container = containerRef.current;\r\n const anyDoc = document as any;\r\n const anyContainer = container as any;\r\n const isNativeFs = !!(document.fullscreenElement || anyDoc.webkitFullscreenElement);\r\n\r\n const enterSimulated = () => {\r\n setIsSimulatedFullscreen(true);\r\n setIsFullscreen(true);\r\n document.body.style.overflow = 'hidden';\r\n document.body.style.touchAction = 'none';\r\n player?.analytics.track('fullscreen_enter', player.getVideoElement());\r\n };\r\n\r\n const exitSimulated = () => {\r\n setIsSimulatedFullscreen(false);\r\n setIsFullscreen(false);\r\n document.body.style.overflow = '';\r\n document.body.style.touchAction = '';\r\n player?.analytics.track('fullscreen_exit', player.getVideoElement());\r\n };\r\n\r\n if (isNativeFs || isSimulatedFullscreen) {\r\n setWasMobileOnFullscreenEnter(false);\r\n if (document.fullscreenElement) {\r\n document.exitFullscreen();\r\n } else if (anyDoc.webkitFullscreenElement) {\r\n anyDoc.webkitExitFullscreen?.();\r\n } else {\r\n exitSimulated();\r\n }\r\n } else {\r\n setWasMobileOnFullscreenEnter(isMobileRef.current);\r\n const requestFs: (() => Promise<void>) | undefined =\r\n container.requestFullscreen?.bind(container) ??\r\n anyContainer.webkitRequestFullscreen?.bind(anyContainer);\r\n if (requestFs) {\r\n requestFs().catch(enterSimulated);\r\n } else {\r\n // iOS Safari: no Fullscreen API — use CSS fixed overlay\r\n enterSimulated();\r\n }\r\n }\r\n };\r\n\r\n // Keep ref current so the stale-closure-safe keyboard handler always calls the latest version\r\n useEffect(() => {\r\n toggleFullscreenRef.current = toggleFullscreen;\r\n });\r\n\r\n const handleSkipAd = () => {\r\n if (!isAdPlaying) return;\r\n const currentAdBreak = adBreaks.find(ab => currentTime >= ab.startTime && currentTime < ab.endTime);\r\n\r\n // Each ad in a pod carries its own skipOffset in the avails metadata, so Skip applies to\r\n // the ad on screen only — skipping the first ad of a pod advances to the second, which\r\n // then runs its own countdown. Skipping the last ad leaves the break entirely.\r\n const currentAd = adPodItems.find(a => currentTime >= a.startTimeSeconds && currentTime < a.endTimeSeconds);\r\n if (currentAd) {\r\n const isLastInPod = currentAd.podIndex >= currentAd.podTotal - 1;\r\n triggerAdSkipRef.current?.(\r\n isLastInPod ? (currentAdBreak?.endTime ?? currentAd.endTimeSeconds) : currentAd.endTimeSeconds,\r\n !isLastInPod\r\n );\r\n return;\r\n }\r\n\r\n // No per-ad metadata (tracking URL absent or returned no avails) — the break is the\r\n // smallest unit we can resolve, so skip it whole.\r\n if (!currentAdBreak) return;\r\n triggerAdSkipRef.current?.(currentAdBreak.endTime);\r\n };\r\n\r\n const dismissOverlay = () => {\r\n if (activeOverlayRef.current) {\r\n dismissedOverlayIdsRef.current.add(activeOverlayRef.current.availId);\r\n }\r\n setActiveOverlay(null);\r\n activeOverlayRef.current = null;\r\n };\r\n\r\n const handleOverlayClick = (overlay: OverlayAdItem) => {\r\n if (overlay.clickTrackingUrl) fetch(overlay.clickTrackingUrl, { mode: 'no-cors' }).catch(() => {});\r\n if (overlay.clickThrough) window.open(overlay.clickThrough, '_blank', 'noopener,noreferrer');\r\n };\r\n\r\n const handleAdCtaClick = (cta: AdCtaInfo) => {\r\n window.open(cta.url, '_blank', 'noopener,noreferrer');\r\n };\r\n\r\n const handleCastToggle = () => {\r\n if (!player) return;\r\n if (castDevice) {\r\n player.casting.stopCasting();\r\n } else {\r\n player.casting.requestSession(src, currentTime);\r\n }\r\n };\r\n\r\n const handleAirPlayToggle = () => {\r\n if (!player) return;\r\n const video = player.getVideoElement();\r\n\r\n if (isAirPlayConnected) {\r\n player.casting.stopAirPlay(video);\r\n return;\r\n }\r\n\r\n if (typeof (video as any).webkitShowPlaybackTargetPicker === 'function') {\r\n player.casting.requestAirPlaySession(video);\r\n } else if (player.casting.isAirPlayAvailable()) {\r\n player.casting.requestAirPlaySession(video);\r\n } else {\r\n console.warn('[PlayerSDK AirPlay] AirPlay is not available on this device.');\r\n }\r\n };\r\n\r\n const handleSeekbarHover = (e: React.MouseEvent<HTMLDivElement>) => {\r\n if (!seekbarRef.current || displayDuration === 0 || !player || isAdPlaying || isAdRedirectPending) return;\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n seekbarWidthRef.current = rect.width;\r\n const tooltipW = 192;\r\n setTooltipLeft(Math.max(tooltipW / 2, Math.min(e.clientX - rect.left, rect.width - tooltipW / 2)));\r\n const percent = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\r\n const videoTarget = contentTimeToVideoTime(percent * displayDuration);\r\n\r\n setHoverX(e.clientX - rect.left);\r\n setHoverTime(videoTarget);\r\n\r\n if (hoverSeekTimer.current) window.clearTimeout(hoverSeekTimer.current);\r\n hoverSeekTimer.current = window.setTimeout(() => {\r\n if (hiddenVideoRef.current) {\r\n hiddenVideoRef.current.currentTime = videoTarget;\r\n }\r\n }, 100);\r\n\r\n const hoverCh = player.keyMoments.getCurrentMoment(videoTarget);\r\n setHoverChapter(hoverCh.keyMoment);\r\n };\r\n\r\n const seekTimeFromTouch = (touch: React.Touch): number => {\r\n if (!seekbarRef.current || displayDuration === 0) return 0;\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (touch.clientX - rect.left) / rect.width));\r\n return contentTimeToVideoTime(percent * displayDuration);\r\n };\r\n\r\n const handleSeekTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {\r\n if (!player || isAdPlaying || isAdRedirectPending || displayDuration === 0) return;\r\n e.preventDefault();\r\n isScrubbing.current = true;\r\n const initialTime = seekTimeFromTouch(e.touches[0]);\r\n setScrubbingTime(initialTime);\r\n\r\n const onTouchMove = (ev: TouchEvent) => {\r\n if (!isScrubbing.current || !seekbarRef.current) return;\r\n const t = ev.touches[0];\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (t.clientX - rect.left) / rect.width));\r\n const targetTime = contentTimeToVideoTime(percent * displayDuration);\r\n setScrubbingTime(targetTime);\r\n setHoverTime(targetTime);\r\n resetControlsTimer();\r\n };\r\n\r\n const onTouchEnd = (ev: TouchEvent) => {\r\n isScrubbing.current = false;\r\n if (seekbarRef.current && ev.changedTouches[0]) {\r\n const t = ev.changedTouches[0];\r\n const rect = seekbarRef.current.getBoundingClientRect();\r\n const percent = Math.max(0, Math.min(1, (t.clientX - rect.left) / rect.width));\r\n performSeek(contentTimeToVideoTime(percent * displayDuration));\r\n }\r\n setScrubbingTime(null);\r\n document.removeEventListener('touchmove', onTouchMove);\r\n document.removeEventListener('touchend', onTouchEnd);\r\n };\r\n\r\n document.addEventListener('touchmove', onTouchMove, { passive: false });\r\n document.addEventListener('touchend', onTouchEnd);\r\n };\r\n\r\n const handleSeekbarLeave = () => {\r\n setHoverX(null);\r\n setHoverThumbnail(null);\r\n setHoverChapter(null);\r\n if (hoverSeekTimer.current) {\r\n window.clearTimeout(hoverSeekTimer.current);\r\n }\r\n };\r\n\r\n const formatTime = (seconds: number) => {\r\n if (isNaN(seconds) || seconds < 0) return '00:00';\r\n const s = Math.floor(seconds);\r\n const hrs = Math.floor(s / 3600);\r\n const mins = Math.floor((s % 3600) / 60);\r\n const secs = s % 60;\r\n\r\n const pad = (num: number) => String(num).padStart(2, '0');\r\n\r\n if (hrs > 0) {\r\n return `${pad(hrs)}:${pad(mins)}:${pad(secs)}`;\r\n }\r\n return `${pad(mins)}:${pad(secs)}`;\r\n };\r\n\r\n // Announce play/pause and mute/unmute to screen readers via the live region.\r\n // announceMountedRef prevents false announcements on initial mount.\r\n useEffect(() => {\r\n if (!announceMountedRef.current) return;\r\n setAnnouncement(isPlaying ? 'Playing' : 'Paused');\r\n }, [isPlaying]);\r\n\r\n useEffect(() => {\r\n if (!announceMountedRef.current) return;\r\n setAnnouncement(isMuted ? 'Muted' : 'Unmuted');\r\n }, [isMuted]);\r\n\r\n useEffect(() => { announceMountedRef.current = true; }, []);\r\n\r\n // Close any open menu when Escape is pressed\r\n useEffect(() => {\r\n const handleEscape = (e: KeyboardEvent) => {\r\n if (e.key === 'Escape' && activeMenuRef.current !== 'none') {\r\n e.preventDefault();\r\n setActiveMenu('none');\r\n }\r\n };\r\n document.addEventListener('keydown', handleEscape);\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, []);\r\n\r\n // When casting, these reflect the remote device's state; otherwise local state\r\n const ctrlIsPlaying = castDevice ? !(remoteState?.isPaused ?? true) : isPlaying;\r\n const ctrlCurrentTime = castDevice ? (remoteState?.currentTime ?? 0) : currentTime;\r\n const ctrlDuration = castDevice ? (remoteState?.duration ?? 0) : displayDuration;\r\n const ctrlVolume = castDevice ? (remoteState?.volumeLevel ?? 1) : volume;\r\n const ctrlMuted = castDevice ? (remoteState?.isMuted ?? false) : isMuted;\r\n\r\n const handleMobileOverlayClick = useCallback((e: React.MouseEvent) => {\r\n if (isAdPlaying || isAdRedirectPending) return;\r\n if ((e.target as HTMLElement).closest('.sdk-mobile-ctrl')) return;\r\n const now = Date.now();\r\n const rect = containerRef.current?.getBoundingClientRect();\r\n const tapX = e.clientX - (rect?.left ?? 0);\r\n const last = lastTapRef.current;\r\n if (last && now - last.time < 300) {\r\n lastTapRef.current = null;\r\n const isLeft = tapX < (rect?.width ?? 0) / 2;\r\n const delta = isLeft ? -10 : 10;\r\n const newContentTime = Math.max(0, Math.min(displayDuration, videoTimeToContentTime(ctrlCurrentTime) + delta));\r\n performSeek(contentTimeToVideoTime(newContentTime), {\r\n eventType: isLeft ? 'fast_backward' : 'fast_forward',\r\n skipSeconds: 10,\r\n });\r\n setDoubleTapSide(isLeft ? 'left' : 'right');\r\n window.setTimeout(() => setDoubleTapSide(null), 700);\r\n resetControlsTimer();\r\n } else {\r\n lastTapRef.current = { time: now, x: tapX };\r\n if (controlsVisible) {\r\n setControlsVisible(false);\r\n if (controlsTimeoutRef.current) window.clearTimeout(controlsTimeoutRef.current);\r\n } else {\r\n resetControlsTimer();\r\n }\r\n }\r\n }, [isAdPlaying, isAdRedirectPending, displayDuration, videoTimeToContentTime, ctrlCurrentTime, contentTimeToVideoTime, performSeek, controlsVisible]);\r\n\r\n const getVolumeIcon = () => {\r\n if (ctrlMuted || ctrlVolume === 0) return <VolumeX size={18} />;\r\n if (ctrlVolume < 0.3) return <Volume size={18} />;\r\n if (ctrlVolume < 0.7) return <Volume1 size={18} />;\r\n return <Volume2 size={18} />;\r\n };\r\n\r\n // Returns a keyboard handler for listbox option items (Enter/Space activates, Escape closes)\r\n const menuItemKeyDown = (action: () => void) => (e: React.KeyboardEvent) => {\r\n if (e.key === 'Enter' || e.key === ' ') {\r\n e.preventDefault();\r\n action();\r\n } else if (e.key === 'Escape') {\r\n e.preventDefault();\r\n setActiveMenu('none');\r\n }\r\n };\r\n\r\n // Returns a keyboard handler for elements acting as buttons (e.g. chapter cards as divs)\r\n const cardKeyDown = (action: () => void) => (e: React.KeyboardEvent) => {\r\n if (e.key === 'Enter' || e.key === ' ') {\r\n e.preventDefault();\r\n action();\r\n }\r\n };\r\n\r\n // Keyboard handler for the seek slider (role=\"slider\") — arrow keys seek ±5 s\r\n const handleSeekbarKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\r\n if (isAdPlaying || isAdRedirectPending) return;\r\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\r\n e.preventDefault();\r\n const delta = e.key === 'ArrowLeft' ? -5 : 5;\r\n const newCt = Math.max(0, Math.min(displayDuration, videoTimeToContentTime(ctrlCurrentTime) + delta));\r\n performSeek(contentTimeToVideoTime(newCt));\r\n } else if (e.key === 'Home') {\r\n e.preventDefault();\r\n performSeek(contentTimeToVideoTime(0));\r\n } else if (e.key === 'End') {\r\n e.preventDefault();\r\n performSeek(contentTimeToVideoTime(displayDuration));\r\n }\r\n };\r\n\r\n const menuToggle = (menu: typeof activeMenu) => {\r\n setActiveMenu(activeMenu === menu ? 'none' : menu);\r\n };\r\n\r\n // Keeps a drop-up menu inside the player bounds. Menus are centered on their\r\n // trigger by default; for triggers near an edge that centered box would be\r\n // clipped, so we nudge it horizontally by exactly the amount it overflows\r\n // (plus a small margin) — just enough to fit, no more. The ref callback runs\r\n // after layout but before paint, so the centered box is measured and the\r\n // shift applied before the open animation plays.\r\n const clampDropupMenu = useCallback((el: HTMLDivElement | null) => {\r\n if (!el) return;\r\n el.style.left = '';\r\n const container = containerRef.current;\r\n if (!container) return;\r\n const menuRect = el.getBoundingClientRect();\r\n const contRect = container.getBoundingClientRect();\r\n const margin = 8;\r\n const overflowRight = menuRect.right - (contRect.right - margin);\r\n const overflowLeft = (contRect.left + margin) - menuRect.left;\r\n if (overflowRight > 0) {\r\n el.style.left = `calc(50% - ${Math.ceil(overflowRight)}px)`;\r\n } else if (overflowLeft > 0) {\r\n el.style.left = `calc(50% + ${Math.ceil(overflowLeft)}px)`;\r\n }\r\n }, []);\r\n\r\n return (\r\n <div\r\n ref={containerRef}\r\n role=\"region\"\r\n aria-label={title || extractedTitle ? `${title || extractedTitle} video player` : 'Video player'}\r\n className={`sdk-player-container ${controlsVisible ? 'controls-active' : ''} ${isSimulatedFullscreen ? 'sdk-fs-simulated' : ''}`}\r\n onMouseMove={handleMouseMove}\r\n onTouchStart={useMobileLayout ? undefined : resetControlsTimer}\r\n onMouseLeave={() => {\r\n // Don't snap-hide on mouse-leave; let the running 10s timer handle it naturally\r\n // This way controls stay visible briefly as the cursor exits the player\r\n }}\r\n style={{\r\n border: isSimulatedFullscreen ? 'none' : `1px solid rgba(255, 255, 255, 0.08)`,\r\n '--sdk-theme-color': themeColor,\r\n '--sdk-theme-color-rgb': hexToRgb(themeColor),\r\n } as React.CSSProperties}\r\n >\r\n <canvas\r\n ref={canvasRef}\r\n width={160}\r\n height={90}\r\n style={{ display: 'none', position: 'absolute', pointerEvents: 'none' }}\r\n aria-hidden=\"true\"\r\n />\r\n\r\n {/* Screen-reader live region for dynamic state announcements */}\r\n <div\r\n role=\"status\"\r\n aria-live=\"polite\"\r\n aria-atomic=\"true\"\r\n className=\"sdk-sr-only\"\r\n >\r\n {announcement}\r\n </div>\r\n\r\n {/* Black cover used during ad-skip seeks to prevent any ad frame from bleeding through */}\r\n <div\r\n ref={adSkipCoverRef}\r\n style={{ opacity: 0, position: 'absolute', inset: 0, background: '#000', zIndex: 100, pointerEvents: 'none', willChange: 'opacity' }}\r\n aria-hidden=\"true\"\r\n />\r\n\r\n {/* The real <video> element lives inside the custom PiP window while it's open —\r\n this fills the space it left behind on the page (mirrors what Chrome's native\r\n PiP shows automatically, which this custom window bypasses). */}\r\n {pipWindow && (\r\n <div\r\n className=\"sdk-pip-placeholder\"\r\n aria-hidden=\"true\"\r\n >\r\n <PictureInPicture size={32} aria-hidden=\"true\" />\r\n <span>Playing in Picture-in-Picture</span>\r\n </div>\r\n )}\r\n\r\n {/* Resume-playback cover: raised from the first paint when initialTime > 0 so frame 0 is\r\n never shown, then dropped once playback lands at the resume point (see timeupdate). */}\r\n <div\r\n ref={resumeCoverRef}\r\n style={{ opacity: initialTime && initialTime > 0 ? 1 : 0, position: 'absolute', inset: 0, background: '#000', zIndex: 100, pointerEvents: 'none', willChange: 'opacity' }}\r\n aria-hidden=\"true\"\r\n />\r\n\r\n <div className={`sdk-spinner-overlay ${isBuffering ? 'active' : ''}`} aria-hidden=\"true\">\r\n <div className=\"sdk-spinner\"></div>\r\n </div>\r\n\r\n {playerError && (\r\n <div style={{\r\n position: 'absolute', inset: 0, zIndex: 50,\r\n background: 'rgba(9,9,14,0.96)',\r\n display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',\r\n gap: 20, padding: '0 24px',\r\n }}>\r\n <ErrorScreen\r\n onRetry={() => {\r\n setPlayerError(false);\r\n setRetryKey(k => k + 1);\r\n }}\r\n />\r\n </div>\r\n )}\r\n\r\n {sourceFallbackNotice && (\r\n <div style={{\r\n // Centered, offset just below the buffering spinner (60px tall, dead-center)\r\n // so the two read as one \"loading + why\" unit during the fallback re-init.\r\n // z-index 102: above the black covers (100) and the spinner (101) — the\r\n // notice fires while the cover is up, so anything lower is invisible for\r\n // most of its 4s lifetime.\r\n position: 'absolute', top: '50%', left: '50%',\r\n transform: 'translate(-50%, calc(-50% + 70px))',\r\n background: 'rgba(0,0,0,0.82)', color: '#fff', fontSize: 13, fontWeight: 500,\r\n padding: '8px 16px', borderRadius: 8, zIndex: 102, pointerEvents: 'none',\r\n maxWidth: '85%', textAlign: 'center', backdropFilter: 'blur(8px)',\r\n border: '1px solid rgba(255,255,255,0.12)',\r\n }}>\r\n {sourceFallbackNotice}\r\n </div>\r\n )}\r\n\r\n {/* Hidden during ad breaks: content captions must never render over an ad */}\r\n {effectiveFeatures.enableSubtitles && !isAdPlaying && subtitleText && activeSubtitleTrack !== 'off' && (\r\n <div className=\"sdk-subtitles-overlay\">\r\n <span\r\n className=\"sdk-subtitles-text\"\r\n style={{\r\n fontSize: subtitleStyle.size === 'small' ? '14px' : subtitleStyle.size === 'large' ? '24px' : '18px',\r\n color: subtitleStyle.color ?? '#ffffff',\r\n background: subtitleStyle.background === 'none'\r\n ? 'transparent'\r\n : subtitleStyle.background === 'solid'\r\n ? 'rgba(0, 0, 0, 0.92)'\r\n : 'rgba(15, 23, 42, 0.78)',\r\n border: subtitleStyle.background === 'none' ? 'none' : '1px solid rgba(255,255,255,0.08)',\r\n backdropFilter: subtitleStyle.background === 'none' ? 'none' : 'blur(8px)',\r\n }}\r\n dangerouslySetInnerHTML={{ __html: subtitleText }}\r\n />\r\n </div>\r\n )}\r\n\r\n {/* Non-linear overlay ad banner */}\r\n {activeOverlay && (\r\n <div className={`sdk-overlay-ad sdk-overlay-ad--${activeOverlay.position}`} role=\"complementary\" aria-label=\"Advertisement\">\r\n <div className=\"sdk-overlay-ad-topbar\">\r\n <span className=\"sdk-overlay-ad-label\">Ad</span>\r\n {activeOverlay.dismissible && (\r\n <button\r\n className=\"sdk-overlay-ad-close\"\r\n onClick={dismissOverlay}\r\n aria-label=\"Close advertisement\"\r\n >\r\n ✕\r\n </button>\r\n )}\r\n </div>\r\n <img\r\n src={activeOverlay.imageUrl}\r\n width={activeOverlay.imageWidth}\r\n height={activeOverlay.imageHeight}\r\n alt={activeOverlay.title || 'Advertisement'}\r\n className=\"sdk-overlay-ad-img\"\r\n onClick={() => handleOverlayClick(activeOverlay)}\r\n />\r\n </div>\r\n )}\r\n\r\n {isAdPlaying && (() => {\r\n // The current individual ad within the pod (if the break came from the tracking URL\r\n // and carries avails/pod metadata) — falls back to the whole ad break otherwise.\r\n const currentAd = adPodItems.find(a => currentTime >= a.startTimeSeconds && currentTime < a.endTimeSeconds);\r\n const currentAdBreak = adBreaks.find(ab => currentTime >= ab.startTime && currentTime < ab.endTime);\r\n const remainingAdTime = currentAd\r\n ? Math.max(0, currentAd.endTimeSeconds - currentTime)\r\n : (currentAdBreak ? Math.max(0, currentAdBreak.endTime - currentTime) : 0);\r\n const adPlayedSeconds = currentAd\r\n ? currentTime - currentAd.startTimeSeconds\r\n : (currentAdBreak ? currentTime - currentAdBreak.startTime : 0);\r\n const isPod = !!currentAd && currentAd.podTotal > 1;\r\n const effectiveSkippable = currentAd ? currentAd.skippable : enableSkipAd;\r\n const effectiveSkipAfter = currentAd ? currentAd.skipOffsetSeconds : skipAdAfterSeconds;\r\n const canSkip = effectiveSkippable && adPlayedSeconds >= effectiveSkipAfter;\r\n const skipCountdown = Math.ceil(effectiveSkipAfter - adPlayedSeconds);\r\n // Elapsed fraction of the current ad, drives the progress border (0→1).\r\n const adTotalSeconds = adPlayedSeconds + remainingAdTime;\r\n const adProgress = adTotalSeconds > 0\r\n ? Math.min(1, Math.max(0, adPlayedSeconds / adTotalSeconds))\r\n : 0;\r\n const SW = 2.5; // border stroke width\r\n const { w: chipW, h: chipH } = adChipSize;\r\n const innerW = Math.max(0, chipW - SW);\r\n const innerH = Math.max(0, chipH - SW);\r\n const rr = Math.min(22 - SW / 2, innerH / 2);\r\n const ox = SW / 2;\r\n const oy = SW / 2;\r\n const straightX = Math.max(0, innerW - 2 * rr);\r\n const straightY = Math.max(0, innerH - 2 * rr);\r\n const borderPath =\r\n `M${ox + rr + straightX / 2},${oy}` +\r\n `h${straightX / 2}` +\r\n `a${rr},${rr} 0 0 1 ${rr},${rr}` +\r\n `v${straightY}` +\r\n `a${rr},${rr} 0 0 1 ${-rr},${rr}` +\r\n `h${-straightX}` +\r\n `a${rr},${rr} 0 0 1 ${-rr},${-rr}` +\r\n `v${-straightY}` +\r\n `a${rr},${rr} 0 0 1 ${rr},${-rr}` +\r\n `h${straightX / 2}`;\r\n const borderLen = 2 * straightX + 2 * straightY + 2 * Math.PI * rr;\r\n return (\r\n <>\r\n <div\r\n ref={adChipRef}\r\n className=\"sdk-ad-countdown\"\r\n role=\"timer\"\r\n aria-label={isPod\r\n ? `Advertisement ${currentAd!.podIndex + 1} of ${currentAd!.podTotal} — ${formatTime(remainingAdTime)} remaining`\r\n : `Advertisement — ${formatTime(remainingAdTime)} remaining`}\r\n aria-live=\"off\"\r\n >\r\n {chipW > 0 && chipH > 0 && (\r\n <svg\r\n className=\"sdk-ad-countdown-border\"\r\n width={chipW}\r\n height={chipH}\r\n viewBox={`0 0 ${chipW} ${chipH}`}\r\n aria-hidden=\"true\"\r\n >\r\n <path className=\"sdk-ad-countdown-border-track\" d={borderPath} strokeWidth={SW} />\r\n <path\r\n className=\"sdk-ad-countdown-border-fill\"\r\n d={borderPath}\r\n strokeWidth={SW}\r\n strokeDasharray={borderLen}\r\n strokeDashoffset={borderLen * (1 - adProgress)}\r\n />\r\n </svg>\r\n )}\r\n <span className=\"sdk-ad-countdown-badge\" aria-hidden=\"true\">Ad</span>\r\n {isPod && (\r\n <span className=\"sdk-ad-countdown-count\" aria-hidden=\"true\">\r\n {currentAd!.podIndex + 1} of {currentAd!.podTotal}\r\n </span>\r\n )}\r\n <span className=\"sdk-ad-countdown-sep\" aria-hidden=\"true\" />\r\n <span className=\"sdk-ad-countdown-time\" aria-hidden=\"true\">{formatTime(remainingAdTime)}</span>\r\n </div>\r\n\r\n {/* Skip Ad button — shown when ad is skippable per avails metadata or enableSkipAd prop */}\r\n {effectiveSkippable && (\r\n <button\r\n className={`sdk-skip-ad-btn ${canSkip ? 'sdk-skip-ad-btn--ready' : ''}`}\r\n onClick={canSkip ? handleSkipAd : undefined}\r\n disabled={!canSkip}\r\n aria-label={canSkip ? 'Skip advertisement' : `Skip advertisement available in ${skipCountdown} seconds`}\r\n aria-disabled={!canSkip}\r\n >\r\n {canSkip ? 'Skip Ad ›' : `Skip in ${skipCountdown}s`}\r\n </button>\r\n )}\r\n\r\n {/* Ad CTA — rendered only when the current ad carries CTA metadata in its\r\n adParameters. Anchored bottom-left, opposite the Skip Ad button, so the\r\n two never collide. Layout adapts to the metadata present:\r\n button-only / title + button / title + description + button. */}\r\n {currentAd?.cta && (() => {\r\n const cta = currentAd.cta;\r\n const ctaButton = (\r\n <button\r\n className=\"sdk-ad-cta-btn\"\r\n onClick={() => handleAdCtaClick(cta)}\r\n aria-label={`${cta.text} — opens advertiser site in a new tab`}\r\n >\r\n <span className=\"sdk-ad-cta-btn-text\">{cta.text}</span>\r\n <ExternalLink size={15} aria-hidden=\"true\" />\r\n </button>\r\n );\r\n return (\r\n <div\r\n className=\"sdk-ad-cta\"\r\n role=\"complementary\"\r\n aria-label=\"Advertisement call to action\"\r\n >\r\n {cta.title ? (\r\n <div className=\"sdk-ad-cta-card\">\r\n <div className=\"sdk-ad-cta-info\">\r\n <div className=\"sdk-ad-cta-title\">{cta.title}</div>\r\n {cta.description && (\r\n <div className=\"sdk-ad-cta-desc\">{cta.description}</div>\r\n )}\r\n </div>\r\n {ctaButton}\r\n </div>\r\n ) : ctaButton}\r\n </div>\r\n );\r\n })()}\r\n </>\r\n );\r\n })()}\r\n\r\n {isEntitlementBlocked && (\r\n <div style={{position:'absolute',inset:0,display:'flex',alignItems:'center',justifyContent:'center',background:'rgba(0,0,0,0.72)',color:'#fff',zIndex:60}}>\r\n <div style={{maxWidth:480,textAlign:'center',padding:24}}>\r\n <h3 style={{margin:'0 0 8px'}}>Playback restricted</h3>\r\n <p style={{margin:0}}>Your account is missing a required license key. Please contact your administrator.</p>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Dark background when casting — controls overlay renders on top */}\r\n {(castDevice || isAirPlayConnected) && (\r\n <div className=\"sdk-cast-bg\" />\r\n )}\r\n\r\n {/* ── Mobile layout ───────────────────────────────────────────── */}\r\n {useMobileLayout && (\r\n <div\r\n className={`sdk-mobile-overlay ${controlsVisible ? 'active' : ''}`}\r\n onClick={handleMobileOverlayClick}\r\n >\r\n {/* Double-tap seek indicator */}\r\n {doubleTapSide && (\r\n <div className={`sdk-mobile-doubletap ${doubleTapSide}`}>\r\n <div className=\"sdk-mobile-doubletap-icon\">\r\n {doubleTapSide === 'left'\r\n ? <RotateCcw size={26} />\r\n : <RotateCw size={26} />}\r\n <span className=\"sdk-mobile-doubletap-label\">10s</span>\r\n </div>\r\n </div>\r\n )}\r\n\r\n {enableControls && (\r\n <>\r\n {/* Header: back + title (landscape) | PiP + Cast + AirPlay */}\r\n <div className=\"sdk-mobile-header sdk-mobile-ctrl\">\r\n <div className=\"sdk-mobile-header-left\">\r\n {(isFullscreen || isSimulatedFullscreen) && (\r\n <button\r\n className=\"sdk-mobile-back-btn\"\r\n onClick={toggleFullscreen}\r\n aria-label=\"Exit fullscreen\"\r\n >\r\n <ChevronLeft size={22} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n {(title || extractedTitle) && (\r\n <div className=\"sdk-mobile-title\">{title || extractedTitle}</div>\r\n )}\r\n </div>\r\n <div className=\"sdk-mobile-header-actions\">\r\n {effectiveFeatures.enablePiP && (\r\n <button\r\n className=\"sdk-mobile-hdr-btn\"\r\n onClick={() => { if (!isAdPlaying) togglePiP(); }}\r\n style={{ color: isPiP ? themeColor : '' }}\r\n aria-label={isPiP ? 'Exit picture-in-picture' : 'Enter picture-in-picture'}\r\n aria-pressed={isPiP}\r\n >\r\n <PictureInPicture size={20} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n {effectiveFeatures.enableChromecast && !isAdPlaying && (\r\n <button\r\n className=\"sdk-mobile-hdr-btn\"\r\n onClick={handleCastToggle}\r\n data-tooltip={castDevice ? `Casting to ${castDevice} — tap to disconnect` : 'Cast to device'}\r\n aria-label={castDevice ? `Stop casting to ${castDevice}` : 'Cast to device'}\r\n aria-pressed={!!castDevice}\r\n style={{ color: castDevice ? '#3b82f6' : '' }}\r\n >\r\n <Cast size={20} fill={castDevice ? 'currentColor' : 'none'} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n {effectiveFeatures.allowAirplay && isAirPlayAvailable && (\r\n <button\r\n className=\"sdk-mobile-hdr-btn\"\r\n onClick={handleAirPlayToggle}\r\n aria-label={isAirPlayConnected ? `Stop AirPlay to ${airPlayTargetName || 'device'}` : 'AirPlay to device'}\r\n aria-pressed={isAirPlayConnected}\r\n style={{ color: isAirPlayConnected ? themeColor : '' }}\r\n >\r\n <Airplay size={20} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {/* Center controls: rewind · play/pause · forward */}\r\n <div className=\"sdk-mobile-center-controls sdk-mobile-ctrl\">\r\n {!isAdPlaying && (\r\n <button\r\n className=\"sdk-mobile-seek-btn\"\r\n onClick={() => performSeek(contentTimeToVideoTime(Math.max(0, videoTimeToContentTime(ctrlCurrentTime) - 10)), { eventType: 'fast_backward', skipSeconds: 10 })}\r\n aria-label=\"Rewind 10 seconds\"\r\n >\r\n <RotateCcw size={22} aria-hidden=\"true\" />\r\n <span className=\"sdk-mobile-seek-label\" aria-hidden=\"true\">10</span>\r\n </button>\r\n )}\r\n <button\r\n className=\"sdk-mobile-play-btn\"\r\n onClick={togglePlay}\r\n aria-label={isEnded ? 'Replay' : ctrlIsPlaying ? 'Pause' : 'Play'}\r\n >\r\n {isEnded ? (\r\n <RotateCcw size={38} aria-hidden=\"true\" />\r\n ) : ctrlIsPlaying ? (\r\n <Pause size={38} fill=\"currentColor\" aria-hidden=\"true\" />\r\n ) : (\r\n <Play size={38} fill=\"currentColor\" style={{ marginLeft: '3px' }} aria-hidden=\"true\" />\r\n )}\r\n </button>\r\n {!isAdPlaying && (\r\n <button\r\n className=\"sdk-mobile-seek-btn\"\r\n onClick={() => performSeek(contentTimeToVideoTime(Math.min(displayDuration, videoTimeToContentTime(ctrlCurrentTime) + 10)), { eventType: 'fast_forward', skipSeconds: 10 })}\r\n aria-label=\"Skip forward 10 seconds\"\r\n >\r\n <RotateCw size={22} aria-hidden=\"true\" />\r\n <span className=\"sdk-mobile-seek-label\" aria-hidden=\"true\">10</span>\r\n </button>\r\n )}\r\n </div>\r\n\r\n {/* Bottom: chapter strip + seekbar + controls row */}\r\n <div className=\"sdk-mobile-bottom-bar sdk-mobile-ctrl\">\r\n {/* Horizontal key moments strip */}\r\n {effectiveFeatures.enableKeyMoments && showChaptersSidebar && localKeyMoments.length > 0 && (\r\n <div className=\"sdk-mobile-chapters-scroll\">\r\n {localKeyMoments.map((ch, idx) => {\r\n const isActive = activeChapter.index === idx;\r\n return (\r\n <div\r\n key={idx}\r\n role=\"button\"\r\n tabIndex={0}\r\n className={`sdk-mobile-chapter-card${isActive ? ' active' : ''}`}\r\n style={{ borderColor: isActive ? themeColor : undefined }}\r\n aria-label={`${ch.title}, ${formatTime(ch.startTime)}${isActive ? ', current chapter' : ''}`}\r\n aria-current={isActive ? 'true' : undefined}\r\n onClick={() => { handleChapterClick(ch.startTime, ch); }}\r\n onKeyDown={cardKeyDown(() => handleChapterClick(ch.startTime, ch))}\r\n >\r\n <div className=\"sdk-mobile-chapter-thumb\">\r\n {(chapterThumbnails[idx] || ch.thumbnail) ? (\r\n <img\r\n src={chapterThumbnails[idx] || ch.thumbnail}\r\n alt=\"\"\r\n aria-hidden=\"true\"\r\n style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}\r\n />\r\n ) : (\r\n <ListVideo size={16} aria-hidden=\"true\" style={{ color: isActive ? themeColor : 'rgba(255,255,255,0.3)' }} />\r\n )}\r\n <span className=\"sdk-mobile-chapter-time-badge\" aria-hidden=\"true\">{formatTime(ch.startTime)}</span>\r\n </div>\r\n <div className=\"sdk-mobile-chapter-title\" style={{ color: isActive ? themeColor : undefined }}>\r\n {ch.title}\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n\r\n {/* Seekbar / Ad Progress Bar — edge to edge */}\r\n <div\r\n ref={seekbarRef}\r\n role={seekLocked ? undefined : 'slider'}\r\n aria-label={seekLocked ? undefined : 'Seek'}\r\n aria-valuemin={seekLocked ? undefined : 0}\r\n aria-valuemax={seekLocked ? undefined : Math.round(displayDuration)}\r\n aria-valuenow={seekLocked ? undefined : Math.round(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))}\r\n aria-valuetext={seekLocked ? undefined : `${formatTime(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))} of ${formatTime(displayDuration)}`}\r\n tabIndex={seekLocked ? undefined : 0}\r\n className={`sdk-seekbar-container sdk-mobile-seekbar${isAdPlaying ? ' sdk-ad-mode' : ''}`}\r\n onMouseDown={seekLocked ? undefined : handleSeekMouseDown}\r\n onMouseMove={seekLocked ? undefined : handleSeekbarHover}\r\n onMouseLeave={seekLocked ? undefined : handleSeekbarLeave}\r\n onTouchStart={seekLocked ? undefined : handleSeekTouchStart}\r\n onKeyDown={seekLocked ? undefined : handleSeekbarKeyDown}\r\n style={{ pointerEvents: seekLocked ? 'none' : undefined, touchAction: 'none' }}\r\n >\r\n {isAdPlaying ? (\r\n <div\r\n className=\"sdk-ad-progress-fill\"\r\n style={{\r\n // Baseline width for first paint / re-renders — sourced from the live\r\n // media clock (not the throttled `currentTime` state) so it's always\r\n // accurate. The rAF loop above takes over for smooth per-frame motion.\r\n // Tracks the current individual ad within a pod, not the whole break.\r\n width: (() => {\r\n const t = player ? player.getCurrentTime() : currentTime;\r\n const win = getActiveAdWindow(t);\r\n return win ? `${Math.min(100, Math.max(0, ((t - win.start) / (win.end - win.start)) * 100))}%` : '0%';\r\n })(),\r\n }}\r\n />\r\n ) : (\r\n <>\r\n <div\r\n className=\"sdk-seekbar-buffered\"\r\n style={{ width: `${duration ? (player ? (player.getBufferedRanges()[0]?.end || 0) : 0) / duration * 100 : 0}%` }}\r\n />\r\n {displayDuration > 0 && adBreaks.filter(ab => !watchedAdBreaksSet.has(ab.startTime)).map((ab, idx) => (\r\n <div\r\n key={idx}\r\n className=\"sdk-seekbar-adbreak\"\r\n style={{\r\n left: `${(videoTimeToContentTime(ab.startTime) / displayDuration) * 100}%`,\r\n width: '3px',\r\n }}\r\n />\r\n ))}\r\n <div\r\n className=\"sdk-seekbar-progress\"\r\n style={{\r\n width: castDevice\r\n ? `${ctrlDuration > 0 ? (ctrlCurrentTime / ctrlDuration) * 100 : 0}%`\r\n : `${displayDuration ? (videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime) / displayDuration) * 100 : 0}%`,\r\n background: '#ffffff',\r\n boxShadow: 'none',\r\n }}\r\n >\r\n <div className=\"sdk-seekbar-handle\" style={{ opacity: 1, transform: 'scale(1)' }} />\r\n </div>\r\n {effectiveFeatures.enableKeyMoments && localKeyMoments.map((ch, idx) => {\r\n if (displayDuration === 0) return null;\r\n return <div key={idx} className=\"sdk-seekbar-chapter-marker sdk-desktop-chapter-dot\" style={{ left: `${(videoTimeToContentTime(ch.startTime) / displayDuration) * 100}%` }} />;\r\n })}\r\n </>\r\n )}\r\n </div>\r\n\r\n {/* Controls row: time | icon buttons */}\r\n <div className=\"sdk-mobile-controls-row\">\r\n {!isAdPlaying && (\r\n <span className=\"sdk-time-display\">\r\n {castDevice\r\n ? `${formatTime(ctrlCurrentTime)} / ${formatTime(ctrlDuration)}`\r\n : `${formatTime(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))} / ${formatTime(displayDuration)}`}\r\n </span>\r\n )}\r\n <div className=\"sdk-mobile-bottom-icons\">\r\n {effectiveFeatures.enableKeyMoments && localKeyMoments.length > 0 && !isAdPlaying && (\r\n <button\r\n className={`sdk-mobile-icon-btn${showChaptersSidebar ? ' active' : ''}`}\r\n onClick={() => { setShowChaptersSidebar(!showChaptersSidebar); }}\r\n aria-label={showChaptersSidebar ? 'Hide chapters' : 'Show chapters'}\r\n aria-pressed={showChaptersSidebar}\r\n >\r\n <ListVideo size={18} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n\r\n {effectiveFeatures.enableSubtitles && !isAdPlaying && availableSubtitles.some(t => !t.isNative) && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-mobile-icon-btn${activeSubtitleTrack !== 'off' ? ' active' : ''}`}\r\n onClick={() => { menuToggle('subtitles'); }}\r\n aria-label=\"Captions\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'subtitles'}\r\n >\r\n <Subtitles size={18} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'subtitles' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Captions\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Subtitles</div>\r\n <div\r\n role=\"option\"\r\n aria-selected={activeSubtitleTrack === 'off'}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSubtitleTrack === 'off' ? 'active' : ''}`}\r\n onClick={() => handleSubtitleChange('off')}\r\n onKeyDown={menuItemKeyDown(() => handleSubtitleChange('off'))}\r\n >\r\n <span>Off</span>\r\n {activeSubtitleTrack === 'off' && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n {availableSubtitles.map((track) => (\r\n <div\r\n key={track.id}\r\n role=\"option\"\r\n aria-selected={activeSubtitleTrack === track.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSubtitleTrack === track.id ? 'active' : ''}`}\r\n onClick={() => handleSubtitleChange(track.id)}\r\n onKeyDown={menuItemKeyDown(() => handleSubtitleChange(track.id))}\r\n >\r\n <span>{getLanguageLabel(track)}</span>\r\n {activeSubtitleTrack === track.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.allowMultipleAudio && audioTracks.length > 1 && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-mobile-icon-btn${activeMenu === 'audio' ? ' active' : ''}`}\r\n onClick={() => { menuToggle('audio'); }}\r\n aria-label=\"Audio track\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'audio'}\r\n >\r\n <Languages size={18} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'audio' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Audio track\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Audio Track</div>\r\n {audioTracks.map((track) => (\r\n <div\r\n key={track.id}\r\n role=\"option\"\r\n aria-selected={activeAudioTrack === track.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeAudioTrack === track.id ? 'active' : ''}`}\r\n onClick={() => { player?.setAudioTrack(track.id); setActiveAudioTrack(track.id); setActiveMenu('none'); setAnnouncement(`Audio: ${getLanguageLabel(track)}`); }}\r\n onKeyDown={menuItemKeyDown(() => { player?.setAudioTrack(track.id); setActiveAudioTrack(track.id); setActiveMenu('none'); setAnnouncement(`Audio: ${getLanguageLabel(track)}`); })}\r\n >\r\n <span>{getLanguageLabel(track)}</span>\r\n {activeAudioTrack === track.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.enablePlaybackSpeed && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-mobile-icon-btn${activeMenu === 'speed' ? ' active' : ''}`}\r\n onClick={() => { menuToggle('speed'); }}\r\n aria-label=\"Playback speed\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'speed'}\r\n >\r\n <Gauge size={18} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'speed' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Playback speed\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Speed</div>\r\n {[0.5, 0.75, 1, 1.25, 1.5, 2].map((rate) => (\r\n <div\r\n key={rate}\r\n role=\"option\"\r\n aria-selected={playbackRate === rate}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${playbackRate === rate ? 'active' : ''}`}\r\n onClick={() => handleSpeedChange(rate)}\r\n onKeyDown={menuItemKeyDown(() => handleSpeedChange(rate))}\r\n >\r\n <span>{rate === 1 ? 'Normal' : `${rate}x`}</span>\r\n {playbackRate === rate && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.enableQuality && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-mobile-icon-btn${activeQuality.id !== -1 ? ' active' : ''}`}\r\n onClick={() => { menuToggle('quality'); }}\r\n aria-label=\"Video quality\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'quality'}\r\n >\r\n <HdIcon size={18} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'quality' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu large\" role=\"listbox\" aria-label=\"Video quality\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Quality</div>\r\n {qualities.map((q) => (\r\n <div\r\n key={q.id}\r\n role=\"option\"\r\n aria-selected={activeQuality.id === q.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeQuality.id === q.id ? 'active' : ''}`}\r\n onClick={() => handleQualityChange(q.id)}\r\n onKeyDown={menuItemKeyDown(() => handleQualityChange(q.id))}\r\n >\r\n <span>{q.label}</span>\r\n {activeQuality.id === q.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {sources && sources.length > 1 && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-mobile-icon-btn${activeMenu === 'sources' ? ' active' : ''}`}\r\n onClick={() => { menuToggle('sources'); }}\r\n aria-label=\"Select source\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'sources'}\r\n >\r\n <MultiSourceHdIcon size={18} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'sources' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Select source\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Source</div>\r\n {sources.map((s, idx) => (\r\n <div\r\n key={idx}\r\n role=\"option\"\r\n aria-selected={activeSourceIndex === idx}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSourceIndex === idx ? 'active' : ''}`}\r\n onClick={() => handleSourceChange(idx)}\r\n onKeyDown={menuItemKeyDown(() => handleSourceChange(idx))}\r\n >\r\n <span>{s.label}</span>\r\n {activeSourceIndex === idx && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n <button\r\n className=\"sdk-mobile-icon-btn\"\r\n onClick={toggleFullscreen}\r\n aria-label={isFullscreen || isSimulatedFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\r\n aria-pressed={isFullscreen || isSimulatedFullscreen}\r\n >\r\n {isFullscreen ? <Minimize size={18} aria-hidden=\"true\" /> : <Maximize size={18} aria-hidden=\"true\" />}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* ── Desktop layout ──────────────────────────────────────────── */}\r\n {!useMobileLayout && (\r\n <div\r\n className=\"sdk-controls-overlay\"\r\n onClick={(e) => {\r\n if (isAdPlaying) return;\r\n if ((e.target as HTMLElement).closest('.sdk-desktop-header') ||\r\n (e.target as HTMLElement).closest('.sdk-desktop-center-controls') ||\r\n (e.target as HTMLElement).closest('.sdk-desktop-bottom')) return;\r\n togglePlay();\r\n }}\r\n >\r\n {/* Top header */}\r\n <div className=\"sdk-desktop-header\">\r\n <div className=\"sdk-desktop-header-left\">\r\n {(isFullscreen || isSimulatedFullscreen) && (\r\n <button\r\n className=\"sdk-desktop-back-btn\"\r\n onClick={(e) => { e.stopPropagation(); toggleFullscreen(); }}\r\n aria-label=\"Exit fullscreen\"\r\n >\r\n <ChevronLeft size={22} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n {(title || extractedTitle) && <span className=\"sdk-desktop-title\">{title || extractedTitle}</span>}\r\n </div>\r\n <div className=\"sdk-desktop-header-right\">\r\n {effectiveFeatures.enableChromecast && !isAdPlaying && (\r\n <button\r\n className=\"sdk-desktop-hdr-btn\"\r\n onClick={(e) => { e.stopPropagation(); handleCastToggle(); }}\r\n data-tooltip={castDevice ? `Casting to ${castDevice} — click to disconnect` : 'Cast to device'}\r\n aria-label={castDevice ? `Stop casting to ${castDevice}` : 'Cast to device'}\r\n aria-pressed={!!castDevice}\r\n style={{ color: castDevice ? '#3b82f6' : '' }}\r\n >\r\n <Cast size={20} fill={castDevice ? 'currentColor' : 'none'} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n {castDevice && (\r\n <span className=\"sdk-cast-indicator\" aria-hidden=\"true\">\r\n <Cast size={10} fill=\"currentColor\" aria-hidden=\"true\" />\r\n {castDevice}\r\n </span>\r\n )}\r\n {effectiveFeatures.allowAirplay && isAirPlayAvailable && (\r\n <button\r\n className=\"sdk-desktop-hdr-btn\"\r\n onClick={(e) => { e.stopPropagation(); handleAirPlayToggle(); }}\r\n data-tooltip={isAirPlayConnected ? `AirPlay to ${airPlayTargetName || 'device'} — click to disconnect` : 'AirPlay to device'}\r\n aria-label={isAirPlayConnected ? `Stop AirPlay to ${airPlayTargetName || 'device'}` : 'AirPlay to device'}\r\n aria-pressed={isAirPlayConnected}\r\n style={{ color: isAirPlayConnected ? themeColor : '' }}\r\n >\r\n <Airplay size={20} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n\r\n {enableControls && (\r\n <>\r\n {/* Center play / seek controls */}\r\n <div className=\"sdk-desktop-center-controls\" style={{ pointerEvents: 'none' }}>\r\n {!isAdPlaying && (\r\n <button\r\n className=\"sdk-desktop-seek-btn\"\r\n style={{ pointerEvents: 'auto' }}\r\n onClick={(e) => { e.stopPropagation(); performSeek(contentTimeToVideoTime(Math.max(0, videoTimeToContentTime(ctrlCurrentTime) - 10)), { eventType: 'fast_backward', skipSeconds: 10 }); }}\r\n aria-label=\"Rewind 10 seconds\"\r\n >\r\n <div style={{ position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\r\n <RotateCcw size={30} aria-hidden=\"true\" />\r\n <span className=\"sdk-desktop-seek-label\" aria-hidden=\"true\">10</span>\r\n </div>\r\n </button>\r\n )}\r\n <button\r\n className=\"sdk-desktop-play-btn\"\r\n style={{ pointerEvents: 'auto' }}\r\n onClick={(e) => { e.stopPropagation(); togglePlay(); }}\r\n aria-label={isEnded ? 'Replay' : ctrlIsPlaying ? 'Pause' : 'Play'}\r\n >\r\n {isEnded\r\n ? <RotateCcw size={38} aria-hidden=\"true\" />\r\n : ctrlIsPlaying\r\n ? <Pause size={38} fill=\"currentColor\" aria-hidden=\"true\" />\r\n : <Play size={38} fill=\"currentColor\" style={{ marginLeft: '4px' }} aria-hidden=\"true\" />}\r\n </button>\r\n {!isAdPlaying && (\r\n <button\r\n className=\"sdk-desktop-seek-btn\"\r\n style={{ pointerEvents: 'auto' }}\r\n onClick={(e) => { e.stopPropagation(); performSeek(contentTimeToVideoTime(Math.min(displayDuration, videoTimeToContentTime(ctrlCurrentTime) + 10)), { eventType: 'fast_forward', skipSeconds: 10 }); }}\r\n aria-label=\"Skip forward 10 seconds\"\r\n >\r\n <div style={{ position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\r\n <RotateCw size={30} aria-hidden=\"true\" />\r\n <span className=\"sdk-desktop-seek-label\" aria-hidden=\"true\">10</span>\r\n </div>\r\n </button>\r\n )}\r\n </div>\r\n\r\n {/* Panel-style chapter list (absolute overlay, right side) */}\r\n {effectiveFeatures.enableKeyMoments && showChaptersSidebar && localKeyMoments.length > 0 && chaptersDisplayMode === 'panel' && (\r\n <div className=\"sdk-chapters-panel\" onClick={(e) => e.stopPropagation()}>\r\n <div className=\"sdk-chapters-panel-header\">\r\n <span className=\"sdk-chapters-panel-title\">Chapters</span>\r\n </div>\r\n <div className=\"sdk-chapters-panel-list\">\r\n {localKeyMoments.map((ch, idx) => {\r\n const isActive = activeChapter.index === idx;\r\n return (\r\n <div\r\n key={idx}\r\n role=\"button\"\r\n tabIndex={0}\r\n className={`sdk-chapters-panel-item${isActive ? ' active' : ''}`}\r\n aria-label={`${ch.title}, ${formatTime(ch.startTime)}${isActive ? ', current chapter' : ''}`}\r\n aria-current={isActive ? 'true' : undefined}\r\n onClick={() => handleChapterClick(ch.startTime, ch)}\r\n onKeyDown={cardKeyDown(() => handleChapterClick(ch.startTime, ch))}\r\n >\r\n <div className=\"sdk-chapters-panel-thumb\">\r\n {(chapterThumbnails[idx] || ch.thumbnail) ? (\r\n <img\r\n src={chapterThumbnails[idx] || ch.thumbnail}\r\n alt=\"\"\r\n aria-hidden=\"true\"\r\n style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}\r\n />\r\n ) : (\r\n <ListVideo size={18} aria-hidden=\"true\" style={{ color: 'rgba(255,255,255,0.3)' }} />\r\n )}\r\n </div>\r\n <div className=\"sdk-chapters-panel-info\">\r\n <div className=\"sdk-chapters-panel-item-title\">{ch.title}</div>\r\n {ch.description && (\r\n <div className=\"sdk-chapters-panel-item-desc\">{ch.description}</div>\r\n )}\r\n <div className=\"sdk-chapters-panel-item-time\" aria-hidden=\"true\">{formatTime(ch.startTime)}</div>\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n )}\r\n\r\n {/* Bottom area: chapters strip + seekbar + controls row */}\r\n <div className=\"sdk-desktop-bottom\">\r\n {/* Chapter thumbnail strip (strip mode only) */}\r\n {effectiveFeatures.enableKeyMoments && showChaptersSidebar && localKeyMoments.length > 0 && chaptersDisplayMode === 'strip' && (\r\n <div className=\"sdk-desktop-chapters-strip\">\r\n {localKeyMoments.map((ch, idx) => {\r\n const isActive = activeChapter.index === idx;\r\n return (\r\n <div\r\n key={idx}\r\n role=\"button\"\r\n tabIndex={0}\r\n className={`sdk-desktop-chapter-card${isActive ? ' active' : ''}`}\r\n aria-label={`${ch.title}, ${formatTime(ch.startTime)}${isActive ? ', current chapter' : ''}`}\r\n aria-current={isActive ? 'true' : undefined}\r\n onClick={(e) => { e.stopPropagation(); handleChapterClick(ch.startTime, ch); }}\r\n onKeyDown={cardKeyDown(() => handleChapterClick(ch.startTime, ch))}\r\n >\r\n <div className=\"sdk-desktop-chapter-thumb\">\r\n {(chapterThumbnails[idx] || ch.thumbnail) ? (\r\n <img\r\n src={chapterThumbnails[idx] || ch.thumbnail}\r\n alt=\"\"\r\n aria-hidden=\"true\"\r\n style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}\r\n />\r\n ) : (\r\n <ListVideo size={18} aria-hidden=\"true\" style={{ color: 'rgba(255,255,255,0.35)' }} />\r\n )}\r\n </div>\r\n <div className=\"sdk-desktop-chapter-title\">{ch.title}</div>\r\n <div className=\"sdk-desktop-chapter-time\" aria-hidden=\"true\">{formatTime(ch.startTime)}</div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n\r\n {/* Seekbar / Ad Progress Bar */}\r\n <div\r\n ref={seekbarRef}\r\n role={seekLocked ? undefined : 'slider'}\r\n aria-label={seekLocked ? undefined : 'Seek'}\r\n aria-valuemin={seekLocked ? undefined : 0}\r\n aria-valuemax={seekLocked ? undefined : Math.round(displayDuration)}\r\n aria-valuenow={seekLocked ? undefined : Math.round(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))}\r\n aria-valuetext={seekLocked ? undefined : `${formatTime(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))} of ${formatTime(displayDuration)}`}\r\n tabIndex={seekLocked ? undefined : 0}\r\n className={`sdk-seekbar-container sdk-desktop-seekbar${isAdPlaying ? ' sdk-ad-mode' : ''}`}\r\n onMouseDown={seekLocked ? undefined : handleSeekMouseDown}\r\n onMouseMove={seekLocked ? undefined : handleSeekbarHover}\r\n onMouseLeave={seekLocked ? undefined : handleSeekbarLeave}\r\n onTouchStart={seekLocked ? undefined : handleSeekTouchStart}\r\n onKeyDown={seekLocked ? undefined : handleSeekbarKeyDown}\r\n style={{ pointerEvents: seekLocked ? 'none' : undefined }}\r\n >\r\n {isAdPlaying ? (\r\n <div\r\n className=\"sdk-ad-progress-fill\"\r\n style={{\r\n // Baseline width for first paint / re-renders — sourced from the live\r\n // media clock (not the throttled `currentTime` state) so it's always\r\n // accurate. The rAF loop above takes over for smooth per-frame motion.\r\n // Tracks the current individual ad within a pod, not the whole break.\r\n width: (() => {\r\n const t = player ? player.getCurrentTime() : currentTime;\r\n const win = getActiveAdWindow(t);\r\n return win ? `${Math.min(100, Math.max(0, ((t - win.start) / (win.end - win.start)) * 100))}%` : '0%';\r\n })(),\r\n }}\r\n />\r\n ) : (\r\n <>\r\n <div\r\n className=\"sdk-seekbar-buffered\"\r\n style={{ width: `${duration ? (player ? (player.getBufferedRanges()[0]?.end || 0) : 0) / duration * 100 : 0}%` }}\r\n />\r\n {displayDuration > 0 && adBreaks.filter(ab => !watchedAdBreaksSet.has(ab.startTime)).map((ab, idx) => (\r\n <div\r\n key={idx}\r\n className=\"sdk-seekbar-adbreak\"\r\n style={{\r\n left: `${(videoTimeToContentTime(ab.startTime) / displayDuration) * 100}%`,\r\n width: '3px',\r\n }}\r\n />\r\n ))}\r\n <div\r\n className=\"sdk-seekbar-progress\"\r\n style={{\r\n width: castDevice\r\n ? `${ctrlDuration > 0 ? (ctrlCurrentTime / ctrlDuration) * 100 : 0}%`\r\n : `${displayDuration ? (videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime) / displayDuration) * 100 : 0}%`,\r\n background: '#ffffff',\r\n boxShadow: 'none',\r\n }}\r\n >\r\n <div className=\"sdk-seekbar-handle\" />\r\n </div>\r\n {effectiveFeatures.enableKeyMoments && localKeyMoments.map((ch, idx) => {\r\n if (displayDuration === 0) return null;\r\n return (\r\n <div\r\n key={idx}\r\n className=\"sdk-seekbar-chapter-marker sdk-desktop-chapter-dot\"\r\n style={{ left: `${(videoTimeToContentTime(ch.startTime) / displayDuration) * 100}%` }}\r\n />\r\n );\r\n })}\r\n {hoverX !== null && hoverThumbnail && (\r\n <div className=\"sdk-thumbnail-tooltip\" style={{ left: `${tooltipLeft}px` }}>\r\n <div className=\"sdk-thumbnail-preview\">\r\n <img\r\n src={hoverThumbnail}\r\n alt=\"preview\"\r\n style={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: '8px', display: 'block' }}\r\n />\r\n </div>\r\n {hoverChapter && (\r\n <div className=\"sdk-thumbnail-chapter-name\">{hoverChapter.title}</div>\r\n )}\r\n <div className=\"sdk-thumbnail-time-label\">{formatTime(videoTimeToContentTime(hoverTime))}</div>\r\n </div>\r\n )}\r\n </>\r\n )}\r\n </div>\r\n\r\n {/* Controls row */}\r\n <div className=\"sdk-desktop-controls-row\">\r\n {/* Left: time + optional volume slider */}\r\n <div className=\"sdk-desktop-left-controls\">\r\n {!isAdPlaying && (\r\n <div className=\"sdk-time-display\">\r\n {castDevice\r\n ? `${formatTime(ctrlCurrentTime)} / ${formatTime(ctrlDuration)}`\r\n : `${formatTime(videoTimeToContentTime(scrubbingTime !== null ? scrubbingTime : currentTime))} / ${formatTime(displayDuration)}`}\r\n </div>\r\n )}\r\n {volumeControlType === 'slider' && (\r\n <div className=\"sdk-volume-control\">\r\n <button\r\n className=\"sdk-desktop-icon-btn\"\r\n onClick={(e) => { e.stopPropagation(); toggleMute(); }}\r\n data-tooltip={ctrlMuted ? 'Unmute' : 'Mute'}\r\n aria-label={ctrlMuted ? 'Unmute' : 'Mute'}\r\n aria-pressed={ctrlMuted}\r\n >\r\n {getVolumeIcon()}\r\n </button>\r\n <input\r\n type=\"range\"\r\n className=\"sdk-volume-slider\"\r\n min={0}\r\n max={1}\r\n step={0.02}\r\n value={ctrlMuted ? 0 : ctrlVolume}\r\n aria-label=\"Volume\"\r\n aria-valuetext={ctrlMuted ? 'Muted' : `${Math.round(ctrlVolume * 100)}%`}\r\n style={{ '--vol-pct': Math.round((ctrlMuted ? 0 : ctrlVolume) * 100) } as React.CSSProperties}\r\n onChange={(e) => {\r\n const val = parseFloat(e.target.value);\r\n if (castDevice) {\r\n player?.casting.remoteSetVolume(val);\r\n if (val > 0) player?.casting.remoteSetMute(false);\r\n } else {\r\n player?.setVolume(val);\r\n if (val > 0 && isMuted) player?.setMute(false);\r\n }\r\n }}\r\n onClick={(e) => e.stopPropagation()}\r\n />\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Right: icon buttons */}\r\n <div className=\"sdk-desktop-right-icons\">\r\n {effectiveFeatures.enableKeyMoments && localKeyMoments.length > 0 && !isAdPlaying && (\r\n <button\r\n className={`sdk-desktop-icon-btn${showChaptersSidebar ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); setShowChaptersSidebar(!showChaptersSidebar); }}\r\n data-tooltip=\"Key Moments\"\r\n aria-label={showChaptersSidebar ? 'Hide chapters' : 'Show chapters'}\r\n aria-pressed={showChaptersSidebar}\r\n >\r\n <ListVideo size={20} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n\r\n {effectiveFeatures.enableSubtitles && !isAdPlaying && availableSubtitles.some(t => !t.isNative) && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-desktop-icon-btn${activeSubtitleTrack !== 'off' ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); menuToggle('subtitles'); }}\r\n data-tooltip=\"Captions\"\r\n aria-label=\"Captions\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'subtitles'}\r\n >\r\n <Subtitles size={20} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'subtitles' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Captions\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Subtitles</div>\r\n <div\r\n role=\"option\"\r\n aria-selected={activeSubtitleTrack === 'off'}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSubtitleTrack === 'off' ? 'active' : ''}`}\r\n onClick={() => handleSubtitleChange('off')}\r\n onKeyDown={menuItemKeyDown(() => handleSubtitleChange('off'))}\r\n >\r\n <span>Off</span>\r\n {activeSubtitleTrack === 'off' && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n {availableSubtitles.map((track) => (\r\n <div\r\n key={track.id}\r\n role=\"option\"\r\n aria-selected={activeSubtitleTrack === track.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSubtitleTrack === track.id ? 'active' : ''}`}\r\n onClick={() => handleSubtitleChange(track.id)}\r\n onKeyDown={menuItemKeyDown(() => handleSubtitleChange(track.id))}\r\n >\r\n <span>{getLanguageLabel(track)}</span>\r\n {activeSubtitleTrack === track.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.allowMultipleAudio && audioTracks.length > 1 && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-desktop-icon-btn${activeMenu === 'audio' ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); menuToggle('audio'); }}\r\n data-tooltip=\"Audio Track\"\r\n aria-label=\"Audio track\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'audio'}\r\n >\r\n <Languages size={20} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'audio' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Audio track\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Audio Track</div>\r\n {audioTracks.map((track) => (\r\n <div\r\n key={track.id}\r\n role=\"option\"\r\n aria-selected={activeAudioTrack === track.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeAudioTrack === track.id ? 'active' : ''}`}\r\n onClick={() => {\r\n player?.setAudioTrack(track.id);\r\n setActiveAudioTrack(track.id);\r\n setActiveMenu('none');\r\n setAnnouncement(`Audio: ${getLanguageLabel(track)}`);\r\n }}\r\n onKeyDown={menuItemKeyDown(() => {\r\n player?.setAudioTrack(track.id);\r\n setActiveAudioTrack(track.id);\r\n setActiveMenu('none');\r\n setAnnouncement(`Audio: ${getLanguageLabel(track)}`);\r\n })}\r\n >\r\n <span>{getLanguageLabel(track)}</span>\r\n {activeAudioTrack === track.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.enablePlaybackSpeed && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-desktop-icon-btn${activeMenu === 'speed' ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); menuToggle('speed'); }}\r\n data-tooltip=\"Playback Speed\"\r\n aria-label=\"Playback speed\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'speed'}\r\n >\r\n <Gauge size={20} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'speed' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Playback speed\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Speed</div>\r\n {[0.5, 0.75, 1, 1.25, 1.5, 2].map((rate) => (\r\n <div\r\n key={rate}\r\n role=\"option\"\r\n aria-selected={playbackRate === rate}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${playbackRate === rate ? 'active' : ''}`}\r\n onClick={() => handleSpeedChange(rate)}\r\n onKeyDown={menuItemKeyDown(() => handleSpeedChange(rate))}\r\n >\r\n <span>{rate === 1 ? 'Normal' : `${rate}x`}</span>\r\n {playbackRate === rate && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.enableQuality && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-desktop-icon-btn${activeQuality.id !== -1 ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); menuToggle('quality'); }}\r\n data-tooltip=\"Quality\"\r\n aria-label=\"Video quality\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'quality'}\r\n >\r\n <HdIcon size={20} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'quality' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu large\" role=\"listbox\" aria-label=\"Video quality\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Quality</div>\r\n {qualities.map((q) => (\r\n <div\r\n key={q.id}\r\n role=\"option\"\r\n aria-selected={activeQuality.id === q.id}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeQuality.id === q.id ? 'active' : ''}`}\r\n onClick={() => handleQualityChange(q.id)}\r\n onKeyDown={menuItemKeyDown(() => handleQualityChange(q.id))}\r\n >\r\n <span>{q.label}</span>\r\n {activeQuality.id === q.id && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {sources && sources.length > 1 && !isAdPlaying && (\r\n <div className=\"sdk-menu-container\">\r\n <button\r\n className={`sdk-desktop-icon-btn${activeMenu === 'sources' ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); menuToggle('sources'); }}\r\n data-tooltip=\"Select Source\"\r\n aria-label=\"Select source\"\r\n aria-haspopup=\"listbox\"\r\n aria-expanded={activeMenu === 'sources'}\r\n >\r\n <MultiSourceHdIcon size={20} aria-hidden=\"true\" />\r\n </button>\r\n {activeMenu === 'sources' && (\r\n <div ref={clampDropupMenu} className=\"sdk-dropup-menu\" role=\"listbox\" aria-label=\"Select source\">\r\n <div className=\"sdk-dropup-header\" aria-hidden=\"true\">Source</div>\r\n {sources.map((s, idx) => (\r\n <div\r\n key={idx}\r\n role=\"option\"\r\n aria-selected={activeSourceIndex === idx}\r\n tabIndex={0}\r\n className={`sdk-dropup-item ${activeSourceIndex === idx ? 'active' : ''}`}\r\n onClick={() => handleSourceChange(idx)}\r\n onKeyDown={menuItemKeyDown(() => handleSourceChange(idx))}\r\n >\r\n <span>{s.label}</span>\r\n {activeSourceIndex === idx && <Check size={12} aria-hidden=\"true\" />}\r\n </div>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n\r\n {effectiveFeatures.enablePiP && !isAdPlaying && (\r\n <button\r\n className={`sdk-desktop-icon-btn${isPiP ? ' active' : ''}`}\r\n onClick={(e) => { e.stopPropagation(); togglePiP(); }}\r\n data-tooltip=\"Picture-in-Picture\"\r\n aria-label={isPiP ? 'Exit picture-in-picture' : 'Enter picture-in-picture'}\r\n aria-pressed={isPiP}\r\n >\r\n <PictureInPicture size={20} aria-hidden=\"true\" />\r\n </button>\r\n )}\r\n\r\n {volumeControlType === 'button' && (\r\n <button\r\n className=\"sdk-desktop-icon-btn\"\r\n onClick={(e) => { e.stopPropagation(); toggleMute(); }}\r\n data-tooltip={ctrlMuted ? 'Unmute' : 'Mute'}\r\n aria-label={ctrlMuted ? 'Unmute' : 'Mute'}\r\n aria-pressed={ctrlMuted}\r\n >\r\n {getVolumeIcon()}\r\n </button>\r\n )}\r\n\r\n {enableFullscreen && (\r\n <button\r\n className=\"sdk-desktop-icon-btn\"\r\n onClick={(e) => { e.stopPropagation(); toggleFullscreen(); }}\r\n data-tooltip={isFullscreen || isSimulatedFullscreen ? 'Exit fullscreen' : 'Fullscreen'}\r\n aria-label={isFullscreen || isSimulatedFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\r\n aria-pressed={isFullscreen || isSimulatedFullscreen}\r\n >\r\n {isFullscreen ? <Minimize size={20} aria-hidden=\"true\" /> : <Maximize size={20} aria-hidden=\"true\" />}\r\n </button>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n )}\r\n\r\n {/* Custom PiP window controls — portaled into the floating window's own document,\r\n driven by the exact same state (currentTime/duration/isPlaying) as the on-page\r\n controls, so it never drifts out of sync with real pause/play the way the\r\n browser's native PiP overlay extrapolation could. */}\r\n {pipWindow && (() => {\r\n // While an ad is playing, show the ad's own position/duration (matching the\r\n // main ad overlay's countdown and the media-session ad branch) instead of the\r\n // content timeline — the content clock is frozen for the whole break, so\r\n // reusing it here would show a duration that never matches what's on screen.\r\n const pipAdBreak = adBreaks.find(ab => ctrlCurrentTime >= ab.startTime && ctrlCurrentTime < ab.endTime);\r\n const pipDuration = pipAdBreak ? pipAdBreak.endTime - pipAdBreak.startTime : displayDuration;\r\n const pipPosition = pipAdBreak\r\n ? Math.max(0, ctrlCurrentTime - pipAdBreak.startTime)\r\n : videoTimeToContentTime(ctrlCurrentTime);\r\n\r\n return createPortal(\r\n <div className={`gb-pip-overlay${ctrlIsPlaying ? '' : ' gb-pip-paused'}`}>\r\n <div />\r\n <div className=\"gb-pip-center\">\r\n <button\r\n className=\"gb-pip-btn\"\r\n onClick={() => performSeek(contentTimeToVideoTime(Math.max(0, videoTimeToContentTime(ctrlCurrentTime) - 10)), { eventType: 'fast_backward', skipSeconds: 10 })}\r\n aria-label=\"Rewind 10 seconds\"\r\n disabled={isAdPlaying}\r\n >\r\n <RotateCcw size={20} aria-hidden=\"true\" />\r\n </button>\r\n <button\r\n className=\"gb-pip-btn gb-pip-play\"\r\n onClick={togglePlay}\r\n aria-label={isEnded ? 'Replay' : ctrlIsPlaying ? 'Pause' : 'Play'}\r\n >\r\n {isEnded\r\n ? <RotateCcw size={24} aria-hidden=\"true\" />\r\n : ctrlIsPlaying\r\n ? <Pause size={24} aria-hidden=\"true\" />\r\n : <Play size={24} aria-hidden=\"true\" style={{ marginLeft: 2 }} />}\r\n </button>\r\n <button\r\n className=\"gb-pip-btn\"\r\n onClick={() => performSeek(contentTimeToVideoTime(Math.min(displayDuration, videoTimeToContentTime(ctrlCurrentTime) + 10)), { eventType: 'fast_forward', skipSeconds: 10 })}\r\n aria-label=\"Skip forward 10 seconds\"\r\n disabled={isAdPlaying}\r\n >\r\n <RotateCw size={20} aria-hidden=\"true\" />\r\n </button>\r\n </div>\r\n <div className=\"gb-pip-bottom\">\r\n <div className=\"gb-pip-progress\" onClick={handlePipSeekClick}>\r\n <div\r\n className=\"gb-pip-progress-fill\"\r\n style={{ width: `${pipDuration ? (pipPosition / pipDuration) * 100 : 0}%` }}\r\n />\r\n </div>\r\n <div className=\"gb-pip-time\">\r\n {formatTime(pipPosition)} / {formatTime(pipDuration)}\r\n </div>\r\n </div>\r\n </div>,\r\n pipWindow.document.body\r\n );\r\n })()}\r\n </div>\r\n );\r\n};\r\n","import type { SdkInitResponse } from './types';\r\n\r\nexport const DEFAULT_API_BASE = 'https://dev-api.go-boss.com';\r\n\r\n/** Fallback heartbeat cadence used when `/init` does not return one. */\r\nexport const DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 30;\r\n\r\nfunction buildHeaders(playerKey: string): HeadersInit {\r\n return {\r\n 'x-player-key': playerKey,\r\n 'Content-Type': 'application/json',\r\n }\r\n}\r\n\r\n/** Optional context accepted by POST /init. `referrer` is intentionally not sent. */\r\nexport interface SdkInitOptions {\r\n videoUrl?: string;\r\n}\r\n\r\nexport async function initSdk(\r\n playerKey: string,\r\n apiBase = DEFAULT_API_BASE,\r\n options: SdkInitOptions = {},\r\n): Promise<SdkInitResponse> {\r\n const res = await fetch(`${apiBase}/api/player/sdk/init`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n body: JSON.stringify({ videoUrl: options.videoUrl }),\r\n });\r\n if (!res.ok) throw new Error(`SDK init failed: ${res.status}`);\r\n return await res.json() as SdkInitResponse;\r\n}\r\n\r\n/**\r\n * Body for POST /session/start. Only `sessionId` is required for HTTP acceptance,\r\n * but `videoId`, `videoTitle` and `contentDurationSec` drive the video-level CMS\r\n * analytics (Top Videos, retention, completion) — send them whenever they're known.\r\n * `platform` and `referrer` are intentionally not sent by this SDK.\r\n */\r\nexport interface StartSessionOptions {\r\n /** Client-generated UUID identifying this play. */\r\n sessionId: string;\r\n /** Stable content ID — the grouping key for video-level analytics. */\r\n videoId?: string;\r\n /** Display title for CMS dashboards. */\r\n videoTitle?: string;\r\n /** Playback URL. */\r\n videoUrl?: string;\r\n /** Full content length in seconds, when known. */\r\n contentDurationSec?: number;\r\n}\r\n\r\nexport async function startSession(\r\n playerKey: string,\r\n options: StartSessionOptions,\r\n apiBase = DEFAULT_API_BASE,\r\n): Promise<void> {\r\n const { sessionId, videoId, videoTitle, videoUrl, contentDurationSec } = options;\r\n const res = await fetch(`${apiBase}/api/player/sdk/session/start`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n // undefined fields are dropped by JSON.stringify, so optional context is\r\n // simply omitted rather than sent as null.\r\n body: JSON.stringify({\r\n sessionId,\r\n videoId,\r\n videoTitle,\r\n videoUrl,\r\n // The API rejects negatives and NaN — only send a usable finite duration.\r\n contentDurationSec:\r\n typeof contentDurationSec === 'number' && Number.isFinite(contentDurationSec) && contentDurationSec > 0\r\n ? contentDurationSec\r\n : undefined,\r\n }),\r\n });\r\n if (!res.ok) throw new Error(`Session start failed: ${res.status}`);\r\n}\r\n\r\nexport async function sendHeartbeat(\r\n playerKey: string,\r\n sessionId: string,\r\n currentTime: number,\r\n duration: number,\r\n apiBase = DEFAULT_API_BASE,\r\n): Promise<void> {\r\n const payload = { sessionId, currentTime, duration };\r\n const res = await fetch(`${apiBase}/api/player/sdk/session/heartbeat`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n body: JSON.stringify(payload),\r\n });\r\n if (!res.ok) throw new Error(`Heartbeat failed: ${res.status}`);\r\n}\r\n\r\n/**\r\n * Ends the session and finalizes billed minutes. `currentTime` and `duration`\r\n * are required by the API — pass the final playback position and content length.\r\n */\r\nexport async function endSession(\r\n playerKey: string,\r\n sessionId: string,\r\n currentTime = 0,\r\n duration = 0,\r\n apiBase = DEFAULT_API_BASE,\r\n): Promise<void> {\r\n const res = await fetch(`${apiBase}/api/player/sdk/session/end`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n body: JSON.stringify({ sessionId, currentTime, duration }),\r\n });\r\n if (!res.ok) throw new Error(`Session end failed: ${res.status}`);\r\n}\r\n\r\n/**\r\n * In-session telemetry payload for POST /api/player/sdk/events.\r\n * `eventType`, `sessionId` and `currentTime` are always required; `videoId`,\r\n * `videoTitle` and any event-specific extras (e.g. `from`/`to`, `speed`,\r\n * `bufferingCount`) are sent at the top level — never wrapped in a client\r\n * `properties` object (the server nests them).\r\n */\r\nexport interface PlayerEventPayload {\r\n eventType: string;\r\n sessionId: string;\r\n currentTime: number;\r\n videoId?: string;\r\n videoTitle?: string;\r\n [key: string]: unknown;\r\n}\r\n\r\n/**\r\n * Records a single in-session analytics event. One event per request.\r\n * The endpoint returns 204 No Content on success, so there is no body to parse.\r\n */\r\nexport async function sendEvent(\r\n playerKey: string,\r\n payload: PlayerEventPayload,\r\n apiBase = DEFAULT_API_BASE,\r\n): Promise<void> {\r\n const res = await fetch(`${apiBase}/api/player/sdk/events`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n body: JSON.stringify(payload),\r\n });\r\n if (!res.ok) throw new Error(`Event ${payload.eventType} failed: ${res.status}`);\r\n}\r\n\r\n/** Fire-and-forget session end that survives tab close (`beforeunload`). */\r\nexport function endSessionKeepalive(\r\n playerKey: string,\r\n sessionId: string,\r\n currentTime = 0,\r\n duration = 0,\r\n apiBase = DEFAULT_API_BASE,\r\n): void {\r\n fetch(`${apiBase}/api/player/sdk/session/end`, {\r\n method: 'POST',\r\n headers: buildHeaders(playerKey),\r\n body: JSON.stringify({ sessionId, currentTime, duration }),\r\n keepalive: true,\r\n });\r\n}\r\n","import type { AnalyticsEvent, Chapter, PlayerDelegate } from '@goboss/types';\r\n\r\n// Screen orientation for the fullscreen callbacks; undefined where unsupported (e.g. older Safari).\r\nfunction currentOrientation(): string | undefined {\r\n if (typeof screen === 'undefined') return undefined;\r\n return screen.orientation?.type ?? undefined;\r\n}\r\n\r\n/**\r\n * Translates the internal analytics event stream into typed `PlayerDelegate`\r\n * callbacks, deriving the per-event arguments (previous quality/subtitle/audio\r\n * track/speed, seek bounds, watch duration) that individual events don't carry.\r\n *\r\n * One instance lives for the lifetime of a player mount. Delegate exceptions are\r\n * caught and logged so a tenant callback can never break playback.\r\n */\r\nexport class PlayerDelegateBridge {\r\n private delegate: PlayerDelegate | undefined;\r\n private autoplay = false;\r\n private keyMoments: Chapter[] = [];\r\n\r\n // True from the first 'play' until 'ended' — distinguishes start vs resume,\r\n // and re-arms so a replay after 'ended' reports a fresh start.\r\n private isPlaybackActive = false;\r\n private hasFinished = false;\r\n private hasDismissed = false;\r\n\r\n // Last observed playback state, used for playerDidDismiss.\r\n private lastCurrentTime = 0;\r\n private lastDuration = 0;\r\n private lastWatchDuration = 0;\r\n\r\n // Previous values for the from/to media-selection callbacks.\r\n private prevQuality: string | undefined;\r\n private prevSubtitle: string | undefined;\r\n private prevAudioTrack: string | undefined;\r\n private prevSpeed = 1;\r\n\r\n // Seek in flight: started on the 'seek' analytics event, finished on the next\r\n // timeupdate (the position only updates once the seek has landed).\r\n private pendingSeek: { fromTime: number; toTime: number; startedWallMs: number } | null = null;\r\n\r\n public setDelegate(delegate: PlayerDelegate | undefined): void {\r\n this.delegate = delegate;\r\n }\r\n\r\n /** Whether playback was configured to start automatically (reported on playerDidStartPlaying). */\r\n public setAutoplay(autoplay: boolean): void {\r\n this.autoplay = autoplay;\r\n }\r\n\r\n /** Key moments currently loaded — used to resolve chapter bounds for playerDidSelectKeyMoment. */\r\n public setKeyMoments(keyMoments: Chapter[]): void {\r\n this.keyMoments = keyMoments;\r\n }\r\n\r\n /** Route one analytics event to its delegate callback. Call for every event. */\r\n public handleAnalyticsEvent(event: AnalyticsEvent): void {\r\n if (typeof event.currentTime === 'number' && !Number.isNaN(event.currentTime)) {\r\n this.lastCurrentTime = event.currentTime;\r\n }\r\n if (typeof event.duration === 'number' && !Number.isNaN(event.duration)) this.lastDuration = event.duration;\r\n if (typeof event.totalWatchTime === 'number') this.lastWatchDuration = event.totalWatchTime;\r\n\r\n switch (event.eventType) {\r\n case 'play':\r\n if (!this.isPlaybackActive) {\r\n this.isPlaybackActive = true;\r\n this.hasFinished = false;\r\n this.call('playerDidStartPlaying', event.currentTime, this.autoplay);\r\n } else {\r\n this.call('playerDidResumePlaying', event.currentTime);\r\n }\r\n break;\r\n\r\n case 'pause':\r\n // The media element fires an implicit 'pause' just before 'ended' — that is\r\n // not a user pause, so suppress it and let playerDidFinishPlaying report it.\r\n if (!(event.duration > 0 && event.currentTime >= event.duration - 0.25)) {\r\n this.call('playerDidPause', event.currentTime);\r\n }\r\n break;\r\n\r\n case 'ended':\r\n this.isPlaybackActive = false;\r\n this.hasFinished = true;\r\n this.call('playerDidFinishPlaying', event.currentTime, event.totalWatchTime ?? this.lastWatchDuration);\r\n break;\r\n\r\n case 'seek':\r\n this.beginSeek(event.seekFrom ?? this.lastCurrentTime, event.seekTo ?? event.currentTime);\r\n break;\r\n\r\n // Skip gestures are seeks too; at track time currentTime is already the target,\r\n // so recover the origin from the skip amount.\r\n case 'fast_forward':\r\n this.beginSeek(Math.max(0, event.currentTime - (event.skipSeconds ?? 10)), event.currentTime);\r\n break;\r\n case 'fast_backward':\r\n this.beginSeek(event.currentTime + (event.skipSeconds ?? 10), event.currentTime);\r\n break;\r\n\r\n case 'buffering_start':\r\n this.call('playerDidStartBuffering', event.currentTime);\r\n break;\r\n case 'buffering_end':\r\n this.call('playerDidFinishBuffering', event.currentTime);\r\n break;\r\n\r\n case 'quality_change': {\r\n const toQuality = event.rendition;\r\n if (toQuality && toQuality !== this.prevQuality) {\r\n this.call('playerDidChangeQuality', this.prevQuality, toQuality, event.isAutoSwitch ?? false);\r\n this.prevQuality = toQuality;\r\n }\r\n break;\r\n }\r\n\r\n case 'subtitle_change':\r\n if (event.subtitleEnabled === false) {\r\n this.call('playerDidDisableSubtitle', this.prevSubtitle);\r\n this.prevSubtitle = undefined;\r\n } else {\r\n const toSubtitle = event.subtitleLanguage ?? '';\r\n this.call('playerDidChangeSubtitle', this.prevSubtitle, toSubtitle);\r\n this.prevSubtitle = toSubtitle;\r\n }\r\n break;\r\n\r\n case 'audio_track_change': {\r\n const toTrack = event.audioTrack ?? '';\r\n this.call('playerDidChangeAudioTrack', this.prevAudioTrack, toTrack);\r\n this.prevAudioTrack = toTrack;\r\n break;\r\n }\r\n\r\n case 'speed_change': {\r\n const toSpeed = event.speed ?? 1;\r\n // ratechange also fires for internal rate resets (e.g. ad breaks) — only\r\n // report actual changes.\r\n if (toSpeed !== this.prevSpeed) {\r\n this.call('playerDidChangePlaybackSpeed', this.prevSpeed, toSpeed);\r\n this.prevSpeed = toSpeed;\r\n }\r\n break;\r\n }\r\n\r\n case 'fullscreen_enter':\r\n this.call('playerDidEnterFullscreen', currentOrientation());\r\n break;\r\n case 'fullscreen_exit':\r\n this.call('playerDidExitFullscreen', currentOrientation());\r\n break;\r\n\r\n case 'key_moments': {\r\n const title = event.momentTitle ?? '';\r\n const startTime = event.momentTime ?? event.currentTime;\r\n const moments = this.keyMoments;\r\n const idx = moments.findIndex(\r\n (m) => (title && m.title === title) || m.startTime === startTime,\r\n );\r\n const chapter = idx >= 0 ? moments[idx] : undefined;\r\n this.call(\r\n 'playerDidSelectKeyMoment',\r\n idx >= 0 ? String(idx) : undefined,\r\n chapter?.title ?? title,\r\n chapter?.startTime ?? startTime,\r\n chapter?.endTime ?? startTime,\r\n );\r\n break;\r\n }\r\n\r\n case 'pip_toggle': {\r\n // core-player packs the direction into errorMessage ('entered' | 'exited').\r\n const entered = event.pipState ? event.pipState === 'entered' : event.errorMessage === 'entered';\r\n if (entered) this.call('playerDidEnterPictureInPicture', event.currentTime);\r\n else this.call('playerDidExitPictureInPicture', event.currentTime);\r\n break;\r\n }\r\n\r\n case 'error':\r\n this.call(\r\n 'playerDidEncounterPlaybackError',\r\n event.errorCode != null ? String(event.errorCode) : 'PLAYER_ERROR',\r\n event.errorMessage ?? 'Unknown player error',\r\n event.isRecoverable ?? false,\r\n );\r\n break;\r\n\r\n case 'network_error':\r\n this.call(\r\n 'playerDidEncounterNetworkError',\r\n event.errorCode ?? 0,\r\n event.errorMessage ?? 'Network error',\r\n event.isRecoverable ?? false,\r\n );\r\n break;\r\n }\r\n }\r\n\r\n /** Call on every timeupdate — completes in-flight seeks and reports position. */\r\n public handleTimeUpdate(currentTime: number): void {\r\n this.lastCurrentTime = currentTime;\r\n if (this.pendingSeek) {\r\n const { fromTime, startedWallMs } = this.pendingSeek;\r\n this.pendingSeek = null;\r\n this.call('playerDidFinishSeeking', fromTime, currentTime, (Date.now() - startedWallMs) / 1000);\r\n }\r\n this.call('playerDidUpdateCurrentTime', currentTime, this.lastDuration);\r\n }\r\n\r\n /**\r\n * Call when the player is torn down (unmount or tab close). Reports\r\n * playerDidDismiss once, and only if playback started but never finished —\r\n * a completed session is already reported via playerDidFinishPlaying.\r\n */\r\n public notifyDismissed(): void {\r\n if (this.hasDismissed || !this.isPlaybackActive || this.hasFinished) return;\r\n this.hasDismissed = true;\r\n this.call('playerDidDismiss', this.lastCurrentTime, this.lastWatchDuration);\r\n }\r\n\r\n private beginSeek(fromTime: number, toTime: number): void {\r\n if (this.pendingSeek) {\r\n // Rapid consecutive seeks before the first one lands (scrubbing): keep the\r\n // original origin, only the destination moves.\r\n this.pendingSeek.toTime = toTime;\r\n return;\r\n }\r\n this.pendingSeek = { fromTime, toTime, startedWallMs: Date.now() };\r\n this.call('playerDidStartSeeking', fromTime);\r\n }\r\n\r\n // Invokes a delegate method if implemented; a throwing tenant callback is\r\n // contained here so it can never break playback or telemetry.\r\n private call<K extends keyof PlayerDelegate>(\r\n method: K,\r\n ...args: Parameters<NonNullable<PlayerDelegate[K]>>\r\n ): void {\r\n const delegate = this.delegate;\r\n const fn = delegate?.[method];\r\n if (typeof fn !== 'function') return;\r\n try {\r\n (fn as (...fnArgs: unknown[]) => void).apply(delegate, args);\r\n } catch (err) {\r\n console.error(`[PlayerSDK Delegate] ${String(method)} callback failed:`, err);\r\n }\r\n }\r\n}\r\n","import { useState, useEffect, useRef, useCallback } from 'react';\r\nimport { WebVideoPlayer, ErrorScreen } from '@goboss/ui-controls';\r\nimport type { FirebaseConfig } from '@goboss/analytics';\r\nimport type { Chapter, AnalyticsEvent } from '@goboss/types';\r\nimport {\r\n initSdk,\r\n startSession,\r\n sendHeartbeat,\r\n endSession,\r\n endSessionKeepalive,\r\n sendEvent,\r\n DEFAULT_HEARTBEAT_INTERVAL_SECONDS,\r\n} from './api';\r\nimport type { SdkInitResponse, SdkInitFeatures, SdkPackage, FeatureFlags, SubtitleStyle } from './types';\r\nimport type { DRMConfig, SubtitleTrack, SourceItem, PlayerDelegate } from '@goboss/types';\r\nimport { PlayerDelegateBridge } from './delegate';\r\n\r\n// Maps API feature names → player FeatureFlags keys.\r\n// Add a new entry here whenever the API adds a new feature.\r\nconst API_FEATURE_MAP: Record<string, keyof FeatureFlags> = {\r\n subtitles: 'subtitle_support',\r\n ads: 'ad_support',\r\n chapterMarkers: 'key_moments',\r\n playbackSpeed: 'playback_speed',\r\n airplay: 'airplay',\r\n chromecast: 'chromecast',\r\n pictureInPicture: 'picture_in_picture',\r\n multipleResolution: 'multiple_resolution',\r\n multipleAudio: 'multiple_audio',\r\n drmProtection: 'drm_protection',\r\n screenRecordingPrevention: 'screen_recording_prevention',\r\n signalStrength: 'signal_strength',\r\n};\r\n\r\nfunction mapApiFeatures(apiFeatures: SdkInitFeatures): FeatureFlags {\r\n const flags: FeatureFlags = {\r\n m3u8_playback: true,\r\n subtitle_support: false,\r\n playback_speed: false,\r\n airplay: false,\r\n chromecast: false,\r\n picture_in_picture: false,\r\n multiple_resolution: false,\r\n multiple_audio: false,\r\n key_moments: false,\r\n drm_protection: false,\r\n screen_recording_prevention: false,\r\n signal_strength: false,\r\n ad_support: false,\r\n };\r\n for (const [apiKey, playerKey] of Object.entries(API_FEATURE_MAP)) {\r\n if (apiKey in apiFeatures) {\r\n flags[playerKey] = apiFeatures[apiKey];\r\n }\r\n }\r\n return flags;\r\n}\r\n\r\n// Builds a URL transformer from a \"hostname=/proxyPath,hostname2=/proxyPath2\" string.\r\nfunction buildUrlTransformer(proxyDomains?: string): (url: string) => string {\r\n if (!proxyDomains) return (url) => url;\r\n const map: Array<{ domain: string; proxyPath: string }> = [];\r\n for (const entry of proxyDomains.split(',')) {\r\n const [domain, proxyPath] = entry.split('=');\r\n if (domain?.trim() && proxyPath?.trim()) {\r\n map.push({ domain: domain.trim(), proxyPath: proxyPath.trim() });\r\n }\r\n }\r\n return (url: string) => {\r\n for (const { domain, proxyPath } of map) {\r\n if (url.includes(domain)) {\r\n try {\r\n const urlObj = new URL(url);\r\n return `${proxyPath}${urlObj.pathname}${urlObj.search}`;\r\n } catch {\r\n return url.replace(`https://${domain}`, proxyPath);\r\n }\r\n }\r\n }\r\n return url;\r\n };\r\n}\r\n\r\nfunction hexToRgb(hex: string): string {\r\n const cleanHex = hex.replace('#', '');\r\n if (cleanHex.length !== 6) return '99, 102, 241';\r\n const r = parseInt(cleanHex.substring(0, 2), 16);\r\n const g = parseInt(cleanHex.substring(2, 4), 16);\r\n const b = parseInt(cleanHex.substring(4, 6), 16);\r\n return `${r}, ${g}, ${b}`;\r\n}\r\n\r\nconst DEFAULT_HEARTBEAT_WINDOW_MS = DEFAULT_HEARTBEAT_INTERVAL_SECONDS * 1000;\r\n\r\n// Maps a player analytics event → an Events API telemetry payload.\r\n// Returns null for events that are NOT part of the Events API contract (play, pause,\r\n// seek, ended, session_start, heartbeat, …) so those are never forwarded.\r\n// Only the event-specific extras are returned here; the caller adds the common\r\n// `sessionId`/`currentTime`/`videoId` fields. Field names follow the API docs\r\n// (top-level, not wrapped in `properties`).\r\nfunction buildTelemetryEvent(\r\n event: AnalyticsEvent,\r\n prevRendition: string | undefined,\r\n): { eventType: string; extras: Record<string, unknown> } | null {\r\n switch (event.eventType) {\r\n case 'pause':\r\n return { eventType: 'pause', extras: {} };\r\n case 'seek':\r\n return {\r\n eventType: 'seek',\r\n extras: {\r\n seekFrom: event.seekFrom ?? 0,\r\n seekTo: event.seekTo ?? event.currentTime ?? 0,\r\n },\r\n };\r\n case 'mute':\r\n return { eventType: 'mute', extras: { isMuted: event.isMuted ?? true } };\r\n case 'fast_forward':\r\n return { eventType: 'fast_forward', extras: { skipSeconds: event.skipSeconds ?? 10 } };\r\n case 'fast_backward':\r\n return { eventType: 'fast_backward', extras: { skipSeconds: event.skipSeconds ?? 10 } };\r\n case 'key_moments':\r\n return {\r\n eventType: 'key_moments',\r\n extras: {\r\n momentTitle: event.momentTitle ?? '',\r\n momentTime: event.momentTime ?? 0,\r\n },\r\n };\r\n case 'buffering_start':\r\n return { eventType: 'buffering_start', extras: { bufferingCount: event.bufferingCount ?? 0 } };\r\n case 'buffering_end':\r\n return { eventType: 'buffering_end', extras: {} };\r\n case 'quality_change':\r\n // The API requires both `from` and `to`. The player only carries the new\r\n // rendition; `from` is the last one we saw. The first LEVEL_SWITCHED of a\r\n // session is the initial rendition pick — there is no previous quality, so\r\n // it is not a \"change\" and is not forwarded (the API would reject it anyway).\r\n if (!prevRendition || !event.rendition || prevRendition === event.rendition) return null;\r\n return {\r\n eventType: 'quality_change',\r\n extras: { from: prevRendition, to: event.rendition },\r\n };\r\n case 'speed_change':\r\n return { eventType: 'speed_change', extras: { speed: event.speed ?? 1 } };\r\n case 'subtitle_change':\r\n // Turning subtitles off is its own API event type.\r\n if (event.subtitleEnabled === false) {\r\n return { eventType: 'subtitle_disable', extras: { enabled: false } };\r\n }\r\n return {\r\n eventType: 'subtitle_change',\r\n extras: {\r\n language: event.subtitleLanguage ?? '',\r\n enabled: true,\r\n },\r\n };\r\n case 'audio_track_change':\r\n return {\r\n eventType: 'audio_track_change',\r\n extras: { track: event.audioTrack ?? '' },\r\n };\r\n case 'pip_toggle':\r\n // core-player packs the direction into errorMessage ('entered' | 'exited').\r\n return { eventType: 'pip_toggle', extras: { enabled: event.errorMessage === 'entered' } };\r\n case 'fullscreen_enter':\r\n return { eventType: 'fullscreen_toggle', extras: { enabled: true } };\r\n case 'fullscreen_exit':\r\n return { eventType: 'fullscreen_toggle', extras: { enabled: false } };\r\n case 'error':\r\n // The API's `player_error`. HTML5 media errors carry a numeric code + message string.\r\n return {\r\n eventType: 'player_error',\r\n extras: {\r\n code: event.errorCode != null ? String(event.errorCode) : 'PLAYER_ERROR',\r\n message: event.errorMessage ?? 'Unknown player error',\r\n },\r\n };\r\n case 'network_error':\r\n return {\r\n eventType: 'network_error',\r\n extras: {\r\n code: event.errorCode ? String(event.errorCode) : 'NETWORK_ERROR',\r\n message: event.errorMessage ?? 'Network error',\r\n },\r\n };\r\n default:\r\n return null;\r\n }\r\n}\r\n\r\nexport interface BossVideoPlayerProps {\r\n /** Player key from the GoBOSS dashboard — identifies your tenant. */\r\n playerKey: string;\r\n /**\r\n * Stream source(s) to play.\r\n * - Pass a single URL string for standard single-stream playback.\r\n * - Pass an array of `{ label, url }` objects to let the user switch between\r\n * multiple streams at runtime (e.g. 4K, Dolby, Stereo). The first item plays by default.\r\n */\r\n src: string | SourceItem[];\r\n /**\r\n * External subtitle tracks (WebVTT/SRT) to load alongside any tracks\r\n * auto-detected from the M3U8 manifest. Merged into a single subtitle menu.\r\n */\r\n subtitles?: SubtitleTrack[];\r\n /** License key for playback authorization. */\r\n licenseKey?: string;\r\n /** Accent color for the player UI (hex). Defaults to '#6366f1'. */\r\n themeColor?: string;\r\n /** Firebase config for analytics. */\r\n firebaseConfig?: FirebaseConfig;\r\n /**\r\n * CDN proxy domain mappings as a comma-separated string.\r\n * Format: \"cdn.example.com=/my-proxy,other.cdn.com=/other-proxy\"\r\n * Any request URL matching a domain is rewritten to the proxy path.\r\n */\r\n proxyDomains?: string;\r\n /**\r\n * Base URL for the GoBOSS API. Defaults to 'https://dev-api.go-boss.com'.\r\n * Override this if you need to route through a reverse proxy.\r\n */\r\n apiBaseUrl?: string;\r\n /** AWS MediaTailor ad tracking URL for skip-ad flow. */\r\n adTrackingUrl?: string;\r\n /** AWS MediaTailor overlay ad tracking URL. */\r\n overlayAdUrl?: string;\r\n videoId?: string;\r\n /** Video title shown in the player UI. */\r\n title?: string;\r\n /** Watermark text stamped on the video. */\r\n watermark?: string;\r\n autoplay?: boolean;\r\n muted?: boolean;\r\n autoFullscreen?: boolean;\r\n enableControls?: boolean;\r\n enableSkipAd?: boolean;\r\n skipAdAfterSeconds?: number;\r\n chaptersDisplayMode?: 'strip' | 'panel';\r\n volumeControlType?: 'slider' | 'button';\r\n subtitleStyle?: SubtitleStyle;\r\n drmConfig?: DRMConfig;\r\n /** Called for every analytics event — lets consumers add custom tracking. */\r\n onAnalyticsEvent?: (event: AnalyticsEvent) => void;\r\n /**\r\n * Typed per-event callbacks (playerDidStartPlaying, playerDidPause, …) that\r\n * receive only the arguments relevant to each event. Prefer this over\r\n * `onAnalyticsEvent` for analytics, logging, and business logic; implement\r\n * only the methods you need.\r\n */\r\n delegate?: PlayerDelegate;\r\n /** Called when chapter/key-moment data is parsed from the stream. */\r\n onKeyMomentsLoaded?: (keyMoments: Chapter[]) => void;\r\n /** Seek to this position (in seconds) when the video is ready. Use for resume-playback. */\r\n initialTime?: number;\r\n /** Called on every timeupdate with the current playback position in seconds. Use to persist watch progress. */\r\n onTimeUpdate?: (currentTime: number) => void;\r\n}\r\n\r\nexport function BossVideoPlayer({\r\n playerKey,\r\n src,\r\n subtitles,\r\n licenseKey,\r\n themeColor = '#6366f1',\r\n firebaseConfig,\r\n proxyDomains,\r\n apiBaseUrl,\r\n adTrackingUrl,\r\n overlayAdUrl,\r\n videoId: videoIdProp,\r\n title,\r\n watermark,\r\n autoplay = false,\r\n muted = false,\r\n autoFullscreen = false,\r\n enableControls = true,\r\n enableSkipAd,\r\n skipAdAfterSeconds,\r\n chaptersDisplayMode = 'strip',\r\n volumeControlType = 'slider',\r\n subtitleStyle,\r\n drmConfig,\r\n onAnalyticsEvent,\r\n delegate,\r\n onKeyMomentsLoaded,\r\n initialTime,\r\n onTimeUpdate,\r\n}: BossVideoPlayerProps) {\r\n const [keyMoments, setKeyMoments] = useState<Chapter[]>([]);\r\n const [activeSourceIdx, setActiveSourceIdx] = useState(0);\r\n\r\n // Delegate bridge — translates the analytics event stream into typed PlayerDelegate\r\n // callbacks. The bridge is stateful (tracks previous quality/subtitle/speed, seek\r\n // bounds, start-vs-resume) so one instance lives for the component lifetime and is\r\n // kept in sync with the latest props via its setters.\r\n const [bridge] = useState(() => new PlayerDelegateBridge());\r\n useEffect(() => {\r\n bridge.setDelegate(delegate);\r\n bridge.setAutoplay(autoplay);\r\n }, [bridge, delegate, autoplay]);\r\n\r\n // Report playerDidDismiss when the player unmounts mid-session (tab close is\r\n // covered by the beforeunload handler below; the bridge fires at most once).\r\n useEffect(() => () => bridge.notifyDismissed(), [bridge]);\r\n\r\n // Normalize src into a SourceItem array so the rest of the component is uniform\r\n const normalizedSources: SourceItem[] = Array.isArray(src)\r\n ? src\r\n : [{ label: 'Default', url: src }];\r\n const activeSrc: string = normalizedSources[activeSourceIdx]?.url ?? normalizedSources[0]?.url ?? '';\r\n\r\n // ── Player key validation state ─────────────────────────────────────────────\r\n // 'loading': waiting for /sdk/init API response\r\n // 'ready': API confirmed — resolvedPackage holds the feature set\r\n // 'error': API rejected the key or network failed\r\n const [sdkStatus, setSdkStatus] = useState<'loading' | 'ready' | 'error'>('loading');\r\n const [resolvedPackage, setResolvedPackage] = useState<SdkPackage | null>(null);\r\n const [retryCount, setRetryCount] = useState(0);\r\n const [errorKind, setErrorKind] = useState<'auth' | 'network'>('network');\r\n\r\n const sdkDataRef = useRef<SdkInitResponse | null>(null);\r\n const isInAdBreakRef = useRef(false);\r\n const sessionIdRef = useRef('');\r\n const sessionStartedRef = useRef(false);\r\n // Blocks event telemetry once the session has ended (via `ended` or tab close),\r\n // since the Events API rejects events for an inactive session.\r\n const sessionEndedRef = useRef(false);\r\n // Last rendition seen, used to fill `from` on the next quality_change.\r\n const prevRenditionRef = useRef<string | undefined>(undefined);\r\n // True from the moment the user switches source until real content actually plays again.\r\n // While true, the heartbeat window stays paused so the blank detection/fallback period of\r\n // an unsupported source (which fires no buffering events, only a hollow `play`) is never\r\n // counted as watched content.\r\n const awaitingSourcePlaybackRef = useRef(false);\r\n\r\n // Tracks real content time toward the heartbeat window (excludes ads + pauses).\r\n // The window length comes from `heartbeatIntervalSeconds` on the /init response.\r\n const heartbeatWindowMsRef = useRef(DEFAULT_HEARTBEAT_WINDOW_MS);\r\n const heartbeatTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const elapsedInWindowRef = useRef(0);\r\n const windowStartRef = useRef<number | null>(null);\r\n const latestPlaybackRef = useRef({ currentTime: 0, duration: 0 });\r\n const startHeartbeatTimerRef = useRef<() => void>(() => {});\r\n\r\n const srcSegments = activeSrc.split('/').filter(Boolean);\r\n const videoId =\r\n videoIdProp ||\r\n (srcSegments[srcSegments.length - 1] ?? '').replace(/\\.m3u8.*$/, '') ||\r\n 'video';\r\n\r\n // Latest playback URL, read from effects/callbacks that must not re-run on a source\r\n // switch (SDK init, session end on tab close).\r\n const activeSrcRef = useRef(activeSrc);\r\n useEffect(() => {\r\n activeSrcRef.current = activeSrc;\r\n }, [activeSrc]);\r\n\r\n // Build URL transformer once when proxyDomains changes\r\n const urlTransformer = useCallback(\r\n () => buildUrlTransformer(proxyDomains),\r\n [proxyDomains],\r\n )();\r\n\r\n const themeRgb = hexToRgb(themeColor);\r\n\r\n // Apply themeColor CSS variables\r\n useEffect(() => {\r\n document.documentElement.style.setProperty('--accent-indigo', themeColor);\r\n document.documentElement.style.setProperty('--accent-indigo-rgb', themeRgb);\r\n }, [themeColor, themeRgb]);\r\n\r\n // Validate player key — blocks video until the API confirms the key and returns features.\r\n // Cleanup resets to 'loading' so playerKey/retryCount changes show a fresh loading state.\r\n useEffect(() => {\r\n let cancelled = false;\r\n\r\n initSdk(playerKey, apiBaseUrl, { videoUrl: activeSrcRef.current })\r\n .then(data => {\r\n if (cancelled) return;\r\n sdkDataRef.current = data;\r\n const { features, planTier, heartbeatIntervalSeconds } = data.result.data;\r\n // Honour the server-provided heartbeat cadence; keep the 30s default otherwise.\r\n heartbeatWindowMsRef.current =\r\n typeof heartbeatIntervalSeconds === 'number' && heartbeatIntervalSeconds > 0\r\n ? heartbeatIntervalSeconds * 1000\r\n : DEFAULT_HEARTBEAT_WINDOW_MS;\r\n const pkg: SdkPackage = {\r\n package: planTier,\r\n is_active: true,\r\n features: mapApiFeatures(features),\r\n limits: { streaming_minutes: 0 },\r\n };\r\n console.log('[SDK] Player key validated. Features:', pkg.features);\r\n setResolvedPackage(pkg);\r\n setSdkStatus('ready');\r\n })\r\n .catch(err => {\r\n if (cancelled) return;\r\n console.error('[SDK] Player key validation failed:', err);\r\n const msg = (err as Error).message ?? '';\r\n setErrorKind(/init failed: 4\\d\\d/.test(msg) ? 'auth' : 'network');\r\n setSdkStatus('error');\r\n });\r\n\r\n return () => {\r\n cancelled = true;\r\n if (heartbeatTimerRef.current) {\r\n clearTimeout(heartbeatTimerRef.current);\r\n heartbeatTimerRef.current = null;\r\n }\r\n elapsedInWindowRef.current = 0;\r\n windowStartRef.current = null;\r\n setSdkStatus('loading');\r\n setResolvedPackage(null);\r\n sessionStartedRef.current = false;\r\n sessionIdRef.current = '';\r\n sessionEndedRef.current = false;\r\n prevRenditionRef.current = undefined;\r\n };\r\n }, [playerKey, apiBaseUrl, retryCount]);\r\n\r\n // Pauses the timer, saving accumulated time in current window\r\n const pauseHeartbeatTimer = useCallback(() => {\r\n if (!heartbeatTimerRef.current) return;\r\n clearTimeout(heartbeatTimerRef.current);\r\n heartbeatTimerRef.current = null;\r\n if (windowStartRef.current !== null) {\r\n elapsedInWindowRef.current += Date.now() - windowStartRef.current;\r\n windowStartRef.current = null;\r\n }\r\n }, []);\r\n\r\n // Stops the timer and resets the window\r\n const stopHeartbeatTimer = useCallback(() => {\r\n if (heartbeatTimerRef.current) {\r\n clearTimeout(heartbeatTimerRef.current);\r\n heartbeatTimerRef.current = null;\r\n }\r\n elapsedInWindowRef.current = 0;\r\n windowStartRef.current = null;\r\n }, []);\r\n\r\n // Starts (or resumes) the 30s real-content-time timer\r\n const startHeartbeatTimer = useCallback(() => {\r\n if (heartbeatTimerRef.current || !sessionIdRef.current) return;\r\n windowStartRef.current = Date.now();\r\n const remaining = heartbeatWindowMsRef.current - elapsedInWindowRef.current;\r\n heartbeatTimerRef.current = setTimeout(() => {\r\n heartbeatTimerRef.current = null;\r\n elapsedInWindowRef.current = 0;\r\n windowStartRef.current = null;\r\n const { currentTime, duration } = latestPlaybackRef.current;\r\n console.log(`[SDK] Heartbeat timer fired — ${heartbeatWindowMsRef.current / 1000}s of real content elapsed`);\r\n sendHeartbeat(playerKey, sessionIdRef.current, currentTime, duration, apiBaseUrl).catch(err =>\r\n console.warn('[SDK] Heartbeat failed:', (err as Error).message),\r\n );\r\n startHeartbeatTimerRef.current();\r\n }, remaining);\r\n }, [playerKey, apiBaseUrl]);\r\n\r\n useEffect(() => {\r\n startHeartbeatTimerRef.current = startHeartbeatTimer;\r\n }, [startHeartbeatTimer]);\r\n\r\n // Deterministic source-switch handling: the moment the user picks a different source,\r\n // pause the heartbeat window and mark that we're awaiting real playback. The new stream\r\n // (especially an unsupported one like Dolby/Atmos) emits no buffering events during its\r\n // blank detection period — only a hollow `play` — so this is the only reliable way to\r\n // stop counting that time. The window resumes when `buffering_end` confirms real frames\r\n // are rendering (in handleAnalytics). The first run (initial mount) is skipped.\r\n const isInitialSourceRef = useRef(true);\r\n useEffect(() => {\r\n if (isInitialSourceRef.current) {\r\n isInitialSourceRef.current = false;\r\n return;\r\n }\r\n awaitingSourcePlaybackRef.current = true;\r\n // The new source's first LEVEL_SWITCHED is its initial pick, not a change from\r\n // the previous source's rendition — clear so no cross-source quality_change is sent.\r\n prevRenditionRef.current = undefined;\r\n pauseHeartbeatTimer();\r\n }, [activeSourceIdx, pauseHeartbeatTimer]);\r\n\r\n // beforeunload: fire session end with keepalive so the request survives tab close\r\n useEffect(() => {\r\n const handleUnload = () => {\r\n bridge.notifyDismissed();\r\n if (!sessionIdRef.current) return;\r\n sessionEndedRef.current = true;\r\n stopHeartbeatTimer();\r\n const { currentTime, duration } = latestPlaybackRef.current;\r\n endSessionKeepalive(playerKey, sessionIdRef.current, currentTime, duration, apiBaseUrl);\r\n };\r\n window.addEventListener('beforeunload', handleUnload);\r\n return () => {\r\n window.removeEventListener('beforeunload', handleUnload);\r\n stopHeartbeatTimer();\r\n };\r\n }, [playerKey, apiBaseUrl, stopHeartbeatTimer, bridge]);\r\n\r\n const handleAnalytics = useCallback((event: AnalyticsEvent) => {\r\n const e = event as unknown as Record<string, unknown>;\r\n\r\n if (event.currentTime !== undefined) {\r\n latestPlaybackRef.current = { currentTime: event.currentTime, duration: event.duration };\r\n }\r\n\r\n if (event.eventType === 'session_start' && !sessionStartedRef.current) {\r\n sessionStartedRef.current = true;\r\n sessionEndedRef.current = false;\r\n sessionIdRef.current = typeof e.sessionId === 'string' ? e.sessionId : '';\r\n // Video identity is sent here — without it the play is excluded from Top Videos,\r\n // retention and completion analytics. `session_start` fires just before the first\r\n // `play`, so the media duration is already known unless the stream is live.\r\n startSession(\r\n playerKey,\r\n {\r\n sessionId: sessionIdRef.current,\r\n videoId,\r\n videoTitle: title,\r\n videoUrl: activeSrcRef.current,\r\n contentDurationSec: event.duration,\r\n },\r\n apiBaseUrl,\r\n ).catch(err =>\r\n console.warn('[SDK] Session start failed:', (err as Error).message),\r\n );\r\n if (!isInAdBreakRef.current) {\r\n startHeartbeatTimer();\r\n }\r\n }\r\n\r\n // `play` is also fired by an unsupported source's hollow autoplay attempt, so while a\r\n // source switch is awaiting real playback we must NOT let it resume the window.\r\n if (\r\n event.eventType === 'play' &&\r\n sessionStartedRef.current &&\r\n !isInAdBreakRef.current &&\r\n !awaitingSourcePlaybackRef.current\r\n ) {\r\n startHeartbeatTimer();\r\n }\r\n\r\n if (event.eventType === 'pause') {\r\n pauseHeartbeatTimer();\r\n }\r\n\r\n // Normal buffering is not \"real content played\" — pause during it.\r\n if (event.eventType === 'buffering_start') {\r\n pauseHeartbeatTimer();\r\n }\r\n\r\n // `buffering_end` only fires when frames actually render (it's gated by a real\r\n // buffering→playing transition), so it's the reliable \"content is truly playing again\"\r\n // signal. Clear the source-switch wait and resume the window here.\r\n if (event.eventType === 'buffering_end' && sessionStartedRef.current && !isInAdBreakRef.current) {\r\n awaitingSourcePlaybackRef.current = false;\r\n startHeartbeatTimer();\r\n }\r\n\r\n if (event.eventType === 'ended') {\r\n stopHeartbeatTimer();\r\n if (sessionIdRef.current) {\r\n sessionEndedRef.current = true;\r\n const { currentTime, duration } = latestPlaybackRef.current;\r\n endSession(playerKey, sessionIdRef.current, currentTime, duration, apiBaseUrl).catch(err =>\r\n console.warn('[SDK] Session end failed:', (err as Error).message),\r\n );\r\n }\r\n }\r\n\r\n // Forward in-session telemetry to the Events API. Guarded so events are only sent\r\n // while a session is active (started, not yet ended) — the API rejects events for\r\n // an inactive session.\r\n if (sessionStartedRef.current && sessionIdRef.current && !sessionEndedRef.current) {\r\n const telemetry = buildTelemetryEvent(event, prevRenditionRef.current);\r\n if (telemetry) {\r\n sendEvent(\r\n playerKey,\r\n {\r\n eventType: telemetry.eventType,\r\n sessionId: sessionIdRef.current,\r\n currentTime: event.currentTime ?? 0,\r\n videoId,\r\n videoTitle: title,\r\n ...telemetry.extras,\r\n },\r\n apiBaseUrl,\r\n ).catch(err =>\r\n console.warn(`[SDK] Event ${telemetry.eventType} failed:`, (err as Error).message),\r\n );\r\n }\r\n }\r\n\r\n // Track the active rendition even when nothing was forwarded (initial pick,\r\n // session not yet started) so the next quality_change carries the right `from`.\r\n if (event.eventType === 'quality_change' && event.rendition) {\r\n prevRenditionRef.current = event.rendition;\r\n }\r\n\r\n console.log('[SDK Analytics]', event);\r\n bridge.handleAnalyticsEvent(event);\r\n onAnalyticsEvent?.(event);\r\n }, [playerKey, apiBaseUrl, videoId, title, startHeartbeatTimer, pauseHeartbeatTimer, stopHeartbeatTimer, onAnalyticsEvent, bridge]);\r\n\r\n const handleAdBreakChange = useCallback((active: boolean) => {\r\n isInAdBreakRef.current = active;\r\n if (active) {\r\n pauseHeartbeatTimer();\r\n console.log('[SDK] Ad break started — heartbeat timer paused');\r\n } else {\r\n if (sessionStartedRef.current) startHeartbeatTimer();\r\n console.log('[SDK] Ad break ended — heartbeat timer resumed');\r\n }\r\n }, [pauseHeartbeatTimer, startHeartbeatTimer]);\r\n\r\n const handleKeyMomentsLoaded = useCallback((parsed: Chapter[]) => {\r\n bridge.setKeyMoments(parsed);\r\n setKeyMoments(parsed);\r\n onKeyMomentsLoaded?.(parsed);\r\n }, [onKeyMomentsLoaded, bridge]);\r\n\r\n const handleTimeUpdate = useCallback((currentTime: number) => {\r\n bridge.handleTimeUpdate(currentTime);\r\n onTimeUpdate?.(currentTime);\r\n }, [onTimeUpdate, bridge]);\r\n\r\n if (sdkStatus === 'loading') {\r\n return (\r\n <div style={{\r\n width: '100%', aspectRatio: '16 / 9', background: '#000', borderRadius: 8,\r\n display: 'flex', alignItems: 'center', justifyContent: 'center',\r\n }}>\r\n <style>{`@keyframes boss-sdk-spin{to{transform:rotate(360deg)}}`}</style>\r\n <div style={{\r\n width: 60, height: 60, borderRadius: '50%',\r\n border: `4px solid rgba(${themeRgb}, 0.1)`,\r\n borderLeftColor: themeColor,\r\n borderRightColor: `rgba(${themeRgb}, 0.2)`,\r\n filter: `drop-shadow(0 0 10px rgba(${themeRgb}, 0.5))`,\r\n animation: 'boss-sdk-spin 1s linear infinite',\r\n }} />\r\n </div>\r\n );\r\n }\r\n\r\n if (sdkStatus === 'error') {\r\n const isAuth = errorKind === 'auth';\r\n return (\r\n <div style={{\r\n width: '100%', aspectRatio: '16 / 9', background: '#09090e', borderRadius: 8,\r\n display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 20,\r\n }}>\r\n <ErrorScreen\r\n title={isAuth ? 'Invalid Player Key' : 'Something Went Wrong'}\r\n subtitle={\r\n isAuth\r\n ? 'Your player key is invalid or expired.\\nPlease check your configuration.'\r\n : 'We encountered an error while connecting.\\nPlease try again.'\r\n }\r\n onRetry={() => setRetryCount(c => c + 1)}\r\n />\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <WebVideoPlayer\r\n src={activeSrc}\r\n sources={normalizedSources}\r\n activeSourceIndex={activeSourceIdx}\r\n onSourceChange={(idx: number) => setActiveSourceIdx(idx)}\r\n subtitles={subtitles}\r\n keyMoments={keyMoments}\r\n themeColor={themeColor}\r\n firebaseConfig={firebaseConfig}\r\n resolvedEntitlement={resolvedPackage}\r\n licenseKey={licenseKey}\r\n title={title}\r\n watermark={watermark}\r\n autoplay={autoplay}\r\n muted={muted}\r\n autoFullscreen={autoFullscreen}\r\n enableControls={enableControls}\r\n enableSkipAd={enableSkipAd}\r\n skipAdAfterSeconds={skipAdAfterSeconds}\r\n chaptersDisplayMode={chaptersDisplayMode}\r\n volumeControlType={volumeControlType}\r\n subtitleStyle={subtitleStyle}\r\n drmConfig={drmConfig}\r\n overlayAdUrl={overlayAdUrl}\r\n adTrackingUrl={adTrackingUrl}\r\n videoId={videoId}\r\n urlTransformer={urlTransformer}\r\n onAnalyticsEvent={handleAnalytics}\r\n onAdBreakChange={handleAdBreakChange}\r\n onKeyMomentsLoaded={handleKeyMomentsLoaded}\r\n initialTime={initialTime}\r\n onTimeUpdate={handleTimeUpdate}\r\n />\r\n );\r\n}\r\n","import React, { createElement } from 'react';\r\nimport { createRoot } from 'react-dom/client';\r\nimport { BossVideoPlayer } from './BossVideoPlayer';\r\nimport type { BossVideoPlayerProps } from './BossVideoPlayer';\r\n\r\nexport interface PlayerProps extends BossVideoPlayerProps {}\r\n\r\nexport interface PlayerInstance {\r\n /** Re-render the player with updated props (src change, theme, etc.) */\r\n update(props: Partial<PlayerProps>): void;\r\n /** Unmounts the player and releases all resources */\r\n destroy(): void;\r\n}\r\n\r\n/**\r\n * Mounts the full video player into any DOM element.\r\n * Identical to <BossVideoPlayer> — handles player key validation, feature gating,\r\n * session tracking, and heartbeat automatically.\r\n *\r\n * Use this in Vue, Angular, Vanilla JS, or any environment where JSX is unavailable.\r\n *\r\n * @example Vanilla JS\r\n * const player = createPlayer(document.getElementById('player'), {\r\n * playerKey: 'bsp_abc123',\r\n * src: 'https://my-cdn.com/video.m3u8',\r\n * });\r\n * player.destroy(); // cleanup\r\n *\r\n * @example Vue 3\r\n * const player = createPlayer(containerRef.value, { playerKey: 'bsp_abc123', src: props.src });\r\n * onUnmounted(() => player.destroy());\r\n *\r\n * @example Angular\r\n * const player = createPlayer(this.containerRef.nativeElement, { playerKey: 'bsp_abc123', src: this.src });\r\n * ngOnDestroy() { this.player.destroy(); }\r\n */\r\nexport function createPlayer(container: HTMLElement, props: PlayerProps): PlayerInstance {\r\n const root = createRoot(container);\r\n let currentProps = props;\r\n\r\n root.render(createElement(BossVideoPlayer as React.FC<PlayerProps>, currentProps));\r\n\r\n return {\r\n update(newProps: Partial<PlayerProps>) {\r\n currentProps = { ...currentProps, ...newProps };\r\n root.render(createElement(BossVideoPlayer as React.FC<PlayerProps>, currentProps));\r\n },\r\n destroy() {\r\n root.unmount();\r\n },\r\n };\r\n}\r\n"],"x_google_ignoreList":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],"mappings":"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"}
|