@vizor-vr/player 0.2.0 → 0.2.1
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/README.md +4 -0
- package/dist/__tests__/autoplay.test.d.ts +2 -0
- package/dist/__tests__/autoplay.test.d.ts.map +1 -0
- package/dist/__tests__/chapter-logic.test.d.ts +2 -0
- package/dist/__tests__/chapter-logic.test.d.ts.map +1 -0
- package/dist/__tests__/chapter-manager.test.d.ts +2 -0
- package/dist/__tests__/chapter-manager.test.d.ts.map +1 -0
- package/dist/__tests__/chapter-menu.test.d.ts +2 -0
- package/dist/__tests__/chapter-menu.test.d.ts.map +1 -0
- package/dist/__tests__/dom-drm.test.d.ts +2 -0
- package/dist/__tests__/dom-drm.test.d.ts.map +1 -0
- package/dist/__tests__/drm-gate.test.d.ts +2 -0
- package/dist/__tests__/drm-gate.test.d.ts.map +1 -0
- package/dist/__tests__/fairplay.test.d.ts +2 -0
- package/dist/__tests__/fairplay.test.d.ts.map +1 -0
- package/dist/__tests__/hub-controller.test.d.ts +2 -0
- package/dist/__tests__/hub-controller.test.d.ts.map +1 -0
- package/dist/__tests__/hub-layout.test.d.ts +2 -0
- package/dist/__tests__/hub-layout.test.d.ts.map +1 -0
- package/dist/__tests__/hub-model.test.d.ts +2 -0
- package/dist/__tests__/hub-model.test.d.ts.map +1 -0
- package/dist/__tests__/hub-panel-renderer.test.d.ts +2 -0
- package/dist/__tests__/hub-panel-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/lens-angle.test.d.ts +2 -0
- package/dist/__tests__/lens-angle.test.d.ts.map +1 -0
- package/dist/__tests__/media-element-drm-gate.test.d.ts +2 -0
- package/dist/__tests__/media-element-drm-gate.test.d.ts.map +1 -0
- package/dist/__tests__/media-element-gating.test.d.ts +2 -0
- package/dist/__tests__/media-element-gating.test.d.ts.map +1 -0
- package/dist/__tests__/player-version-lock.test.d.ts +2 -0
- package/dist/__tests__/player-version-lock.test.d.ts.map +1 -0
- package/dist/__tests__/source-manager-drm.test.d.ts +2 -0
- package/dist/__tests__/source-manager-drm.test.d.ts.map +1 -0
- package/dist/__tests__/vz-chapter.test.d.ts +2 -0
- package/dist/__tests__/vz-chapter.test.d.ts.map +1 -0
- package/dist/__tests__/vz-cinema-gating.test.d.ts +2 -0
- package/dist/__tests__/vz-cinema-gating.test.d.ts.map +1 -0
- package/dist/__tests__/vz-hub-item.test.d.ts +2 -0
- package/dist/__tests__/vz-hub-item.test.d.ts.map +1 -0
- package/dist/__tests__/vz-tour-gating.test.d.ts +2 -0
- package/dist/__tests__/vz-tour-gating.test.d.ts.map +1 -0
- package/dist/__tests__/watch-time.test.d.ts +2 -0
- package/dist/__tests__/watch-time.test.d.ts.map +1 -0
- package/dist/analytics/index.d.ts.map +1 -1
- package/dist/annotations/annotation-popup.d.ts.map +1 -1
- package/dist/{annotations-C0ugfCn4.js → annotations-Du6ZWrqC.js} +82 -13
- package/dist/annotations-Du6ZWrqC.js.map +1 -0
- package/dist/captions/caption-renderer.d.ts.map +1 -1
- package/dist/{captions-BP0H2E03.js → captions-YvnHAwLY.js} +4 -4
- package/dist/captions-YvnHAwLY.js.map +1 -0
- package/dist/chapters/chapter-logic.d.ts +47 -0
- package/dist/chapters/chapter-logic.d.ts.map +1 -0
- package/dist/chapters/chapter-manager.d.ts +42 -0
- package/dist/chapters/chapter-manager.d.ts.map +1 -0
- package/dist/chapters/chapter-menu.d.ts +24 -0
- package/dist/chapters/chapter-menu.d.ts.map +1 -0
- package/dist/{collab-BXPomrS0.js → collab-BLpllHtb.js} +20 -14
- package/dist/collab-BLpllHtb.js.map +1 -0
- package/dist/collaborative/chat-button.d.ts.map +1 -1
- package/dist/collaborative/voice-button.d.ts.map +1 -1
- package/dist/controls/cc-button.d.ts.map +1 -1
- package/dist/controls/compass.d.ts.map +1 -1
- package/dist/controls/controls-overlay.d.ts.map +1 -1
- package/dist/controls/fullscreen-button.d.ts.map +1 -1
- package/dist/controls/icons.d.ts +1 -0
- package/dist/controls/icons.d.ts.map +1 -1
- package/dist/controls/live-indicator.d.ts.map +1 -1
- package/dist/controls/play-button.d.ts.map +1 -1
- package/dist/controls/playlist-controls.d.ts.map +1 -1
- package/dist/controls/quality-menu.d.ts.map +1 -1
- package/dist/controls/seek-bar.d.ts +9 -0
- package/dist/controls/seek-bar.d.ts.map +1 -1
- package/dist/controls/volume-control.d.ts.map +1 -1
- package/dist/controls/vr-button.d.ts.map +1 -1
- package/dist/core/base-element.d.ts +8 -0
- package/dist/core/base-element.d.ts.map +1 -1
- package/dist/core/media-element.d.ts +70 -1
- package/dist/core/media-element.d.ts.map +1 -1
- package/dist/{dash-adapter-tid9ZyhO.js → dash-adapter-BMpHWcBA.js} +9 -2
- package/dist/dash-adapter-BMpHWcBA.js.map +1 -0
- package/dist/delivery/__tests__/delivery-client.test.d.ts +2 -0
- package/dist/delivery/__tests__/delivery-client.test.d.ts.map +1 -0
- package/dist/delivery/__tests__/delivery-hydrate.test.d.ts +2 -0
- package/dist/delivery/__tests__/delivery-hydrate.test.d.ts.map +1 -0
- package/dist/delivery/delivery-client.d.ts +27 -0
- package/dist/delivery/delivery-client.d.ts.map +1 -0
- package/dist/delivery/delivery-hydrate.d.ts +18 -0
- package/dist/delivery/delivery-hydrate.d.ts.map +1 -0
- package/dist/delivery/dom-drm.d.ts +9 -0
- package/dist/delivery/dom-drm.d.ts.map +1 -0
- package/dist/elements/vz-chapter.d.ts +27 -0
- package/dist/elements/vz-chapter.d.ts.map +1 -0
- package/dist/elements/vz-cinema.d.ts +20 -0
- package/dist/elements/vz-cinema.d.ts.map +1 -1
- package/dist/elements/vz-hub-item.d.ts +23 -0
- package/dist/elements/vz-hub-item.d.ts.map +1 -0
- package/dist/elements/vz-hub.d.ts +57 -0
- package/dist/elements/vz-hub.d.ts.map +1 -0
- package/dist/elements/vz-playlist.d.ts +14 -0
- package/dist/elements/vz-playlist.d.ts.map +1 -1
- package/dist/elements/vz-tour.d.ts +16 -0
- package/dist/elements/vz-tour.d.ts.map +1 -1
- package/dist/hls-adapter-C_a65zia.js +161 -0
- package/dist/hls-adapter-C_a65zia.js.map +1 -0
- package/dist/hub/hub-controller.d.ts +31 -0
- package/dist/hub/hub-controller.d.ts.map +1 -0
- package/dist/hub/hub-layout.d.ts +23 -0
- package/dist/hub/hub-layout.d.ts.map +1 -0
- package/dist/hub/hub-model.d.ts +27 -0
- package/dist/hub/hub-model.d.ts.map +1 -0
- package/dist/hub/hub-panel-manager.d.ts +16 -0
- package/dist/hub/hub-panel-manager.d.ts.map +1 -0
- package/dist/hub/hub-panel-renderer.d.ts +26 -0
- package/dist/hub/hub-panel-renderer.d.ts.map +1 -0
- package/dist/license/__tests__/cache.test.d.ts +2 -0
- package/dist/license/__tests__/cache.test.d.ts.map +1 -0
- package/dist/license/__tests__/feature-gate.test.d.ts +2 -0
- package/dist/license/__tests__/feature-gate.test.d.ts.map +1 -0
- package/dist/license/__tests__/format-gate.test.d.ts +2 -0
- package/dist/license/__tests__/format-gate.test.d.ts.map +1 -0
- package/dist/license/__tests__/license-checker.test.d.ts.map +1 -0
- package/dist/license/__tests__/master-key.test.d.ts.map +1 -0
- package/dist/license/__tests__/verify-no-ed25519.test.d.ts +2 -0
- package/dist/license/__tests__/verify-no-ed25519.test.d.ts.map +1 -0
- package/dist/license/__tests__/verify.test.d.ts +2 -0
- package/dist/license/__tests__/verify.test.d.ts.map +1 -0
- package/dist/license/cache.d.ts +4 -0
- package/dist/license/cache.d.ts.map +1 -0
- package/dist/license/drm-gate.d.ts +15 -0
- package/dist/license/drm-gate.d.ts.map +1 -0
- package/dist/license/feature-gate.d.ts +27 -0
- package/dist/license/feature-gate.d.ts.map +1 -0
- package/dist/license/format-gate.d.ts +23 -0
- package/dist/license/format-gate.d.ts.map +1 -0
- package/dist/license/license-checker.d.ts.map +1 -1
- package/dist/license/master-key.d.ts +11 -7
- package/dist/license/master-key.d.ts.map +1 -1
- package/dist/license/trusted-key.d.ts +7 -0
- package/dist/license/trusted-key.d.ts.map +1 -0
- package/dist/license/verify.d.ts +7 -0
- package/dist/license/verify.d.ts.map +1 -0
- package/dist/{register-CwnmiGEs.js → register-BmZt_300.js} +1482 -163
- package/dist/register-BmZt_300.js.map +1 -0
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +1 -1
- package/dist/renderer/camera-controller.d.ts +1 -1
- package/dist/renderer/camera-controller.d.ts.map +1 -1
- package/dist/renderer/projections/fisheye-projection.d.ts +4 -0
- package/dist/renderer/projections/fisheye-projection.d.ts.map +1 -1
- package/dist/renderer/projections/spherical-projection.d.ts +26 -0
- package/dist/renderer/projections/spherical-projection.d.ts.map +1 -0
- package/dist/renderer/renderer.d.ts.map +1 -1
- package/dist/renderer/shaders/fisheye.glsl.d.ts +10 -1
- package/dist/renderer/shaders/fisheye.glsl.d.ts.map +1 -1
- package/dist/sri.json +8 -8
- package/dist/streaming/dash-adapter.d.ts +2 -2
- package/dist/streaming/dash-adapter.d.ts.map +1 -1
- package/dist/streaming/fairplay.d.ts +21 -0
- package/dist/streaming/fairplay.d.ts.map +1 -0
- package/dist/streaming/hls-adapter.d.ts +2 -2
- package/dist/streaming/hls-adapter.d.ts.map +1 -1
- package/dist/streaming/progressive-adapter.d.ts +2 -2
- package/dist/streaming/progressive-adapter.d.ts.map +1 -1
- package/dist/streaming/source-manager.d.ts +3 -1
- package/dist/streaming/source-manager.d.ts.map +1 -1
- package/dist/streaming/types.d.ts +9 -3
- package/dist/streaming/types.d.ts.map +1 -1
- package/dist/styles/controls.d.ts +1 -1
- package/dist/styles/controls.d.ts.map +1 -1
- package/dist/utils/static-html.d.ts +12 -0
- package/dist/utils/static-html.d.ts.map +1 -0
- package/dist/vizor.js +4 -4
- package/package.json +8 -5
- package/dist/__tests__/license-checker.test.d.ts.map +0 -1
- package/dist/__tests__/master-key.test.d.ts.map +0 -1
- package/dist/annotations-C0ugfCn4.js.map +0 -1
- package/dist/captions-BP0H2E03.js.map +0 -1
- package/dist/collab-BXPomrS0.js.map +0 -1
- package/dist/dash-adapter-tid9ZyhO.js.map +0 -1
- package/dist/hls-adapter-BivH46L_.js +0 -97
- package/dist/hls-adapter-BivH46L_.js.map +0 -1
- package/dist/register-CwnmiGEs.js.map +0 -1
- /package/dist/{__tests__ → license/__tests__}/license-checker.test.d.ts +0 -0
- /package/dist/{__tests__ → license/__tests__}/master-key.test.d.ts +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-BmZt_300.js","sources":["../src/core/html-element.ts","../src/core/player-state.ts","../src/core/event-emitter.ts","../src/config/meta-config.ts","../src/license/verify.ts","../src/license/master-key.ts","../src/license/cache.ts","../src/license/license-checker.ts","../src/license/watermark-guard.ts","../src/license/watermark.ts","../src/license/integrity.ts","../src/core/base-element.ts","../src/renderer/projections/equirect-projection.ts","../src/renderer/projections/flat-projection.ts","../src/renderer/projections/cubemap-projection.ts","../src/renderer/shaders/eac.glsl.ts","../src/renderer/projections/eac-projection.ts","../src/renderer/shaders/fisheye.glsl.ts","../src/renderer/projections/fisheye-projection.ts","../src/renderer/projections/spherical-projection.ts","../src/renderer/camera-controller.ts","../src/renderer/renderer.ts","../src/streaming/progressive-adapter.ts","../src/streaming/adapter-factory.ts","../src/streaming/source-manager.ts","../src/utils/fullscreen.ts","../src/styles/controls.ts","../src/controls/controls-overlay.ts","../src/controls/play-button.ts","../src/controls/seek-bar.ts","../src/controls/volume-control.ts","../src/controls/fullscreen-button.ts","../src/controls/vr-button.ts","../src/controls/compass.ts","../src/controls/quality-menu.ts","../src/controls/keyboard-shortcuts.ts","../src/xr/xr-controller.ts","../src/xr/xr-manager.ts","../src/controls/live-indicator.ts","../src/analytics/analytics-collector.ts","../src/analytics/gaze-tracker.ts","../src/analytics/index.ts","../src/delivery/delivery-client.ts","../src/delivery/delivery-hydrate.ts","../src/license/format-gate.ts","../src/license/feature-gate.ts","../src/license/drm-gate.ts","../src/delivery/dom-drm.ts","../src/chapters/chapter-logic.ts","../src/chapters/chapter-menu.ts","../src/chapters/chapter-manager.ts","../src/controls/cc-button.ts","../src/core/media-element.ts","../src/elements/vz-video.ts","../src/elements/vz-img.ts","../src/hotspots/hotspot-renderer.ts","../src/hotspots/hotspot-interaction.ts","../src/hotspots/hotspot-manager.ts","../src/elements/vz-tour.ts","../src/elements/vz-tour-img.ts","../src/elements/vz-tour-portal.ts","../src/renderer/projections/cinema-projection.ts","../src/elements/vz-cinema.ts","../src/elements/vz-live.ts","../src/controls/playlist-controls.ts","../src/controls/playlist-panel.ts","../src/elements/vz-playlist.ts","../src/elements/vz-annotation.ts","../src/elements/vz-chapter.ts","../src/elements/vz-hub-item.ts","../src/hub/hub-layout.ts","../src/hub/hub-panel-renderer.ts","../src/hub/hub-panel-manager.ts","../src/hub/hub-controller.ts","../src/hub/hub-model.ts","../src/elements/vz-hub.ts","../src/register.ts"],"sourcesContent":["/**\n * SSR-safe HTMLElement base.\n *\n * On a server (no DOM) `HTMLElement` is undefined, so `class X extends\n * HTMLElement` throws a ReferenceError at module-evaluation time — which crashes\n * any framework that imports the player during SSR (Next.js RSC, Nuxt,\n * SvelteKit). Fall back to a no-op stand-in there so the package can be imported\n * without throwing. The classes are never instantiated on the server because\n * custom-element registration is separately guarded to a no-op when\n * `customElements` is unavailable.\n */\nexport const HTMLElementBase: typeof HTMLElement =\n typeof HTMLElement !== 'undefined'\n ? HTMLElement\n : (class {} as unknown as typeof HTMLElement);\n","import type { VzFormat, StereoMode, VzError, QualityLevel, LicenseResult } from '@vizor-vr/shared';\n\n/** Shape of all player state */\nexport interface PlayerStateShape {\n // Playback\n playing: boolean;\n paused: boolean;\n currentTime: number;\n duration: number;\n buffered: number;\n seeking: boolean;\n ended: boolean;\n volume: number;\n muted: boolean;\n playbackRate: number;\n\n // Loading\n loading: boolean;\n loadProgress: number;\n\n // Renderer\n format: VzFormat | null;\n stereoMode: StereoMode;\n\n // Quality\n currentQuality: string;\n availableQualities: QualityLevel[];\n autoQuality: boolean;\n\n // Camera\n lat: number;\n lon: number;\n fov: number;\n\n // XR\n xrSupported: boolean;\n xrActive: boolean;\n\n // Controls\n controlsVisible: boolean;\n fullscreen: boolean;\n\n // Live\n isLive: boolean;\n isAtLiveEdge: boolean;\n\n // License\n license: LicenseResult | null;\n\n // Tour\n currentProbeId: string | null;\n tourProbeCount: number;\n\n // Error\n error: VzError | null;\n}\n\ntype Listener<T> = (value: T, prev: T) => void;\ntype AnyListener = (key: string, value: unknown, prev: unknown) => void;\n\nconst INITIAL_STATE: PlayerStateShape = {\n playing: false,\n paused: true,\n currentTime: 0,\n duration: 0,\n buffered: 0,\n seeking: false,\n ended: false,\n volume: 1,\n muted: false,\n playbackRate: 1,\n loading: false,\n loadProgress: 0,\n format: null,\n stereoMode: 'mono',\n currentQuality: 'auto',\n availableQualities: [],\n autoQuality: true,\n lat: 0,\n lon: 0,\n fov: 75,\n xrSupported: false,\n xrActive: false,\n controlsVisible: true,\n fullscreen: false,\n isLive: false,\n isAtLiveEdge: false,\n license: null,\n currentProbeId: null,\n tourProbeCount: 0,\n error: null,\n};\n\n/**\n * Lightweight reactive state store for the player.\n * Supports per-key subscriptions and batch updates.\n */\nexport class PlayerState {\n private state: PlayerStateShape;\n private listeners = new Map<keyof PlayerStateShape, Set<Listener<unknown>>>();\n private globalListeners = new Set<AnyListener>();\n\n constructor(initial?: Partial<PlayerStateShape>) {\n this.state = { ...INITIAL_STATE, ...initial };\n }\n\n /** Get the current value of a state key */\n get<K extends keyof PlayerStateShape>(key: K): PlayerStateShape[K] {\n return this.state[key];\n }\n\n /** Get the entire state snapshot (read-only) */\n getSnapshot(): Readonly<PlayerStateShape> {\n return this.state;\n }\n\n /** Set a single state value. No-op if value is unchanged. */\n set<K extends keyof PlayerStateShape>(key: K, value: PlayerStateShape[K]): void {\n const prev = this.state[key];\n if (Object.is(prev, value)) return;\n this.state[key] = value;\n this.notify(key, value, prev);\n }\n\n /** Batch multiple state updates. Notifications fire after all values are set. */\n batch(updates: Partial<PlayerStateShape>): void {\n const changed: Array<[keyof PlayerStateShape, unknown, unknown]> = [];\n for (const key of Object.keys(updates) as Array<keyof PlayerStateShape>) {\n const value = updates[key];\n const prev = this.state[key];\n if (!Object.is(prev, value)) {\n (this.state as unknown as Record<string, unknown>)[key] = value;\n changed.push([key, value, prev]);\n }\n }\n for (const [key, value, prev] of changed) {\n this.notify(key, value, prev);\n }\n }\n\n /** Subscribe to changes on a specific state key. Returns unsubscribe function. */\n subscribe<K extends keyof PlayerStateShape>(\n key: K,\n listener: Listener<PlayerStateShape[K]>,\n ): () => void {\n if (!this.listeners.has(key)) {\n this.listeners.set(key, new Set());\n }\n const typedListener = listener as Listener<unknown>;\n this.listeners.get(key)!.add(typedListener);\n return () => {\n this.listeners.get(key)?.delete(typedListener);\n };\n }\n\n /** Subscribe to all state changes. Returns unsubscribe function. */\n subscribeAll(listener: AnyListener): () => void {\n this.globalListeners.add(listener);\n return () => {\n this.globalListeners.delete(listener);\n };\n }\n\n /** Reset state to initial values */\n reset(): void {\n const oldState = { ...this.state };\n this.state = { ...INITIAL_STATE };\n for (const key of Object.keys(oldState) as Array<keyof PlayerStateShape>) {\n if (!Object.is(oldState[key], this.state[key])) {\n this.notify(key, this.state[key], oldState[key]);\n }\n }\n }\n\n private notify(key: keyof PlayerStateShape, value: unknown, prev: unknown): void {\n this.listeners.get(key)?.forEach((fn) => fn(value, prev));\n this.globalListeners.forEach((fn) => fn(key as string, value, prev));\n }\n}\n","import type { VzEvent, VzEventDetailMap } from '@vizor-vr/shared';\n\ntype EventCallback = (event: CustomEvent) => void;\n\n/**\n * Typed event emitter that dispatches CustomEvents on the host element.\n * Supports both HTML5 media events and Vizor custom events.\n */\nexport class VzEventEmitter {\n private element: HTMLElement;\n private handlers = new Map<string, Set<EventCallback>>();\n\n constructor(element: HTMLElement) {\n this.element = element;\n }\n\n /** Emit a standard media event (no detail) */\n emitMedia(type: string): void {\n const event = new Event(type, { bubbles: true, composed: true });\n this.element.dispatchEvent(event);\n }\n\n /** Emit a Vizor custom event with typed detail */\n emit<E extends keyof VzEventDetailMap>(\n type: E,\n detail?: VzEventDetailMap[E],\n ): void {\n const event = new CustomEvent(type, {\n bubbles: true,\n composed: true,\n detail,\n });\n this.element.dispatchEvent(event);\n }\n\n /** Add an event listener */\n on(type: VzEvent | string, callback: EventCallback): () => void {\n if (!this.handlers.has(type)) {\n this.handlers.set(type, new Set());\n }\n this.handlers.get(type)!.add(callback);\n this.element.addEventListener(type, callback as EventListener);\n return () => this.off(type, callback);\n }\n\n /** Remove an event listener */\n off(type: VzEvent | string, callback: EventCallback): void {\n this.handlers.get(type)?.delete(callback);\n this.element.removeEventListener(type, callback as EventListener);\n }\n\n /** Remove all event listeners */\n removeAll(): void {\n for (const [type, callbacks] of this.handlers) {\n for (const callback of callbacks) {\n this.element.removeEventListener(type, callback as EventListener);\n }\n }\n this.handlers.clear();\n }\n}\n","import type { PlayerConfig } from '@vizor-vr/shared';\nimport { DEFAULT_CONFIG } from '@vizor-vr/shared';\n\n/** Meta tag name to config key mapping */\nconst META_MAP: Record<string, keyof PlayerConfig> = {\n 'api-key': 'apiKey',\n 'license-key': 'licenseKey',\n 'license-mode': 'licenseMode',\n 'api-endpoint': 'apiEndpoint',\n 'primary-color': 'primaryColor',\n 'secondary-color': 'secondaryColor',\n 'brand-name': 'brandName',\n 'brand-logo': 'brandLogo',\n 'brand-url': 'brandUrl',\n 'watermark': 'watermarkLogo',\n 'hide-compass': 'hideCompass',\n 'hide-vr-button': 'hideVrButton',\n 'hide-settings': 'hideSettings',\n 'hide-fullscreen': 'hideFullscreen',\n 'hide-controls': 'hideControls',\n 'hide-title': 'hideTitle',\n 'hide-brand-logo': 'hideBrandLogo',\n 'hide-watermark': 'hideWatermark',\n};\n\n/** Boolean meta tags (presence = true) */\nconst BOOLEAN_METAS = new Set([\n 'hideCompass',\n 'hideVrButton',\n 'hideSettings',\n 'hideFullscreen',\n 'hideControls',\n 'hideTitle',\n 'hideBrandLogo',\n 'hideWatermark',\n]);\n\n/**\n * Read all <meta name=\"vz:*\"> tags from the document head\n * and return partial config overrides.\n */\nexport function readMetaConfig(): Partial<PlayerConfig> {\n const config: Partial<Record<string, unknown>> = {};\n\n const metas = document.querySelectorAll('meta[name^=\"vz:\"]');\n metas.forEach((meta) => {\n const rawName = meta.getAttribute('name');\n if (!rawName) return;\n\n const metaKey = rawName.replace('vz:', '');\n const configKey = META_MAP[metaKey];\n if (!configKey) return;\n\n const content = meta.getAttribute('content') ?? '';\n\n if (BOOLEAN_METAS.has(configKey)) {\n config[configKey] = content === '' || content === 'true';\n } else {\n config[configKey] = content;\n }\n });\n\n return config as Partial<PlayerConfig>;\n}\n\n/**\n * Resolve the full player config by merging:\n * 1. Defaults\n * 2. Meta tag overrides\n * 3. Element attribute overrides\n */\nexport function resolveConfig(\n elementAttrs: Partial<PlayerConfig>,\n metaOverrides?: Partial<PlayerConfig>,\n): PlayerConfig {\n const meta = metaOverrides ?? readMetaConfig();\n return {\n ...DEFAULT_CONFIG,\n ...meta,\n ...elementAttrs,\n };\n}\n","/**\n * Decode + verify a standalone license key's Ed25519 signature.\n *\n * SECURITY: the signature is ALWAYS verified against `trustedPublicKeyHex` — the\n * key pinned into the build — never a key read from the license envelope itself.\n * (The previous implementation trusted `payload.publicKey`, letting anyone\n * self-sign an \"enterprise\" license with their own keypair.)\n *\n * Fail-closed: returns `null` — never a partially-trusted payload — whenever the\n * key cannot be positively verified, including when:\n * - no trusted key is pinned,\n * - the envelope/payload can't be decoded,\n * - the `@noble/ed25519` verifier is unavailable (optional peer not installed),\n * - the signature does not verify.\n */\n// Type-only import (erased at runtime) so the optional @noble/ed25519 peer is\n// still typed without pulling it in eagerly.\nimport type * as EdModule from '@noble/ed25519';\n\nexport interface VerifiedPayload {\n tier: string;\n domains?: string[];\n expiresAt?: string;\n // NOTE: any `publicKey` field carried in the envelope is intentionally ignored.\n}\n\nexport async function verifyLicenseKey(\n licenseKey: string,\n trustedPublicKeyHex: string,\n): Promise<VerifiedPayload | null> {\n // No pinned key → nothing to trust → fail closed.\n if (!trustedPublicKeyHex) return null;\n\n // Decode base64url → { payload, signature }.\n let envelope: { payload: unknown; signature: unknown };\n let payload: VerifiedPayload;\n try {\n const decoded = atob(licenseKey.replace(/-/g, '+').replace(/_/g, '/'));\n envelope = JSON.parse(decoded);\n if (\n !envelope ||\n typeof envelope.payload !== 'string' ||\n typeof envelope.signature !== 'string'\n ) {\n return null;\n }\n payload = JSON.parse(envelope.payload) as VerifiedPayload;\n } catch {\n return null;\n }\n\n // Load the verifier. If it is unavailable we MUST fail closed rather than\n // skip the check (the old code skipped, granting the claimed tier for free).\n let ed: typeof EdModule;\n try {\n ed = await import('@noble/ed25519');\n } catch {\n return null;\n }\n\n try {\n const msgBytes = new TextEncoder().encode(envelope.payload as string);\n const sigBytes = hexToBytes(envelope.signature as string);\n const pubBytes = hexToBytes(trustedPublicKeyHex);\n const valid = await ed.verifyAsync(sigBytes, msgBytes, pubBytes);\n if (!valid) return null;\n } catch {\n // Malformed signature/key length, hashing failure, etc. → fail closed.\n return null;\n }\n\n return payload;\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n const clean = hex.length % 2 === 0 ? hex : '';\n const bytes = new Uint8Array(clean.length / 2);\n for (let i = 0; i < bytes.length; i++) {\n bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);\n }\n return bytes;\n}\n","import type { LicenseResult } from '@vizor-vr/shared';\nimport { getFeaturesForTier } from '@vizor-vr/shared';\nimport { verifyLicenseKey } from './verify.js';\nimport { getTrustedPublicKey } from './trusted-key.js';\n\n/**\n * Check whether a standalone license key is a master key (→ enterprise features).\n *\n * A master key is an ordinary server-signed license whose verified payload tier\n * is `'master'`. It is verified against the pinned trusted public key exactly\n * like any other license — a REAL Ed25519 signature is required.\n *\n * SECURITY: the previous implementation granted enterprise on two plain\n * string-equality checks (`tier === 'master'` and `payload.publicKey === masterPubKey`)\n * with NO signature verification, so anyone who knew the master public key could\n * self-sign a master license. That bypass is gone.\n *\n * @returns an enterprise `LicenseResult`, or `null` if this is not a validly\n * signed master key.\n */\nexport async function checkMasterKey(licenseKey: string): Promise<LicenseResult | null> {\n const payload = await verifyLicenseKey(licenseKey, getTrustedPublicKey());\n if (!payload || payload.tier !== 'master') return null;\n\n return {\n valid: true,\n tier: 'enterprise',\n features: getFeaturesForTier('enterprise'),\n domains: payload.domains ?? [],\n expiresAt: payload.expiresAt,\n };\n}\n","import type { LicenseResult } from '@vizor-vr/shared';\n\n/**\n * Phone-home result cache.\n *\n * SECURITY: the previous cache lived under a single fixed localStorage key with\n * no binding or integrity, so any page script could write\n * `{ result: { valid: true, tier: 'enterprise' } }` and have it honoured. This\n * version:\n * - keys each entry by a hash of (licenseKey + domain) so an entry can't be\n * replayed for a different license/domain, and\n * - stores an integrity tag over (licenseKey + domain + result + ts) that is\n * re-checked on read, so a tampered/hand-planted blob is rejected.\n *\n * A public browser bundle can't hold a real secret, so this is defence-in-depth\n * (it defeats the trivial injection above), not an unbreakable guarantee — the\n * authoritative check remains the pinned-key signature verification.\n */\n\nconst CACHE_PREFIX = '__vz_lk_';\nconst MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24h\n\ninterface CacheEnvelope {\n result: LicenseResult;\n ts: number;\n tag: string;\n}\n\n/** Small, fast, non-cryptographic string hash (djb2). */\nfunction hashString(input: string): string {\n let h = 5381;\n for (let i = 0; i < input.length; i++) {\n h = (h * 33) ^ input.charCodeAt(i);\n }\n // >>> 0 → unsigned; base36 for compactness.\n return (h >>> 0).toString(36);\n}\n\nfunction cacheKeyFor(licenseKey: string, domain: string): string {\n return CACHE_PREFIX + hashString(`${licenseKey}|${domain}`);\n}\n\nfunction integrityTag(licenseKey: string, domain: string, result: LicenseResult, ts: number): string {\n return hashString(`${licenseKey}|${domain}|${JSON.stringify(result)}|${ts}`);\n}\n\nexport function writeLicenseCache(\n licenseKey: string,\n domain: string,\n result: LicenseResult,\n now: number = Date.now(),\n): void {\n if (typeof localStorage === 'undefined') return;\n try {\n const envelope: CacheEnvelope = {\n result,\n ts: now,\n tag: integrityTag(licenseKey, domain, result, now),\n };\n localStorage.setItem(cacheKeyFor(licenseKey, domain), JSON.stringify(envelope));\n } catch {\n // localStorage unavailable / quota — non-fatal.\n }\n}\n\nexport function readLicenseCache(\n licenseKey: string,\n domain: string,\n now: number = Date.now(),\n): LicenseResult | null {\n if (typeof localStorage === 'undefined') return null;\n const key = cacheKeyFor(licenseKey, domain);\n try {\n const raw = localStorage.getItem(key);\n if (!raw) return null;\n\n const parsed = JSON.parse(raw) as Partial<CacheEnvelope>;\n if (!parsed || typeof parsed.ts !== 'number' || typeof parsed.tag !== 'string' || !parsed.result) {\n localStorage.removeItem(key);\n return null;\n }\n\n // Reject stale entries.\n if (now - parsed.ts > MAX_AGE_MS) {\n localStorage.removeItem(key);\n return null;\n }\n\n // Reject tampered / hand-planted entries (tag must match).\n const expected = integrityTag(licenseKey, domain, parsed.result, parsed.ts);\n if (parsed.tag !== expected) {\n localStorage.removeItem(key);\n return null;\n }\n\n return parsed.result;\n } catch {\n return null;\n }\n}\n","import type { LicenseResult, LicenseMode } from '@vizor-vr/shared';\nimport { getFeaturesForTier } from '@vizor-vr/shared';\nimport { checkMasterKey } from './master-key.js';\nimport { verifyLicenseKey } from './verify.js';\nimport { getTrustedPublicKey } from './trusted-key.js';\nimport { readLicenseCache, writeLicenseCache } from './cache.js';\n\nexport interface LicenseConfig {\n apiKey?: string;\n licenseKey?: string;\n licenseMode?: LicenseMode;\n apiEndpoint?: string;\n /** If true, skip phone-home for standalone keys (useful for testing) */\n skipPhoneHome?: boolean;\n}\n\nconst DEFAULT_API_ENDPOINT = 'https://api.vizor.io';\n\n/**\n * Validate a license and return the result.\n *\n * SaaS mode: POST to API with apiKey + domain.\n * Standalone mode: decode base64url key → verify Ed25519 signature → check expiry/domain.\n * No key: return free tier.\n */\nexport async function checkLicense(config: LicenseConfig): Promise<LicenseResult> {\n const mode = config.licenseMode ?? (config.licenseKey ? 'standalone' : 'saas');\n\n if (mode === 'standalone' && config.licenseKey) {\n // Master keys are ordinary server-signed licenses whose verified tier is\n // 'master' — checked (with a real signature) before the generic path.\n const masterResult = await checkMasterKey(config.licenseKey);\n if (masterResult) return masterResult;\n\n return checkStandaloneLicense(config.licenseKey, config.apiEndpoint, config.skipPhoneHome);\n }\n\n if (mode === 'saas' && config.apiKey) {\n return checkSaasLicense(config.apiKey, config.apiEndpoint);\n }\n\n // No valid key provided — free tier\n return freeTierResult('No license key provided');\n}\n\nasync function checkSaasLicense(apiKey: string, apiEndpoint?: string): Promise<LicenseResult> {\n const endpoint = apiEndpoint || DEFAULT_API_ENDPOINT;\n const domain = typeof window !== 'undefined' ? window.location.hostname : 'localhost';\n\n try {\n const res = await fetch(`${endpoint}/api/v1/license/validate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiKey, domain }),\n });\n\n if (!res.ok) {\n console.warn('[Vizor] License validation failed:', res.status);\n return freeTierResult(`API returned ${res.status}`);\n }\n\n const result: LicenseResult = await res.json();\n return result;\n } catch (err) {\n console.warn('[Vizor] License validation network error:', err);\n return freeTierResult('Network error during license validation');\n }\n}\n\nfunction currentHostname(): string {\n return typeof window !== 'undefined' ? window.location.hostname : 'localhost';\n}\n\nasync function checkStandaloneLicense(\n licenseKey: string,\n apiEndpoint?: string,\n skipPhoneHome?: boolean,\n): Promise<LicenseResult> {\n // Verify the signature against the PINNED trusted key. Fail-closed: an\n // unverifiable license (bad signature, foreign key, missing verifier, no\n // pinned key) yields a free tier — never the tier the envelope claims.\n const payload = await verifyLicenseKey(licenseKey, getTrustedPublicKey());\n if (!payload) {\n return freeTierResult('Invalid or unverifiable license key');\n }\n\n // Check expiry\n if (payload.expiresAt) {\n const expires = new Date(payload.expiresAt);\n if (expires < new Date()) {\n return freeTierResult('License expired');\n }\n }\n\n // Check domain\n const currentDomain = currentHostname();\n const domains = payload.domains ?? [];\n if (domains.length > 0 && !matchesDomain(currentDomain, domains)) {\n return freeTierResult('Domain not authorized');\n }\n\n // Local validation passed — build initial result\n const tier = payload.tier as LicenseResult['tier'];\n const localResult: LicenseResult = {\n valid: true,\n tier,\n features: getFeaturesForTier(tier),\n domains,\n expiresAt: payload.expiresAt,\n };\n\n // Phone home to refresh subscription status (async, non-blocking on first load per session)\n if (!skipPhoneHome && typeof window !== 'undefined') {\n phoneHome(licenseKey, currentDomain, apiEndpoint);\n }\n\n // Prefer a fresher server result if we have a valid cached one (bound to this\n // license + domain and integrity-checked — a page-planted blob is ignored).\n const cached = readLicenseCache(licenseKey, currentDomain);\n if (cached) {\n return cached;\n }\n\n return localResult;\n}\n\n/**\n * Phone home to server on first load per session.\n * Updates the bound, integrity-tagged localStorage cache with the server result.\n */\nfunction phoneHome(licenseKey: string, domain: string, apiEndpoint?: string): void {\n // Only phone home once per session\n const sessionKey = '__vz_ph_done';\n if (typeof sessionStorage !== 'undefined') {\n try {\n if (sessionStorage.getItem(sessionKey)) return;\n sessionStorage.setItem(sessionKey, '1');\n } catch {\n // sessionStorage unavailable\n }\n }\n\n const endpoint = apiEndpoint || DEFAULT_API_ENDPOINT;\n\n fetch(`${endpoint}/api/v1/license/validate-standalone`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ licenseKey, domain }),\n })\n .then((res) => {\n if (!res.ok) throw new Error(`Status ${res.status}`);\n return res.json();\n })\n .then((serverResult: LicenseResult) => {\n writeLicenseCache(licenseKey, domain, serverResult);\n })\n .catch(() => {\n // Network error — use local result (already returned)\n console.debug('[Vizor] Phone-home failed, using local license result');\n });\n}\n\nfunction freeTierResult(message: string): LicenseResult {\n return {\n valid: false,\n tier: 'free',\n features: getFeaturesForTier('free'),\n message,\n };\n}\n\nfunction matchesDomain(hostname: string, allowedDomains: string[]): boolean {\n return allowedDomains.some((pattern) => {\n if (pattern.startsWith('*.')) {\n const suffix = pattern.slice(1); // .example.com\n return hostname.endsWith(suffix) || hostname === pattern.slice(2);\n }\n return hostname === pattern;\n });\n}\n","/**\n * Watermark Guard — MutationObserver that re-injects the watermark\n * if it's removed from the shadow DOM.\n *\n * Protection strategies:\n * 1. MutationObserver watches for childList changes\n * 2. Re-injects watermark with random delay (100-500ms)\n * 3. Creates a backup watermark with a different class name\n * 4. CSS uses !important to resist style overrides\n */\n\nimport { sanitizeUrl } from '../utils/sanitize.js';\n\nlet observer: MutationObserver | null = null;\nlet guardActive = false;\n\n/** CSS for hardened watermark styling */\nconst HARDENED_WATERMARK_CSS = `\n .vz-watermark, .vz-wm-backup {\n position: absolute !important;\n bottom: 48px !important;\n right: 12px !important;\n z-index: 100 !important;\n pointer-events: auto !important;\n display: block !important;\n visibility: visible !important;\n opacity: 1 !important;\n transform: none !important;\n clip: auto !important;\n clip-path: none !important;\n width: auto !important;\n height: auto !important;\n overflow: visible !important;\n }\n .vz-watermark a, .vz-wm-backup a {\n display: inline-block !important;\n padding: 4px 10px !important;\n background: rgba(0, 0, 0, 0.4) !important;\n border-radius: 4px !important;\n color: rgba(255, 255, 255, 0.6) !important;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;\n font-size: 10px !important;\n text-decoration: none !important;\n letter-spacing: 0.02em !important;\n visibility: visible !important;\n opacity: 1 !important;\n }\n`;\n\n/**\n * Initialize the watermark guard on a shadow root.\n * Watches for removal of .vz-watermark and re-injects it.\n */\nexport function initWatermarkGuard(\n shadowRoot: ShadowRoot | HTMLElement,\n linkUrl: string = 'https://vizor.io',\n logoUrl?: string,\n): void {\n if (guardActive) return;\n guardActive = true;\n\n // Inject hardened CSS\n const guardStyle = document.createElement('style');\n guardStyle.setAttribute('data-vz-guard', '1');\n guardStyle.textContent = HARDENED_WATERMARK_CSS;\n shadowRoot.appendChild(guardStyle);\n\n // Create backup watermark (hidden initially, different class)\n const backup = createWatermarkEl('vz-wm-backup', linkUrl, logoUrl);\n backup.style.display = 'none';\n shadowRoot.appendChild(backup);\n\n observer = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n if (mutation.type !== 'childList') continue;\n\n // Check if primary watermark was removed\n const primary = shadowRoot.querySelector('.vz-watermark');\n if (!primary) {\n // Re-inject with random delay to make it harder to script removal\n const delay = 100 + Math.random() * 400;\n setTimeout(() => {\n if (!shadowRoot.querySelector('.vz-watermark')) {\n const newWatermark = createWatermarkEl('vz-watermark', linkUrl, logoUrl);\n shadowRoot.appendChild(newWatermark);\n }\n }, delay);\n\n // Show backup immediately\n const backupEl = shadowRoot.querySelector('.vz-wm-backup') as HTMLElement;\n if (backupEl) backupEl.style.display = 'block';\n }\n\n // Check if backup was removed\n const backupCheck = shadowRoot.querySelector('.vz-wm-backup');\n if (!backupCheck) {\n const newBackup = createWatermarkEl('vz-wm-backup', linkUrl, logoUrl);\n shadowRoot.appendChild(newBackup);\n }\n\n // Check if guard style was removed\n const guardStyleCheck = shadowRoot.querySelector('[data-vz-guard]');\n if (!guardStyleCheck) {\n const newStyle = document.createElement('style');\n newStyle.setAttribute('data-vz-guard', '1');\n newStyle.textContent = HARDENED_WATERMARK_CSS;\n shadowRoot.appendChild(newStyle);\n }\n }\n });\n\n observer.observe(shadowRoot, { childList: true, subtree: false });\n}\n\n/**\n * Destroy the watermark guard (for cleanup/testing).\n */\nexport function destroyWatermarkGuard(): void {\n if (observer) {\n observer.disconnect();\n observer = null;\n }\n guardActive = false;\n}\n\n/**\n * Check if the watermark guard is currently active.\n */\nexport function isWatermarkGuardActive(): boolean {\n return guardActive;\n}\n\nfunction createWatermarkEl(className: string, linkUrl: string, logoUrl?: string): HTMLElement {\n const el = document.createElement('div');\n el.className = className;\n\n // URLs are already sanitized by createWatermark; sanitize again defensively\n // and build via DOM APIs so author-supplied values can never inject markup.\n const safeLink = sanitizeUrl(linkUrl, 'https://vizor.io');\n const safeLogo = sanitizeUrl(logoUrl, '');\n\n const anchor = document.createElement('a');\n anchor.href = safeLink;\n anchor.target = '_blank';\n anchor.rel = 'noopener';\n\n if (safeLogo) {\n const img = document.createElement('img');\n img.src = safeLogo;\n img.alt = 'Watermark';\n img.style.cssText =\n 'max-height:24px;max-width:120px;width:auto;height:auto;object-fit:contain';\n anchor.appendChild(img);\n } else {\n anchor.textContent = 'Powered by Vizor';\n }\n\n el.appendChild(anchor);\n return el;\n}\n","import type { LicenseResult } from '@vizor-vr/shared';\nimport { initWatermarkGuard } from './watermark-guard.js';\nimport { sanitizeUrl } from '../utils/sanitize.js';\n\nexport interface WatermarkOptions {\n /** Custom logo image URL (replaces default text) */\n logoUrl?: string;\n /** Link URL when watermark is clicked */\n linkUrl?: string;\n}\n\n/**\n * Create a watermark element if the license doesn't allow removal.\n * Returns the element (appended to container) or null if no watermark needed.\n *\n * - Free tier (no custom branding): shows \"Powered by Vizor\" text\n * - Paid tier with custom branding + watermarkLogo: shows custom logo image\n * - Paid tier with removeWatermark: no watermark at all\n */\nexport function createWatermark(\n container: ShadowRoot | HTMLElement,\n license: LicenseResult,\n options?: WatermarkOptions,\n): HTMLElement | null {\n if (license.features.removeWatermark) return null;\n\n const el = document.createElement('div');\n el.className = 'vz-watermark';\n\n // Sanitize author-supplied URLs (brand-url / brand-logo attributes) to avoid\n // attribute-injection / javascript: URLs. Build via DOM APIs, never innerHTML.\n const linkUrl = sanitizeUrl(options?.linkUrl, 'https://vizor.io');\n const logoUrl = sanitizeUrl(options?.logoUrl, '');\n\n const anchor = document.createElement('a');\n anchor.href = linkUrl;\n anchor.target = '_blank';\n anchor.rel = 'noopener';\n\n if (logoUrl) {\n const img = document.createElement('img');\n img.src = logoUrl;\n img.alt = 'Watermark';\n img.className = 'vz-watermark-logo';\n anchor.appendChild(img);\n } else {\n anchor.textContent = 'Powered by Vizor';\n }\n el.appendChild(anchor);\n\n const style = document.createElement('style');\n style.textContent = `\n .vz-watermark {\n position: absolute;\n bottom: 48px;\n right: 12px;\n z-index: 100;\n pointer-events: auto;\n }\n .vz-watermark a {\n display: inline-block;\n padding: 4px 10px;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 6px;\n color: rgba(255, 255, 255, 0.6);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 10px;\n text-decoration: none;\n letter-spacing: 0.02em;\n transition: background 0.2s, color 0.2s, border-color 0.2s;\n }\n .vz-watermark a:hover {\n background: rgba(0, 0, 0, 0.55);\n border-color: rgba(255, 255, 255, 0.1);\n color: rgba(255, 255, 255, 0.85);\n }\n .vz-watermark-logo {\n display: block;\n max-height: 24px;\n max-width: 120px;\n width: auto;\n height: auto;\n object-fit: contain;\n }\n `;\n\n container.appendChild(style);\n container.appendChild(el);\n\n // Start watermark guard to prevent removal via DevTools (pass sanitized URLs)\n initWatermarkGuard(container, linkUrl, logoUrl || undefined);\n\n return el;\n}\n\n/** Remove watermark from container */\nexport function removeWatermark(container: ShadowRoot | HTMLElement): void {\n const el = container.querySelector('.vz-watermark');\n if (el) el.remove();\n}\n","import type { LicenseResult } from '@vizor-vr/shared';\nimport { getFeaturesForTier } from '@vizor-vr/shared';\n\n/**\n * Registry of functions to integrity-check.\n * Maps function name to its expected SHA-256 hash (computed at init time).\n */\nconst registeredChecks: Map<string, string> = new Map();\nlet initialized = false;\n\n/**\n * Compute a SHA-256 hash of a function's source code.\n * Uses the Web Crypto API (available in all modern browsers).\n */\nexport async function computeFunctionHash(fn: Function): Promise<string> {\n const source = fn.toString();\n const bytes = new TextEncoder().encode(source);\n\n if (typeof crypto !== 'undefined' && crypto.subtle) {\n const hash = await crypto.subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n }\n\n // Fallback: simple hash for environments without Web Crypto\n let hash = 0;\n for (let i = 0; i < source.length; i++) {\n const char = source.charCodeAt(i);\n hash = ((hash << 5) - hash + char) | 0;\n }\n return Math.abs(hash).toString(16);\n}\n\n/**\n * Register a function for integrity checking.\n * Must be called during initialization, before any potential tampering.\n */\nexport async function registerIntegrityCheck(name: string, fn: Function): Promise<void> {\n const hash = await computeFunctionHash(fn);\n registeredChecks.set(name, hash);\n initialized = true;\n}\n\n/**\n * Verify integrity of all registered functions.\n *\n * If any function's source has changed since registration,\n * returns a free-tier result (tamper detected).\n * Otherwise returns null (integrity OK).\n */\nexport async function verifyIntegrity(): Promise<LicenseResult | null> {\n if (!initialized || registeredChecks.size === 0) {\n return null; // No checks registered — skip\n }\n\n // We need to re-import the functions to check their current state\n // But since we stored hashes at registration time, we compare against stored hashes\n // The caller should pass the same function references for verification\n\n return null; // Integrity checks happen via verifyFunctionIntegrity\n}\n\n/**\n * Verify a specific function's integrity against its registered hash.\n * Returns a free-tier result if tampered, null if OK.\n */\nexport async function verifyFunctionIntegrity(\n name: string,\n fn: Function,\n): Promise<LicenseResult | null> {\n const expectedHash = registeredChecks.get(name);\n if (!expectedHash) return null; // Not registered\n\n const currentHash = await computeFunctionHash(fn);\n\n if (currentHash !== expectedHash) {\n console.warn(`[Vizor] Integrity check failed for ${name}`);\n return {\n valid: false,\n tier: 'free',\n features: getFeaturesForTier('free'),\n message: 'License validation integrity check failed',\n };\n }\n\n return null; // OK\n}\n\n/**\n * Check if integrity system has been initialized.\n */\nexport function isIntegrityInitialized(): boolean {\n return initialized;\n}\n\n/**\n * Get count of registered checks.\n */\nexport function getRegisteredCheckCount(): number {\n return registeredChecks.size;\n}\n","import type { PlayerConfig, VzFormat, FeatureFlags, LicenseResult } from '@vizor-vr/shared';\nimport { HTMLElementBase } from './html-element.js';\nimport { parseFormat, getFeaturesForTier } from '@vizor-vr/shared';\nimport { PlayerState } from './player-state.js';\nimport { VzEventEmitter } from './event-emitter.js';\nimport { readMetaConfig, resolveConfig } from '../config/meta-config.js';\nimport { checkLicense } from '../license/license-checker.js';\nimport { createWatermark, removeWatermark } from '../license/watermark.js';\nimport { registerIntegrityCheck, verifyFunctionIntegrity } from '../license/integrity.js';\n\n/** Attribute name to config key mapping */\nconst ATTR_MAP: Record<string, keyof PlayerConfig> = {\n format: 'format',\n title: 'title',\n author: 'author',\n 'author-href': 'authorHref',\n poster: 'poster',\n preview: 'preview',\n loop: 'loop',\n muted: 'muted',\n autoplay: 'autoplay',\n preload: 'preload',\n fps: 'fps',\n crossorigin: 'crossorigin',\n 'cors-fallback-url': 'corsFallbackUrl',\n 'cors-fallback-redirect-confirmation': 'corsFallbackRedirectConfirmation',\n 'audio-format': 'audioFormat',\n seekable: 'seekable',\n 'display-mode': 'displayMode',\n width: 'width',\n height: 'height',\n aspect: 'aspect',\n 'start-lat': 'startLat',\n 'start-lon': 'startLon',\n 'focal-length': 'focalLength',\n 'lens-angle': 'lensAngle',\n 'min-focal-length': 'minFocalLength',\n 'max-focal-length': 'maxFocalLength',\n 'license-key': 'licenseKey',\n 'license-mode': 'licenseMode',\n 'api-endpoint': 'apiEndpoint',\n 'content-id': 'contentId',\n 'collab-server': 'collabServer',\n 'collab-room': 'collabRoom',\n 'collab-role': 'collabRole',\n 'collab-user-id': 'collabUserId',\n 'collab-display-name': 'collabDisplayName',\n 'collab-password': 'collabPassword',\n 'api-key': 'apiKey',\n 'primary-color': 'primaryColor',\n 'secondary-color': 'secondaryColor',\n 'brand-name': 'brandName',\n 'brand-logo': 'brandLogo',\n 'brand-url': 'brandUrl',\n 'hide-compass': 'hideCompass',\n 'hide-vr-button': 'hideVrButton',\n 'hide-settings': 'hideSettings',\n 'hide-fullscreen': 'hideFullscreen',\n 'hide-controls': 'hideControls',\n 'hide-title': 'hideTitle',\n 'hide-brand-logo': 'hideBrandLogo',\n 'hide-watermark': 'hideWatermark',\n};\n\n/** Boolean attributes (presence = true) */\nconst BOOLEAN_ATTRS = new Set([\n 'loop', 'muted', 'autoplay', 'seekable', 'cors-fallback-redirect-confirmation',\n 'hide-compass', 'hide-vr-button', 'hide-settings', 'hide-fullscreen',\n 'hide-controls', 'hide-title', 'hide-brand-logo', 'hide-watermark',\n]);\n\n/** Numeric attributes */\nconst NUMBER_ATTRS = new Set([\n 'fps',\n 'start-lat',\n 'start-lon',\n 'lens-angle',\n 'focal-length',\n 'min-focal-length',\n 'max-focal-length',\n]);\n\n/**\n * Abstract base class for all Vizor custom elements.\n *\n * Responsibilities:\n * - Shadow DOM creation\n * - Attribute observation and reflection to config\n * - Meta tag configuration reading\n * - Responsive sizing via ResizeObserver\n * - Poster image display\n * - Event emission\n */\nexport abstract class VzBaseElement extends HTMLElementBase {\n static get observedAttributes(): string[] {\n return Object.keys(ATTR_MAP);\n }\n\n protected shadow: ShadowRoot;\n protected state: PlayerState;\n protected events: VzEventEmitter;\n protected config!: PlayerConfig;\n protected license: LicenseResult | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private metaConfig: Partial<PlayerConfig> | null = null;\n private watermarkEl: HTMLElement | null = null;\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: 'open' });\n this.state = new PlayerState();\n this.events = new VzEventEmitter(this);\n }\n\n connectedCallback(): void {\n // Read meta tags once (cached per page)\n if (!this.metaConfig) {\n this.metaConfig = readMetaConfig();\n }\n\n // Resolve full config\n this.config = this.buildConfig();\n\n // Apply sizing\n this.applySizing();\n\n // Set up resize observer\n this.resizeObserver = new ResizeObserver(() => this.onResize());\n this.resizeObserver.observe(this);\n\n // Inject base styles\n this.injectBaseStyles();\n\n // Optional async pre-connect hydration (e.g. a content-id embed fetching its\n // config from the delivery API). Returns a promise ONLY when it needs to\n // defer onConnected(); DOM-driven embeds keep the fully synchronous path.\n const pending = this.beforeConnect();\n if (pending && typeof (pending as PromiseLike<void>).then === 'function') {\n (pending as Promise<void>).then(\n () => this.finishConnect(),\n () => this.finishConnect(),\n );\n } else {\n this.finishConnect();\n }\n }\n\n /**\n * Optional async hook run after config/styles are resolved but BEFORE\n * onConnected(). Return a promise to defer onConnected() until it settles\n * (e.g. to inject fetched DOM children); return void (default) to keep the\n * connect path synchronous. Subclasses override.\n */\n protected beforeConnect(): Promise<void> | void {\n return;\n }\n\n private finishConnect(): void {\n // The element may have been removed while an async beforeConnect() ran.\n if (!this.isConnected) return;\n // Subclass initialization\n this.onConnected();\n // License check (fire-and-forget, non-blocking)\n this.checkLicenseAsync();\n }\n\n disconnectedCallback(): void {\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.events.removeAll();\n removeWatermark(this.shadow);\n this.watermarkEl = null;\n this.onDisconnected();\n }\n\n attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void {\n if (oldVal === newVal) return;\n this.config = this.buildConfig();\n this.onAttributeChanged(name, oldVal, newVal);\n }\n\n // --- Public API ---\n\n /** Get the resolved player configuration */\n getConfig(): Readonly<PlayerConfig> {\n return this.config;\n }\n\n /** Get the reactive state store */\n getState(): PlayerState {\n return this.state;\n }\n\n /** Get the current license result (null until validated) */\n getLicense(): Readonly<LicenseResult> | null {\n return this.license;\n }\n\n /** Check if a feature is enabled by the current license */\n protected hasFeature(feature: keyof FeatureFlags): boolean {\n if (!this.license) return false;\n return this.license.features[feature] === true;\n }\n\n // --- Protected hooks for subclasses ---\n\n protected abstract onConnected(): void;\n protected abstract onDisconnected(): void;\n protected abstract onAttributeChanged(\n name: string,\n oldVal: string | null,\n newVal: string | null,\n ): void;\n protected abstract onResize(): void;\n\n /** Called after license validation completes (override for post-license setup) */\n protected onLicenseResolved(): void {\n // Default no-op — subclasses can override\n }\n\n // --- Private helpers ---\n\n /** Async license validation (non-blocking) */\n private async checkLicenseAsync(): Promise<void> {\n try {\n // Register integrity checks on first run\n await registerIntegrityCheck('checkLicense', checkLicense);\n\n this.license = await checkLicense({\n apiKey: this.config.apiKey,\n licenseKey: this.config.licenseKey,\n licenseMode: this.config.licenseMode,\n apiEndpoint: this.config.apiEndpoint,\n });\n\n // Verify integrity of critical license functions\n const integrityFail = await verifyFunctionIntegrity('checkLicense', checkLicense);\n if (integrityFail) {\n this.license = integrityFail;\n }\n\n // Create watermark if needed\n // Only use custom brand logo if the license allows it\n const logoUrl = this.license.features.brandLogoColors\n ? this.config.brandLogo ?? this.config.watermarkLogo\n : this.config.watermarkLogo;\n this.watermarkEl = createWatermark(this.shadow, this.license, {\n logoUrl,\n linkUrl: this.config.brandUrl,\n });\n\n // Notify subclass that license has resolved (for theme gating etc.)\n this.onLicenseResolved();\n\n // Emit license event\n this.events.emit('vz-license', {\n valid: this.license.valid,\n tier: this.license.tier,\n message: this.license.message,\n });\n } catch (err) {\n console.warn('[Vizor] License check failed:', err);\n this.license = {\n valid: false,\n tier: 'free',\n features: getFeaturesForTier('free'),\n message: 'License check error',\n };\n this.watermarkEl = createWatermark(this.shadow, this.license, {\n logoUrl: this.config.watermarkLogo,\n linkUrl: this.config.brandUrl,\n });\n this.onLicenseResolved();\n this.events.emit('vz-license', {\n valid: false,\n tier: 'free',\n message: 'License check error',\n });\n }\n }\n\n /** Build config from defaults + meta tags + element attributes */\n private buildConfig(): PlayerConfig {\n const attrs: Partial<PlayerConfig> = {};\n\n for (const [attrName, configKey] of Object.entries(ATTR_MAP)) {\n const rawVal = this.getAttribute(attrName);\n if (rawVal === null && !BOOLEAN_ATTRS.has(attrName)) continue;\n\n if (BOOLEAN_ATTRS.has(attrName)) {\n (attrs as Record<string, unknown>)[configKey] = this.hasAttribute(attrName);\n } else if (NUMBER_ATTRS.has(attrName)) {\n const num = parseFloat(rawVal!);\n if (!isNaN(num)) {\n (attrs as Record<string, unknown>)[configKey] = num;\n }\n } else if (configKey === 'format') {\n const fmt = parseFormat(rawVal!);\n if (fmt) attrs.format = fmt;\n } else if (configKey === 'preload') {\n if (['auto', 'metadata', 'none'].includes(rawVal!)) {\n attrs.preload = rawVal as 'auto' | 'metadata' | 'none';\n }\n } else if (configKey === 'audioFormat') {\n if (rawVal === 'ambix') attrs.audioFormat = 'ambix';\n } else if (configKey === 'collabRole') {\n if (rawVal === 'host' || rawVal === 'viewer') {\n attrs.collabRole = rawVal;\n }\n } else {\n (attrs as Record<string, unknown>)[configKey] = rawVal;\n }\n }\n\n return resolveConfig(attrs, this.metaConfig ?? undefined);\n }\n\n /** Apply width/height/aspect styling to the host element */\n private applySizing(): void {\n if (!this.style.display || this.style.display === '') {\n this.style.display = 'block';\n }\n\n if (this.config.width) {\n this.style.width = this.config.width.includes('%') || this.config.width.includes('px')\n ? this.config.width\n : `${this.config.width}px`;\n } else if (!this.style.width) {\n this.style.width = '100%';\n }\n\n if (this.config.height) {\n this.style.height = this.config.height.includes('%') || this.config.height.includes('px')\n ? this.config.height\n : `${this.config.height}px`;\n } else if (this.config.aspect) {\n this.style.aspectRatio = this.config.aspect.replace(':', '/');\n } else if (!this.style.height) {\n this.style.aspectRatio = '16/9';\n }\n\n this.style.position = 'relative';\n this.style.overflow = 'hidden';\n this.style.backgroundColor = '#000';\n }\n\n /** Inject minimal base styles into shadow DOM */\n private injectBaseStyles(): void {\n const style = document.createElement('style');\n style.textContent = `\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n background: #000;\n user-select: none;\n -webkit-user-select: none;\n touch-action: none;\n }\n :host([hidden]) {\n display: none;\n }\n .vz-container {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n canvas {\n display: block;\n width: 100%;\n height: 100%;\n }\n .vz-poster {\n position: absolute;\n inset: 0;\n background-size: cover;\n background-position: center;\n z-index: 1;\n cursor: pointer;\n }\n .vz-poster.hidden {\n display: none;\n }\n `;\n this.shadow.appendChild(style);\n }\n}\n","import {\n SphereGeometry,\n MeshBasicMaterial,\n Mesh,\n} from 'three';\nimport type { PerspectiveCamera, Texture } from 'three';\nimport type { StereoMode } from '@vizor-vr/shared';\nimport { SPHERE_DEFAULTS, CAMERA_DEFAULTS } from '@vizor-vr/shared';\nimport type { IProjection } from './projection.js';\n\n/**\n * Equirectangular projection for 360 and 180 content.\n * Creates an inverted sphere with the texture mapped on the inside.\n * For stereo content, creates two spheres on different Three.js layers\n * with UV remapping for side-by-side or top-bottom layouts.\n */\nexport class EquirectProjection implements IProjection {\n private meshes: Mesh[] = [];\n private fovH: number;\n\n constructor(fovH: number = 360) {\n this.fovH = fovH;\n }\n\n createMeshes(texture: Texture, stereoMode: StereoMode): Mesh[] {\n this.dispose();\n\n if (stereoMode === 'mono') {\n const mesh = this.createSphere(texture);\n mesh.layers.set(0);\n this.meshes = [mesh];\n return this.meshes;\n }\n\n // Stereo: create two spheres on layers 1 (left) and 2 (right)\n const meshL = this.createSphere(texture);\n const meshR = this.createSphere(texture);\n\n this.remapUVs(meshL.geometry as SphereGeometry, stereoMode, 'left');\n this.remapUVs(meshR.geometry as SphereGeometry, stereoMode, 'right');\n\n meshL.layers.set(1);\n meshR.layers.set(2);\n\n this.meshes = [meshL, meshR];\n return this.meshes;\n }\n\n configureCamera(camera: PerspectiveCamera): void {\n camera.fov = CAMERA_DEFAULTS.fov;\n camera.near = CAMERA_DEFAULTS.near;\n camera.far = CAMERA_DEFAULTS.far;\n camera.position.set(0, 0, 0);\n camera.updateProjectionMatrix();\n }\n\n update(_camera: PerspectiveCamera, _time: number): void {\n // No per-frame updates needed for equirectangular\n }\n\n dispose(): void {\n for (const mesh of this.meshes) {\n mesh.geometry.dispose();\n (mesh.material as MeshBasicMaterial).dispose();\n }\n this.meshes = [];\n }\n\n private createSphere(texture: Texture): Mesh {\n const { radius, widthSegments, heightSegments } = SPHERE_DEFAULTS;\n\n // For 180 content, only create a half-sphere\n const phiLength = (this.fovH / 360) * Math.PI * 2;\n const phiStart = this.fovH === 180 ? Math.PI / 2 : 0;\n\n const geometry = new SphereGeometry(\n radius,\n widthSegments,\n heightSegments,\n phiStart,\n phiLength,\n );\n\n // Invert the sphere so texture renders on the inside\n geometry.scale(-1, 1, 1);\n\n const material = new MeshBasicMaterial({\n map: texture,\n });\n\n return new Mesh(geometry, material);\n }\n\n /**\n * Remap UV coordinates for stereo content.\n * SBS: left eye uses left half (u: 0-0.5), right eye uses right half (u: 0.5-1)\n * TB: left eye uses top half (v: 0.5-1), right eye uses bottom half (v: 0-0.5)\n */\n private remapUVs(\n geometry: SphereGeometry,\n stereoMode: StereoMode,\n eye: 'left' | 'right',\n ): void {\n const uvAttr = geometry.attributes.uv!;\n\n for (let i = 0; i < uvAttr.count; i++) {\n let u = uvAttr.getX(i);\n let v = uvAttr.getY(i);\n\n if (stereoMode === 'sbs') {\n // Side-by-side: compress U to half, offset for right eye\n u = eye === 'left' ? u * 0.5 : u * 0.5 + 0.5;\n } else if (stereoMode === 'tb') {\n // Top-bottom: compress V to half, offset for bottom (right) eye\n v = eye === 'left' ? v * 0.5 + 0.5 : v * 0.5;\n }\n\n uvAttr.setXY(i, u, v);\n }\n\n uvAttr.needsUpdate = true;\n }\n}\n","import { PlaneGeometry, MeshBasicMaterial, Mesh, MathUtils } from 'three';\nimport type { PerspectiveCamera, Texture } from 'three';\nimport type { StereoMode } from '@vizor-vr/shared';\nimport type { IProjection } from './projection.js';\n\n/** Distance of the camera from the plane (z-axis). */\nconst CAM_Z = 3;\n\n/**\n * Flat projection for standard 2D and 3D (SBS/TB) video.\n *\n * Behaves like `object-fit: contain` — the plane preserves the video's\n * native aspect ratio and scales to fit as large as possible within the\n * viewport. Letterbox / pillarbox bars appear only when the video and\n * container aspect ratios differ.\n */\nexport class FlatProjection implements IProjection {\n private meshes: Mesh[] = [];\n /** Video aspect ratio (accounts for stereo layout). */\n private videoAspect = 16 / 9;\n private stereoMode: StereoMode = 'mono';\n\n createMeshes(texture: Texture, stereoMode: StereoMode): Mesh[] {\n this.dispose();\n this.stereoMode = stereoMode;\n\n // Determine video aspect ratio from texture (accounting for stereo layout)\n let aspect = texture.image\n ? (texture.image as HTMLVideoElement).videoWidth /\n (texture.image as HTMLVideoElement).videoHeight\n : 16 / 9;\n\n // For SBS stereo, each eye sees half the width\n if (stereoMode === 'sbs') aspect /= 2;\n // For TB stereo, each eye sees half the height\n if (stereoMode === 'tb') aspect *= 2;\n\n this.videoAspect = aspect || 16 / 9;\n\n // Start with a 1×1 unit plane — real size is set each frame in update()\n if (stereoMode === 'mono') {\n const mesh = this.createPlane(texture, 1, 1);\n mesh.layers.set(0);\n this.meshes = [mesh];\n return this.meshes;\n }\n\n const meshL = this.createPlane(texture, 1, 1);\n const meshR = this.createPlane(texture, 1, 1);\n\n this.remapUVs(meshL, stereoMode, 'left');\n this.remapUVs(meshR, stereoMode, 'right');\n\n meshL.layers.set(1);\n meshR.layers.set(2);\n\n this.meshes = [meshL, meshR];\n return this.meshes;\n }\n\n configureCamera(camera: PerspectiveCamera): void {\n camera.fov = 60;\n camera.near = 0.1;\n camera.far = 100;\n camera.position.set(0, 0, CAM_Z);\n camera.lookAt(0, 0, 0);\n camera.updateProjectionMatrix();\n }\n\n /**\n * Scale the plane every frame to fit inside the camera frustum\n * while preserving the video's native aspect ratio (`object-fit: contain`).\n *\n * Also re-reads the video dimensions so the aspect ratio updates if\n * the source changes (e.g. adaptive streaming resolution switch).\n */\n update(camera: PerspectiveCamera, _time: number): void {\n if (this.meshes.length === 0) return;\n\n // Re-read video dimensions in case the source resolution changed\n const tex = (this.meshes[0]!.material as MeshBasicMaterial).map;\n if (tex?.image) {\n const img = tex.image as HTMLVideoElement;\n if (img.videoWidth && img.videoHeight) {\n let a = img.videoWidth / img.videoHeight;\n if (this.stereoMode === 'sbs') a /= 2;\n if (this.stereoMode === 'tb') a *= 2;\n this.videoAspect = a;\n }\n }\n\n // Visible rectangle at z = 0 given current FOV and aspect\n const vFov = MathUtils.degToRad(camera.fov);\n const visH = 2 * CAM_Z * Math.tan(vFov / 2);\n const visW = visH * camera.aspect;\n\n // object-fit: contain — fit inside viewport preserving video aspect\n let planeW: number;\n let planeH: number;\n\n if (this.videoAspect > camera.aspect) {\n // Video is wider than viewport → fit to width, letterbox top/bottom\n planeW = visW;\n planeH = visW / this.videoAspect;\n } else {\n // Video is taller than viewport → fit to height, pillarbox left/right\n planeH = visH;\n planeW = visH * this.videoAspect;\n }\n\n for (const mesh of this.meshes) {\n mesh.scale.set(planeW, planeH, 1);\n }\n }\n\n dispose(): void {\n for (const mesh of this.meshes) {\n mesh.geometry.dispose();\n (mesh.material as MeshBasicMaterial).dispose();\n }\n this.meshes = [];\n }\n\n private createPlane(texture: Texture, width: number, height: number): Mesh {\n const geometry = new PlaneGeometry(width, height);\n const material = new MeshBasicMaterial({ map: texture });\n const mesh = new Mesh(geometry, material);\n mesh.position.set(0, 0, 0);\n return mesh;\n }\n\n private remapUVs(mesh: Mesh, stereoMode: StereoMode, eye: 'left' | 'right'): void {\n const geometry = mesh.geometry as PlaneGeometry;\n const uvAttr = geometry.attributes.uv!;\n\n for (let i = 0; i < uvAttr.count; i++) {\n let u = uvAttr.getX(i);\n let v = uvAttr.getY(i);\n\n if (stereoMode === 'sbs') {\n u = eye === 'left' ? u * 0.5 : u * 0.5 + 0.5;\n } else if (stereoMode === 'tb') {\n v = eye === 'left' ? v * 0.5 + 0.5 : v * 0.5;\n }\n\n uvAttr.setXY(i, u, v);\n }\n\n uvAttr.needsUpdate = true;\n }\n}\n","import {\n BoxGeometry,\n MeshBasicMaterial,\n Mesh,\n} from 'three';\nimport type { PerspectiveCamera, Texture, BufferAttribute } from 'three';\nimport type { StereoMode } from '@vizor-vr/shared';\nimport { CAMERA_DEFAULTS } from '@vizor-vr/shared';\nimport type { IProjection } from './projection.js';\n\n/**\n * Standard 3x2 cubemap atlas layout.\n *\n * Three.js BoxGeometry face order (by group index):\n * 0: +X 1: -X 2: +Y 3: -Y 4: +Z 5: -Z\n *\n * Atlas tile positions (col, row) for each face:\n * Row 0 (top): +X(0,0) -X(1,0) +Y(2,0)\n * Row 1 (bottom): -Y(0,1) +Z(1,1) -Z(2,1)\n */\nconst FACE_MAP: ReadonlyArray<{ col: number; row: number }> = [\n { col: 0, row: 0 }, // +X\n { col: 1, row: 0 }, // -X\n { col: 2, row: 0 }, // +Y\n { col: 0, row: 1 }, // -Y\n { col: 1, row: 1 }, // +Z\n { col: 2, row: 1 }, // -Z\n];\n\nconst COLS = 3;\nconst ROWS = 2;\n\n/**\n * Cubemap projection for standard 3x2 atlas cubemap content.\n * Creates an inverted box with each face's UVs remapped to the\n * corresponding tile in the atlas texture.\n * For stereo content, creates two boxes on different Three.js layers\n * with UV remapping for side-by-side or top-bottom layouts.\n */\nexport class CubemapProjection implements IProjection {\n private meshes: Mesh[] = [];\n\n createMeshes(texture: Texture, stereoMode: StereoMode): Mesh[] {\n this.dispose();\n\n if (stereoMode === 'mono') {\n const mesh = this.createBox(texture);\n this.remapFaceUVs(mesh.geometry as BoxGeometry, 'mono', 'left');\n mesh.layers.set(0);\n this.meshes = [mesh];\n return this.meshes;\n }\n\n // Stereo: create two boxes on layers 1 (left) and 2 (right)\n const meshL = this.createBox(texture);\n const meshR = this.createBox(texture);\n\n const effectiveMode = stereoMode === 'separate' ? 'tb' : stereoMode;\n\n this.remapFaceUVs(meshL.geometry as BoxGeometry, effectiveMode, 'left');\n this.remapFaceUVs(meshR.geometry as BoxGeometry, effectiveMode, 'right');\n\n meshL.layers.set(1);\n meshR.layers.set(2);\n\n this.meshes = [meshL, meshR];\n return this.meshes;\n }\n\n configureCamera(camera: PerspectiveCamera): void {\n camera.fov = CAMERA_DEFAULTS.fov;\n camera.near = CAMERA_DEFAULTS.near;\n camera.far = CAMERA_DEFAULTS.far;\n camera.position.set(0, 0, 0);\n camera.updateProjectionMatrix();\n }\n\n update(_camera: PerspectiveCamera, _time: number): void {\n // No per-frame updates needed for cubemap\n }\n\n dispose(): void {\n for (const mesh of this.meshes) {\n mesh.geometry.dispose();\n (mesh.material as MeshBasicMaterial).dispose();\n }\n this.meshes = [];\n }\n\n private createBox(texture: Texture): Mesh {\n const geometry = new BoxGeometry(1, 1, 1);\n\n // Invert the box so texture renders on the inside\n geometry.scale(-1, 1, 1);\n\n const material = new MeshBasicMaterial({\n map: texture,\n });\n\n return new Mesh(geometry, material);\n }\n\n /**\n * Remap UV coordinates for cubemap atlas layout, with optional stereo offset.\n *\n * BoxGeometry has 6 faces, each with 4 vertices (indices faceIndex*4 .. faceIndex*4+3).\n * Default UVs for each face span [0,1] in both U and V.\n * We remap each face to its tile in the 3x2 atlas, then apply stereo compression\n * if needed.\n *\n * For TB stereo the entire atlas occupies either the top or bottom half of the\n * texture, so V coordinates are compressed to half and offset accordingly.\n * For SBS stereo the atlas occupies either the left or right half, so U\n * coordinates are compressed to half and offset accordingly.\n */\n private remapFaceUVs(\n geometry: BoxGeometry,\n stereoMode: StereoMode,\n eye: 'left' | 'right',\n ): void {\n const uvAttr = geometry.attributes.uv as BufferAttribute;\n\n for (let face = 0; face < 6; face++) {\n const { col, row } = FACE_MAP[face]!;\n\n // Base tile region in the atlas (full texture, no stereo offset)\n let uMin = col / COLS;\n let uMax = (col + 1) / COLS;\n // Row 0 is the top of the image (v: 0.5 to 1.0), row 1 is bottom (v: 0 to 0.5)\n let vMin = 1 - (row + 1) / ROWS;\n let vMax = 1 - row / ROWS;\n\n // Apply stereo compression\n if (stereoMode === 'tb') {\n // Top-bottom: compress V range to half, offset for eye\n const vRange = vMax - vMin;\n const halfRange = vRange * 0.5;\n if (eye === 'left') {\n // Left eye uses top half of texture: shift V up\n vMin = vMin * 0.5 + 0.5;\n vMax = vMin + halfRange;\n } else {\n // Right eye uses bottom half of texture\n vMin = vMin * 0.5;\n vMax = vMin + halfRange;\n }\n } else if (stereoMode === 'sbs') {\n // Side-by-side: compress U range to half, offset for eye\n const uRange = uMax - uMin;\n const halfRange = uRange * 0.5;\n if (eye === 'left') {\n // Left eye uses left half of texture\n uMin = uMin * 0.5;\n uMax = uMin + halfRange;\n } else {\n // Right eye uses right half of texture: shift U right\n uMin = uMin * 0.5 + 0.5;\n uMax = uMin + halfRange;\n }\n }\n\n // Each face has 4 vertices starting at face * 4\n const baseIndex = face * 4;\n for (let v = 0; v < 4; v++) {\n const idx = baseIndex + v;\n // Map the default [0,1] UVs to the tile region\n const defaultU = uvAttr.getX(idx);\n const defaultV = uvAttr.getY(idx);\n\n const mappedU = uMin + defaultU * (uMax - uMin);\n const mappedV = vMin + defaultV * (vMax - vMin);\n\n uvAttr.setXY(idx, mappedU, mappedV);\n }\n }\n\n uvAttr.needsUpdate = true;\n }\n}\n","/** EAC vertex shader — passes the face-local UV and the face's atlas tile region. */\nexport const eacVertexShader = `\nattribute vec4 faceRegion;\nvarying vec2 vUv;\nvarying vec4 vFaceRegion;\n\nvoid main() {\n vUv = uv;\n vFaceRegion = faceRegion;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\n/**\n * EAC fragment shader. The equi-angular (tan) correction must be applied in each\n * cube face's LOCAL 0..1 space, not in atlas space — so `vUv` is the face-local\n * coordinate and `vFaceRegion` (xy = tile origin, zw = tile size) maps the\n * corrected coordinate into the face's tile in the 3x2 atlas. The face edges\n * (uv 0 and 1) map to themselves and the centre stays centred, so face\n * orientation is preserved while interior samples are redistributed correctly.\n */\nexport const eacFragmentShader = `\nuniform sampler2D map;\nvarying vec2 vUv;\nvarying vec4 vFaceRegion;\n\nconst float PI = 3.14159265359;\n\nvoid main() {\n // Equi-angular correction in face-local space: 0..1 -> -1..1 -> tan -> 0..1.\n vec2 ndc = vUv * 2.0 - 1.0;\n ndc = tan(ndc * PI / 4.0);\n vec2 faceUv = clamp(ndc * 0.5 + 0.5, 0.0, 1.0);\n\n // Map the corrected face-local UV into this face's atlas tile.\n vec2 atlasUv = vFaceRegion.xy + faceUv * vFaceRegion.zw;\n\n gl_FragColor = texture2D(map, atlasUv);\n}\n`;\n","import {\n BoxGeometry,\n ShaderMaterial,\n Mesh,\n FrontSide,\n BufferAttribute,\n} from 'three';\nimport type { PerspectiveCamera, Texture } from 'three';\nimport type { StereoMode } from '@vizor-vr/shared';\nimport { CAMERA_DEFAULTS } from '@vizor-vr/shared';\nimport type { IProjection } from './projection.js';\nimport { eacVertexShader, eacFragmentShader } from '../shaders/eac.glsl.js';\n\n/**\n * Standard 3x2 cubemap atlas layout.\n *\n * Three.js BoxGeometry face order (by group index):\n * 0: +X 1: -X 2: +Y 3: -Y 4: +Z 5: -Z\n *\n * Atlas tile positions (col, row) for each face:\n * Row 0 (top): +X(0,0) -X(1,0) +Y(2,0)\n * Row 1 (bottom): -Y(0,1) +Z(1,1) -Z(2,1)\n */\nconst FACE_MAP: ReadonlyArray<{ col: number; row: number }> = [\n { col: 0, row: 0 }, // +X\n { col: 1, row: 0 }, // -X\n { col: 2, row: 0 }, // +Y\n { col: 0, row: 1 }, // -Y\n { col: 1, row: 1 }, // +Z\n { col: 2, row: 1 }, // -Z\n];\n\nconst COLS = 3;\nconst ROWS = 2;\n\n/**\n * Equi-Angular Cubemap (EAC) projection for 3x2 atlas cubemap content.\n * Uses the same box geometry and UV atlas layout as CubemapProjection,\n * but applies a ShaderMaterial with tan() angular correction in the\n * fragment shader to correct for the equi-angular sampling distortion.\n *\n * For stereo content, creates two boxes on different Three.js layers\n * with UV remapping for side-by-side or top-bottom layouts.\n */\nexport class EacProjection implements IProjection {\n private meshes: Mesh[] = [];\n\n createMeshes(texture: Texture, stereoMode: StereoMode): Mesh[] {\n this.dispose();\n\n if (stereoMode === 'mono') {\n const mesh = this.createBox(texture);\n this.setupFaceAtlas(mesh.geometry as BoxGeometry, 'mono', 'left');\n mesh.layers.set(0);\n this.meshes = [mesh];\n return this.meshes;\n }\n\n // Stereo: create two boxes on layers 1 (left) and 2 (right)\n const meshL = this.createBox(texture);\n const meshR = this.createBox(texture);\n\n const effectiveMode = stereoMode === 'separate' ? 'tb' : stereoMode;\n\n this.setupFaceAtlas(meshL.geometry as BoxGeometry, effectiveMode, 'left');\n this.setupFaceAtlas(meshR.geometry as BoxGeometry, effectiveMode, 'right');\n\n meshL.layers.set(1);\n meshR.layers.set(2);\n\n this.meshes = [meshL, meshR];\n return this.meshes;\n }\n\n configureCamera(camera: PerspectiveCamera): void {\n camera.fov = CAMERA_DEFAULTS.fov;\n camera.near = CAMERA_DEFAULTS.near;\n camera.far = CAMERA_DEFAULTS.far;\n camera.position.set(0, 0, 0);\n camera.updateProjectionMatrix();\n }\n\n update(_camera: PerspectiveCamera, _time: number): void {\n // No per-frame updates needed for EAC\n }\n\n dispose(): void {\n for (const mesh of this.meshes) {\n mesh.geometry.dispose();\n (mesh.material as ShaderMaterial).dispose();\n }\n this.meshes = [];\n }\n\n private createBox(texture: Texture): Mesh {\n const geometry = new BoxGeometry(1, 1, 1);\n\n // Invert the box so texture renders on the inside\n geometry.scale(-1, 1, 1);\n\n const material = new ShaderMaterial({\n uniforms: { map: { value: texture } },\n vertexShader: eacVertexShader,\n fragmentShader: eacFragmentShader,\n side: FrontSide,\n });\n\n return new Mesh(geometry, material);\n }\n\n /**\n * Provide each face's atlas tile as a per-vertex `faceRegion` attribute\n * (xy = tile origin, zw = tile size), leaving geometry.uv as the default\n * face-local [0,1] coordinates. The fragment shader applies the equi-angular\n * correction in that face-local space and then maps the result into the tile\n * — applying the correction in atlas space (the previous approach) is wrong\n * because the tan() distortion is per-face.\n *\n * BoxGeometry has 6 faces, each with 4 vertices (indices face*4 .. face*4+3).\n * For TB stereo the atlas occupies the top or bottom half of the texture (V\n * compressed/offset); for SBS, the left or right half (U compressed/offset).\n */\n private setupFaceAtlas(\n geometry: BoxGeometry,\n stereoMode: StereoMode,\n eye: 'left' | 'right',\n ): void {\n const vertexCount = geometry.attributes.uv!.count;\n const regions = new Float32Array(vertexCount * 4);\n\n for (let face = 0; face < 6; face++) {\n const { col, row } = FACE_MAP[face]!;\n\n // Base tile region in the atlas (full texture, no stereo offset).\n let uMin = col / COLS;\n let uMax = (col + 1) / COLS;\n // Row 0 is the top of the image (v: 0.5 to 1.0), row 1 is bottom (v: 0 to 0.5).\n let vMin = 1 - (row + 1) / ROWS;\n let vMax = 1 - row / ROWS;\n\n if (stereoMode === 'tb') {\n const halfRange = (vMax - vMin) * 0.5;\n if (eye === 'left') {\n vMin = vMin * 0.5 + 0.5; // top half of the texture\n vMax = vMin + halfRange;\n } else {\n vMin = vMin * 0.5; // bottom half\n vMax = vMin + halfRange;\n }\n } else if (stereoMode === 'sbs') {\n const halfRange = (uMax - uMin) * 0.5;\n if (eye === 'left') {\n uMin = uMin * 0.5; // left half of the texture\n uMax = uMin + halfRange;\n } else {\n uMin = uMin * 0.5 + 0.5; // right half\n uMax = uMin + halfRange;\n }\n }\n\n const baseIndex = face * 4;\n for (let v = 0; v < 4; v++) {\n const idx = (baseIndex + v) * 4;\n regions[idx] = uMin;\n regions[idx + 1] = vMin;\n regions[idx + 2] = uMax - uMin;\n regions[idx + 3] = vMax - vMin;\n }\n }\n\n geometry.setAttribute('faceRegion', new BufferAttribute(regions, 4));\n }\n}\n","/**\n * GLSL shaders for dual fisheye projection.\n *\n * A dual fisheye texture contains two circular fisheye images side by side:\n * - Left circle: front hemisphere (z >= 0)\n * - Right circle: back hemisphere (z < 0)\n *\n * For stereo content (SBS or TB), the entire dual-fisheye pair is packed\n * into a sub-region of the texture. Uniforms uScaleX/uScaleY/uOffsetX/uOffsetY\n * select the appropriate region per eye.\n */\n\n/** Fisheye vertex shader -- passes world position to fragment shader */\nexport const fisheyeVertexShader = /* glsl */ `\nvarying vec3 vWorldPosition;\n\nvoid main() {\n vec4 worldPos = modelMatrix * vec4(position, 1.0);\n vWorldPosition = worldPos.xyz;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`;\n\n/**\n * Dual fisheye fragment shader -- maps sphere surface to circular fisheye UV.\n *\n * Stereo uniforms allow selecting a sub-region of the texture:\n * - uScaleX / uScaleY: scale factor applied to the final UV (1.0 = full, 0.5 = half)\n * - uOffsetX / uOffsetY: offset added after scaling (0.0 or 0.5 for stereo halves)\n */\nexport const fisheyeFragmentShader = /* glsl */ `\nuniform sampler2D map;\nuniform float uScaleX;\nuniform float uScaleY;\nuniform float uOffsetX;\nuniform float uOffsetY;\nuniform float uLensFov; // capture-lens angle in RADIANS (WS-F, 120-250 deg)\n\nvarying vec3 vWorldPosition;\n\nconst float PI = 3.14159265359;\n\nvoid main() {\n vec3 dir = normalize(vWorldPosition);\n\n // Determine which hemisphere: front (z >= 0) or back (z < 0)\n bool isFront = dir.z >= 0.0;\n float faceSign = isFront ? 1.0 : -1.0;\n\n // Polar angle from the hemisphere pole (0 at pole, PI/2 at equator)\n float theta = acos(clamp(abs(dir.z), -1.0, 1.0));\n\n // Azimuthal angle around the hemisphere pole\n float phi = atan(dir.y, dir.x * faceSign);\n\n // Map to circular fisheye UV using equidistant projection.\n // r = 1 at the lens edge (uLensFov/2 from the pole). For lenses wider than\n // 180 deg the hemisphere uses only the inner part of the circle; for\n // narrower lenses r can exceed 1 — clamp to the edge (looks better than\n // holes; decided by visual test).\n float r = min(theta / (uLensFov * 0.5), 1.0);\n\n // Convert polar (r, phi) to cartesian circle coordinates, centered at (0.5, 0.5)\n vec2 circleUV = vec2(r * cos(phi), r * sin(phi)) * 0.5 + 0.5;\n\n // Place front hemisphere in left half of the dual-fisheye region,\n // back hemisphere in right half\n float xOffset = isFront ? 0.0 : 0.5;\n vec2 uv = vec2(circleUV.x * 0.5 + xOffset, circleUV.y);\n\n // Apply stereo region selection\n uv = vec2(uv.x * uScaleX + uOffsetX, uv.y * uScaleY + uOffsetY);\n\n gl_FragColor = texture2D(map, uv);\n}\n`;\n\n/**\n * Single-fisheye fragment shader (WS-F \"spherical\" / true VR180).\n *\n * Unlike the dual-fisheye shader there is NO hemisphere split: the texture\n * (or per-eye sub-region for stereo) holds ONE circular fisheye image whose\n * optical axis is the forward (+z after sphere inversion) direction, covering\n * uLensFov degrees. Directions beyond the lens edge clamp to it.\n */\nexport const sphericalFragmentShader = /* glsl */ `\nuniform sampler2D map;\nuniform float uScaleX;\nuniform float uScaleY;\nuniform float uOffsetX;\nuniform float uOffsetY;\nuniform float uLensFov; // capture-lens angle in RADIANS\n\nvarying vec3 vWorldPosition;\n\nvoid main() {\n vec3 dir = normalize(vWorldPosition);\n\n // Angle from the forward optical axis (0 straight ahead). The camera\n // controller's default view (lat 0, lon 0) looks along +x, so that is the\n // content center for VR180 footage.\n float theta = acos(clamp(dir.x, -1.0, 1.0));\n\n // Azimuth around the axis: +z is screen-right at the default view, +y up.\n float phi = atan(dir.y, dir.z);\n\n // Equidistant mapping; clamp beyond the lens edge.\n float r = min(theta / (uLensFov * 0.5), 1.0);\n\n vec2 circleUV = vec2(r * cos(phi), r * sin(phi)) * 0.5 + 0.5;\n\n // Stereo region selection (full texture for mono).\n vec2 uv = vec2(circleUV.x * uScaleX + uOffsetX, circleUV.y * uScaleY + uOffsetY);\n\n gl_FragColor = texture2D(map, uv);\n}\n`;\n","import {\n SphereGeometry,\n ShaderMaterial,\n Mesh,\n FrontSide,\n} from 'three';\nimport type { PerspectiveCamera, Texture } from 'three';\nimport type { StereoMode } from '@vizor-vr/shared';\nimport { SPHERE_DEFAULTS, CAMERA_DEFAULTS } from '@vizor-vr/shared';\nimport type { IProjection } from './projection.js';\nimport { fisheyeVertexShader, fisheyeFragmentShader } from '../shaders/fisheye.glsl.js';\n\n/**\n * Stereo uniform values for each eye / stereo mode combination.\n *\n * For mono: the shader sees the full texture (scale 1, offset 0).\n * For SBS: each eye's dual-fisheye pair occupies a horizontal half.\n * For TB: each eye's dual-fisheye pair occupies a vertical half.\n */\ninterface StereoUniforms {\n uScaleX: number;\n uScaleY: number;\n uOffsetX: number;\n uOffsetY: number;\n}\n\nfunction stereoUniforms(\n stereoMode: StereoMode,\n eye: 'left' | 'right',\n): StereoUniforms {\n switch (stereoMode) {\n case 'sbs':\n return {\n uScaleX: 0.5,\n uScaleY: 1.0,\n uOffsetX: eye === 'left' ? 0.0 : 0.5,\n uOffsetY: 0.0,\n };\n case 'tb':\n return {\n uScaleX: 1.0,\n uScaleY: 0.5,\n uOffsetX: 0.0,\n // TB: left eye in top half (offset 0.5), right eye in bottom half (offset 0.0)\n uOffsetY: eye === 'left' ? 0.5 : 0.0,\n };\n case 'mono':\n default:\n return {\n uScaleX: 1.0,\n uScaleY: 1.0,\n uOffsetX: 0.0,\n uOffsetY: 0.0,\n };\n }\n}\n\n/**\n * Dual fisheye projection for 360 content captured with dual-fisheye cameras\n * (e.g. Ricoh Theta, Insta360).\n *\n * The texture contains two circular fisheye images side by side:\n * - Left circle: front hemisphere (z >= 0)\n * - Right circle: back hemisphere (z < 0)\n *\n * Uses a full inverted SphereGeometry (like equirectangular) but with a\n * ShaderMaterial that computes fisheye UV mapping from world-space direction\n * vectors rather than relying on geometry UVs.\n *\n * For stereo content, two spheres are created on separate Three.js layers\n * with shader uniforms selecting the appropriate texture sub-region per eye.\n */\n/** Clamp a capture-lens angle to the supported 120-250 deg range (default 180). */\nexport function clampLensAngle(deg: number | undefined | null): number {\n if (deg == null || Number.isNaN(deg)) return 180;\n return Math.min(250, Math.max(120, deg));\n}\n\nexport class FisheyeProjection implements IProjection {\n private meshes: Mesh[] = [];\n private readonly lensFovRad: number;\n\n constructor(lensAngleDeg?: number) {\n this.lensFovRad = (clampLensAngle(lensAngleDeg) * Math.PI) / 180;\n }\n\n createMeshes(texture: Texture, stereoMode: StereoMode): Mesh[] {\n this.dispose();\n\n if (stereoMode === 'mono') {\n const mesh = this.createSphere(texture, 'mono', 'left');\n mesh.layers.set(0);\n this.meshes = [mesh];\n return this.meshes;\n }\n\n // Stereo: create two spheres on layers 1 (left eye) and 2 (right eye)\n const effectiveMode = stereoMode === 'separate' ? 'tb' : stereoMode;\n\n const meshL = this.createSphere(texture, effectiveMode, 'left');\n const meshR = this.createSphere(texture, effectiveMode, 'right');\n\n meshL.layers.set(1);\n meshR.layers.set(2);\n\n this.meshes = [meshL, meshR];\n return this.meshes;\n }\n\n configureCamera(camera: PerspectiveCamera): void {\n camera.fov = CAMERA_DEFAULTS.fov;\n camera.near = CAMERA_DEFAULTS.near;\n camera.far = CAMERA_DEFAULTS.far;\n camera.position.set(0, 0, 0);\n camera.updateProjectionMatrix();\n }\n\n update(_camera: PerspectiveCamera, _time: number): void {\n // No per-frame updates needed for dual fisheye\n }\n\n dispose(): void {\n for (const mesh of this.meshes) {\n mesh.geometry.dispose();\n (mesh.material as ShaderMaterial).dispose();\n }\n this.meshes = [];\n }\n\n private createSphere(\n texture: Texture,\n stereoMode: StereoMode,\n eye: 'left' | 'right',\n ): Mesh {\n const { radius, widthSegments, heightSegments } = SPHERE_DEFAULTS;\n\n const geometry = new SphereGeometry(radius, widthSegments, heightSegments);\n\n // Invert the sphere so texture renders on the inside.\n // With scale(-1,1,1) the winding order flips, so use FrontSide (not BackSide).\n geometry.scale(-1, 1, 1);\n\n const uniforms = stereoUniforms(stereoMode, eye);\n\n const material = new ShaderMaterial({\n uniforms: {\n map: { value: texture },\n uScaleX: { value: uniforms.uScaleX },\n uScaleY: { value: uniforms.uScaleY },\n uOffsetX: { value: uniforms.uOffsetX },\n uOffsetY: { value: uniforms.uOffsetY },\n uLensFov: { value: this.lensFovRad },\n },\n vertexShader: fisheyeVertexShader,\n fragmentShader: fisheyeFragmentShader,\n side: FrontSide,\n });\n\n return new Mesh(geometry, material);\n }\n}\n","import { SphereGeometry, ShaderMaterial, Mesh, FrontSide } from 'three';\nimport type { PerspectiveCamera, Texture } from 'three';\nimport type { StereoMode } from '@vizor-vr/shared';\nimport { SPHERE_DEFAULTS, CAMERA_DEFAULTS } from '@vizor-vr/shared';\nimport type { IProjection } from './projection.js';\nimport { fisheyeVertexShader, sphericalFragmentShader } from '../shaders/fisheye.glsl.js';\nimport { clampLensAngle } from './fisheye-projection.js';\n\n/**\n * True VR180 \"spherical\" projection (WS-F): one circular fisheye image per\n * eye, optical axis forward, covering the capture lens angle (default 180°,\n * supported 120–250°). This replaces the old silent equirect fallback that\n * rendered real VR180-camera footage with wrong geometry.\n *\n * Texture layouts mirror the fisheye projection's stereo handling:\n * mono — the full texture is one fisheye circle\n * sbs — left eye in the left half, right eye in the right half\n * tb — left eye in the TOP half, right eye in the bottom half\n */\nexport class SphericalProjection implements IProjection {\n private meshes: Mesh[] = [];\n private readonly lensFovRad: number;\n\n constructor(lensAngleDeg?: number) {\n this.lensFovRad = (clampLensAngle(lensAngleDeg) * Math.PI) / 180;\n }\n\n createMeshes(texture: Texture, stereoMode: StereoMode): Mesh[] {\n this.dispose();\n\n if (stereoMode === 'mono') {\n const mesh = this.createSphere(texture, { uScaleX: 1, uScaleY: 1, uOffsetX: 0, uOffsetY: 0 });\n mesh.layers.set(0);\n this.meshes = [mesh];\n return this.meshes;\n }\n\n const effectiveMode = stereoMode === 'separate' ? 'tb' : stereoMode;\n const rects =\n effectiveMode === 'sbs'\n ? {\n left: { uScaleX: 0.5, uScaleY: 1, uOffsetX: 0.0, uOffsetY: 0 },\n right: { uScaleX: 0.5, uScaleY: 1, uOffsetX: 0.5, uOffsetY: 0 },\n }\n : {\n // TB: left eye top half, right eye bottom half (matches fisheye).\n left: { uScaleX: 1, uScaleY: 0.5, uOffsetX: 0, uOffsetY: 0.5 },\n right: { uScaleX: 1, uScaleY: 0.5, uOffsetX: 0, uOffsetY: 0.0 },\n };\n\n const meshL = this.createSphere(texture, rects.left);\n const meshR = this.createSphere(texture, rects.right);\n meshL.layers.set(1);\n meshR.layers.set(2);\n this.meshes = [meshL, meshR];\n return this.meshes;\n }\n\n configureCamera(camera: PerspectiveCamera): void {\n camera.fov = CAMERA_DEFAULTS.fov;\n camera.near = CAMERA_DEFAULTS.near;\n camera.far = CAMERA_DEFAULTS.far;\n camera.position.set(0, 0, 0);\n camera.updateProjectionMatrix();\n }\n\n update(_camera: PerspectiveCamera, _time: number): void {\n // No per-frame updates needed.\n }\n\n dispose(): void {\n for (const mesh of this.meshes) {\n mesh.geometry.dispose();\n (mesh.material as ShaderMaterial).dispose();\n }\n this.meshes = [];\n }\n\n private createSphere(\n texture: Texture,\n rect: { uScaleX: number; uScaleY: number; uOffsetX: number; uOffsetY: number },\n ): Mesh {\n const { radius, widthSegments, heightSegments } = SPHERE_DEFAULTS;\n const geometry = new SphereGeometry(radius, widthSegments, heightSegments);\n geometry.scale(-1, 1, 1);\n\n const material = new ShaderMaterial({\n uniforms: {\n map: { value: texture },\n uScaleX: { value: rect.uScaleX },\n uScaleY: { value: rect.uScaleY },\n uOffsetX: { value: rect.uOffsetX },\n uOffsetY: { value: rect.uOffsetY },\n uLensFov: { value: this.lensFovRad },\n },\n vertexShader: fisheyeVertexShader,\n fragmentShader: sphericalFragmentShader,\n side: FrontSide,\n });\n\n return new Mesh(geometry, material);\n }\n}\n","import { type PerspectiveCamera, Euler, Quaternion, Vector2 } from 'three';\nimport { clamp, degToRad, radToDeg, normalizeAngle } from '@vizor-vr/shared';\nimport { CAMERA_DEFAULTS } from '@vizor-vr/shared';\nimport type { PlayerState } from '../core/player-state.js';\n\ninterface CameraConfig {\n startLat: number;\n startLon: number;\n focalLength: number;\n minFocalLength: number;\n maxFocalLength: number;\n isPanoramic: boolean;\n fovH?: number; // Horizontal FOV in degrees (360 for equirect, 180 for half-sphere)\n}\n\n/**\n * Unified camera controller for the VR player.\n *\n * Handles:\n * - Mouse drag (desktop)\n * - Touch drag (mobile)\n * - Device orientation / gyroscope (mobile magic window)\n * - Keyboard arrow keys\n * - FOV zoom (scroll wheel / pinch)\n *\n * WebXR head tracking is handled by Three.js WebXRManager directly.\n */\nexport class CameraController {\n private camera: PerspectiveCamera;\n private state: PlayerState;\n private domElement: HTMLElement;\n\n // Current orientation in degrees\n private lat: number;\n private lon: number;\n private targetLat: number;\n private targetLon: number;\n private fov: number;\n\n // Config\n private isPanoramic: boolean;\n private minFov: number;\n private maxFov: number;\n private fovH: number; // Horizontal FOV of content (360 or 180)\n\n // Enable/disable (disabled during WebXR when headset drives camera)\n private enabled = true;\n\n // Drag state\n private isDragging = false;\n private lastPointer = new Vector2();\n\n // Device orientation\n private deviceOrientationEnabled = false;\n private deviceAlpha = 0;\n private deviceBeta = 0;\n private deviceGamma = 0;\n\n // Bound handlers for cleanup\n private boundHandlers: Array<[string, EventListener, EventTarget]> = [];\n\n constructor(camera: PerspectiveCamera, domElement: HTMLElement, state: PlayerState, config: CameraConfig) {\n this.camera = camera;\n this.domElement = domElement;\n this.state = state;\n\n this.lat = config.startLat;\n this.lon = config.startLon;\n this.targetLat = config.startLat;\n this.targetLon = config.startLon;\n this.fov = config.focalLength;\n this.isPanoramic = config.isPanoramic;\n this.minFov = config.minFocalLength;\n this.maxFov = config.maxFocalLength;\n this.fovH = config.fovH ?? 360;\n\n this.camera.fov = this.fov;\n this.camera.updateProjectionMatrix();\n\n if (this.isPanoramic) {\n this.attachListeners();\n }\n\n this.updateCamera();\n }\n\n /** Enable or disable input processing (disabled during WebXR) */\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n if (!enabled) {\n this.isDragging = false;\n }\n }\n\n /** Switch panoramic mode at runtime (e.g. cinema overrides flat → panoramic) */\n setPanoramic(panoramic: boolean): void {\n if (this.isPanoramic === panoramic) return;\n this.isPanoramic = panoramic;\n if (panoramic) {\n this.fovH = 360; // Allow full horizontal rotation\n this.attachListeners();\n }\n }\n\n /** Call per frame in animation loop */\n update(): void {\n if (!this.isPanoramic || !this.enabled) return;\n\n // Smooth damping toward target\n const d = CAMERA_DEFAULTS.dampingFactor;\n this.lat += (this.targetLat - this.lat) * d;\n this.lon += (this.targetLon - this.lon) * d;\n\n // Clamp latitude\n this.lat = clamp(this.lat, -85, 85);\n\n // Clamp longitude for 180-degree content\n if (this.fovH < 360) {\n const halfFovH = this.fovH / 2;\n this.lon = clamp(this.lon, -halfFovH, halfFovH);\n this.targetLon = clamp(this.targetLon, -halfFovH, halfFovH);\n }\n\n // Update state\n this.state.batch({\n lat: this.lat,\n lon: normalizeAngle(this.lon),\n fov: this.fov,\n });\n\n this.updateCamera();\n }\n\n /** Get current orientation */\n getOrientation(): { lat: number; lon: number; fov: number } {\n return {\n lat: this.lat,\n lon: normalizeAngle(this.lon),\n fov: this.fov,\n };\n }\n\n /** Set orientation programmatically */\n setOrientation(lat: number, lon: number, snap = false): void {\n this.targetLat = lat;\n this.targetLon = lon;\n if (snap) {\n this.lat = lat;\n this.lon = lon;\n this.updateCamera();\n }\n }\n\n /** Enable device orientation (gyroscope) */\n enableDeviceOrientation(): void {\n if (this.deviceOrientationEnabled) return;\n this.deviceOrientationEnabled = true;\n\n const handler = ((e: DeviceOrientationEvent) => {\n this.deviceAlpha = e.alpha ?? 0;\n this.deviceBeta = e.beta ?? 0;\n this.deviceGamma = e.gamma ?? 0;\n this.applyDeviceOrientation();\n }) as EventListener;\n\n window.addEventListener('deviceorientation', handler);\n this.boundHandlers.push(['deviceorientation', handler, window]);\n }\n\n /** Dispose all event listeners */\n dispose(): void {\n for (const [event, handler, target] of this.boundHandlers) {\n target.removeEventListener(event, handler);\n }\n this.boundHandlers = [];\n }\n\n private attachListeners(): void {\n // Mouse\n this.addHandler(this.domElement, 'pointerdown', this.onPointerDown.bind(this));\n this.addHandler(this.domElement, 'pointermove', this.onPointerMove.bind(this));\n this.addHandler(this.domElement, 'pointerup', this.onPointerUp.bind(this));\n this.addHandler(this.domElement, 'pointerleave', this.onPointerUp.bind(this));\n\n // Scroll zoom\n this.addHandler(this.domElement, 'wheel', this.onWheel.bind(this));\n\n // Keyboard\n this.addHandler(this.domElement, 'keydown', this.onKeyDown.bind(this));\n\n // Make focusable for keyboard events\n if (!this.domElement.hasAttribute('tabindex')) {\n this.domElement.setAttribute('tabindex', '0');\n }\n }\n\n private addHandler(target: EventTarget, event: string, handler: EventListener): void {\n target.addEventListener(event, handler, { passive: event === 'wheel' ? false : true });\n this.boundHandlers.push([event, handler, target]);\n }\n\n private onPointerDown(e: Event): void {\n if (!this.enabled) return;\n const pe = e as PointerEvent;\n this.isDragging = true;\n this.lastPointer.set(pe.clientX, pe.clientY);\n this.domElement.setPointerCapture(pe.pointerId);\n }\n\n private onPointerMove(e: Event): void {\n if (!this.isDragging) return;\n const pe = e as PointerEvent;\n const dx = pe.clientX - this.lastPointer.x;\n const dy = pe.clientY - this.lastPointer.y;\n this.lastPointer.set(pe.clientX, pe.clientY);\n\n const sensitivity = CAMERA_DEFAULTS.dragSensitivity;\n this.targetLon -= dx * sensitivity;\n this.targetLat += dy * sensitivity;\n }\n\n private onPointerUp(e: Event): void {\n const pe = e as PointerEvent;\n this.isDragging = false;\n this.domElement.releasePointerCapture(pe.pointerId);\n }\n\n private onWheel(e: Event): void {\n if (!this.enabled) return;\n const we = e as WheelEvent;\n we.preventDefault();\n this.fov = clamp(\n this.fov + we.deltaY * CAMERA_DEFAULTS.zoomSpeed,\n this.minFov,\n this.maxFov,\n );\n this.camera.fov = this.fov;\n this.camera.updateProjectionMatrix();\n }\n\n private onKeyDown(e: Event): void {\n if (!this.enabled) return;\n const ke = e as KeyboardEvent;\n const speed = CAMERA_DEFAULTS.keyboardPanSpeed;\n\n switch (ke.code) {\n case 'ArrowLeft':\n this.targetLon += speed;\n break;\n case 'ArrowRight':\n this.targetLon -= speed;\n break;\n case 'ArrowUp':\n this.targetLat += speed;\n break;\n case 'ArrowDown':\n this.targetLat -= speed;\n break;\n default:\n return; // Don't prevent default for unhandled keys\n }\n }\n\n private applyDeviceOrientation(): void {\n // Convert device orientation to look direction\n // Beta (x): front-to-back tilt (-180 to 180)\n // Gamma (y): left-to-right tilt (-90 to 90)\n // Alpha (z): compass direction (0 to 360)\n this.targetLat = clamp(this.deviceBeta - 90, -85, 85);\n this.targetLon = -this.deviceAlpha;\n }\n\n private updateCamera(): void {\n if (!this.isPanoramic) return;\n\n const phi = degToRad(90 - this.lat);\n const theta = degToRad(this.lon);\n\n // Convert spherical to cartesian for lookAt target\n const x = Math.sin(phi) * Math.cos(theta);\n const y = Math.cos(phi);\n const z = Math.sin(phi) * Math.sin(theta);\n\n this.camera.lookAt(x * 500, y * 500, z * 500);\n }\n}\n","import {\n Scene,\n WebGLRenderer,\n PerspectiveCamera,\n VideoTexture,\n SRGBColorSpace,\n LinearFilter,\n Color,\n Mesh,\n} from 'three';\nimport type { Texture, Material } from 'three';\nimport type { VzFormat, FormatMeta } from '@vizor-vr/shared';\nimport { getFormatMeta } from '@vizor-vr/shared';\nimport type { PlayerConfig } from '@vizor-vr/shared';\nimport type { PlayerState } from '../core/player-state.js';\nimport type { StereoMode } from '@vizor-vr/shared';\nimport type { IProjection } from './projections/projection.js';\nimport { EquirectProjection } from './projections/equirect-projection.js';\nimport { FlatProjection } from './projections/flat-projection.js';\nimport { CubemapProjection } from './projections/cubemap-projection.js';\nimport { EacProjection } from './projections/eac-projection.js';\nimport { FisheyeProjection, clampLensAngle } from './projections/fisheye-projection.js';\nimport { SphericalProjection } from './projections/spherical-projection.js';\nimport { CameraController } from './camera-controller.js';\nimport type { AudioManager } from '../audio/audio-manager.js';\n\n/**\n * Main renderer facade.\n * Manages the Three.js scene, camera, projection, and animation loop.\n */\nexport class VzRenderer {\n private scene: Scene;\n private renderer: WebGLRenderer;\n private camera: PerspectiveCamera;\n private cameraController: CameraController;\n private projection: IProjection | null = null;\n private videoTexture: VideoTexture | null = null;\n private audioManager: AudioManager | null = null;\n private state: PlayerState;\n private config: PlayerConfig;\n private canvas: HTMLCanvasElement;\n private isRunning = false;\n private containerEl: HTMLElement;\n private intersectionObserver: IntersectionObserver | null = null;\n private isVisible = true;\n private fpsHistory: number[] = [];\n private lastFrameTime = 0;\n\n constructor(shadowRoot: ShadowRoot, containerEl: HTMLElement, state: PlayerState, config: PlayerConfig) {\n this.state = state;\n this.config = config;\n this.containerEl = containerEl;\n\n // Create Three.js scene\n this.scene = new Scene();\n this.scene.background = new Color(0x000000);\n\n // Create camera\n this.camera = new PerspectiveCamera(75, 1, 0.1, 1000);\n this.camera.position.set(0, 0, 0);\n\n // Enable left eye layer for 2D mode (stereo fallback)\n this.camera.layers.enable(1);\n\n // Create renderer\n this.canvas = document.createElement('canvas');\n this.renderer = new WebGLRenderer({\n canvas: this.canvas,\n antialias: true,\n alpha: false,\n });\n this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n this.renderer.xr.enabled = true;\n\n // Add canvas to shadow DOM container\n containerEl.appendChild(this.canvas);\n\n // IntersectionObserver: pause render loop when off-screen\n if (typeof IntersectionObserver !== 'undefined') {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => {\n this.isVisible = entries[0]?.isIntersecting ?? true;\n },\n { threshold: 0.1 },\n );\n this.intersectionObserver.observe(containerEl);\n }\n\n // Create camera controller\n const formatMeta = config.format ? getFormatMeta(config.format) : null;\n const isPanoramic = formatMeta ? !formatMeta.isFlat : true;\n\n this.cameraController = new CameraController(this.camera, containerEl, state, {\n startLat: config.startLat,\n startLon: config.startLon,\n focalLength: config.focalLength,\n minFocalLength: config.minFocalLength,\n maxFocalLength: config.maxFocalLength,\n isPanoramic,\n // Spherical (VR180-family) content pans as far as the capture lens\n // covers — the lens angle IS the horizontal FOV (WS-F). Other formats\n // keep their registry fovH (fisheye duals are 360).\n fovH:\n formatMeta?.projection === 'spherical'\n ? clampLensAngle(this.config.lensAngle)\n : formatMeta?.fovH,\n });\n\n // Initial resize\n this.resize();\n }\n\n /** Set up the projection and texture for a video element */\n setupVideo(videoElement: HTMLVideoElement, format: VzFormat): void {\n const formatMeta = getFormatMeta(format);\n if (!formatMeta) {\n console.error(`[Vizor] Unknown format: ${format}`);\n return;\n }\n\n // Create video texture\n this.videoTexture = new VideoTexture(videoElement);\n this.videoTexture.colorSpace = SRGBColorSpace;\n this.videoTexture.minFilter = LinearFilter;\n this.videoTexture.magFilter = LinearFilter;\n this.videoTexture.generateMipmaps = false;\n\n // Create projection based on format\n this.setProjection(formatMeta, this.videoTexture);\n }\n\n /** Set up the projection and texture for an image */\n setupImage(texture: Texture, format: VzFormat): void {\n const formatMeta = getFormatMeta(format);\n if (!formatMeta) {\n console.error(`[Vizor] Unknown format: ${format}`);\n return;\n }\n\n this.setProjection(formatMeta, texture);\n }\n\n /**\n * Load a compressed KTX2 texture (optional, GPU-compressed).\n * Falls back gracefully if KTX2Loader is not available.\n */\n async loadCompressedTexture(url: string, format: VzFormat): Promise<void> {\n const formatMeta = getFormatMeta(format);\n if (!formatMeta) {\n console.error(`[Vizor] Unknown format: ${format}`);\n return;\n }\n\n try {\n const { KTX2Loader } = await import('three/examples/jsm/loaders/KTX2Loader.js');\n const loader = new KTX2Loader();\n loader.setTranscoderPath('https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/libs/basis/');\n loader.detectSupport(this.renderer);\n\n const texture = await loader.loadAsync(url);\n this.setProjection(formatMeta, texture);\n loader.dispose();\n } catch (err) {\n console.warn('[Vizor] KTX2 loading failed, falling back to standard texture:', err);\n }\n }\n\n /** Start the animation loop */\n start(): void {\n if (this.isRunning) return;\n this.isRunning = true;\n this.renderer.setAnimationLoop(this.tick.bind(this));\n }\n\n /** Stop the animation loop */\n stop(): void {\n this.isRunning = false;\n this.renderer.setAnimationLoop(null);\n }\n\n /** Handle container resize */\n resize(): void {\n const rect = this.containerEl.getBoundingClientRect();\n const width = rect.width;\n const height = rect.height;\n\n if (width === 0 || height === 0) return;\n\n this.camera.aspect = width / height;\n this.camera.updateProjectionMatrix();\n this.renderer.setSize(width, height, false);\n }\n\n /** Get the WebGL renderer (for WebXR integration) */\n getThreeRenderer(): WebGLRenderer {\n return this.renderer;\n }\n\n /** Get the camera (for WebXR integration) */\n getCamera(): PerspectiveCamera {\n return this.camera;\n }\n\n /** Get the camera controller */\n getCameraController(): CameraController {\n return this.cameraController;\n }\n\n /** Get the Three.js scene (for hotspot integration) */\n getScene(): Scene {\n return this.scene;\n }\n\n /** Set the audio manager for spatial audio orientation updates */\n setAudioManager(manager: AudioManager): void {\n this.audioManager = manager;\n }\n\n /** Clean up all GPU resources */\n dispose(): void {\n this.stop();\n this.intersectionObserver?.disconnect();\n this.intersectionObserver = null;\n this.cameraController.dispose();\n this.projection?.dispose();\n this.disposeSceneChildren();\n this.videoTexture?.dispose();\n this.renderer.dispose();\n this.renderer.forceContextLoss();\n\n // Remove canvas from DOM\n this.canvas.remove();\n }\n\n /** Set a custom projection (for cinema mode) */\n setCustomProjection(projection: IProjection, texture: Texture, stereoMode: StereoMode): void {\n this.projection?.dispose();\n this.disposeSceneChildren();\n\n this.projection = projection;\n projection.configureCamera(this.camera);\n const meshes = projection.createMeshes(texture, stereoMode);\n for (const mesh of meshes) {\n this.scene.add(mesh);\n }\n\n this.state.set('stereoMode', stereoMode);\n }\n\n /** Dispose all meshes, materials, and textures in the scene */\n private disposeSceneChildren(): void {\n this.scene.traverse((obj) => {\n if (obj instanceof Mesh) {\n obj.geometry?.dispose();\n const materials = Array.isArray(obj.material) ? obj.material : [obj.material];\n for (const mat of materials as Material[]) {\n // Dispose textures attached to a standard material's `map`.\n if ('map' in mat && (mat as Record<string, unknown>).map) {\n ((mat as Record<string, unknown>).map as Texture).dispose();\n }\n // Dispose textures stored in ShaderMaterial uniforms (EAC/fisheye\n // projections keep the texture in `uniforms`, not `.map`) — otherwise\n // those textures leak GPU memory on projection swap or teardown.\n const uniforms = (mat as unknown as Record<string, unknown>).uniforms as\n | Record<string, { value?: unknown }>\n | undefined;\n if (uniforms) {\n for (const key of Object.keys(uniforms)) {\n const value = uniforms[key]?.value as { isTexture?: boolean; dispose?: () => void } | undefined;\n if (value && value.isTexture && typeof value.dispose === 'function') {\n value.dispose();\n }\n }\n }\n mat.dispose();\n }\n }\n });\n while (this.scene.children.length > 0) {\n this.scene.remove(this.scene.children[0]!);\n }\n }\n\n private setProjection(formatMeta: FormatMeta, texture: Texture): void {\n // Clean up previous projection\n this.projection?.dispose();\n this.disposeSceneChildren();\n\n // Create new projection\n this.projection = this.createProjection(formatMeta);\n this.projection.configureCamera(this.camera);\n\n // Create meshes and add to scene\n const meshes = this.projection.createMeshes(texture, formatMeta.stereoMode);\n for (const mesh of meshes) {\n this.scene.add(mesh);\n }\n\n // Update state\n this.state.batch({\n format: formatMeta.format,\n stereoMode: formatMeta.stereoMode,\n });\n }\n\n private createProjection(formatMeta: FormatMeta): IProjection {\n switch (formatMeta.projection) {\n case 'equirect':\n return new EquirectProjection(formatMeta.fovH);\n case 'spherical':\n // True VR180 (WS-F): single fisheye per eye — no more silent\n // equirect fallback that rendered fisheye footage with wrong geometry.\n return new SphericalProjection(this.config.lensAngle);\n case 'flat':\n return new FlatProjection();\n case 'cubemap':\n return new CubemapProjection();\n case 'eac':\n return new EacProjection();\n case 'fisheye':\n return new FisheyeProjection(this.config.lensAngle);\n default:\n return new EquirectProjection(360);\n }\n }\n\n private tick(_timestamp: number, _xrFrame?: unknown): void {\n if (!this.isRunning) return;\n\n // Skip rendering when off-screen (IntersectionObserver)\n if (!this.isVisible) return;\n\n // Adaptive quality: track FPS and adjust pixel ratio\n this.adaptPixelRatio(_timestamp);\n\n // Update camera orientation (damped)\n this.cameraController.update();\n\n // Update projection\n this.projection?.update(this.camera, _timestamp);\n\n // Update spatial audio orientation from camera\n this.audioManager?.updateOrientation(this.camera);\n\n // Update video texture only when playing\n if (this.videoTexture) {\n const video = this.videoTexture.image as HTMLVideoElement;\n if (video && !video.paused && video.readyState >= 2) {\n this.videoTexture.needsUpdate = true;\n }\n }\n\n // Render\n this.renderer.render(this.scene, this.camera);\n }\n\n /**\n * Adaptive quality: adjust pixel ratio based on recent FPS.\n * Drop at <30fps, increase at >55fps. Checked every 60 frames.\n */\n private adaptPixelRatio(timestamp: number): void {\n if (this.lastFrameTime > 0) {\n const delta = timestamp - this.lastFrameTime;\n if (delta > 0) {\n this.fpsHistory.push(1000 / delta);\n }\n }\n this.lastFrameTime = timestamp;\n\n if (this.fpsHistory.length < 60) return;\n\n const avgFps = this.fpsHistory.reduce((a, b) => a + b, 0) / this.fpsHistory.length;\n this.fpsHistory.length = 0;\n\n const current = this.renderer.getPixelRatio();\n const maxRatio = Math.min(window.devicePixelRatio, 2);\n\n if (avgFps < 30 && current > 0.75) {\n this.renderer.setPixelRatio(Math.max(0.75, current - 0.25));\n } else if (avgFps > 55 && current < maxRatio) {\n this.renderer.setPixelRatio(Math.min(maxRatio, current + 0.25));\n }\n }\n}\n","import type { QualityLevel } from '@vizor-vr/shared';\nimport type { IStreamingAdapter, DrmContext } from './types.js';\n\n/**\n * Progressive MP4/WebM adapter.\n * Sets the video source directly — no adaptive bitrate.\n */\nexport class ProgressiveAdapter implements IStreamingAdapter {\n private videoElement: HTMLVideoElement | null = null;\n private _isAttached = false;\n\n get supportsABR(): boolean {\n return false;\n }\n\n get isAttached(): boolean {\n return this._isAttached;\n }\n\n attach(videoElement: HTMLVideoElement): void {\n this.videoElement = videoElement;\n this._isAttached = true;\n }\n\n load(url: string, _drm?: DrmContext): void {\n if (!this.videoElement) return;\n this.videoElement.src = url;\n }\n\n getQualityLevels(): QualityLevel[] {\n return [];\n }\n\n setQualityLevel(_index: number): void {\n // No-op for progressive\n }\n\n destroy(): void {\n if (this.videoElement) {\n this.videoElement.removeAttribute('src');\n }\n this.videoElement = null;\n this._isAttached = false;\n }\n}\n","import type { IStreamingAdapter, AdapterType } from './types.js';\nimport { ProgressiveAdapter } from './progressive-adapter.js';\n\n/**\n * Create a streaming adapter by type.\n * HLS and DASH adapters are lazy-loaded via dynamic import\n * to keep the core bundle small.\n */\nexport async function createAdapter(type: AdapterType): Promise<IStreamingAdapter> {\n switch (type) {\n case 'hls': {\n const { HlsAdapter } = await import('./hls-adapter.js');\n return new HlsAdapter();\n }\n case 'dash': {\n const { DashAdapter } = await import('./dash-adapter.js');\n return new DashAdapter();\n }\n case 'progressive':\n default:\n return new ProgressiveAdapter();\n }\n}\n","import type { ParsedSource, QualityLevel } from '@vizor-vr/shared';\nimport type { PlayerState } from '../core/player-state.js';\nimport type { VzEventEmitter } from '../core/event-emitter.js';\nimport type { IStreamingAdapter, AdapterType, DrmContext } from './types.js';\nimport { createAdapter } from './adapter-factory.js';\n\n/**\n * Manages source selection and streaming adapter lifecycle.\n *\n * Responsibilities:\n * - Detect MIME type to determine adapter type (progressive/hls/dash)\n * - Instantiate the correct adapter via lazy-loaded factory\n * - Coordinate quality switching with PlayerState\n * - Handle multi-source progressive switching (preserves currentTime)\n */\nexport class SourceManager {\n private adapter: IStreamingAdapter | null = null;\n private videoElement: HTMLVideoElement | null = null;\n private state: PlayerState | null = null;\n private events: VzEventEmitter | null = null;\n private sources: ParsedSource[] = [];\n private drm: DrmContext | undefined;\n\n /**\n * Load sources into the video element using the appropriate adapter.\n */\n async loadSources(\n sources: ParsedSource[],\n videoElement: HTMLVideoElement,\n state: PlayerState,\n events: VzEventEmitter,\n drm?: DrmContext,\n ): Promise<void> {\n this.sources = sources;\n this.videoElement = videoElement;\n this.state = state;\n this.events = events;\n this.drm = drm;\n\n if (sources.length === 0) return;\n\n // Destroy previous adapter\n this.adapter?.destroy();\n\n // Determine the best adapter type from available sources\n const { adapterType, source } = this.selectSource(sources);\n\n // Create and attach adapter\n this.adapter = await createAdapter(adapterType);\n this.adapter.attach(videoElement);\n this.adapter.load(source.url, drm);\n\n // Update quality levels in state once adapter is loaded\n this.pollQualityLevels();\n }\n\n /** Get available quality levels from the current adapter */\n getQualityLevels(): QualityLevel[] {\n return this.adapter?.getQualityLevels() ?? [];\n }\n\n /** Set quality level (-1 for auto) */\n setQualityLevel(index: number): void {\n if (!this.adapter) return;\n this.adapter.setQualityLevel(index);\n\n const levels = this.adapter.getQualityLevels();\n this.state?.batch({\n availableQualities: levels,\n currentQuality: index === -1 ? 'auto' : (levels[index]?.label ?? 'auto'),\n autoQuality: index === -1,\n });\n\n this.events?.emit('vz-quality-change', {\n quality: index === -1 ? 'auto' : (levels[index]?.label ?? 'unknown'),\n auto: index === -1,\n });\n }\n\n /** Whether the current adapter supports ABR */\n get supportsABR(): boolean {\n return this.adapter?.supportsABR ?? false;\n }\n\n /** Switch to a different progressive source by index (preserves currentTime) */\n switchSource(index: number): void {\n if (!this.videoElement || !this.adapter || index >= this.sources.length) return;\n\n const currentTime = this.videoElement.currentTime;\n const wasPlaying = !this.videoElement.paused;\n\n this.adapter.load(this.sources[index]!.url, this.drm);\n\n // Restore playback position after the new source loads\n const onLoaded = () => {\n this.videoElement!.currentTime = currentTime;\n if (wasPlaying) this.videoElement!.play().catch(() => {});\n this.videoElement!.removeEventListener('loadeddata', onLoaded);\n };\n this.videoElement.addEventListener('loadeddata', onLoaded);\n }\n\n /** Dispose the adapter and release resources */\n dispose(): void {\n this.adapter?.destroy();\n this.adapter = null;\n this.videoElement = null;\n this.state = null;\n this.events = null;\n this.sources = [];\n }\n\n /**\n * Select the best source and adapter type.\n * Prefers adaptive (HLS/DASH) over progressive when available.\n */\n private selectSource(sources: ParsedSource[]): { adapterType: AdapterType; source: ParsedSource } {\n // Look for HLS first\n const hlsSource = sources.find((s) => this.detectAdapterType(s) === 'hls');\n if (hlsSource) return { adapterType: 'hls', source: hlsSource };\n\n // Then DASH\n const dashSource = sources.find((s) => this.detectAdapterType(s) === 'dash');\n if (dashSource) return { adapterType: 'dash', source: dashSource };\n\n // Fall back to first progressive source\n return { adapterType: 'progressive', source: sources[0]! };\n }\n\n /** Detect adapter type from source MIME type or URL extension */\n private detectAdapterType(source: ParsedSource): AdapterType {\n const type = source.type.toLowerCase();\n const url = source.url.toLowerCase();\n\n if (\n type === 'application/x-mpegurl' ||\n type === 'application/vnd.apple.mpegurl' ||\n url.endsWith('.m3u8')\n ) {\n return 'hls';\n }\n\n if (type === 'application/dash+xml' || url.endsWith('.mpd')) {\n return 'dash';\n }\n\n return 'progressive';\n }\n\n /**\n * Poll for quality levels after a short delay.\n * Adaptive adapters need time to parse the manifest before levels are available.\n */\n private pollQualityLevels(): void {\n if (!this.adapter || !this.state) return;\n\n const check = () => {\n if (!this.adapter || !this.state) return;\n const levels = this.adapter.getQualityLevels();\n if (levels.length > 0) {\n this.state.batch({\n availableQualities: levels,\n currentQuality: 'auto',\n autoQuality: true,\n });\n }\n };\n\n // Check immediately and again after manifest parsing\n check();\n setTimeout(check, 1000);\n setTimeout(check, 3000);\n }\n}\n","/** Cross-browser fullscreen API wrapper */\nexport function requestFullscreen(element: HTMLElement): Promise<void> {\n if (element.requestFullscreen) {\n return element.requestFullscreen();\n }\n // Webkit prefix (Safari)\n const el = element as HTMLElement & { webkitRequestFullscreen?: () => Promise<void> };\n if (el.webkitRequestFullscreen) {\n return el.webkitRequestFullscreen();\n }\n return Promise.reject(new Error('Fullscreen not supported'));\n}\n\nexport function exitFullscreen(): Promise<void> {\n if (document.exitFullscreen) {\n return document.exitFullscreen();\n }\n const doc = document as Document & { webkitExitFullscreen?: () => Promise<void> };\n if (doc.webkitExitFullscreen) {\n return doc.webkitExitFullscreen();\n }\n return Promise.reject(new Error('Fullscreen not supported'));\n}\n\nexport function isFullscreen(): boolean {\n return !!document.fullscreenElement;\n}\n\nexport function onFullscreenChange(callback: () => void): () => void {\n document.addEventListener('fullscreenchange', callback);\n document.addEventListener('webkitfullscreenchange', callback);\n return () => {\n document.removeEventListener('fullscreenchange', callback);\n document.removeEventListener('webkitfullscreenchange', callback);\n };\n}\n","/**\n * Controls CSS injected into the shadow DOM.\n * Vimeo-inspired design: thin progress bar, subtle gradient,\n * frosted glass big play, smooth transitions.\n *\n * CSS custom properties for theming:\n * --vz-primary Accent color (default: #f43f5e)\n * --vz-controls-bg Control bar gradient end (default: rgba(0,0,0,0.6))\n * --vz-progress-height Progress bar resting height (default: 3px)\n * --vz-progress-color Progress fill color (default: --vz-primary)\n * --vz-controls-transition Fade duration (default: 0.3s)\n * --vz-button-size Button hit-area size (default: 36px)\n */\nexport const CONTROLS_CSS = `\n /* ------------------------------------------------------------------ */\n /* Layout — wrapper + seek above button row */\n /* ------------------------------------------------------------------ */\n\n .vz-controls-wrapper {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 10;\n user-select: none;\n -webkit-user-select: none;\n transition: opacity var(--vz-controls-transition, 0.3s) ease,\n transform 0.2s ease;\n }\n\n .vz-controls-wrapper.hidden {\n opacity: 0;\n pointer-events: none;\n transform: translateY(4px);\n }\n\n /* Progress bar sits above the button row */\n .vz-seek-row {\n position: relative;\n width: 100%;\n padding: 0 12px;\n cursor: pointer;\n z-index: 11;\n }\n\n /* Button row */\n .vz-controls {\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 4px 8px 6px;\n background: linear-gradient(transparent, var(--vz-controls-bg, rgba(0, 0, 0, 0.6)));\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n color: #fff;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 13px;\n }\n\n .vz-controls-left {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n }\n\n .vz-controls-center {\n flex: 1;\n min-width: 0;\n }\n\n .vz-controls-right {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n }\n\n /* ------------------------------------------------------------------ */\n /* Buttons */\n /* ------------------------------------------------------------------ */\n\n .vz-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: var(--vz-button-size, 36px);\n height: var(--vz-button-size, 36px);\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: #fff;\n cursor: pointer;\n flex-shrink: 0;\n transition: opacity 0.15s, transform 0.15s;\n opacity: 0.9;\n }\n\n .vz-btn:hover {\n opacity: 1;\n transform: scale(1.1);\n }\n\n .vz-btn:active {\n transform: scale(0.95);\n }\n\n .vz-btn:focus-visible {\n outline: 2px solid var(--vz-primary, #f43f5e);\n outline-offset: 2px;\n }\n\n .vz-btn svg {\n width: 20px;\n height: 20px;\n fill: currentColor;\n }\n\n /* ------------------------------------------------------------------ */\n /* Seek / Progress bar */\n /* ------------------------------------------------------------------ */\n\n .vz-seek-container {\n position: relative;\n display: flex;\n align-items: center;\n width: 100%;\n height: 18px;\n cursor: pointer;\n }\n\n .vz-seek-track {\n position: relative;\n width: 100%;\n height: var(--vz-progress-height, 3px);\n background: rgba(255, 255, 255, 0.2);\n border-radius: 99px;\n overflow: visible;\n transition: height 0.15s ease;\n }\n\n .vz-seek-container:hover .vz-seek-track {\n height: 5px;\n }\n\n .vz-seek-chapters {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 3;\n }\n\n .vz-seek-chapter-marker {\n position: absolute;\n top: 50%;\n width: 2px;\n height: 100%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.55);\n box-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.5);\n pointer-events: none;\n }\n\n .vz-seek-buffered {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: linear-gradient(\n 90deg,\n rgba(255, 255, 255, 0.25),\n rgba(255, 255, 255, 0.35)\n );\n border-radius: 99px;\n }\n\n .vz-seek-played {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--vz-progress-color, var(--vz-primary, #f43f5e));\n border-radius: 99px;\n box-shadow: 0 0 8px rgba(244, 63, 94, 0.4);\n box-shadow: 0 0 8px color-mix(in srgb, var(--vz-primary, #f43f5e) 40%, transparent);\n }\n\n .vz-seek-thumb {\n position: absolute;\n top: 50%;\n width: 13px;\n height: 13px;\n border-radius: 50%;\n background: #fff;\n box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);\n transform: translate(-50%, -50%) scale(0);\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n pointer-events: none;\n }\n\n .vz-seek-container:hover .vz-seek-thumb {\n transform: translate(-50%, -50%) scale(1);\n box-shadow: 0 0 6px rgba(244, 63, 94, 0.5);\n box-shadow: 0 0 6px color-mix(in srgb, var(--vz-primary, #f43f5e) 50%, transparent);\n }\n\n .vz-seek-input {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n opacity: 0;\n cursor: pointer;\n -webkit-appearance: none;\n appearance: none;\n }\n\n /* Hover time tooltip */\n .vz-seek-tooltip {\n position: absolute;\n bottom: 100%;\n margin-bottom: 6px;\n padding: 3px 7px;\n background: var(--vz-secondary, rgba(0, 0, 0, 0.85));\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n color: #fff;\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n border-radius: 4px;\n white-space: nowrap;\n pointer-events: none;\n transform: translateX(-50%);\n opacity: 0;\n transition: opacity 0.12s;\n }\n\n .vz-seek-container:hover .vz-seek-tooltip {\n opacity: 1;\n }\n\n .vz-time {\n font-size: 12px;\n font-weight: 400;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n padding: 0 6px;\n flex-shrink: 0;\n opacity: 0.9;\n letter-spacing: 0.02em;\n }\n\n /* ------------------------------------------------------------------ */\n /* Volume — horizontal slide-out on hover */\n /* ------------------------------------------------------------------ */\n\n .vz-volume {\n display: flex;\n align-items: center;\n gap: 0;\n overflow: hidden;\n }\n\n .vz-volume-slider-wrap {\n width: 0;\n overflow: hidden;\n transition: width 0.2s ease;\n }\n\n .vz-volume:hover .vz-volume-slider-wrap {\n width: 64px;\n }\n\n .vz-volume-slider {\n width: 60px;\n height: 4px;\n margin: 0 4px;\n -webkit-appearance: none;\n appearance: none;\n background: rgba(255, 255, 255, 0.3);\n border-radius: 99px;\n outline: none;\n cursor: pointer;\n }\n\n .vz-volume-slider::-webkit-slider-thumb {\n -webkit-appearance: none;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: #fff;\n cursor: pointer;\n box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);\n }\n\n .vz-volume-slider::-moz-range-thumb {\n width: 12px;\n height: 12px;\n border: none;\n border-radius: 50%;\n background: #fff;\n cursor: pointer;\n box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);\n }\n\n /* ------------------------------------------------------------------ */\n /* Quality menu */\n /* ------------------------------------------------------------------ */\n\n .vz-quality-menu {\n position: relative;\n }\n\n .vz-quality-dropdown {\n position: absolute;\n bottom: 100%;\n right: 0;\n margin-bottom: 8px;\n background: var(--vz-secondary, rgba(20, 20, 30, 0.95));\n backdrop-filter: blur(16px) saturate(1.3);\n -webkit-backdrop-filter: blur(16px) saturate(1.3);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n padding: 4px 0;\n min-width: 120px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.04);\n display: none;\n z-index: 20;\n }\n\n .vz-quality-dropdown.open {\n display: block;\n }\n\n .vz-quality-option {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 16px;\n cursor: pointer;\n font-size: 13px;\n color: rgba(255, 255, 255, 0.7);\n white-space: nowrap;\n transition: color 0.12s, background 0.12s;\n }\n\n .vz-quality-option:hover {\n background: rgba(244, 63, 94, 0.08);\n background: color-mix(in srgb, var(--vz-primary, #f43f5e) 8%, transparent);\n color: #fff;\n }\n\n .vz-quality-option.active {\n color: var(--vz-primary, #f43f5e);\n }\n\n .vz-quality-check {\n width: 16px;\n font-size: 14px;\n }\n\n /* ------------------------------------------------------------------ */\n /* Compass */\n /* ------------------------------------------------------------------ */\n\n .vz-compass {\n position: relative;\n }\n\n .vz-compass svg {\n fill: none;\n stroke: currentColor;\n transition: transform 0.1s ease-out;\n }\n\n /* ------------------------------------------------------------------ */\n /* Loading spinner — CSS ring */\n /* ------------------------------------------------------------------ */\n\n .vz-loading {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 44px;\n height: 44px;\n z-index: 5;\n pointer-events: none;\n }\n\n .vz-loading-spinner {\n width: 100%;\n height: 100%;\n border: 2.5px solid rgba(255, 255, 255, 0.15);\n border-top-color: var(--vz-primary, #f43f5e);\n border-radius: 50%;\n animation: vz-spin 0.75s linear infinite;\n }\n\n .vz-loading.hidden {\n display: none;\n }\n\n @keyframes vz-spin {\n to { transform: rotate(360deg); }\n }\n\n /* ------------------------------------------------------------------ */\n /* Big play button — frosted glass */\n /* ------------------------------------------------------------------ */\n\n .vz-big-play {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 68px;\n height: 68px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 50%;\n background: rgba(15, 10, 20, 0.4);\n backdrop-filter: blur(12px) saturate(1.2);\n -webkit-backdrop-filter: blur(12px) saturate(1.2);\n color: #fff;\n cursor: pointer;\n z-index: 5;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background-color 0.25s ease, transform 0.25s ease, opacity 0.25s ease, box-shadow 0.25s ease;\n opacity: 1;\n box-shadow: 0 0 20px rgba(244, 63, 94, 0.15);\n box-shadow: 0 0 20px color-mix(in srgb, var(--vz-primary, #f43f5e) 15%, transparent);\n }\n\n .vz-big-play:hover {\n background: rgba(15, 10, 20, 0.55);\n transform: translate(-50%, -50%) scale(1.08);\n box-shadow: 0 0 30px rgba(244, 63, 94, 0.25);\n box-shadow: 0 0 30px color-mix(in srgb, var(--vz-primary, #f43f5e) 25%, transparent);\n }\n\n .vz-big-play.hidden {\n opacity: 0;\n pointer-events: none;\n transform: translate(-50%, -50%) scale(0.85);\n }\n\n .vz-big-play svg {\n width: 30px;\n height: 30px;\n fill: currentColor;\n margin-left: 3px; /* visual centering for play triangle */\n }\n\n /* ------------------------------------------------------------------ */\n /* Watermark */\n /* ------------------------------------------------------------------ */\n\n .vz-watermark {\n position: absolute;\n bottom: 48px;\n right: 12px;\n z-index: 15;\n pointer-events: auto;\n }\n\n /* ------------------------------------------------------------------ */\n /* Controls behavior variants */\n /* ------------------------------------------------------------------ */\n\n /* \"always\" mode — never hide */\n :host([controls-behavior=\"always\"]) .vz-controls-wrapper {\n opacity: 1 !important;\n pointer-events: auto !important;\n transform: none !important;\n }\n\n /* \"minimal\" mode — only thin progress bar always visible */\n :host([controls-behavior=\"minimal\"]) .vz-controls-wrapper.hidden .vz-controls {\n opacity: 0;\n pointer-events: none;\n }\n\n :host([controls-behavior=\"minimal\"]) .vz-controls-wrapper.hidden .vz-seek-row {\n opacity: 1;\n pointer-events: auto;\n }\n\n :host([controls-behavior=\"minimal\"]) .vz-controls-wrapper.hidden {\n opacity: 1;\n pointer-events: auto;\n transform: none;\n }\n\n /* ------------------------------------------------------------------ */\n /* Live indicator */\n /* ------------------------------------------------------------------ */\n\n .vz-live-indicator {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 0 8px;\n cursor: pointer;\n flex-shrink: 0;\n transition: opacity 0.2s;\n user-select: none;\n }\n\n .vz-live-indicator:hover {\n opacity: 1;\n }\n\n .vz-live-dot {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 12px;\n height: 12px;\n }\n\n .vz-live-dot svg {\n width: 10px;\n height: 10px;\n }\n\n .vz-live-dot.at-edge {\n color: #ef4444;\n animation: vz-live-pulse 2s ease-in-out infinite;\n }\n\n .vz-live-dot.behind {\n color: #6b7280;\n animation: none;\n }\n\n .vz-live-label {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: rgba(255, 255, 255, 0.9);\n }\n\n @keyframes vz-live-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n\n /* ------------------------------------------------------------------ */\n /* Caption overlay (HUD) */\n /* ------------------------------------------------------------------ */\n\n .vz-caption-overlay {\n position: absolute;\n bottom: 60px;\n left: 50%;\n transform: translateX(-50%);\n max-width: 80%;\n padding: 4px 12px;\n background: rgba(0, 0, 0, 0.75);\n border-radius: 4px;\n color: #fff;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 16px;\n line-height: 1.4;\n text-align: center;\n z-index: 12;\n pointer-events: none;\n opacity: 0;\n transition: opacity 0.15s ease;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n .vz-caption-overlay.visible {\n opacity: 1;\n }\n\n /* ------------------------------------------------------------------ */\n /* CC button & dropdown */\n /* ------------------------------------------------------------------ */\n\n .vz-cc-menu {\n position: relative;\n }\n\n .vz-cc-btn.active {\n color: var(--vz-primary, #f43f5e);\n opacity: 1;\n }\n\n .vz-cc-dropdown {\n position: absolute;\n bottom: 100%;\n right: 0;\n margin-bottom: 8px;\n background: var(--vz-secondary, rgba(20, 20, 30, 0.95));\n backdrop-filter: blur(16px) saturate(1.3);\n -webkit-backdrop-filter: blur(16px) saturate(1.3);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n padding: 4px 0;\n min-width: 120px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.04);\n display: none;\n z-index: 20;\n }\n\n .vz-cc-dropdown.open {\n display: block;\n }\n\n .vz-cc-option {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 16px;\n cursor: pointer;\n font-size: 13px;\n color: rgba(255, 255, 255, 0.7);\n white-space: nowrap;\n transition: color 0.12s, background 0.12s;\n }\n\n .vz-cc-option:hover {\n background: rgba(244, 63, 94, 0.08);\n background: color-mix(in srgb, var(--vz-primary, #f43f5e) 8%, transparent);\n color: #fff;\n }\n\n .vz-cc-option.active {\n color: var(--vz-primary, #f43f5e);\n }\n\n .vz-cc-check {\n width: 16px;\n font-size: 14px;\n }\n\n /* ------------------------------------------------------------------ */\n /* Chapters menu */\n /* ------------------------------------------------------------------ */\n\n .vz-chapter-menu {\n position: relative;\n }\n\n .vz-chapter-dropdown {\n position: absolute;\n bottom: 100%;\n right: 0;\n margin-bottom: 8px;\n background: var(--vz-secondary, rgba(20, 20, 30, 0.95));\n backdrop-filter: blur(16px) saturate(1.3);\n -webkit-backdrop-filter: blur(16px) saturate(1.3);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n padding: 4px 0;\n min-width: 200px;\n max-height: 260px;\n overflow-y: auto;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.04);\n display: none;\n z-index: 20;\n }\n\n .vz-chapter-dropdown.open {\n display: block;\n }\n\n .vz-chapter-option {\n display: flex;\n align-items: baseline;\n gap: 10px;\n padding: 8px 16px;\n cursor: pointer;\n font-size: 13px;\n color: rgba(255, 255, 255, 0.7);\n white-space: nowrap;\n transition: color 0.12s, background 0.12s;\n }\n\n .vz-chapter-option:hover {\n background: color-mix(in srgb, var(--vz-primary, #f43f5e) 8%, transparent);\n color: #fff;\n }\n\n .vz-chapter-option.active {\n color: var(--vz-primary, #f43f5e);\n }\n\n .vz-chapter-time {\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.45);\n flex-shrink: 0;\n }\n\n .vz-chapter-option.active .vz-chapter-time {\n color: var(--vz-primary, #f43f5e);\n }\n\n .vz-chapter-label {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n /* ------------------------------------------------------------------ */\n /* Chat badge */\n /* ------------------------------------------------------------------ */\n\n .vz-chat-badge {\n position: absolute;\n top: 2px;\n right: 2px;\n min-width: 16px;\n height: 16px;\n border-radius: 8px;\n background: #ef4444;\n color: #fff;\n font-size: 10px;\n font-weight: 700;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0 4px;\n pointer-events: none;\n line-height: 1;\n }\n\n /* ------------------------------------------------------------------ */\n /* Responsive */\n /* ------------------------------------------------------------------ */\n\n @media (max-width: 400px) {\n .vz-volume-slider-wrap,\n .vz-volume-slider {\n display: none;\n }\n }\n`;\n","import type { PlayerConfig, LicenseResult } from '@vizor-vr/shared';\nimport { setStaticHtml } from '../utils/static-html.js';\nimport { DEFAULT_CONFIG } from '@vizor-vr/shared';\nimport type { PlayerState } from '../core/player-state.js';\nimport type { VzEventEmitter } from '../core/event-emitter.js';\nimport { CONTROLS_CSS } from '../styles/controls.js';\n\nconst CONTROLS_HIDE_TIMEOUT = 3000;\n\n/**\n * Controls overlay container rendered in shadow DOM.\n * Vimeo-inspired layout: seek bar above button row, smooth slide-up.\n *\n * Layout:\n * .vz-controls-wrapper (outer — animated show/hide)\n * .vz-seek-row (progress bar — full width)\n * .vz-controls (button bar — gradient background)\n * .vz-controls-left (play, time)\n * .vz-controls-center (spacer)\n * .vz-controls-right (volume, quality, compass, vr, fullscreen)\n */\nexport class ControlsOverlay {\n private wrapper: HTMLDivElement;\n private seekRow: HTMLDivElement;\n private container: HTMLDivElement;\n private leftSection: HTMLDivElement;\n private centerSection: HTMLDivElement;\n private rightSection: HTMLDivElement;\n private loadingEl: HTMLDivElement;\n private hideTimer: ReturnType<typeof setTimeout> | null = null;\n private state: PlayerState;\n private cleanups: Array<() => void> = [];\n\n constructor(\n shadowRoot: ShadowRoot,\n containerEl: HTMLElement,\n state: PlayerState,\n config: PlayerConfig,\n _events: VzEventEmitter,\n ) {\n this.state = state;\n\n // Inject controls CSS\n const style = document.createElement('style');\n style.textContent = CONTROLS_CSS;\n shadowRoot.appendChild(style);\n\n // Create outer wrapper (handles show/hide animation)\n this.wrapper = document.createElement('div');\n this.wrapper.className = 'vz-controls-wrapper';\n\n // Apply theme colors\n if (config.primaryColor) {\n this.wrapper.style.setProperty('--vz-primary', config.primaryColor);\n }\n if (config.secondaryColor) {\n this.wrapper.style.setProperty('--vz-secondary', config.secondaryColor);\n }\n\n // Seek row — full-width progress bar above buttons\n this.seekRow = document.createElement('div');\n this.seekRow.className = 'vz-seek-row';\n\n // Button row\n this.container = document.createElement('div');\n this.container.className = 'vz-controls';\n\n // Create sections\n this.leftSection = document.createElement('div');\n this.leftSection.className = 'vz-controls-left';\n this.centerSection = document.createElement('div');\n this.centerSection.className = 'vz-controls-center';\n this.rightSection = document.createElement('div');\n this.rightSection.className = 'vz-controls-right';\n\n this.container.appendChild(this.leftSection);\n this.container.appendChild(this.centerSection);\n this.container.appendChild(this.rightSection);\n\n this.wrapper.appendChild(this.seekRow);\n this.wrapper.appendChild(this.container);\n containerEl.appendChild(this.wrapper);\n\n // Create loading spinner\n this.loadingEl = document.createElement('div');\n this.loadingEl.className = 'vz-loading hidden';\n setStaticHtml(this.loadingEl, '<div class=\"vz-loading-spinner\"></div>');\n containerEl.appendChild(this.loadingEl);\n\n // Auto-hide: show on pointer activity over the container parent\n const onActivity = () => this.show();\n containerEl.addEventListener('pointermove', onActivity);\n containerEl.addEventListener('pointerenter', onActivity);\n this.cleanups.push(\n () => containerEl.removeEventListener('pointermove', onActivity),\n () => containerEl.removeEventListener('pointerenter', onActivity),\n );\n\n // Stop pointer events from reaching the CameraController on the parent container.\n this.wrapper.addEventListener('pointerdown', (e) => e.stopPropagation());\n\n // Keep visible while interacting with controls\n this.wrapper.addEventListener('pointerenter', () => {\n if (this.hideTimer) {\n clearTimeout(this.hideTimer);\n this.hideTimer = null;\n }\n });\n this.wrapper.addEventListener('pointerleave', () => {\n this.scheduleHide();\n });\n\n // Subscribe to loading state\n const unsubLoading = state.subscribe('loading', (loading) => {\n this.loadingEl.classList.toggle('hidden', !loading);\n });\n this.cleanups.push(unsubLoading);\n\n // Subscribe to playing state — auto-hide when playing\n const unsubPlaying = state.subscribe('playing', (playing) => {\n if (playing) {\n this.scheduleHide();\n } else {\n this.show();\n if (this.hideTimer) {\n clearTimeout(this.hideTimer);\n this.hideTimer = null;\n }\n }\n });\n this.cleanups.push(unsubPlaying);\n\n // Start visible\n this.show();\n }\n\n /** Show controls and reset the auto-hide timer */\n show(): void {\n this.wrapper.classList.remove('hidden');\n this.state.set('controlsVisible', true);\n this.scheduleHide();\n }\n\n /** Hide controls */\n hide(): void {\n if (!this.state.get('playing')) return; // Don't hide when paused\n this.wrapper.classList.add('hidden');\n this.state.set('controlsVisible', false);\n }\n\n /** Add a control element to the overlay */\n addControl(el: HTMLElement, section: 'left' | 'center' | 'right' | 'seek'): void {\n switch (section) {\n case 'left':\n this.leftSection.appendChild(el);\n break;\n case 'center':\n this.centerSection.appendChild(el);\n break;\n case 'right':\n this.rightSection.appendChild(el);\n break;\n case 'seek':\n this.seekRow.appendChild(el);\n break;\n }\n }\n\n /**\n * Update theme colors after license validation.\n *\n * Color customisation is always respected — the `primary-color` attribute\n * is the element's accent color regardless of license tier. Tier gating\n * for color pickers lives in the dashboard embed builder UI, not here.\n */\n updateTheme(config: PlayerConfig, _license: LicenseResult): void {\n this.wrapper.style.setProperty(\n '--vz-primary',\n config.primaryColor ?? DEFAULT_CONFIG.primaryColor,\n );\n this.wrapper.style.setProperty(\n '--vz-secondary',\n config.secondaryColor ?? DEFAULT_CONFIG.secondaryColor,\n );\n }\n\n /** Clean up all event listeners and timers */\n dispose(): void {\n if (this.hideTimer) clearTimeout(this.hideTimer);\n for (const cleanup of this.cleanups) cleanup();\n this.cleanups = [];\n this.wrapper.remove();\n this.loadingEl.remove();\n }\n\n private scheduleHide(): void {\n if (this.hideTimer) clearTimeout(this.hideTimer);\n this.hideTimer = setTimeout(() => this.hide(), CONTROLS_HIDE_TIMEOUT);\n }\n}\n","import type { PlayerState } from '../core/player-state.js';\nimport { setStaticHtml } from '../utils/static-html.js';\nimport type { VzMediaElement } from '../core/media-element.js';\nimport { ICON_PLAY, ICON_PAUSE } from './icons.js';\n\n/**\n * Play/pause toggle button.\n */\nexport class PlayButton {\n private button: HTMLButtonElement;\n private bigPlay: HTMLButtonElement;\n private unsubscribes: Array<() => void> = [];\n\n constructor(state: PlayerState, mediaElement: VzMediaElement, containerEl: HTMLElement) {\n // Small control bar button\n this.button = document.createElement('button');\n this.button.className = 'vz-btn vz-play-btn';\n this.button.setAttribute('aria-label', 'Play');\n setStaticHtml(this.button, ICON_PLAY);\n\n // Big center play button\n this.bigPlay = document.createElement('button');\n this.bigPlay.className = 'vz-big-play';\n this.bigPlay.setAttribute('aria-label', 'Play');\n setStaticHtml(this.bigPlay, ICON_PLAY);\n containerEl.appendChild(this.bigPlay);\n\n const toggle = () => {\n if (state.get('paused') || state.get('ended')) {\n mediaElement.play().catch(() => {});\n } else {\n mediaElement.pause();\n }\n };\n\n this.button.addEventListener('click', toggle);\n this.bigPlay.addEventListener('click', toggle);\n\n // Stop pointer events from reaching the CameraController\n this.bigPlay.addEventListener('pointerdown', (e) => e.stopPropagation());\n\n // Subscribe to state changes\n const unsubPlaying = state.subscribe('playing', (playing) => {\n setStaticHtml(this.button, playing ? ICON_PAUSE : ICON_PLAY);\n this.button.setAttribute('aria-label', playing ? 'Pause' : 'Play');\n this.bigPlay.classList.toggle('hidden', playing);\n });\n this.unsubscribes.push(unsubPlaying);\n\n const unsubEnded = state.subscribe('ended', (ended) => {\n if (ended) {\n setStaticHtml(this.button, ICON_PLAY);\n this.button.setAttribute('aria-label', 'Play');\n this.bigPlay.classList.remove('hidden');\n }\n });\n this.unsubscribes.push(unsubEnded);\n }\n\n getElement(): HTMLButtonElement {\n return this.button;\n }\n\n dispose(): void {\n for (const unsub of this.unsubscribes) unsub();\n this.bigPlay.remove();\n }\n}\n","import type { PlayerState } from '../core/player-state.js';\nimport type { VzMediaElement } from '../core/media-element.js';\nimport type { ChapterMarker } from '../chapters/chapter-logic.js';\n\n/**\n * Seek bar with played/buffered progress, time display, and hover tooltip.\n */\nexport class SeekBar {\n private container: HTMLDivElement;\n private timeDisplay: HTMLSpanElement;\n private playedBar: HTMLDivElement;\n private bufferedBar: HTMLDivElement;\n private thumb: HTMLDivElement;\n private tooltip: HTMLDivElement;\n private input: HTMLInputElement;\n private track: HTMLDivElement;\n private chapterLayer: HTMLDivElement | null = null;\n private unsubscribes: Array<() => void> = [];\n\n constructor(state: PlayerState, mediaElement: VzMediaElement) {\n // Time display\n this.timeDisplay = document.createElement('span');\n this.timeDisplay.className = 'vz-time';\n this.timeDisplay.textContent = '0:00 / 0:00';\n\n // Seek container\n this.container = document.createElement('div');\n this.container.className = 'vz-seek-container';\n\n // Hide seek bar and time display in live mode (replaced by live indicator)\n const unsubLive = state.subscribe('isLive', (isLive) => {\n this.container.style.display = isLive ? 'none' : '';\n this.timeDisplay.style.display = isLive ? 'none' : '';\n });\n this.unsubscribes.push(unsubLive);\n\n this.track = document.createElement('div');\n this.track.className = 'vz-seek-track';\n\n this.bufferedBar = document.createElement('div');\n this.bufferedBar.className = 'vz-seek-buffered';\n\n this.playedBar = document.createElement('div');\n this.playedBar.className = 'vz-seek-played';\n\n this.thumb = document.createElement('div');\n this.thumb.className = 'vz-seek-thumb';\n\n // Hover time tooltip\n this.tooltip = document.createElement('div');\n this.tooltip.className = 'vz-seek-tooltip';\n this.tooltip.textContent = '0:00';\n\n this.input = document.createElement('input');\n this.input.className = 'vz-seek-input';\n this.input.type = 'range';\n this.input.min = '0';\n this.input.max = '1000';\n this.input.value = '0';\n this.input.setAttribute('aria-label', 'Seek');\n\n this.track.appendChild(this.bufferedBar);\n this.track.appendChild(this.playedBar);\n this.track.appendChild(this.thumb);\n this.track.appendChild(this.tooltip);\n this.container.appendChild(this.track);\n this.container.appendChild(this.input);\n\n // Input events — seeking\n this.input.addEventListener('input', () => {\n const duration = state.get('duration');\n if (duration > 0) {\n const time = (parseInt(this.input.value) / 1000) * duration;\n mediaElement.seek(time);\n }\n });\n\n // Hover tooltip — show time at cursor position\n this.container.addEventListener('pointermove', (e) => {\n const duration = state.get('duration');\n if (duration <= 0) return;\n\n const rect = this.track.getBoundingClientRect();\n const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));\n const hoverTime = pct * duration;\n\n this.tooltip.textContent = this.formatTime(hoverTime);\n this.tooltip.style.left = `${pct * 100}%`;\n });\n\n // Subscribe to state\n const unsubTime = state.subscribe('currentTime', (time) => {\n const duration = state.get('duration');\n this.updateProgress(time, duration);\n });\n this.unsubscribes.push(unsubTime);\n\n const unsubDuration = state.subscribe('duration', (duration) => {\n const time = state.get('currentTime');\n this.updateProgress(time, duration);\n });\n this.unsubscribes.push(unsubDuration);\n\n const unsubBuffered = state.subscribe('buffered', (buffered) => {\n const duration = state.get('duration');\n if (duration > 0) {\n this.bufferedBar.style.width = `${(buffered / duration) * 100}%`;\n }\n });\n this.unsubscribes.push(unsubBuffered);\n }\n\n getElement(): HTMLDivElement {\n return this.container;\n }\n\n /**\n * Render chapter tick markers on the seek track. Markers are purely visual\n * (pointer-events: none) so they never interfere with the range input; the\n * native `title` shows the chapter label on hover. Re-render on duration\n * change by calling again; pass [] to clear.\n */\n setChapterMarkers(markers: ChapterMarker[]): void {\n if (!this.chapterLayer) {\n this.chapterLayer = document.createElement('div');\n this.chapterLayer.className = 'vz-seek-chapters';\n this.track.appendChild(this.chapterLayer);\n }\n this.chapterLayer.replaceChildren();\n for (const m of markers) {\n const tick = document.createElement('div');\n tick.className = 'vz-seek-chapter-marker';\n tick.style.left = `${m.position * 100}%`;\n tick.title = m.title;\n this.chapterLayer.appendChild(tick);\n }\n }\n\n getTimeDisplay(): HTMLSpanElement {\n return this.timeDisplay;\n }\n\n dispose(): void {\n for (const unsub of this.unsubscribes) unsub();\n }\n\n private updateProgress(time: number, duration: number): void {\n if (duration > 0) {\n const pct = (time / duration) * 100;\n this.playedBar.style.width = `${pct}%`;\n this.thumb.style.left = `${pct}%`;\n this.input.value = String(Math.round((time / duration) * 1000));\n }\n this.timeDisplay.textContent = `${this.formatTime(time)} / ${this.formatTime(duration)}`;\n }\n\n private formatTime(seconds: number): string {\n if (isNaN(seconds) || !isFinite(seconds)) return '0:00';\n const s = Math.floor(seconds);\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n const sec = s % 60;\n if (h > 0) {\n return `${h}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;\n }\n return `${m}:${String(sec).padStart(2, '0')}`;\n }\n}\n","import type { PlayerState } from '../core/player-state.js';\nimport { setStaticHtml } from '../utils/static-html.js';\nimport type { VzMediaElement } from '../core/media-element.js';\nimport { ICON_VOLUME_HIGH, ICON_VOLUME_LOW, ICON_VOLUME_MUTED } from './icons.js';\n\n/**\n * Volume slider and mute toggle button.\n * Slider slides out horizontally on hover.\n */\nexport class VolumeControl {\n private container: HTMLDivElement;\n private unsubscribes: Array<() => void> = [];\n\n constructor(state: PlayerState, mediaElement: VzMediaElement) {\n this.container = document.createElement('div');\n this.container.className = 'vz-volume';\n\n // Mute button\n const muteBtn = document.createElement('button');\n muteBtn.className = 'vz-btn vz-mute-btn';\n muteBtn.setAttribute('aria-label', 'Mute');\n setStaticHtml(muteBtn, ICON_VOLUME_HIGH);\n\n muteBtn.addEventListener('click', () => {\n mediaElement.muted = !mediaElement.muted;\n });\n\n // Volume slider wrapped for slide-out animation\n const sliderWrap = document.createElement('div');\n sliderWrap.className = 'vz-volume-slider-wrap';\n\n const slider = document.createElement('input');\n slider.className = 'vz-volume-slider';\n slider.type = 'range';\n slider.min = '0';\n slider.max = '100';\n slider.value = String(Math.round(state.get('volume') * 100));\n slider.setAttribute('aria-label', 'Volume');\n\n const updateSliderTrack = (pct: number) => {\n slider.style.background = `linear-gradient(to right, var(--vz-primary, #f43f5e) ${pct}%, rgba(255,255,255,0.3) ${pct}%)`;\n };\n\n // Set initial track fill\n updateSliderTrack(Math.round(state.get('volume') * 100));\n\n slider.addEventListener('input', () => {\n const vol = parseInt(slider.value) / 100;\n mediaElement.volume = vol;\n if (vol > 0 && mediaElement.muted) {\n mediaElement.muted = false;\n }\n updateSliderTrack(parseInt(slider.value));\n });\n\n sliderWrap.appendChild(slider);\n this.container.appendChild(muteBtn);\n this.container.appendChild(sliderWrap);\n\n // Subscribe to state\n const unsubVolume = state.subscribe('volume', (volume) => {\n const pct = Math.round(volume * 100);\n slider.value = String(pct);\n updateSliderTrack(pct);\n if (!state.get('muted')) {\n setStaticHtml(muteBtn, volume > 0.5 ? ICON_VOLUME_HIGH : (volume > 0 ? ICON_VOLUME_LOW : ICON_VOLUME_MUTED));\n }\n });\n this.unsubscribes.push(unsubVolume);\n\n const unsubMuted = state.subscribe('muted', (muted) => {\n setStaticHtml(muteBtn, muted ? ICON_VOLUME_MUTED : (state.get('volume') > 0.5 ? ICON_VOLUME_HIGH : ICON_VOLUME_LOW));\n muteBtn.setAttribute('aria-label', muted ? 'Unmute' : 'Mute');\n });\n this.unsubscribes.push(unsubMuted);\n }\n\n getElement(): HTMLDivElement {\n return this.container;\n }\n\n dispose(): void {\n for (const unsub of this.unsubscribes) unsub();\n }\n}\n","import type { PlayerState } from '../core/player-state.js';\nimport { setStaticHtml } from '../utils/static-html.js';\nimport { ICON_FULLSCREEN, ICON_FULLSCREEN_EXIT } from './icons.js';\n\ninterface FullscreenTarget {\n toggleFullscreen(): void;\n}\n\n/**\n * Fullscreen toggle button.\n */\nexport class FullscreenButton {\n private button: HTMLButtonElement;\n private unsubscribes: Array<() => void> = [];\n\n constructor(state: PlayerState, mediaElement: FullscreenTarget) {\n this.button = document.createElement('button');\n this.button.className = 'vz-btn vz-fullscreen-btn';\n this.button.setAttribute('aria-label', 'Enter fullscreen');\n setStaticHtml(this.button, ICON_FULLSCREEN);\n\n this.button.addEventListener('click', () => {\n mediaElement.toggleFullscreen();\n });\n\n const unsubFs = state.subscribe('fullscreen', (fs) => {\n setStaticHtml(this.button, fs ? ICON_FULLSCREEN_EXIT : ICON_FULLSCREEN);\n this.button.setAttribute('aria-label', fs ? 'Exit fullscreen' : 'Enter fullscreen');\n });\n this.unsubscribes.push(unsubFs);\n }\n\n getElement(): HTMLButtonElement {\n return this.button;\n }\n\n dispose(): void {\n for (const unsub of this.unsubscribes) unsub();\n }\n}\n","import type { PlayerState } from '../core/player-state.js';\nimport { setStaticHtml } from '../utils/static-html.js';\nimport { ICON_VR } from './icons.js';\n\n/**\n * Enter VR button.\n * Checks WebXR support and hides itself if unavailable.\n * WebXR session management is Phase 2 — currently a placeholder.\n */\nexport class VrButton {\n private button: HTMLButtonElement;\n private unsubscribes: Array<() => void> = [];\n\n constructor(state: PlayerState, onToggleXR?: () => void) {\n this.button = document.createElement('button');\n this.button.className = 'vz-btn vz-vr-btn';\n this.button.setAttribute('aria-label', 'Enter VR');\n setStaticHtml(this.button, ICON_VR);\n this.button.style.display = 'none'; // Hidden until XR is confirmed supported\n\n this.button.addEventListener('click', () => {\n onToggleXR?.();\n });\n\n // Check for WebXR support\n if (navigator.xr) {\n navigator.xr.isSessionSupported('immersive-vr').then((supported) => {\n state.set('xrSupported', supported);\n if (supported) {\n this.button.style.display = '';\n }\n }).catch(() => {\n // WebXR not available\n });\n }\n\n const unsubXr = state.subscribe('xrActive', (active) => {\n this.button.setAttribute('aria-label', active ? 'Exit VR' : 'Enter VR');\n });\n this.unsubscribes.push(unsubXr);\n }\n\n getElement(): HTMLButtonElement {\n return this.button;\n }\n\n dispose(): void {\n for (const unsub of this.unsubscribes) unsub();\n }\n}\n","import type { PlayerState } from '../core/player-state.js';\nimport { setStaticHtml } from '../utils/static-html.js';\nimport type { VzRenderer } from '../renderer/renderer.js';\nimport { ICON_COMPASS } from './icons.js';\n\n/**\n * Compass indicator for 360 content.\n * Rotates to show the current viewing direction.\n * Click to reset to north (lon=0).\n */\nexport class Compass {\n private button: HTMLButtonElement;\n private unsubscribes: Array<() => void> = [];\n\n constructor(state: PlayerState, renderer: VzRenderer) {\n this.button = document.createElement('button');\n this.button.className = 'vz-btn vz-compass';\n this.button.setAttribute('aria-label', 'Reset to north');\n setStaticHtml(this.button, ICON_COMPASS);\n\n this.button.addEventListener('click', () => {\n const controller = renderer.getCameraController();\n controller.setOrientation(state.get('lat'), 0);\n });\n\n const unsubLon = state.subscribe('lon', (lon) => {\n const svg = this.button.querySelector('svg');\n if (svg) {\n svg.style.transform = `rotate(${-lon}deg)`;\n }\n });\n this.unsubscribes.push(unsubLon);\n }\n\n getElement(): HTMLButtonElement {\n return this.button;\n }\n\n dispose(): void {\n for (const unsub of this.unsubscribes) unsub();\n }\n}\n","import type { PlayerState } from '../core/player-state.js';\nimport { setStaticHtml } from '../utils/static-html.js';\nimport type { SourceManager } from '../streaming/source-manager.js';\nimport { ICON_SETTINGS } from './icons.js';\n\n/**\n * Quality level selector dropdown.\n * Hidden when only one quality level is available (progressive MP4).\n */\nexport class QualityMenu {\n private container: HTMLDivElement;\n private dropdown: HTMLDivElement;\n private unsubscribes: Array<() => void> = [];\n private isOpen = false;\n\n constructor(state: PlayerState, sourceManager: SourceManager) {\n this.container = document.createElement('div');\n this.container.className = 'vz-quality-menu';\n\n const btn = document.createElement('button');\n btn.className = 'vz-btn vz-quality-btn';\n btn.setAttribute('aria-label', 'Quality');\n setStaticHtml(btn, ICON_SETTINGS);\n\n this.dropdown = document.createElement('div');\n this.dropdown.className = 'vz-quality-dropdown';\n\n this.container.appendChild(btn);\n this.container.appendChild(this.dropdown);\n\n // Toggle dropdown\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.isOpen = !this.isOpen;\n this.dropdown.classList.toggle('open', this.isOpen);\n });\n\n // Close on outside click\n const onDocClick = () => {\n if (this.isOpen) {\n this.isOpen = false;\n this.dropdown.classList.remove('open');\n }\n };\n document.addEventListener('click', onDocClick);\n this.unsubscribes.push(() => document.removeEventListener('click', onDocClick));\n\n // Subscribe to quality levels\n const unsubLevels = state.subscribe('availableQualities', (levels) => {\n // Hide if no quality options\n this.container.style.display = levels.length <= 1 ? 'none' : '';\n this.renderOptions(state, sourceManager);\n });\n this.unsubscribes.push(unsubLevels);\n\n // Initially hidden\n this.container.style.display = 'none';\n }\n\n getElement(): HTMLDivElement {\n return this.container;\n }\n\n dispose(): void {\n for (const unsub of this.unsubscribes) unsub();\n }\n\n private renderOptions(state: PlayerState, sourceManager: SourceManager): void {\n const levels = state.get('availableQualities');\n const currentQuality = state.get('currentQuality');\n this.dropdown.replaceChildren();\n\n // Auto option\n const autoOption = document.createElement('div');\n autoOption.className = `vz-quality-option ${currentQuality === 'auto' ? 'active' : ''}`;\n const autoCheck = document.createElement('span');\n autoCheck.className = 'vz-quality-check';\n autoCheck.textContent = currentQuality === 'auto' ? '✓' : '';\n autoOption.append(autoCheck, document.createTextNode('Auto'));\n autoOption.addEventListener('click', () => {\n sourceManager.setQualityLevel(-1);\n this.isOpen = false;\n this.dropdown.classList.remove('open');\n });\n this.dropdown.appendChild(autoOption);\n\n // Quality levels\n levels.forEach((level, index) => {\n const option = document.createElement('div');\n const isActive = level.label === currentQuality;\n option.className = `vz-quality-option ${isActive ? 'active' : ''}`;\n // Build with DOM APIs, not innerHTML: level.label originates in the HLS\n // manifest and must render as inert text, never parsed HTML.\n const check = document.createElement('span');\n check.className = 'vz-quality-check';\n check.textContent = isActive ? '✓' : '';\n option.append(check, document.createTextNode(level.label));\n option.addEventListener('click', () => {\n sourceManager.setQualityLevel(index);\n this.isOpen = false;\n this.dropdown.classList.remove('open');\n });\n this.dropdown.appendChild(option);\n });\n }\n}\n","import type { PlayerState } from '../core/player-state.js';\nimport type { VzMediaElement } from '../core/media-element.js';\nimport type { CaptionRenderer } from '../captions/caption-renderer.js';\nimport type { ChatPanel } from '../collaborative/chat-panel.js';\nimport type { VoiceButton } from '../collaborative/voice-button.js';\nimport { SEEK_STEP, VOLUME_STEP } from '@vizor-vr/shared';\n\n/**\n * Keyboard shortcut handler for the player.\n * Listens on the host element to avoid conflicts with\n * CameraController's arrow key handling in the container.\n */\nexport class KeyboardShortcuts {\n private hostElement: HTMLElement;\n private handler: (e: KeyboardEvent) => void;\n\n private captionRenderer: CaptionRenderer | null;\n private chatPanel: ChatPanel | null;\n private voiceButton: VoiceButton | null;\n\n constructor(\n hostElement: HTMLElement,\n state: PlayerState,\n mediaElement: VzMediaElement,\n captionRenderer?: CaptionRenderer | null,\n chatPanel?: ChatPanel | null,\n voiceButton?: VoiceButton | null,\n ) {\n this.captionRenderer = captionRenderer ?? null;\n this.chatPanel = chatPanel ?? null;\n this.voiceButton = voiceButton ?? null;\n this.hostElement = hostElement;\n\n // Make the element focusable\n if (!hostElement.hasAttribute('tabindex')) {\n hostElement.setAttribute('tabindex', '0');\n }\n\n this.handler = (e: KeyboardEvent) => {\n // Don't handle if a modifier key is held\n if (e.ctrlKey || e.metaKey || e.altKey) return;\n\n switch (e.code) {\n case 'Space':\n case 'KeyK':\n e.preventDefault();\n if (state.get('paused') || state.get('ended')) {\n mediaElement.play().catch(() => {});\n } else {\n mediaElement.pause();\n }\n break;\n\n case 'KeyM':\n e.preventDefault();\n mediaElement.muted = !mediaElement.muted;\n break;\n\n case 'KeyF':\n e.preventDefault();\n mediaElement.toggleFullscreen();\n break;\n\n case 'Home':\n e.preventDefault();\n mediaElement.seek(0);\n break;\n\n case 'End':\n e.preventDefault();\n mediaElement.seek(state.get('duration'));\n break;\n\n case 'ArrowLeft':\n // Only handle for seeking in flat (non-panoramic) mode\n // Panoramic mode arrow keys are handled by CameraController\n if (!this.isPanoramic(state)) {\n e.preventDefault();\n mediaElement.seek(Math.max(0, mediaElement.currentTime - SEEK_STEP));\n }\n break;\n\n case 'ArrowRight':\n if (!this.isPanoramic(state)) {\n e.preventDefault();\n mediaElement.seek(Math.min(state.get('duration'), mediaElement.currentTime + SEEK_STEP));\n }\n break;\n\n case 'ArrowUp':\n if (!this.isPanoramic(state)) {\n e.preventDefault();\n mediaElement.volume = Math.min(1, mediaElement.volume + VOLUME_STEP);\n }\n break;\n\n case 'ArrowDown':\n if (!this.isPanoramic(state)) {\n e.preventDefault();\n mediaElement.volume = Math.max(0, mediaElement.volume - VOLUME_STEP);\n }\n break;\n\n case 'Comma':\n // Previous frame (when paused)\n if (state.get('paused')) {\n e.preventDefault();\n mediaElement.seek(Math.max(0, mediaElement.currentTime - (1 / 30)));\n }\n break;\n\n case 'Period':\n // Next frame (when paused)\n if (state.get('paused')) {\n e.preventDefault();\n mediaElement.seek(mediaElement.currentTime + (1 / 30));\n }\n break;\n\n case 'KeyC':\n e.preventDefault();\n this.captionRenderer?.toggle();\n break;\n\n case 'KeyT':\n e.preventDefault();\n this.chatPanel?.toggle();\n break;\n\n case 'KeyV':\n e.preventDefault();\n this.voiceButton?.toggle();\n break;\n\n default:\n return; // Don't prevent default for unhandled keys\n }\n };\n\n hostElement.addEventListener('keydown', this.handler as EventListener);\n }\n\n dispose(): void {\n this.hostElement.removeEventListener('keydown', this.handler as EventListener);\n }\n\n private isPanoramic(state: PlayerState): boolean {\n const format = state.get('format');\n if (!format) return false;\n return format.includes('360') || format.includes('180');\n }\n}\n","import type { WebGLRenderer, Group } from 'three';\n\n/**\n * Handles VR controller input during WebXR sessions.\n *\n * Listens for `select` events on controllers (trigger press).\n * Default behavior: toggle play/pause. Tours can override via setSelectHandler.\n *\n * Three.js dispatches XR controller events (select, selectstart, selectend,\n * squeeze, etc.) on the Group returned by renderer.xr.getController().\n * These aren't in the typed Object3DEventMap, so we use EventDispatcher directly.\n */\nexport class XRControllerInput {\n private renderer: WebGLRenderer;\n private onSelect: (() => void) | null = null;\n private cleanups: Array<() => void> = [];\n private controller0: Group | null = null;\n private controller1: Group | null = null;\n\n constructor(renderer: WebGLRenderer) {\n this.renderer = renderer;\n }\n\n /** Set custom handler for controller select (trigger press) */\n setSelectHandler(handler: () => void): void {\n this.onSelect = handler;\n }\n\n /** Start listening for controller events */\n attach(): void {\n this.detach();\n\n this.controller0 = this.renderer.xr.getController(0);\n this.controller1 = this.renderer.xr.getController(1);\n\n const handler = () => {\n this.onSelect?.();\n };\n\n // Three.js XR controller events (select, squeeze, etc.) are dispatched\n // via EventDispatcher but aren't in the typed event map.\n // Use type assertion to bypass the strict event map check.\n const c0 = this.controller0 as Group & { addEventListener(type: string, listener: () => void): void; removeEventListener(type: string, listener: () => void): void };\n const c1 = this.controller1 as Group & { addEventListener(type: string, listener: () => void): void; removeEventListener(type: string, listener: () => void): void };\n\n c0.addEventListener('select', handler);\n c1.addEventListener('select', handler);\n\n this.cleanups.push(\n () => c0.removeEventListener('select', handler),\n () => c1.removeEventListener('select', handler),\n );\n }\n\n /** Stop listening for controller events */\n detach(): void {\n for (const cleanup of this.cleanups) cleanup();\n this.cleanups = [];\n this.controller0 = null;\n this.controller1 = null;\n }\n\n dispose(): void {\n this.detach();\n this.onSelect = null;\n }\n}\n","import type { WebGLRenderer } from 'three';\nimport type { PlayerState } from '../core/player-state.js';\nimport type { VzEventEmitter } from '../core/event-emitter.js';\nimport type { CameraController } from '../renderer/camera-controller.js';\nimport { XRControllerInput } from './xr-controller.js';\n\n/**\n * Manages the WebXR session lifecycle.\n *\n * When entering XR:\n * - Requests an immersive-vr session\n * - Connects it to Three.js via renderer.xr.setSession()\n * - Three.js swaps to XRSession.requestAnimationFrame and renders per-eye\n * - Our layer-based stereo (Layer 1=left, Layer 2=right) maps directly\n * - CameraController is disabled (headset drives camera)\n *\n * When exiting XR:\n * - Three.js reverts to window.requestAnimationFrame\n * - CameraController is re-enabled\n */\nexport class XRManager {\n private renderer: WebGLRenderer;\n private state: PlayerState;\n private events: VzEventEmitter;\n private cameraController: CameraController;\n private session: XRSession | null = null;\n private controllerInput: XRControllerInput;\n\n constructor(\n renderer: WebGLRenderer,\n state: PlayerState,\n events: VzEventEmitter,\n cameraController: CameraController,\n ) {\n this.renderer = renderer;\n this.state = state;\n this.events = events;\n this.cameraController = cameraController;\n this.controllerInput = new XRControllerInput(renderer);\n }\n\n /** Check if immersive-vr sessions are supported */\n static async isSupported(): Promise<boolean> {\n if (!navigator.xr) return false;\n try {\n return await navigator.xr.isSessionSupported('immersive-vr');\n } catch {\n return false;\n }\n }\n\n /** Request and start an immersive-vr session */\n async enterXR(): Promise<void> {\n if (this.session) return;\n if (!navigator.xr) throw new Error('WebXR not available');\n\n const session = await navigator.xr.requestSession('immersive-vr', {\n optionalFeatures: ['local-floor', 'hand-tracking'],\n });\n\n this.session = session;\n\n // Connect session to Three.js renderer — this makes Three.js use\n // XRSession.requestAnimationFrame and render to the XR framebuffer\n await this.renderer.xr.setSession(session);\n\n // Get reference space (prefer local-floor for standing experiences)\n let referenceSpace: XRReferenceSpace;\n try {\n referenceSpace = await session.requestReferenceSpace('local-floor');\n } catch {\n referenceSpace = await session.requestReferenceSpace('local');\n }\n this.renderer.xr.setReferenceSpace(referenceSpace);\n\n // Disable mouse/touch camera — headset drives camera\n this.cameraController.setEnabled(false);\n\n // Attach controller input (trigger = play/pause by default)\n this.controllerInput.attach();\n\n // Update state and emit event\n this.state.set('xrActive', true);\n this.events.emit('vz-xr-enter');\n\n // Listen for session end (user exits via headset button)\n session.addEventListener('end', this.onSessionEnd);\n }\n\n /** End the current XR session */\n async exitXR(): Promise<void> {\n if (!this.session) return;\n await this.session.end();\n // onSessionEnd will handle cleanup\n }\n\n /** Toggle XR on/off */\n async toggleXR(): Promise<void> {\n if (this.session) {\n await this.exitXR();\n } else {\n await this.enterXR();\n }\n }\n\n /** Whether an XR session is currently active */\n get isActive(): boolean {\n return this.session !== null;\n }\n\n /** Set handler for controller select (trigger press) */\n setSelectHandler(handler: () => void): void {\n this.controllerInput.setSelectHandler(handler);\n }\n\n /** Clean up */\n dispose(): void {\n this.controllerInput.dispose();\n if (this.session) {\n this.session.removeEventListener('end', this.onSessionEnd);\n this.session.end().catch(() => {});\n this.session = null;\n }\n }\n\n private onSessionEnd = (): void => {\n this.session?.removeEventListener('end', this.onSessionEnd);\n this.session = null;\n\n // Detach controller input\n this.controllerInput.detach();\n\n // Re-enable mouse/touch camera controller\n this.cameraController.setEnabled(true);\n\n // Update state and emit event\n this.state.set('xrActive', false);\n this.events.emit('vz-xr-exit');\n };\n}\n","import type { PlayerState } from '../core/player-state.js';\nimport { setStaticHtml } from '../utils/static-html.js';\nimport { ICON_LIVE_DOT } from './icons.js';\n\n/**\n * Live indicator control.\n * Shows a red pulsing dot + \"LIVE\" text when at the live edge.\n * Grey dot when behind the live edge. Clicking seeks back to live.\n */\nexport class LiveIndicator {\n private container: HTMLDivElement;\n private dotEl: HTMLSpanElement;\n private labelEl: HTMLSpanElement;\n private unsubscribes: Array<() => void> = [];\n\n constructor(state: PlayerState, private onSeekToLive: () => void) {\n this.container = document.createElement('div');\n this.container.className = 'vz-live-indicator';\n this.container.title = 'Jump to live';\n\n this.dotEl = document.createElement('span');\n this.dotEl.className = 'vz-live-dot at-edge';\n setStaticHtml(this.dotEl, ICON_LIVE_DOT);\n\n this.labelEl = document.createElement('span');\n this.labelEl.className = 'vz-live-label';\n this.labelEl.textContent = 'LIVE';\n\n this.container.appendChild(this.dotEl);\n this.container.appendChild(this.labelEl);\n\n // Click → seek to live edge\n this.container.addEventListener('click', () => {\n this.onSeekToLive();\n });\n\n // Subscribe to live edge state\n const unsubEdge = state.subscribe('isAtLiveEdge', (atEdge) => {\n if (atEdge) {\n this.dotEl.classList.add('at-edge');\n this.dotEl.classList.remove('behind');\n this.container.title = 'Live';\n } else {\n this.dotEl.classList.remove('at-edge');\n this.dotEl.classList.add('behind');\n this.container.title = 'Click to jump to live';\n }\n });\n this.unsubscribes.push(unsubEdge);\n\n // Only show when live\n const unsubLive = state.subscribe('isLive', (isLive) => {\n this.container.style.display = isLive ? '' : 'none';\n });\n this.unsubscribes.push(unsubLive);\n\n // Start hidden (will be shown when isLive becomes true)\n this.container.style.display = 'none';\n }\n\n getElement(): HTMLDivElement {\n return this.container;\n }\n\n dispose(): void {\n for (const unsub of this.unsubscribes) unsub();\n this.unsubscribes = [];\n }\n}\n","/**\n * Analytics event collector.\n *\n * Captures player events (play, pause, seek, etc.), batches them,\n * and POSTs to the analytics API endpoint at regular intervals.\n *\n * Feature-gated: only active when `license.features.analytics === true`.\n */\n\nimport type { AnalyticsEventType, AnalyticsEvent } from '@vizor-vr/shared';\nimport { ANALYTICS_BATCH_INTERVAL } from '@vizor-vr/shared';\n\nexport interface AnalyticsCollectorConfig {\n contentId: string;\n sessionId: string;\n apiEndpoint: string;\n apiKey?: string;\n}\n\nexport class AnalyticsCollector {\n private queue: AnalyticsEvent[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private config: AnalyticsCollectorConfig;\n private visibilityHandler: (() => void) | null = null;\n\n constructor(config: AnalyticsCollectorConfig) {\n this.config = config;\n\n // Start periodic flush\n this.flushTimer = setInterval(() => {\n this.flush();\n }, ANALYTICS_BATCH_INTERVAL);\n\n // Flush on page unload (store reference for cleanup)\n if (typeof window !== 'undefined') {\n this.visibilityHandler = () => {\n if (document.visibilityState === 'hidden') {\n this.flush();\n }\n };\n window.addEventListener('visibilitychange', this.visibilityHandler);\n }\n }\n\n /** Record an analytics event */\n track(type: AnalyticsEventType, data?: Record<string, unknown>): void {\n this.queue.push({\n type,\n contentId: this.config.contentId,\n sessionId: this.config.sessionId,\n timestamp: Date.now(),\n data,\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,\n });\n }\n\n /** Flush the event queue to the API */\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const events = [...this.queue];\n this.queue = [];\n\n try {\n const url = `${this.config.apiEndpoint}/api/v1/analytics/events`;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.config.apiKey) {\n headers['x-api-key'] = this.config.apiKey;\n }\n\n // Use sendBeacon if available (for page unload), otherwise fetch\n if (document.visibilityState === 'hidden' && navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify({ events })], {\n type: 'application/json',\n });\n navigator.sendBeacon(url, blob);\n } else {\n await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify({ events }),\n keepalive: true,\n });\n }\n } catch {\n // Re-add failed events to queue (up to 100 max to prevent memory leak)\n this.queue.unshift(...events.slice(0, Math.max(0, 100 - this.queue.length)));\n }\n }\n\n /** Stop the collector and flush remaining events */\n dispose(): void {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n if (this.visibilityHandler) {\n window.removeEventListener('visibilitychange', this.visibilityHandler);\n this.visibilityHandler = null;\n }\n this.flush();\n }\n}\n","/**\n * Gaze tracker for heatmap analytics.\n *\n * Samples camera orientation (lat, lon, fov) at ~3Hz and batches the\n * samples before POSTing to the analytics API.\n *\n * Feature-gated: only active when `license.features.analyticsHeatmaps === true`.\n */\n\nimport type { GazeSample } from '@vizor-vr/shared';\nimport { GAZE_SAMPLE_INTERVAL, GAZE_BATCH_INTERVAL } from '@vizor-vr/shared';\n\nexport interface GazeTrackerConfig {\n contentId: string;\n sessionId: string;\n apiEndpoint: string;\n apiKey?: string;\n}\n\n/** Function returning the current camera orientation */\nexport type OrientationProvider = () => { lat: number; lon: number; fov: number };\n\n/** Function returning the current video time */\nexport type ContentTimeProvider = () => number;\n\nexport class GazeTracker {\n private queue: GazeSample[] = [];\n private sampleTimer: ReturnType<typeof setInterval> | null = null;\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private config: GazeTrackerConfig;\n private getOrientation: OrientationProvider;\n private getContentTime: ContentTimeProvider;\n private active = false;\n private visibilityHandler: (() => void) | null = null;\n\n constructor(\n config: GazeTrackerConfig,\n getOrientation: OrientationProvider,\n getContentTime: ContentTimeProvider,\n ) {\n this.config = config;\n this.getOrientation = getOrientation;\n this.getContentTime = getContentTime;\n\n // Flush on page hide (store reference for cleanup)\n if (typeof window !== 'undefined') {\n this.visibilityHandler = () => {\n if (document.visibilityState === 'hidden') {\n this.flush();\n }\n };\n window.addEventListener('visibilitychange', this.visibilityHandler);\n }\n }\n\n /** Start sampling gaze data (call when video is playing) */\n start(): void {\n if (this.active) return;\n this.active = true;\n\n // Sample at ~3Hz\n this.sampleTimer = setInterval(() => {\n this.sample();\n }, GAZE_SAMPLE_INTERVAL);\n\n // Flush at batch interval\n this.flushTimer = setInterval(() => {\n this.flush();\n }, GAZE_BATCH_INTERVAL);\n }\n\n /** Stop sampling (call when video is paused) */\n stop(): void {\n if (!this.active) return;\n this.active = false;\n\n if (this.sampleTimer) {\n clearInterval(this.sampleTimer);\n this.sampleTimer = null;\n }\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n }\n\n /** Take a single gaze sample */\n private sample(): void {\n const orientation = this.getOrientation();\n const contentTime = this.getContentTime();\n\n this.queue.push({\n contentId: this.config.contentId,\n sessionId: this.config.sessionId,\n contentTime,\n lat: Math.round(orientation.lat * 100) / 100,\n lon: Math.round(orientation.lon * 100) / 100,\n fov: Math.round(orientation.fov * 10) / 10,\n });\n }\n\n /** Flush gaze samples to the API */\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n\n const samples = [...this.queue];\n this.queue = [];\n\n try {\n const url = `${this.config.apiEndpoint}/api/v1/analytics/gaze`;\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.config.apiKey) {\n headers['x-api-key'] = this.config.apiKey;\n }\n\n if (document.visibilityState === 'hidden' && navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify({ samples })], {\n type: 'application/json',\n });\n navigator.sendBeacon(url, blob);\n } else {\n await fetch(url, {\n method: 'POST',\n headers,\n body: JSON.stringify({ samples }),\n keepalive: true,\n });\n }\n } catch {\n // Re-add failed samples (cap at 300 to limit memory)\n this.queue.unshift(...samples.slice(0, Math.max(0, 300 - this.queue.length)));\n }\n }\n\n /** Stop tracking and flush remaining data */\n dispose(): void {\n this.stop();\n this.flush();\n if (this.visibilityHandler) {\n window.removeEventListener('visibilitychange', this.visibilityHandler);\n this.visibilityHandler = null;\n }\n }\n}\n","/**\n * Analytics initialization.\n *\n * Creates the AnalyticsCollector and GazeTracker based on license features,\n * attaches event listeners to the player, and returns a cleanup function.\n */\n\nimport type { FeatureFlags } from '@vizor-vr/shared';\nimport type { PlayerState } from '../core/player-state.js';\nimport type { VzEventEmitter } from '../core/event-emitter.js';\nimport { AnalyticsCollector } from './analytics-collector.js';\nimport { GazeTracker, type OrientationProvider, type ContentTimeProvider } from './gaze-tracker.js';\n\nexport interface AnalyticsOptions {\n contentId: string;\n apiEndpoint: string;\n apiKey?: string;\n features: FeatureFlags;\n getOrientation: OrientationProvider;\n getContentTime: ContentTimeProvider;\n}\n\nexport interface AnalyticsHandle {\n collector: AnalyticsCollector | null;\n gazeTracker: GazeTracker | null;\n dispose: () => void;\n}\n\n/**\n * Initialize analytics subsystem.\n *\n * Attaches event listeners to state changes and player events.\n * Returns a handle with collector, gazeTracker, and dispose function.\n */\nexport function initAnalytics(\n state: PlayerState,\n events: VzEventEmitter,\n options: AnalyticsOptions,\n): AnalyticsHandle {\n const sessionId = crypto.randomUUID();\n const cleanups: Array<() => void> = [];\n\n let collector: AnalyticsCollector | null = null;\n let gazeTracker: GazeTracker | null = null;\n\n // --- Analytics event collection ---\n if (options.features.analytics) {\n collector = new AnalyticsCollector({\n contentId: options.contentId,\n sessionId,\n apiEndpoint: options.apiEndpoint,\n apiKey: options.apiKey,\n });\n\n // --- Watch-time accumulation (WS-C delivery estimation) ---\n // Wall-clock time while `playing` is true, reported as `watch_time` events\n // ({ seconds, quality }) on pause/ended/dispose and roughly every\n // WATCH_TIME_REPORT_SECONDS during continuous playback, so a closed tab\n // loses at most one interval. The API estimates delivered GB from these.\n const WATCH_TIME_REPORT_SECONDS = 60;\n let playStartMs: number | null = null;\n let accumulatedMs = 0;\n\n const reportWatchTime = () => {\n if (playStartMs != null) {\n accumulatedMs += Date.now() - playStartMs;\n playStartMs = state.get('playing') ? Date.now() : null;\n }\n const seconds = Math.round(accumulatedMs / 1000);\n if (seconds >= 1) {\n const quality = state.get('currentQuality');\n collector!.track('watch_time', { seconds, ...(quality ? { quality } : {}) });\n accumulatedMs = 0;\n }\n };\n\n // Periodic report during continuous playback.\n const watchTimer = setInterval(() => {\n const liveMs = accumulatedMs + (playStartMs != null ? Date.now() - playStartMs : 0);\n if (liveMs >= WATCH_TIME_REPORT_SECONDS * 1000) reportWatchTime();\n }, 10_000);\n cleanups.push(() => clearInterval(watchTimer));\n\n // Report before the tab goes hidden, then flush so the event ships (the\n // collector's own visibilitychange flush ran first — it was registered in\n // the constructor above — so it missed this event).\n if (typeof window !== 'undefined') {\n const watchVisibilityHandler = () => {\n if (document.visibilityState === 'hidden') {\n reportWatchTime();\n void collector!.flush();\n }\n };\n window.addEventListener('visibilitychange', watchVisibilityHandler);\n cleanups.push(() => window.removeEventListener('visibilitychange', watchVisibilityHandler));\n }\n\n // Track play/pause/ended from state changes\n cleanups.push(\n state.subscribe('playing', (playing) => {\n collector!.track(playing ? 'play' : 'pause');\n if (playing) {\n playStartMs = Date.now();\n } else {\n reportWatchTime();\n }\n }),\n );\n\n cleanups.push(\n state.subscribe('ended', (ended) => {\n if (ended) {\n collector!.track('ended');\n reportWatchTime();\n }\n }),\n );\n\n // Flush any remaining watch time when the player is torn down (runs before\n // collector.dispose(), which performs the final network flush).\n cleanups.push(() => reportWatchTime());\n\n cleanups.push(\n state.subscribe('seeking', (seeking) => {\n if (seeking) collector!.track('seek', { time: state.get('currentTime') });\n }),\n );\n\n cleanups.push(\n state.subscribe('loading', (loading) => {\n if (loading && state.get('playing')) {\n collector!.track('buffering');\n }\n }),\n );\n\n cleanups.push(\n state.subscribe('fullscreen', (fs) => {\n collector!.track('fullscreen', { active: fs });\n }),\n );\n\n // Track custom events\n const trackVzEvent = (type: string, detail?: unknown) => {\n switch (type) {\n case 'vz-quality-change':\n collector!.track('quality_change', detail as Record<string, unknown>);\n break;\n case 'vz-xr-enter':\n collector!.track('vr_enter');\n break;\n case 'vz-xr-exit':\n collector!.track('vr_exit');\n break;\n case 'vz-tour-navigate':\n collector!.track('tour_navigate', detail as Record<string, unknown>);\n break;\n case 'vz-error':\n collector!.track('error', detail as Record<string, unknown>);\n break;\n }\n };\n\n // Listen for custom events via the event emitter\n const eventTypes = [\n 'vz-quality-change',\n 'vz-xr-enter',\n 'vz-xr-exit',\n 'vz-tour-navigate',\n 'vz-error',\n ];\n\n for (const eventType of eventTypes) {\n const cleanup = events.on(eventType, (e) => {\n trackVzEvent(eventType, (e as CustomEvent).detail);\n });\n cleanups.push(cleanup);\n }\n }\n\n // --- Gaze tracking ---\n if (options.features.analyticsHeatmaps) {\n gazeTracker = new GazeTracker(\n {\n contentId: options.contentId,\n sessionId,\n apiEndpoint: options.apiEndpoint,\n apiKey: options.apiKey,\n },\n options.getOrientation,\n options.getContentTime,\n );\n\n // Start/stop gaze tracking based on play state\n cleanups.push(\n state.subscribe('playing', (playing) => {\n if (playing) {\n gazeTracker!.start();\n } else {\n gazeTracker!.stop();\n }\n }),\n );\n }\n\n return {\n collector,\n gazeTracker,\n dispose: () => {\n for (const cleanup of cleanups) cleanup();\n collector?.dispose();\n gazeTracker?.dispose();\n },\n };\n}\n","/**\n * Client for the public content-delivery API (api-key gated).\n *\n * The player fetches a content item's config + authored overlays by content-id\n * (reusing the same api-key path analytics uses) so an embed that only carries\n * `content-id` + `api-key` can render authored annotations/captions without any\n * DOM children. All fetchers are non-throwing: a failure returns null so the\n * player degrades gracefully to whatever DOM the embed provided.\n */\n\nimport type {\n DeliveryContentConfig,\n DeliveryTourConfig,\n DeliveryAnnotation,\n AnnotationData,\n} from '@vizor-vr/shared';\n\nexport interface ContentFetchOptions {\n apiEndpoint: string;\n contentId: string;\n apiKey?: string;\n}\n\nexport interface TourFetchOptions {\n apiEndpoint: string;\n tourId: string;\n apiKey?: string;\n}\n\nfunction trimTrailingSlash(value: string): string {\n return value.replace(/\\/+$/, '');\n}\n\nasync function getJson<T>(\n url: string,\n apiKey: string | undefined,\n fetchImpl: typeof fetch,\n): Promise<T | null> {\n try {\n const headers: Record<string, string> = {};\n if (apiKey) headers['x-api-key'] = apiKey;\n const res = await fetchImpl(url, { headers });\n if (!res.ok) return null;\n return (await res.json()) as T;\n } catch {\n return null;\n }\n}\n\n/** Fetch a content item's delivery config (player config + annotations + captions). */\nexport async function fetchDeliveryContent(\n opts: ContentFetchOptions,\n fetchImpl: typeof fetch = fetch,\n): Promise<DeliveryContentConfig | null> {\n const url = `${trimTrailingSlash(opts.apiEndpoint)}/api/v1/delivery/content/${encodeURIComponent(\n opts.contentId,\n )}`;\n return getJson<DeliveryContentConfig>(url, opts.apiKey, fetchImpl);\n}\n\n/** Fetch a tour's delivery graph (probes + portals). */\nexport async function fetchDeliveryTour(\n opts: TourFetchOptions,\n fetchImpl: typeof fetch = fetch,\n): Promise<DeliveryTourConfig | null> {\n const url = `${trimTrailingSlash(opts.apiEndpoint)}/api/v1/delivery/tours/${encodeURIComponent(\n opts.tourId,\n )}`;\n return getJson<DeliveryTourConfig>(url, opts.apiKey, fetchImpl);\n}\n\n/** Map the delivery wire shape to the player's AnnotationData (null → undefined). */\nexport function deliveryAnnotationsToData(annotations: DeliveryAnnotation[]): AnnotationData[] {\n return annotations.map((a) => ({\n id: a.id,\n lat: a.lat,\n lon: a.lon,\n timeStart: a.timeStart ?? undefined,\n timeEnd: a.timeEnd ?? undefined,\n title: a.title,\n icon: a.icon,\n htmlContent: a.htmlContent ?? undefined,\n sortOrder: a.sortOrder,\n }));\n}\n","/**\n * DOM hydration for content-id embeds.\n *\n * Given a fetched delivery config, inject the equivalent `<source>`,\n * `<vz-annotation>` and `<track>` children into the host element BEFORE the\n * media element's synchronous `onConnected()` runs, so the existing DOM-driven\n * init (loadSources/initAnnotations/initCaptions) discovers them with no\n * special-casing. A minimal `<vz-video content-id api-key>` thus renders the\n * authored sources + overlays.\n */\n\nimport type { DeliveryContentConfig } from '@vizor-vr/shared';\nimport { sanitizeAnnotationHtml } from '../utils/sanitize.js';\n\n/** Encode WebVTT text as a UTF-8 data URI (safe for non-Latin1, unlike btoa). */\nfunction vttDataUri(vtt: string): string {\n return `data:text/vtt;charset=utf-8,${encodeURIComponent(vtt)}`;\n}\n\n/** Append `<source>` children for each delivery source. */\nfunction injectSources(host: HTMLElement, config: DeliveryContentConfig): void {\n for (const source of config.sources) {\n const el = document.createElement('source');\n el.setAttribute('src', source.url);\n el.setAttribute('type', source.type);\n if (source.quality) el.setAttribute('quality', source.quality);\n if (source.fps != null) el.setAttribute('fps', String(source.fps));\n if (source.format) el.setAttribute('format', source.format);\n if (source.audioFormat) el.setAttribute('audio-format', source.audioFormat);\n host.appendChild(el);\n }\n}\n\n/** Append `<vz-annotation>` children for each authored annotation. */\nfunction injectAnnotations(host: HTMLElement, config: DeliveryContentConfig): void {\n for (const a of config.annotations) {\n const el = document.createElement('vz-annotation');\n el.setAttribute('lat', String(a.lat));\n el.setAttribute('lon', String(a.lon));\n el.setAttribute('title', a.title);\n if (a.icon) el.setAttribute('icon', a.icon);\n if (a.timeStart != null) el.setAttribute('time-start', String(a.timeStart));\n if (a.timeEnd != null) el.setAttribute('time-end', String(a.timeEnd));\n el.setAttribute('sort-order', String(a.sortOrder));\n // Rich body — sanitized server-side at authoring; sanitize again here\n // (defense-in-depth) before it touches the DOM, and vz-annotation sanitizes\n // once more when it reads its own innerHTML.\n // nosemgrep: no-raw-innerhtml -- content passed through sanitizeAnnotationHtml (DOMPurify allowlist)\n if (a.htmlContent) el.innerHTML = sanitizeAnnotationHtml(a.htmlContent);\n host.appendChild(el);\n }\n}\n\n/** Append `<vz-chapter>` children for each authored chapter. */\nfunction injectChapters(host: HTMLElement, config: DeliveryContentConfig): void {\n for (const c of config.chapters) {\n const el = document.createElement('vz-chapter');\n el.setAttribute('start', String(c.startTime));\n el.setAttribute('title', c.title);\n el.setAttribute('sort-order', String(c.sortOrder));\n el.setAttribute('chapter-id', c.id);\n host.appendChild(el);\n }\n}\n\n/** Append `<track>` children carrying each caption's WebVTT as a data URI. */\nfunction injectCaptions(host: HTMLElement, config: DeliveryContentConfig): void {\n for (const c of config.captions) {\n const el = document.createElement('track');\n el.setAttribute('kind', 'captions');\n el.setAttribute('srclang', c.srclang);\n el.setAttribute('label', c.label);\n el.setAttribute('src', vttDataUri(c.vttContent));\n host.appendChild(el);\n }\n}\n\n/**\n * Inject a fetched delivery config's sources + annotations + captions into the\n * host element as DOM children. Idempotency/precedence is the caller's job\n * (only hydrate when the author provided no DOM of their own).\n */\nexport function applyDeliveryConfigToDom(host: HTMLElement, config: DeliveryContentConfig): void {\n // Per-content capture-lens angle (WS-F): authored DOM attribute wins.\n if (config.lensAngle != null && !host.hasAttribute('lens-angle')) {\n host.setAttribute('lens-angle', String(config.lensAngle));\n }\n // Animated hover preview (WS-E): tier gating happens in the element (the\n // license resolves later); hydration only plumbs the URL through.\n if (config.previewUrl && !host.hasAttribute('preview')) {\n host.setAttribute('preview', config.previewUrl);\n }\n injectSources(host, config);\n injectAnnotations(host, config);\n injectCaptions(host, config);\n injectChapters(host, config);\n}\n","import { VzFormat, getFormatMeta } from '@vizor-vr/shared';\nimport type { FeatureFlags, FormatMeta, ProjectionType } from '@vizor-vr/shared';\n\n/**\n * Tier gating for playback formats. A free-tier viewer must not get the paid\n * stereo/180 formats, but per product decision we degrade gracefully rather than\n * hard-fail: a stereo format renders as its mono equivalent (one eye), and only\n * formats with no sensible fallback (a 180 half-sphere projection) are blocked.\n */\nexport type FormatGateDecision =\n | { action: 'allow' }\n | { action: 'downgrade'; to: VzFormat }\n | { action: 'block'; requires: 'stereoFormats' | 'formats180' };\n\n/** Mono format that renders the same projection as a stereo one (single eye). */\nconst MONO_BY_PROJECTION: Partial<Record<ProjectionType, VzFormat>> = {\n flat: VzFormat.MONO_FLAT,\n equirect: VzFormat.MONO_360,\n cubemap: VzFormat.MONO_CUBEMAP,\n eac: VzFormat.MONO_EAC,\n fisheye: VzFormat.MONO_FISHEYE,\n};\n\nfunction monoEquivalent(meta: FormatMeta): VzFormat | undefined {\n // A 180 projection has no mono half-sphere format — nothing to fall back to.\n if (meta.fovH === 180) return undefined;\n return MONO_BY_PROJECTION[meta.projection];\n}\n\n/**\n * Decide whether a format may play under the given license features, returning a\n * graceful downgrade where one exists.\n */\nexport function resolveFormatGate(\n format: VzFormat | string,\n features: FeatureFlags,\n): FormatGateDecision {\n const meta = getFormatMeta(format);\n if (!meta) return { action: 'allow' }; // unknown format — don't break playback\n\n const is180 = meta.fovH === 180;\n const isStereo = meta.stereoMode !== 'mono';\n\n // 180 first: a half-sphere projection can't be shown as full 360, so block.\n if (is180 && !features.formats180) return { action: 'block', requires: 'formats180' };\n\n if (isStereo && !features.stereoFormats) {\n const mono = monoEquivalent(meta);\n return mono ? { action: 'downgrade', to: mono } : { action: 'block', requires: 'stereoFormats' };\n }\n\n return { action: 'allow' };\n}\n","import type { FeatureFlags } from '@vizor-vr/shared';\n\n/**\n * Tier gating for player features (webxr, spatialAudio, tours, cinema, playlist,\n * …). This is the feature-flag analogue of `resolveFormatGate` in format-gate.ts.\n *\n * Per product decision the player degrades gracefully rather than hard-failing:\n * a tier that lacks a feature falls back to a sensible baseline (e.g. a tour\n * shows its start panorama without portal navigation, a playlist plays its first\n * item, spatial audio plays as plain stereo) instead of blocking. So the only\n * two outcomes here are `allow` (feature licensed) and `degrade` (feature not\n * licensed — the caller applies its own fallback). A caller that has *no*\n * sensible fallback can still choose to render an upgrade overlay on `degrade`.\n */\nexport type FeatureGateDecision =\n | { action: 'allow' }\n | { action: 'degrade' };\n\n/**\n * Decide whether a feature may run under the current license.\n *\n * @param _feature the feature flag name (kept for call-site clarity + future\n * per-feature policy; the decision today depends only on `enabled`).\n * @param enabled whether the resolved license enables the feature.\n */\nexport function resolveFeatureGate(\n _feature: keyof FeatureFlags,\n enabled: boolean,\n): FeatureGateDecision {\n return enabled ? { action: 'allow' } : { action: 'degrade' };\n}\n","/**\n * DRM tier gate. Unlike other player features, a DRM asset is encrypted-only —\n * there is no plain fallback — so an unlicensed tier is BLOCKED (an upgrade\n * overlay) rather than gracefully degraded. The two inputs are whether the\n * asset carries a DRM config and whether the resolved license enables `drm`.\n */\nexport type DrmGateDecision = { action: 'none' } | { action: 'allow' } | { action: 'block' };\n\nexport function resolveDrmGate(hasDrmConfig: boolean, drmFeatureEnabled: boolean): DrmGateDecision {\n if (!hasDrmConfig) return { action: 'none' };\n return drmFeatureEnabled ? { action: 'allow' } : { action: 'block' };\n}\n","/**\n * Build a DeliveryDrm from `drm-*` DOM attributes on a self-hosted embed\n * (the DOM-attribute DRM path — an alternative to the delivery API). Requires\n * a key id + all three license URLs; a partial set returns null (treated as\n * non-DRM) rather than half-configuring the CDM.\n */\nimport type { DeliveryDrm } from '@vizor-vr/shared';\n\nexport function parseDomDrmConfig(host: HTMLElement): DeliveryDrm | null {\n const keyId = host.getAttribute('drm-key-id');\n const widevine = host.getAttribute('drm-widevine-url');\n const playready = host.getAttribute('drm-playready-url');\n const fairplay = host.getAttribute('drm-fairplay-url');\n if (!keyId || !widevine || !playready || !fairplay) return null;\n\n return {\n scheme: 'cbcs',\n keyId,\n licenseUrls: { widevine, playready, fairplay },\n fairplayCertUrl: host.getAttribute('drm-fairplay-cert-url'),\n };\n}\n","/**\n * Pure, DOM-free chapter helpers. Extracted so the seek-bar marker math and the\n * active-chapter tracking that drives `vz-chapter-change` can be unit-tested\n * without a renderer or a real media element.\n */\n\nexport interface PlayerChapter {\n id: string;\n title: string;\n /** Start offset in seconds. Chapters are contiguous (end = next start). */\n startTime: number;\n sortOrder: number;\n}\n\nexport interface ChapterMarker {\n id: string;\n title: string;\n startTime: number;\n /** Fractional position along the seek bar in [0, 1]. */\n position: number;\n}\n\n/** A chapter as parsed from the DOM/delivery, before ids/order are normalized. */\nexport interface RawChapter {\n id?: string | null;\n title: string;\n startTime: number;\n sortOrder?: number | null;\n}\n\n/**\n * Turn raw parsed chapters (from `<vz-chapter>` children or a delivery config)\n * into a sorted, fully-populated list: sort by startTime, then fill any missing\n * id with a stable `chapter-<n>` index and default sortOrder from position.\n */\nexport function normalizeChapters(raw: RawChapter[]): PlayerChapter[] {\n const withDefaults = raw.map((c, i) => ({\n id: c.id ?? '',\n title: c.title,\n startTime: c.startTime,\n sortOrder: c.sortOrder ?? i,\n }));\n return sortChapters(withDefaults).map((c, i) => ({\n ...c,\n id: c.id || `chapter-${i}`,\n }));\n}\n\n/** Return a new array sorted by startTime ascending (input untouched). */\nexport function sortChapters(chapters: PlayerChapter[]): PlayerChapter[] {\n return [...chapters].sort((a, b) => a.startTime - b.startTime);\n}\n\n/**\n * The chapter active at `currentTime`: the last chapter whose startTime is at or\n * before it (chapters are contiguous). Returns null when the time precedes the\n * first chapter or the list is empty. Accepts unsorted input.\n */\nexport function resolveActiveChapter(\n chapters: PlayerChapter[],\n currentTime: number,\n): PlayerChapter | null {\n let active: PlayerChapter | null = null;\n for (const c of sortChapters(chapters)) {\n if (c.startTime <= currentTime) active = c;\n else break;\n }\n return active;\n}\n\n/**\n * Map each chapter to a fractional seek-bar position (startTime / duration),\n * clamped to [0, 1] and sorted by startTime. Returns [] until the duration is\n * known (0 or negative), so callers can re-run when `duration` resolves.\n */\nexport function chapterMarkerPositions(\n chapters: PlayerChapter[],\n duration: number,\n): ChapterMarker[] {\n if (!(duration > 0)) return [];\n return sortChapters(chapters).map((c) => ({\n id: c.id,\n title: c.title,\n startTime: c.startTime,\n position: Math.max(0, Math.min(1, c.startTime / duration)),\n }));\n}\n","import type { PlayerChapter } from './chapter-logic.js';\nimport { setStaticHtml } from '../utils/static-html.js';\nimport { ICON_CHAPTERS } from '../controls/icons.js';\n\n/** Format a second offset as m:ss or h:mm:ss for the chapter list. */\nfunction formatTimestamp(seconds: number): string {\n if (isNaN(seconds) || !isFinite(seconds) || seconds < 0) return '0:00';\n const s = Math.floor(seconds);\n const h = Math.floor(s / 3600);\n const m = Math.floor((s % 3600) / 60);\n const sec = s % 60;\n if (h > 0) return `${h}:${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;\n return `${m}:${String(sec).padStart(2, '0')}`;\n}\n\n/**\n * Chapters button + dropdown list. Mirrors the CcButton/QualityMenu pattern: a\n * button in the controls bar toggles a dropdown of chapters (timestamp + title).\n * Selecting a chapter seeks to its start. Titles render as inert text — they are\n * author/delivery-supplied and only length-validated server-side.\n */\nexport class ChapterMenu {\n private container: HTMLDivElement;\n private btn: HTMLButtonElement;\n private dropdown: HTMLDivElement;\n private chapters: PlayerChapter[];\n private onSelect: (startTime: number) => void;\n private activeId: string | null = null;\n private isOpen = false;\n private unsubscribes: Array<() => void> = [];\n\n constructor(chapters: PlayerChapter[], onSelect: (startTime: number) => void) {\n this.chapters = chapters;\n this.onSelect = onSelect;\n\n this.container = document.createElement('div');\n this.container.className = 'vz-chapter-menu';\n\n this.btn = document.createElement('button');\n this.btn.className = 'vz-btn vz-chapter-btn';\n this.btn.setAttribute('aria-label', 'Chapters');\n this.btn.title = 'Chapters';\n setStaticHtml(this.btn, ICON_CHAPTERS);\n\n this.dropdown = document.createElement('div');\n this.dropdown.className = 'vz-chapter-dropdown';\n\n this.container.appendChild(this.btn);\n this.container.appendChild(this.dropdown);\n\n this.btn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.isOpen = !this.isOpen;\n this.dropdown.classList.toggle('open', this.isOpen);\n });\n\n const onDocClick = () => {\n if (this.isOpen) {\n this.isOpen = false;\n this.dropdown.classList.remove('open');\n }\n };\n document.addEventListener('click', onDocClick);\n this.unsubscribes.push(() => document.removeEventListener('click', onDocClick));\n\n this.renderOptions();\n }\n\n getElement(): HTMLDivElement {\n return this.container;\n }\n\n /** Highlight the chapter with the given id (or clear when null). */\n setActive(id: string | null): void {\n this.activeId = id;\n const rows = this.dropdown.querySelectorAll<HTMLElement>('.vz-chapter-option');\n rows.forEach((row) => {\n row.classList.toggle('active', row.dataset.chapterId === id);\n });\n }\n\n dispose(): void {\n for (const unsub of this.unsubscribes) unsub();\n this.unsubscribes = [];\n this.container.remove();\n }\n\n private renderOptions(): void {\n this.dropdown.replaceChildren();\n for (const chapter of this.chapters) {\n const row = document.createElement('div');\n row.className = 'vz-chapter-option';\n row.dataset.chapterId = chapter.id;\n if (chapter.id === this.activeId) row.classList.add('active');\n\n const time = document.createElement('span');\n time.className = 'vz-chapter-time';\n time.textContent = formatTimestamp(chapter.startTime);\n\n // title is author/delivery-supplied and only length-validated server-side\n // — render as inert text, never parsed HTML.\n const label = document.createElement('span');\n label.className = 'vz-chapter-label';\n label.textContent = chapter.title;\n\n row.appendChild(time);\n row.appendChild(label);\n row.addEventListener('click', (e) => {\n e.stopPropagation();\n this.isOpen = false;\n this.dropdown.classList.remove('open');\n this.onSelect(chapter.startTime);\n });\n this.dropdown.appendChild(row);\n }\n }\n}\n","import type { PlayerState } from '../core/player-state.js';\nimport type { VzEventEmitter } from '../core/event-emitter.js';\nimport {\n resolveActiveChapter,\n chapterMarkerPositions,\n type PlayerChapter,\n type ChapterMarker,\n} from './chapter-logic.js';\nimport { ChapterMenu } from './chapter-menu.js';\n\n/** Minimal structural view of SeekBar the manager needs (keeps it testable). */\nexport interface ChapterSeekBar {\n setChapterMarkers(markers: ChapterMarker[]): void;\n}\n\n/** Minimal structural view of ControlsOverlay the manager needs. */\nexport interface ChapterControlsOverlay {\n addControl(el: HTMLElement, section: 'left' | 'center' | 'right' | 'seek'): void;\n}\n\nexport interface ChapterManagerOptions {\n chapters: PlayerChapter[];\n state: PlayerState;\n events: VzEventEmitter;\n /** Seek callback (media element's seek). */\n seek: (time: number) => void;\n seekBar?: ChapterSeekBar | null;\n controlsOverlay?: ChapterControlsOverlay | null;\n}\n\n/**\n * Wires manual chapters into a media element: renders seek-bar tick markers\n * (reactive to duration), adds a chapter menu to the controls bar, and tracks\n * the active chapter from `currentTime`, firing `vz-chapter-change` on each\n * boundary crossing. Created only when the tier includes chapters (the gate\n * lives in the media element's onLicenseResolved).\n */\nexport class ChapterManager {\n private chapters: PlayerChapter[];\n private state: PlayerState;\n private events: VzEventEmitter;\n private seek: (time: number) => void;\n private seekBar: ChapterSeekBar | null;\n private menu: ChapterMenu | null = null;\n private activeId: string | null = null;\n private unsubscribes: Array<() => void> = [];\n\n constructor(opts: ChapterManagerOptions) {\n this.chapters = opts.chapters;\n this.state = opts.state;\n this.events = opts.events;\n this.seek = opts.seek;\n this.seekBar = opts.seekBar ?? null;\n\n if (opts.controlsOverlay) {\n this.menu = new ChapterMenu(this.chapters, (t) => this.seek(t));\n opts.controlsOverlay.addControl(this.menu.getElement(), 'right');\n }\n\n this.renderMarkers(this.state.get('duration'));\n this.unsubscribes.push(this.state.subscribe('duration', (d) => this.renderMarkers(d)));\n\n this.updateActive(this.state.get('currentTime'));\n this.unsubscribes.push(this.state.subscribe('currentTime', (t) => this.updateActive(t)));\n }\n\n private renderMarkers(duration: number): void {\n this.seekBar?.setChapterMarkers(chapterMarkerPositions(this.chapters, duration));\n }\n\n private updateActive(currentTime: number): void {\n const active = resolveActiveChapter(this.chapters, currentTime);\n const id = active?.id ?? null;\n if (id === this.activeId) return;\n this.activeId = id;\n this.menu?.setActive(id);\n if (active) {\n this.events.emit('vz-chapter-change', {\n chapterId: active.id,\n title: active.title,\n startTime: active.startTime,\n });\n }\n }\n\n dispose(): void {\n for (const u of this.unsubscribes) u();\n this.unsubscribes = [];\n this.menu?.dispose();\n this.menu = null;\n }\n}\n","import type { PlayerState } from '../core/player-state.js';\nimport { setStaticHtml } from '../utils/static-html.js';\nimport type { CaptionRenderer } from '../captions/caption-renderer.js';\nimport { ICON_CC } from './icons.js';\n\n/**\n * Closed-caption toggle button with language dropdown.\n *\n * UI pattern matches QualityMenu: a button that toggles a\n * dropdown of options (Off + one per loaded track).\n */\nexport class CcButton {\n private container: HTMLDivElement;\n private dropdown: HTMLDivElement;\n private btn: HTMLButtonElement;\n private captionRenderer: CaptionRenderer;\n private unsubscribes: Array<() => void> = [];\n private isOpen = false;\n\n constructor(state: PlayerState, captionRenderer: CaptionRenderer) {\n this.captionRenderer = captionRenderer;\n\n this.container = document.createElement('div');\n this.container.className = 'vz-cc-menu';\n\n this.btn = document.createElement('button');\n this.btn.className = 'vz-btn vz-cc-btn';\n this.btn.setAttribute('aria-label', 'Captions');\n setStaticHtml(this.btn, ICON_CC);\n\n this.dropdown = document.createElement('div');\n this.dropdown.className = 'vz-cc-dropdown';\n\n this.container.appendChild(this.btn);\n this.container.appendChild(this.dropdown);\n\n // Toggle dropdown on click\n this.btn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.isOpen = !this.isOpen;\n this.dropdown.classList.toggle('open', this.isOpen);\n if (this.isOpen) this.renderOptions();\n });\n\n // Close on outside click\n const onDocClick = () => {\n if (this.isOpen) {\n this.isOpen = false;\n this.dropdown.classList.remove('open');\n }\n };\n document.addEventListener('click', onDocClick);\n this.unsubscribes.push(() => document.removeEventListener('click', onDocClick));\n\n // Update active indicator when time changes (cue might switch track appearance)\n this.updateButtonState();\n }\n\n getElement(): HTMLDivElement {\n return this.container;\n }\n\n /** Show or hide the CC button based on whether tracks are loaded */\n updateVisibility(): void {\n this.container.style.display = this.captionRenderer.hasTracks() ? '' : 'none';\n this.updateButtonState();\n }\n\n dispose(): void {\n for (const unsub of this.unsubscribes) unsub();\n this.unsubscribes = [];\n }\n\n private updateButtonState(): void {\n if (this.captionRenderer.isActive()) {\n this.btn.classList.add('active');\n } else {\n this.btn.classList.remove('active');\n }\n }\n\n private renderOptions(): void {\n this.dropdown.replaceChildren();\n const tracks = this.captionRenderer.getTracks();\n const activeIndex = this.captionRenderer.getActiveTrackIndex();\n\n // \"Off\" option\n const offOption = document.createElement('div');\n offOption.className = `vz-cc-option ${activeIndex === -1 ? 'active' : ''}`;\n const offCheck = document.createElement('span');\n offCheck.className = 'vz-cc-check';\n offCheck.textContent = activeIndex === -1 ? '✓' : '';\n offOption.append(offCheck, document.createTextNode('Off'));\n offOption.addEventListener('click', () => {\n this.captionRenderer.setActiveTrack(-1);\n this.isOpen = false;\n this.dropdown.classList.remove('open');\n this.updateButtonState();\n });\n this.dropdown.appendChild(offOption);\n\n // Track options\n tracks.forEach((track, index) => {\n const option = document.createElement('div');\n const isActive = index === activeIndex;\n option.className = `vz-cc-option ${isActive ? 'active' : ''}`;\n // Build with DOM APIs, not innerHTML: track.label is untrusted (it comes\n // from a <track label> attribute / the captions API, which only\n // length-validates it) and must render as inert text, never parsed HTML.\n const check = document.createElement('span');\n check.className = 'vz-cc-check';\n check.textContent = isActive ? '✓' : '';\n option.appendChild(check);\n option.appendChild(document.createTextNode(track.label));\n option.addEventListener('click', () => {\n this.captionRenderer.setActiveTrack(index);\n this.isOpen = false;\n this.dropdown.classList.remove('open');\n this.updateButtonState();\n });\n this.dropdown.appendChild(option);\n });\n }\n}\n","import type { VzFormat, ParsedSource, DeliveryDrm } from '@vizor-vr/shared';\nimport { parseFormat, MEDIA_EVENTS } from '@vizor-vr/shared';\nimport { VzBaseElement } from './base-element.js';\nimport { VzRenderer } from '../renderer/renderer.js';\nimport { SourceManager } from '../streaming/source-manager.js';\nimport { requestFullscreen, exitFullscreen, isFullscreen, onFullscreenChange } from '../utils/fullscreen.js';\nimport { ControlsOverlay } from '../controls/controls-overlay.js';\nimport { PlayButton } from '../controls/play-button.js';\nimport { SeekBar } from '../controls/seek-bar.js';\nimport { VolumeControl } from '../controls/volume-control.js';\nimport { FullscreenButton } from '../controls/fullscreen-button.js';\nimport { VrButton } from '../controls/vr-button.js';\nimport { Compass } from '../controls/compass.js';\nimport { QualityMenu } from '../controls/quality-menu.js';\nimport { KeyboardShortcuts } from '../controls/keyboard-shortcuts.js';\nimport { XRManager } from '../xr/xr-manager.js';\nimport { AudioManager } from '../audio/audio-manager.js';\nimport { LiveIndicator } from '../controls/live-indicator.js';\nimport { initAnalytics, type AnalyticsHandle } from '../analytics/index.js';\nimport { fetchDeliveryContent } from '../delivery/delivery-client.js';\nimport { applyDeliveryConfigToDom } from '../delivery/delivery-hydrate.js';\nimport { resolveFormatGate } from '../license/format-gate.js';\nimport { resolveFeatureGate } from '../license/feature-gate.js';\nimport { resolveDrmGate } from '../license/drm-gate.js';\nimport { parseDomDrmConfig } from '../delivery/dom-drm.js';\nimport { ChapterManager } from '../chapters/chapter-manager.js';\nimport { normalizeChapters, type PlayerChapter, type RawChapter } from '../chapters/chapter-logic.js';\nimport type { VzChapterElement } from '../elements/vz-chapter.js';\nimport { AnnotationManager } from '../annotations/annotation-manager.js';\nimport type { VzAnnotationElement } from '../elements/vz-annotation.js';\nimport type { AnnotationData } from '@vizor-vr/shared';\nimport { CaptionRenderer } from '../captions/caption-renderer.js';\nimport { CcButton } from '../controls/cc-button.js';\nimport { CollaborativeClient } from '../collaborative/collab-client.js';\nimport { GazeCursorManager } from '../collaborative/gaze-cursor-manager.js';\nimport { GazeSender } from '../collaborative/gaze-sender.js';\nimport { ChatPanel } from '../collaborative/chat-panel.js';\nimport { ChatButton } from '../collaborative/chat-button.js';\nimport { VoiceManager } from '../collaborative/voice-manager.js';\nimport { VoiceButton } from '../collaborative/voice-button.js';\n\n/**\n * Abstract base class for all media-playing elements (video, live-video, cinema).\n *\n * Extends VzBaseElement with:\n * - Three.js renderer initialization\n * - Hidden <video> element creation and management\n * - Source parsing from <source> children\n * - HTML5 Media API mirroring\n * - Controls overlay rendering\n */\n/**\n * WS-D autoplay: honor the browser muted-autoplay policy. Autoplay without\n * muted is force-muted (with a console.info) — browsers block audible\n * autoplay — and playback is attempted ONCE when the video can play; a\n * rejected play() promise is swallowed (the poster/controls remain usable).\n */\nexport function applyAutoplayPolicy(\n video: HTMLVideoElement,\n config: { autoplay?: boolean; muted: boolean },\n): void {\n if (!config.autoplay) return;\n if (!video.muted) {\n console.info('[Vizor] autoplay requires muted playback — muting (browser policy)');\n video.muted = true;\n }\n let attempted = false;\n video.addEventListener('canplay', () => {\n if (attempted) return;\n attempted = true;\n const p = video.play();\n // Some browsers may still refuse; that must never throw.\n if (p && typeof p.catch === 'function') p.catch(() => {});\n });\n}\n\nexport abstract class VzMediaElement extends VzBaseElement {\n protected renderer!: VzRenderer;\n protected videoElement!: HTMLVideoElement;\n protected containerEl!: HTMLDivElement;\n protected sourceManager!: SourceManager;\n /** When true, base class skips auto-setupVideo in loadeddata (used by cinema) */\n protected suppressAutoSetup = false;\n private cleanupFullscreen: (() => void) | null = null;\n private timeUpdateTimer: ReturnType<typeof setInterval> | null = null;\n private controlsOverlay: ControlsOverlay | null = null;\n private playButton: PlayButton | null = null;\n private seekBar: SeekBar | null = null;\n private volumeControl: VolumeControl | null = null;\n private fullscreenButton: FullscreenButton | null = null;\n private vrButton: VrButton | null = null;\n private compass: Compass | null = null;\n private qualityMenu: QualityMenu | null = null;\n private keyboardShortcuts: KeyboardShortcuts | null = null;\n private xrManager: XRManager | null = null;\n private audioManager: AudioManager | null = null;\n private liveIndicator: LiveIndicator | null = null;\n private analyticsHandle: AnalyticsHandle | null = null;\n private annotationManager: AnnotationManager | null = null;\n private chapterManager: ChapterManager | null = null;\n private pendingChapters: PlayerChapter[] = [];\n private captionRenderer: CaptionRenderer | null = null;\n private ccButton: CcButton | null = null;\n private collabClient: CollaborativeClient | null = null;\n private gazeCursorManager: GazeCursorManager | null = null;\n private gazeSender: GazeSender | null = null;\n private chatPanel: ChatPanel | null = null;\n private chatButton: ChatButton | null = null;\n private voiceManager: VoiceManager | null = null;\n private voiceButton: VoiceButton | null = null;\n private drmConfig: DeliveryDrm | null = null;\n private deferredDrmSources: ParsedSource[] = [];\n\n /**\n * A content-id embed (`<vz-video content-id api-key>` with no DOM `<source>`)\n * fetches its config from the delivery API and injects the equivalent\n * `<source>`/`<vz-annotation>`/`<track>` DOM children before onConnected() runs\n * — so authored content actually reaches the viewer. A DOM-authored embed\n * (any `<source>` child) wins and skips the fetch entirely.\n */\n protected beforeConnect(): Promise<void> | void {\n const { contentId, apiKey } = this.config;\n if (!contentId || !apiKey) return;\n if (this.querySelector('source')) return;\n return this.hydrateFromDeliveryApi(contentId, apiKey);\n }\n\n private async hydrateFromDeliveryApi(contentId: string, apiKey: string): Promise<void> {\n const apiEndpoint = this.config.apiEndpoint ?? window.location.origin;\n const config = await fetchDeliveryContent({ apiEndpoint, contentId, apiKey });\n if (!config) return;\n // The content item is the source of truth for format/poster when the embed\n // didn't set them explicitly.\n if (!this.hasAttribute('format')) this.config.format = config.format;\n if (!this.config.poster && config.poster) this.config.poster = config.poster;\n applyDeliveryConfigToDom(this, config);\n if (config.drm) this.drmConfig = config.drm;\n }\n\n protected onConnected(): void {\n // Create container div\n this.containerEl = document.createElement('div');\n this.containerEl.className = 'vz-container';\n this.shadow.appendChild(this.containerEl);\n\n // Create poster overlay\n if (this.config.poster) {\n const posterEl = document.createElement('div');\n posterEl.className = 'vz-poster';\n posterEl.style.backgroundImage = `url(${this.config.poster})`;\n posterEl.addEventListener('click', () => {\n this.play().catch(() => {});\n posterEl.classList.add('hidden');\n });\n // WS-E: animated WebP preview on poster hover, tier-gated. The license\n // resolves async, so the feature check runs at hover time.\n if (this.config.preview) {\n posterEl.addEventListener('mouseenter', () => {\n if (this.hasFeature('animatedPreviews')) {\n posterEl.style.backgroundImage = `url(${this.config.preview})`;\n }\n });\n posterEl.addEventListener('mouseleave', () => {\n posterEl.style.backgroundImage = `url(${this.config.poster})`;\n });\n }\n this.shadow.appendChild(posterEl);\n }\n\n // Create hidden video element\n this.videoElement = this.createVideoElement();\n\n // Initialize Three.js renderer\n this.renderer = new VzRenderer(\n this.shadow,\n this.containerEl,\n this.state,\n this.config,\n );\n\n // Mirror HTML5 media events\n this.mirrorMediaEvents();\n\n // Set up video texture once the video has data\n this.videoElement.addEventListener('loadeddata', () => {\n if (this.config.format && !this.suppressAutoSetup) {\n this.renderer.setupVideo(this.videoElement, this.config.format);\n }\n });\n\n // Initialize source manager\n this.sourceManager = new SourceManager();\n\n // DOM-attribute DRM path (self-hosted embeds); the delivery path, if any, already set drmConfig.\n this.drmConfig = this.drmConfig ?? parseDomDrmConfig(this);\n\n // Parse and load sources (must happen after event listeners are attached)\n this.loadSources();\n\n // Spatial (ambisonic) audio is a paid feature. The license is unknown at\n // onConnected() (this.license is null), so we cannot gate it here — the\n // ambix AudioManager init is deferred to onLicenseResolved() where\n // hasFeature('spatialAudio') is known. Until then normal (non-ambix) audio\n // plays untouched straight from the hidden <video>, which is also the\n // graceful fallback when the tier lacks spatial audio.\n\n // Listen for fullscreen changes\n this.cleanupFullscreen = onFullscreenChange(() => {\n const fs = isFullscreen();\n this.state.set('fullscreen', fs);\n if (fs) {\n this.events.emit('vz-fullscreen-enter');\n } else {\n this.events.emit('vz-fullscreen-exit');\n }\n this.renderer.resize();\n });\n\n // Initialize WebXR manager\n this.xrManager = new XRManager(\n this.renderer.getThreeRenderer(),\n this.state,\n this.events,\n this.renderer.getCameraController(),\n );\n\n // Default XR controller action: toggle play/pause\n this.xrManager.setSelectHandler(() => {\n if (this.state.get('paused') || this.state.get('ended')) {\n this.play().catch(() => {});\n } else {\n this.pause();\n }\n });\n\n // Initialize controls overlay\n if (!this.config.hideControls) {\n this.controlsOverlay = new ControlsOverlay(\n this.shadow, this.containerEl, this.state, this.config, this.events,\n );\n\n this.playButton = new PlayButton(this.state, this, this.containerEl);\n this.controlsOverlay.addControl(this.playButton.getElement(), 'left');\n\n this.seekBar = new SeekBar(this.state, this);\n this.controlsOverlay.addControl(this.seekBar.getElement(), 'seek');\n this.controlsOverlay.addControl(this.seekBar.getTimeDisplay(), 'left');\n\n // Live indicator — shown only when isLive is true (replaces time display)\n this.liveIndicator = new LiveIndicator(this.state, () => {\n // Seek to live edge — subclass (VzLiveElement) provides seekToLiveEdge\n const seekable = this.videoElement.seekable;\n if (seekable.length > 0) {\n this.videoElement.currentTime = seekable.end(seekable.length - 1);\n this.state.set('isAtLiveEdge', true);\n }\n });\n this.controlsOverlay.addControl(this.liveIndicator.getElement(), 'left');\n\n this.volumeControl = new VolumeControl(this.state, this);\n this.controlsOverlay.addControl(this.volumeControl.getElement(), 'right');\n\n this.qualityMenu = new QualityMenu(this.state, this.sourceManager);\n this.controlsOverlay.addControl(this.qualityMenu.getElement(), 'right');\n\n this.compass = new Compass(this.state, this.renderer);\n this.controlsOverlay.addControl(this.compass.getElement(), 'right');\n\n this.vrButton = new VrButton(this.state, () => {\n this.xrManager?.toggleXR().catch((err) => {\n console.error('[Vizor] XR toggle failed:', err);\n this.events.emit('vz-error', { code: 'XR_ERROR', message: String(err) });\n });\n });\n this.controlsOverlay.addControl(this.vrButton.getElement(), 'right');\n\n this.fullscreenButton = new FullscreenButton(this.state, this);\n this.controlsOverlay.addControl(this.fullscreenButton.getElement(), 'right');\n }\n\n // Initialize analytics after license check (feature-gated)\n if (this.config.contentId) {\n this.events.on('vz-license', () => {\n if (this.license && (this.license.features.analytics || this.license.features.analyticsHeatmaps)) {\n this.analyticsHandle = initAnalytics(this.state, this.events, {\n contentId: this.config.contentId!,\n apiEndpoint: this.config.apiEndpoint ?? window.location.origin,\n apiKey: this.config.apiKey,\n features: this.license.features,\n getOrientation: () => this.renderer.getCameraController().getOrientation(),\n getContentTime: () => this.videoElement.currentTime,\n });\n }\n });\n }\n\n // Initialize annotation system if <vz-annotation> children are present\n this.initAnnotations();\n\n // Initialize captions if <track> children are present\n this.initCaptions();\n\n // Parse <vz-chapter> children (the chapter UI is wired on license resolve)\n this.initChapters();\n\n // Initialize collaborative viewing if configured\n this.initCollab();\n\n // Keyboard shortcuts always active (even without visible controls)\n this.keyboardShortcuts = new KeyboardShortcuts(this, this.state, this, this.captionRenderer, this.chatPanel, this.voiceButton);\n\n // Start rendering\n this.renderer.start();\n\n // Emit ready\n this.events.emit('vz-ready');\n\n // Subclass hook\n this.onMediaConnected();\n }\n\n protected onDisconnected(): void {\n this.cleanupFullscreen?.();\n if (this.timeUpdateTimer) {\n clearInterval(this.timeUpdateTimer);\n }\n this.keyboardShortcuts?.dispose();\n this.playButton?.dispose();\n this.seekBar?.dispose();\n this.volumeControl?.dispose();\n this.fullscreenButton?.dispose();\n this.vrButton?.dispose();\n this.liveIndicator?.dispose();\n this.compass?.dispose();\n this.qualityMenu?.dispose();\n this.controlsOverlay?.dispose();\n this.xrManager?.dispose();\n this.audioManager?.dispose();\n this.audioManager = null;\n this.analyticsHandle?.dispose();\n this.analyticsHandle = null;\n this.annotationManager?.dispose();\n this.annotationManager = null;\n this.chapterManager?.dispose();\n this.chapterManager = null;\n this.ccButton?.dispose();\n this.ccButton = null;\n this.captionRenderer?.dispose();\n this.captionRenderer = null;\n this.gazeSender?.dispose();\n this.gazeSender = null;\n this.gazeCursorManager?.dispose();\n this.gazeCursorManager = null;\n this.voiceButton?.dispose();\n this.voiceButton = null;\n this.voiceManager?.dispose();\n this.voiceManager = null;\n this.chatButton?.dispose();\n this.chatButton = null;\n this.chatPanel?.dispose();\n this.chatPanel = null;\n this.collabClient?.dispose();\n this.collabClient = null;\n this.sourceManager.dispose();\n this.videoElement.pause();\n this.videoElement.removeAttribute('src');\n this.videoElement.load();\n this.renderer.dispose();\n this.drmConfig = null;\n this.deferredDrmSources = [];\n this.onMediaDisconnected();\n }\n\n protected onResize(): void {\n this.renderer?.resize();\n }\n\n /** After license resolves, enforce theme + format/feature tier gating */\n protected override onLicenseResolved(): void {\n if (this.controlsOverlay && this.license) {\n this.controlsOverlay.updateTheme(this.config, this.license);\n }\n this.applyFormatGate();\n this.applyWebxrGate();\n this.applySpatialAudioGate();\n this.applyChaptersGate();\n this.applyDrmGate();\n }\n\n /**\n * Manual chapters are a paid feature. Once the license is known, wire the\n * chapter UI (seek-bar markers + menu) and active-chapter tracking that fires\n * `vz-chapter-change`, but only when the tier includes chapters. Unlicensed\n * tiers degrade gracefully: no markers, no menu, no event (the video plays\n * unaffected).\n */\n private applyChaptersGate(): void {\n if (this.chapterManager || this.pendingChapters.length === 0) return;\n if (resolveFeatureGate('chapters', this.hasFeature('chapters')).action !== 'allow') return;\n this.chapterManager = new ChapterManager({\n chapters: this.pendingChapters,\n state: this.state,\n events: this.events,\n seek: (t) => this.seek(t),\n seekBar: this.seekBar,\n controlsOverlay: this.controlsOverlay,\n });\n }\n\n /**\n * WebXR (Enter-VR) is a paid feature. Once the license is known, if the tier\n * doesn't include it, remove the VR button so it can't be used — flat/pointer\n * viewing continues unaffected (graceful downgrade). Licensed users keep the\n * button, which reveals itself only when the device reports XR support.\n */\n private applyWebxrGate(): void {\n if (!this.license) return;\n if (resolveFeatureGate('webxr', this.hasFeature('webxr')).action === 'allow') return;\n this.vrButton?.dispose();\n this.vrButton?.getElement().remove();\n this.vrButton = null;\n }\n\n /**\n * Spatial (ambisonic) audio is a paid feature. When the source is authored as\n * ambix and the tier includes it, wire up the AudioManager here (license is\n * now known). When the tier lacks it, we do nothing — the hidden <video>'s\n * own audio track plays as ordinary stereo, the graceful fallback.\n */\n private applySpatialAudioGate(): void {\n if (this.config.audioFormat !== 'ambix') return;\n if (this.audioManager) return; // already initialized\n if (resolveFeatureGate('spatialAudio', this.hasFeature('spatialAudio')).action !== 'allow') {\n return;\n }\n this.audioManager = new AudioManager(this.state, this.events);\n this.audioManager.init(this.videoElement, 'ambix').catch((err) => {\n console.warn('[Vizor] Audio init error:', err);\n });\n this.renderer.setAudioManager(this.audioManager);\n }\n\n /**\n * Enforce format tier gating once the license is known. A stereo format the\n * tier doesn't include is downgraded to its mono equivalent (re-projecting if\n * the renderer already set up the ungated one); a format with no fallback (a\n * 180 half-sphere) is blocked behind an upgrade overlay. Prevents free users\n * from silently receiving paid formats.\n */\n private applyFormatGate(): void {\n if (!this.license || !this.config.format) return;\n const decision = resolveFormatGate(this.config.format, this.license.features);\n if (decision.action === 'downgrade') {\n this.config.format = decision.to;\n if (this.renderer && this.videoElement.readyState >= 2) {\n this.renderer.setupVideo(this.videoElement, decision.to);\n }\n } else if (decision.action === 'block') {\n this.suppressAutoSetup = true;\n this.videoElement.pause();\n this.showFormatUpgradeOverlay(decision.requires);\n }\n }\n\n private showFormatUpgradeOverlay(requires: 'stereoFormats' | 'formats180'): void {\n const label = requires === 'formats180' ? '180° video' : 'Stereoscopic 3D';\n this.showUpgradeOverlay('vz-format-gate', `${label} playback requires a paid Vizor plan.`);\n }\n\n private showUpgradeOverlay(className: string, message: string): void {\n if (!this.containerEl || this.shadow.querySelector('.' + className)) return;\n const overlay = document.createElement('div');\n overlay.className = className;\n overlay.style.cssText =\n 'position:absolute;inset:0;display:flex;align-items:center;justify-content:center;' +\n 'text-align:center;padding:1rem;background:rgba(0,0,0,0.85);color:#fff;z-index:10;' +\n 'font:500 14px/1.5 system-ui,-apple-system,sans-serif;';\n overlay.textContent = message;\n this.containerEl.appendChild(overlay);\n }\n\n /**\n * DRM assets are encrypted-only (no plain fallback). Once the license is known:\n * a tier WITHOUT `drm` is blocked behind an upgrade overlay (and NO license\n * request is made); a licensed tier performs the deferred encrypted source load\n * with the DRM context (license URLs + api-key) so the adapter can drive EME.\n * EME/CDM errors during playback surface via the adapter's existing error events.\n */\n private applyDrmGate(): void {\n const decision = resolveDrmGate(this.drmConfig !== null, this.hasFeature('drm'));\n if (decision.action === 'none') return;\n if (decision.action === 'block') {\n this.videoElement.pause();\n this.showUpgradeOverlay('vz-drm-gate', 'DRM-protected playback requires a Vizor Enterprise plan.');\n return;\n }\n const drm = { config: this.drmConfig!, apiKey: this.config.apiKey };\n this.sourceManager\n .loadSources(this.deferredDrmSources, this.videoElement, this.state, this.events, drm)\n .catch((err) => {\n console.error('[Vizor] DRM source loading error:', err);\n this.events.emit('vz-error', { code: 'DRM_LOAD_ERROR', message: String(err) });\n });\n }\n\n protected onAttributeChanged(name: string, _oldVal: string | null, _newVal: string | null): void {\n if (name === 'format' && this.config.format && this.renderer) {\n this.renderer.setupVideo(this.videoElement, this.config.format);\n }\n }\n\n // --- Public HTML5 Media API ---\n\n play(): Promise<void> {\n // Hide poster on play\n const poster = this.shadow.querySelector('.vz-poster');\n if (poster) poster.classList.add('hidden');\n // Resume AudioContext on user gesture (browsers require this)\n this.audioManager?.resume().catch(() => {});\n return this.videoElement.play();\n }\n\n pause(): void {\n this.videoElement.pause();\n }\n\n seek(time: number): void {\n this.videoElement.currentTime = time;\n }\n\n get duration(): number {\n return this.videoElement.duration;\n }\n\n get currentTime(): number {\n return this.videoElement.currentTime;\n }\n\n set currentTime(t: number) {\n this.videoElement.currentTime = t;\n }\n\n get paused(): boolean {\n return this.videoElement.paused;\n }\n\n get volume(): number {\n return this.videoElement.volume;\n }\n\n set volume(v: number) {\n this.videoElement.volume = v;\n }\n\n get muted(): boolean {\n return this.videoElement.muted;\n }\n\n set muted(m: boolean) {\n this.videoElement.muted = m;\n }\n\n get playbackRate(): number {\n return this.videoElement.playbackRate;\n }\n\n set playbackRate(r: number) {\n this.videoElement.playbackRate = r;\n }\n\n toggleFullscreen(): void {\n if (isFullscreen()) {\n exitFullscreen();\n } else {\n requestFullscreen(this);\n }\n }\n\n /** Get the Three.js renderer (for advanced use / WebXR) */\n getRenderer(): VzRenderer {\n return this.renderer;\n }\n\n // --- Protected hooks for subclasses ---\n\n protected abstract onMediaConnected(): void;\n protected abstract onMediaDisconnected(): void;\n\n // --- Private helpers ---\n\n /** Parse <vz-annotation> children and create annotation manager if any exist */\n private initAnnotations(): void {\n const annotationEls = this.querySelectorAll('vz-annotation');\n if (annotationEls.length === 0) return;\n\n const annotations: AnnotationData[] = [];\n annotationEls.forEach((el) => {\n const vzEl = el as VzAnnotationElement;\n if (typeof vzEl.getData === 'function') {\n annotations.push(vzEl.getData());\n }\n });\n\n if (annotations.length === 0) return;\n\n this.annotationManager = new AnnotationManager(\n this.renderer.getScene(),\n this.renderer.getCamera(),\n this.containerEl,\n this.shadow,\n );\n this.annotationManager.setAnnotations(annotations);\n\n // Update annotation visibility based on current playback time\n this.state.subscribe('currentTime', (time) => {\n this.annotationManager?.update(time);\n });\n }\n\n /**\n * Parse `<vz-chapter>` children into a normalized pending list. The chapter UI\n * (markers/menu/event) is wired later in applyChaptersGate() once the license\n * is known, since chapters are tier-gated.\n */\n private initChapters(): void {\n const chapterEls = this.querySelectorAll('vz-chapter');\n if (chapterEls.length === 0) return;\n\n const raw: RawChapter[] = [];\n chapterEls.forEach((el) => {\n const vzEl = el as VzChapterElement;\n if (typeof vzEl.getData === 'function') {\n raw.push(vzEl.getData());\n }\n });\n\n this.pendingChapters = normalizeChapters(raw);\n }\n\n /** Initialize collaborative viewing if collab attributes are set */\n private initCollab(): void {\n const { collabServer, collabRoom, collabRole, collabDisplayName } = this.config;\n if (!collabServer || !collabRoom) return;\n\n const userId = this.config.collabUserId ?? crypto.randomUUID();\n const role = collabRole ?? 'viewer';\n const displayName = collabDisplayName ?? `User-${userId.slice(0, 6)}`;\n\n // Create gaze cursor manager for peer cursors\n this.gazeCursorManager = new GazeCursorManager(this.renderer.getScene());\n\n // Create chat panel and button\n this.chatPanel = new ChatPanel(this.shadow, (text) => {\n // Add the local message to the panel immediately\n this.chatPanel?.addMessage({\n userId,\n displayName,\n text,\n timestamp: Date.now(),\n isLocal: true,\n });\n // Send via WebSocket\n this.collabClient?.sendChat(text);\n });\n\n if (!this.config.hideControls && this.controlsOverlay) {\n this.chatButton = new ChatButton(this.chatPanel);\n this.controlsOverlay.addControl(this.chatButton.getElement(), 'right');\n }\n\n // Create voice manager (signaling relay via WS)\n this.voiceManager = new VoiceManager(\n (targetUserId, sdp, candidate) => {\n this.collabClient?.sendVoiceSignal(targetUserId, sdp, candidate);\n },\n );\n\n // Create voice button (first click = start, subsequent = toggle mute)\n if (!this.config.hideControls && this.controlsOverlay) {\n this.voiceButton = new VoiceButton(this.voiceManager, () => {\n // When voice starts, connect to all existing peers\n // (peers who join later will be auto-connected via onUserJoined)\n });\n this.controlsOverlay.addControl(this.voiceButton.getElement(), 'right');\n }\n\n this.collabClient = new CollaborativeClient({\n serverUrl: collabServer,\n roomCode: collabRoom,\n role,\n userId,\n displayName,\n state: this.state,\n mediaElement: this,\n password: this.config.collabPassword,\n onPeerGaze: (peerUserId, lat, lon) => {\n this.gazeCursorManager?.updateGaze(peerUserId, lat, lon);\n },\n onUserJoined: (user) => {\n if (user.userId !== userId) {\n this.gazeCursorManager?.addPeer(user.userId, user.displayName);\n // Auto-connect voice to new peer if voice is active\n if (this.voiceManager?.isActive()) {\n this.voiceManager.connectToPeer(user.userId).catch(() => {});\n }\n }\n },\n onUserLeft: (leftUserId) => {\n this.gazeCursorManager?.removePeer(leftUserId);\n // Clean up voice connection to departed peer\n this.voiceManager?.removePeer(leftUserId);\n },\n onChatMessage: (msgUserId, msgDisplayName, text, timestamp) => {\n // Add remote message to chat panel\n this.chatPanel?.addMessage({\n userId: msgUserId,\n displayName: msgDisplayName,\n text,\n timestamp,\n isLocal: false,\n });\n // Increment unread badge if panel is closed\n if (!this.chatPanel?.isVisible()) {\n this.chatButton?.incrementUnread();\n }\n },\n onVoiceSignal: (fromUserId, sdp, candidate) => {\n this.voiceManager?.handleSignal(fromUserId, sdp, candidate).catch(() => {});\n },\n onParticipantsChange: (users) => {\n // On initial room_state, add cursors for all existing peers\n for (const user of users) {\n if (user.userId !== userId) {\n this.gazeCursorManager?.addPeer(user.userId, user.displayName);\n // Set initial gaze if provided\n if (user.lat !== undefined && user.lon !== undefined) {\n this.gazeCursorManager?.updateGaze(user.userId, user.lat, user.lon);\n }\n }\n }\n },\n });\n this.collabClient.connect();\n\n // Create gaze sender to broadcast local camera orientation\n this.gazeSender = new GazeSender(\n this.renderer.getCameraController(),\n (msg) => this.collabClient?.send(msg),\n );\n this.gazeSender.start();\n\n // Update gaze cursor interpolation each frame via currentTime subscription\n // (currentTime updates at display refresh rate via timeupdate)\n const unsubGaze = this.state.subscribe('currentTime', () => {\n this.gazeCursorManager?.update();\n });\n this.collabClient.addCleanup(unsubGaze);\n }\n\n /** Initialize caption system from <track> children */\n private initCaptions(): void {\n const trackElements = this.querySelectorAll('track');\n if (trackElements.length === 0) return;\n\n this.captionRenderer = new CaptionRenderer(this.shadow, this.state);\n\n // Load tracks asynchronously; once done, wire up the CC button\n this.captionRenderer.loadTracks(this).then(() => {\n if (!this.captionRenderer?.hasTracks()) return;\n\n // Create CC button if controls are visible\n if (!this.config.hideControls && this.controlsOverlay) {\n this.ccButton = new CcButton(this.state, this.captionRenderer);\n this.controlsOverlay.addControl(this.ccButton.getElement(), 'right');\n this.ccButton.updateVisibility();\n }\n }).catch((err) => {\n console.warn('[Vizor] Caption init error:', err);\n });\n }\n\n private createVideoElement(): HTMLVideoElement {\n const video = document.createElement('video');\n // crossorigin is required for WebGL texture use\n video.crossOrigin = this.config.crossorigin ?? 'anonymous';\n video.playsInline = true;\n video.preload = this.config.preload === 'none' ? 'metadata' : this.config.preload;\n video.loop = this.config.loop;\n video.muted = this.config.muted;\n // WS-D: real autoplay (muted-autoplay-only per browser policy).\n applyAutoplayPolicy(video, this.config);\n\n // Hidden - used only as texture source\n video.style.display = 'none';\n\n // Log errors for debugging\n video.addEventListener('error', () => {\n const err = video.error;\n console.error('[Vizor] Video error:', err?.code, err?.message);\n this.events.emit('vz-error', {\n code: `MEDIA_ERROR_${err?.code ?? 'UNKNOWN'}`,\n message: err?.message ?? 'Unknown video error',\n });\n });\n\n this.shadow.appendChild(video);\n\n return video;\n }\n\n /** Get the source manager (for quality switching) */\n getSourceManager(): SourceManager {\n return this.sourceManager;\n }\n\n /** Parse <source> children and set up video sources */\n private loadSources(): void {\n const sourceElements = this.querySelectorAll('source');\n const sources: ParsedSource[] = [];\n\n sourceElements.forEach((el) => {\n const src = el.getAttribute('src');\n const type = el.getAttribute('type') ?? 'video/mp4';\n if (!src) return;\n\n sources.push({\n url: src,\n type,\n quality: el.getAttribute('quality') ?? undefined,\n fps: el.hasAttribute('fps') ? parseFloat(el.getAttribute('fps')!) : undefined,\n format: el.hasAttribute('format') ? parseFormat(el.getAttribute('format')!) : undefined,\n audioFormat: el.getAttribute('audio-format') === 'ambix' ? 'ambix' : undefined,\n });\n });\n\n if (sources.length === 0) {\n // Check for direct src attribute\n const src = this.getAttribute('src');\n if (src) {\n sources.push({ url: src, type: 'video/mp4' });\n }\n }\n\n if (sources.length === 0) return;\n\n // A DRM asset is encrypted-only and tier-gated: defer the adapter load until\n // onLicenseResolved()/applyDrmGate() knows whether the tier includes `drm`.\n if (this.drmConfig) {\n this.deferredDrmSources = sources;\n return;\n }\n\n // Use SourceManager to select the best adapter and load\n this.sourceManager.loadSources(sources, this.videoElement, this.state, this.events).catch((err) => {\n console.error('[Vizor] Source loading error:', err);\n this.events.emit('vz-error', { code: 'SOURCE_LOAD_ERROR', message: String(err) });\n });\n }\n\n /** Mirror HTML5 media events from the hidden video to the custom element */\n private mirrorMediaEvents(): void {\n for (const eventName of MEDIA_EVENTS) {\n this.videoElement.addEventListener(eventName, () => {\n // Update state\n this.syncStateFromVideo();\n\n // Re-dispatch event on the custom element\n this.events.emitMedia(eventName);\n });\n }\n\n // High-frequency timeupdate polling for smoother seek bar\n this.timeUpdateTimer = setInterval(() => {\n if (!this.videoElement.paused) {\n this.state.set('currentTime', this.videoElement.currentTime);\n }\n }, 250);\n }\n\n /** Sync the reactive state from the current video element state */\n private syncStateFromVideo(): void {\n const v = this.videoElement;\n this.state.batch({\n playing: !v.paused && !v.ended,\n paused: v.paused,\n currentTime: v.currentTime,\n duration: isNaN(v.duration) ? 0 : v.duration,\n buffered: v.buffered.length > 0 ? v.buffered.end(v.buffered.length - 1) : 0,\n seeking: v.seeking,\n ended: v.ended,\n volume: v.volume,\n muted: v.muted,\n playbackRate: v.playbackRate,\n loading: v.readyState < 3,\n });\n }\n}\n","import { VzMediaElement } from '../core/media-element.js';\n\n/**\n * <vz-video> - VR-capable video player element.\n *\n * Supports all VzFormat types (mono/stereo, 180/360, flat, cubemap).\n * Mirrors the HTML5 <video> API with added VR capabilities.\n *\n * @example\n * ```html\n * <vz-video title=\"My Video\" format=\"STEREO_360_LR\" poster=\"thumb.jpg\">\n * <source src=\"video.mp4\" type=\"video/mp4\" />\n * </vz-video>\n * ```\n */\nexport class VzVideoElement extends VzMediaElement {\n protected onMediaConnected(): void {\n // Video-specific initialization\n // Controls, analytics, etc. will be added in later phases\n }\n\n protected onMediaDisconnected(): void {\n // Video-specific cleanup\n }\n}\n\n/** Register the <vz-video> custom element */\nexport function registerVideo(): void {\n if (!customElements.get('vz-video')) {\n customElements.define('vz-video', VzVideoElement);\n }\n}\n","import { TextureLoader, SRGBColorSpace } from 'three';\nimport type { VzFormat } from '@vizor-vr/shared';\nimport { getFormatMeta } from '@vizor-vr/shared';\nimport { VzBaseElement } from '../core/base-element.js';\nimport { VzRenderer } from '../renderer/renderer.js';\nimport { ControlsOverlay } from '../controls/controls-overlay.js';\nimport { FullscreenButton } from '../controls/fullscreen-button.js';\nimport { Compass } from '../controls/compass.js';\nimport { requestFullscreen, exitFullscreen, isFullscreen, onFullscreenChange } from '../utils/fullscreen.js';\n\n/**\n * <vz-img> - VR-capable panoramic image viewer element.\n *\n * @example\n * ```html\n * <vz-img src=\"panorama.jpg\" format=\"MONO_360\" title=\"My Panorama\" />\n * ```\n */\nexport class VzImgElement extends VzBaseElement {\n // `src` is not part of the shared attribute map, so observe it explicitly —\n // otherwise runtime `src` changes never reach onAttributeChanged (dead code).\n static get observedAttributes(): string[] {\n return [...super.observedAttributes, 'src'];\n }\n\n private renderer!: VzRenderer;\n private containerEl!: HTMLDivElement;\n private controlsOverlay: ControlsOverlay | null = null;\n private compass: Compass | null = null;\n private fullscreenButton: FullscreenButton | null = null;\n private cleanupFullscreen: (() => void) | null = null;\n\n protected onConnected(): void {\n // Create container\n this.containerEl = document.createElement('div');\n this.containerEl.className = 'vz-container';\n this.shadow.appendChild(this.containerEl);\n\n // Initialize renderer\n this.renderer = new VzRenderer(\n this.shadow,\n this.containerEl,\n this.state,\n this.config,\n );\n\n // Load image and set up projection\n const src = this.getAttribute('src');\n if (src && this.config.format) {\n this.loadImage(src, this.config.format);\n }\n\n // Add controls (compass + fullscreen only for images)\n if (!this.config.hideControls) {\n this.controlsOverlay = new ControlsOverlay(\n this.shadow, this.containerEl, this.state, this.config, this.events,\n );\n\n this.compass = new Compass(this.state, this.renderer);\n this.controlsOverlay.addControl(this.compass.getElement(), 'right');\n\n this.fullscreenButton = new FullscreenButton(this.state, this);\n this.controlsOverlay.addControl(this.fullscreenButton.getElement(), 'right');\n }\n\n // Fullscreen support\n this.cleanupFullscreen = onFullscreenChange(() => {\n const fs = isFullscreen();\n this.state.set('fullscreen', fs);\n this.renderer.resize();\n });\n\n this.renderer.start();\n this.events.emit('vz-ready');\n }\n\n protected onDisconnected(): void {\n this.cleanupFullscreen?.();\n this.compass?.dispose();\n this.fullscreenButton?.dispose();\n this.controlsOverlay?.dispose();\n this.renderer.dispose();\n }\n\n toggleFullscreen(): void {\n if (isFullscreen()) {\n exitFullscreen();\n } else {\n requestFullscreen(this);\n }\n }\n\n protected onAttributeChanged(name: string): void {\n if (name === 'src' || name === 'format') {\n const src = this.getAttribute('src');\n if (src && this.config.format) {\n this.loadImage(src, this.config.format);\n }\n }\n }\n\n protected onResize(): void {\n this.renderer?.resize();\n }\n\n private loadImage(src: string, format: VzFormat): void {\n const loader = new TextureLoader();\n loader.load(\n src,\n (texture) => {\n texture.colorSpace = SRGBColorSpace;\n this.renderer.setupImage(texture, format);\n },\n undefined,\n (err) => {\n console.error('[Vizor] Failed to load image:', err);\n this.events.emit('vz-error', { code: 'IMAGE_LOAD_ERROR', message: String(err) });\n },\n );\n }\n}\n\n/** Register the <vz-img> custom element */\nexport function registerImg(): void {\n if (!customElements.get('vz-img')) {\n customElements.define('vz-img', VzImgElement);\n }\n}\n","import {\n Sprite,\n SpriteMaterial,\n CanvasTexture,\n LinearFilter,\n} from 'three';\nimport type { Scene } from 'three';\nimport type { TourPortal } from '@vizor-vr/shared';\nimport { latLonToSpherical } from '@vizor-vr/shared';\n\nexport interface HotspotSprite {\n portal: TourPortal;\n sprite: Sprite;\n}\n\nconst HOTSPOT_DISTANCE = 200;\nconst HOTSPOT_SCALE_X = 30;\nconst HOTSPOT_SCALE_Y = 15;\nconst CANVAS_W = 256;\nconst CANVAS_H = 128;\n\n/**\n * Renders 3D sprite labels for tour portal hotspots.\n * Positioned on the inside of the panoramic sphere at each portal's lat/lon.\n */\nexport class HotspotRenderer {\n private sprites: HotspotSprite[] = [];\n private scene: Scene;\n\n constructor(scene: Scene) {\n this.scene = scene;\n }\n\n /** Create sprite meshes for all portals and add to scene */\n setPortals(portals: TourPortal[]): void {\n this.clear();\n\n for (const portal of portals) {\n const sprite = this.createSprite(portal);\n sprite.userData.portalTo = portal.to;\n this.sprites.push({ portal, sprite });\n this.scene.add(sprite);\n }\n }\n\n /** Remove all hotspot sprites from scene */\n clear(): void {\n for (const { sprite } of this.sprites) {\n this.scene.remove(sprite);\n sprite.material.map?.dispose();\n sprite.material.dispose();\n }\n this.sprites = [];\n }\n\n /** Get all current sprites (for raycasting) */\n getSprites(): Sprite[] {\n return this.sprites.map((h) => h.sprite);\n }\n\n dispose(): void {\n this.clear();\n }\n\n private createSprite(portal: TourPortal): Sprite {\n // Draw label using Canvas2D\n const canvas = document.createElement('canvas');\n canvas.width = CANVAS_W;\n canvas.height = CANVAS_H;\n const ctx = canvas.getContext('2d')!;\n\n // Rounded rectangle background\n const pad = 8;\n ctx.fillStyle = 'rgba(0, 160, 255, 0.85)';\n ctx.beginPath();\n this.roundRect(ctx, pad, pad, CANVAS_W - pad * 2, CANVAS_H - pad * 2, 16);\n ctx.fill();\n\n // Label text\n const label = portal.title ?? portal.to;\n ctx.fillStyle = '#fff';\n ctx.font = 'bold 24px sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(label, CANVAS_W / 2, CANVAS_H / 2 - 4, CANVAS_W - 32);\n\n // Small arrow indicator\n ctx.font = '16px sans-serif';\n ctx.fillText('\\u2192', CANVAS_W / 2, CANVAS_H / 2 + 20);\n\n const texture = new CanvasTexture(canvas);\n texture.minFilter = LinearFilter;\n texture.magFilter = LinearFilter;\n\n const material = new SpriteMaterial({\n map: texture,\n sizeAttenuation: true,\n depthTest: false,\n });\n const sprite = new Sprite(material);\n\n // Position on sphere interior at the portal's lat/lon\n const distance = portal.distance ?? HOTSPOT_DISTANCE;\n const lat = portal.lat ?? 0;\n const lon = portal.lon ?? 0;\n const { phi, theta } = latLonToSpherical(lat, lon);\n\n sprite.position.set(\n distance * Math.sin(phi) * Math.cos(theta),\n distance * Math.cos(phi),\n distance * Math.sin(phi) * Math.sin(theta),\n );\n sprite.scale.set(HOTSPOT_SCALE_X, HOTSPOT_SCALE_Y, 1);\n\n return sprite;\n }\n\n private roundRect(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n w: number,\n h: number,\n r: number,\n ): void {\n ctx.moveTo(x + r, y);\n ctx.arcTo(x + w, y, x + w, y + h, r);\n ctx.arcTo(x + w, y + h, x, y + h, r);\n ctx.arcTo(x, y + h, x, y, r);\n ctx.arcTo(x, y, x + w, y, r);\n ctx.closePath();\n }\n}\n","import { Raycaster, Vector2 } from 'three';\nimport type { PerspectiveCamera, Sprite } from 'three';\n\nexport type HotspotClickHandler = (portalTo: string) => void;\n\n/**\n * Handles click detection on hotspot sprites via raycasting.\n * Converts mouse/touch clicks to normalized device coordinates,\n * raycasts through the scene, and triggers navigation on hit.\n */\nexport class HotspotInteraction {\n private raycaster = new Raycaster();\n private pointer = new Vector2();\n private camera: PerspectiveCamera;\n private getSprites: () => Sprite[];\n private onClick: HotspotClickHandler;\n private domElement: HTMLElement;\n private cleanups: Array<() => void> = [];\n\n constructor(\n camera: PerspectiveCamera,\n domElement: HTMLElement,\n getSprites: () => Sprite[],\n onClick: HotspotClickHandler,\n ) {\n this.camera = camera;\n this.domElement = domElement;\n this.getSprites = getSprites;\n this.onClick = onClick;\n }\n\n /** Start listening for click events */\n attach(): void {\n const handler = (e: MouseEvent) => {\n const rect = this.domElement.getBoundingClientRect();\n this.pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;\n this.pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;\n\n this.raycaster.setFromCamera(this.pointer, this.camera);\n const sprites = this.getSprites();\n const intersects = this.raycaster.intersectObjects(sprites);\n\n if (intersects.length > 0) {\n const portalTo = intersects[0]!.object.userData.portalTo as string;\n if (portalTo) {\n e.stopPropagation();\n this.onClick(portalTo);\n }\n }\n };\n\n this.domElement.addEventListener('click', handler);\n this.cleanups.push(() => this.domElement.removeEventListener('click', handler));\n }\n\n /** Stop listening */\n detach(): void {\n for (const cleanup of this.cleanups) cleanup();\n this.cleanups = [];\n }\n\n dispose(): void {\n this.detach();\n }\n}\n","import type { Scene, PerspectiveCamera } from 'three';\nimport type { TourPortal } from '@vizor-vr/shared';\nimport { HotspotRenderer } from './hotspot-renderer.js';\nimport { HotspotInteraction, type HotspotClickHandler } from './hotspot-interaction.js';\n\n/**\n * Orchestrates hotspot rendering and interaction.\n * Used by VzTourElement to manage portal sprites and click detection.\n */\nexport class HotspotManager {\n private renderer: HotspotRenderer;\n private interaction: HotspotInteraction;\n\n constructor(\n scene: Scene,\n camera: PerspectiveCamera,\n domElement: HTMLElement,\n onNavigate: HotspotClickHandler,\n ) {\n this.renderer = new HotspotRenderer(scene);\n this.interaction = new HotspotInteraction(\n camera,\n domElement,\n () => this.renderer.getSprites(),\n onNavigate,\n );\n this.interaction.attach();\n }\n\n /** Set portals for the current probe */\n setPortals(portals: TourPortal[]): void {\n this.renderer.setPortals(portals);\n }\n\n /** Clear all hotspots */\n clear(): void {\n this.renderer.clear();\n }\n\n dispose(): void {\n this.interaction.dispose();\n this.renderer.dispose();\n }\n}\n","import { TextureLoader, SRGBColorSpace } from 'three';\nimport type { Texture } from 'three';\nimport type { TourProbe } from '@vizor-vr/shared';\nimport { VzBaseElement } from '../core/base-element.js';\nimport { VzRenderer } from '../renderer/renderer.js';\nimport { HotspotManager } from '../hotspots/hotspot-manager.js';\nimport { ControlsOverlay } from '../controls/controls-overlay.js';\nimport { FullscreenButton } from '../controls/fullscreen-button.js';\nimport { Compass } from '../controls/compass.js';\nimport { VrButton } from '../controls/vr-button.js';\nimport { XRManager } from '../xr/xr-manager.js';\nimport { requestFullscreen, exitFullscreen, isFullscreen, onFullscreenChange } from '../utils/fullscreen.js';\nimport { resolveFeatureGate } from '../license/feature-gate.js';\nimport type { VzTourImgElement } from './vz-tour-img.js';\n\n/**\n * <vz-tour> - Interactive panoramic tour with navigable probes.\n *\n * @example\n * ```html\n * <vz-tour start-probe-id=\"lobby\" style=\"width:100%; aspect-ratio:16/9\">\n * <vz-tour-img probe-id=\"lobby\" format=\"MONO_360\" src=\"lobby.jpg\" title=\"Lobby\">\n * <vz-tour-portal to=\"office\" lat=\"10\" lon=\"45\" title=\"Go to Office\" />\n * </vz-tour-img>\n * <vz-tour-img probe-id=\"office\" format=\"MONO_360\" src=\"office.jpg\" title=\"Office\">\n * <vz-tour-portal to=\"lobby\" lat=\"0\" lon=\"-135\" title=\"Back to Lobby\" />\n * </vz-tour-img>\n * </vz-tour>\n * ```\n */\nexport class VzTourElement extends VzBaseElement {\n private renderer!: VzRenderer;\n private containerEl!: HTMLDivElement;\n private probes = new Map<string, TourProbe>();\n private currentProbeId: string | null = null;\n private hotspotManager: HotspotManager | null = null;\n private textureCache = new Map<string, Texture>();\n private controlsOverlay: ControlsOverlay | null = null;\n private compass: Compass | null = null;\n private fullscreenButton: FullscreenButton | null = null;\n private vrButton: VrButton | null = null;\n private xrManager: XRManager | null = null;\n private cleanupFullscreen: (() => void) | null = null;\n private loader = new TextureLoader();\n /**\n * True once the license resolves without the `tours` feature. In that state\n * the tour degrades to a single static panorama (the start probe): portal\n * navigation between probes is disabled.\n */\n private toursLocked = false;\n\n protected onConnected(): void {\n // Create container\n this.containerEl = document.createElement('div');\n this.containerEl.className = 'vz-container';\n this.shadow.appendChild(this.containerEl);\n\n // Initialize renderer\n this.renderer = new VzRenderer(\n this.shadow,\n this.containerEl,\n this.state,\n this.config,\n );\n\n // Parse child elements into probes map\n this.parseProbes();\n\n // Create hotspot manager for portal interaction\n this.hotspotManager = new HotspotManager(\n this.renderer.getScene(),\n this.renderer.getCamera(),\n this.containerEl,\n (probeId) => this.navigateTo(probeId),\n );\n\n // Initialize WebXR manager\n this.xrManager = new XRManager(\n this.renderer.getThreeRenderer(),\n this.state,\n this.events,\n this.renderer.getCameraController(),\n );\n\n // Add controls (compass + fullscreen + VR)\n if (!this.config.hideControls) {\n this.controlsOverlay = new ControlsOverlay(\n this.shadow, this.containerEl, this.state, this.config, this.events,\n );\n\n this.compass = new Compass(this.state, this.renderer);\n this.controlsOverlay.addControl(this.compass.getElement(), 'right');\n\n this.vrButton = new VrButton(this.state, () => {\n this.xrManager?.toggleXR().catch((err) => {\n console.error('[Vizor] XR toggle failed:', err);\n this.events.emit('vz-error', { code: 'XR_ERROR', message: String(err) });\n });\n });\n this.controlsOverlay.addControl(this.vrButton.getElement(), 'right');\n\n this.fullscreenButton = new FullscreenButton(this.state, this);\n this.controlsOverlay.addControl(this.fullscreenButton.getElement(), 'right');\n }\n\n // Fullscreen support\n this.cleanupFullscreen = onFullscreenChange(() => {\n const fs = isFullscreen();\n this.state.set('fullscreen', fs);\n this.renderer.resize();\n });\n\n // Load initial probe\n const startId = this.getAttribute('start-probe-id') ?? this.probes.keys().next().value;\n if (startId) {\n this.navigateTo(startId);\n }\n\n this.renderer.start();\n this.events.emit('vz-ready');\n }\n\n protected onDisconnected(): void {\n this.cleanupFullscreen?.();\n this.compass?.dispose();\n this.fullscreenButton?.dispose();\n this.vrButton?.dispose();\n this.controlsOverlay?.dispose();\n this.xrManager?.dispose();\n this.hotspotManager?.dispose();\n\n // Dispose cached textures\n for (const texture of this.textureCache.values()) {\n texture.dispose();\n }\n this.textureCache.clear();\n\n this.renderer.dispose();\n }\n\n protected onAttributeChanged(_name: string): void {\n // Re-parse if children changed\n }\n\n protected onResize(): void {\n this.renderer?.resize();\n }\n\n /**\n * Interactive tours are a paid feature, and WebXR (Enter-VR) is gated\n * separately. Once the license resolves, enforce both.\n *\n * - tours not licensed → graceful downgrade to a single static panorama: the\n * start probe stays visible but portal navigation is disabled (existing\n * portals cleared, future ones suppressed).\n * - webxr not licensed → remove the VR button (flat viewing continues).\n */\n protected override onLicenseResolved(): void {\n if (resolveFeatureGate('webxr', this.hasFeature('webxr')).action !== 'allow') {\n this.vrButton?.dispose();\n this.vrButton?.getElement().remove();\n this.vrButton = null;\n }\n\n if (resolveFeatureGate('tours', this.hasFeature('tours')).action !== 'allow') {\n this.toursLocked = true;\n // Drop the portals rendered for the current probe so navigation stops.\n this.hotspotManager?.clear();\n }\n }\n\n /** Navigate to a specific probe by ID */\n async navigateTo(probeId: string): Promise<void> {\n const probe = this.probes.get(probeId);\n if (!probe) {\n console.error(`[Vizor] Tour probe not found: ${probeId}`);\n return;\n }\n\n const prevProbeId = this.currentProbeId;\n this.currentProbeId = probeId;\n\n // Clear existing hotspots during transition\n this.hotspotManager?.clear();\n\n // Load texture (from cache or network)\n this.state.set('loading', true);\n try {\n const texture = await this.loadOrGetTexture(probe.src);\n this.renderer.setupImage(texture, probe.format);\n } catch (err) {\n console.error('[Vizor] Failed to load probe image:', err);\n this.events.emit('vz-error', { code: 'TOUR_LOAD_ERROR', message: String(err) });\n this.state.set('loading', false);\n return;\n }\n this.state.set('loading', false);\n\n // Apply initial camera orientation if specified\n if (probe.rotationY !== undefined) {\n this.renderer.getCameraController().setOrientation(0, probe.rotationY);\n }\n\n // Set new hotspots for this probe's portals — unless the tour is locked to\n // a single panorama (unlicensed graceful downgrade), where navigation off.\n if (!this.toursLocked) {\n this.hotspotManager?.setPortals(probe.portals);\n }\n\n // Update state\n this.state.batch({\n currentProbeId: probeId,\n });\n\n // Emit navigation event\n if (prevProbeId) {\n this.events.emit('vz-tour-navigate', {\n fromProbeId: prevProbeId,\n toProbeId: probeId,\n });\n }\n\n // Preload neighboring probes\n this.preloadNeighbors(probe);\n }\n\n /** Get current probe ID */\n get currentProbe(): string | null {\n return this.currentProbeId;\n }\n\n toggleFullscreen(): void {\n if (isFullscreen()) {\n exitFullscreen();\n } else {\n requestFullscreen(this);\n }\n }\n\n /** Parse all <vz-tour-img> children into the probes map */\n private parseProbes(): void {\n this.probes.clear();\n const tourImgs = this.querySelectorAll('vz-tour-img') as NodeListOf<VzTourImgElement>;\n\n for (const img of tourImgs) {\n const probe = img.getProbeData();\n if (probe.probeId) {\n this.probes.set(probe.probeId, probe);\n }\n }\n\n this.state.set('tourProbeCount', this.probes.size);\n }\n\n /** Load a texture, using cache if available */\n private loadOrGetTexture(src: string): Promise<Texture> {\n const cached = this.textureCache.get(src);\n if (cached) return Promise.resolve(cached);\n\n return new Promise((resolve, reject) => {\n this.loader.load(\n src,\n (texture) => {\n texture.colorSpace = SRGBColorSpace;\n this.textureCache.set(src, texture);\n resolve(texture);\n },\n undefined,\n reject,\n );\n });\n }\n\n /** Preload textures for neighboring probes (fire-and-forget) */\n private preloadNeighbors(probe: TourProbe): void {\n for (const portal of probe.portals) {\n const target = this.probes.get(portal.to);\n if (target && !this.textureCache.has(target.src)) {\n this.loader.load(\n target.src,\n (texture) => {\n texture.colorSpace = SRGBColorSpace;\n this.textureCache.set(target.src, texture);\n },\n );\n }\n }\n }\n}\n\n/** Register the <vz-tour> custom element */\nexport function registerTour(): void {\n if (!customElements.get('vz-tour')) {\n customElements.define('vz-tour', VzTourElement);\n }\n}\n","import type { TourProbe, VzFormat } from '@vizor-vr/shared';\nimport { parseFormat } from '@vizor-vr/shared';\nimport { HTMLElementBase } from '../core/html-element.js';\nimport type { VzTourPortalElement } from './vz-tour-portal.js';\n\n/**\n * <vz-tour-img> - A single panoramic probe in a tour.\n * Parsed by the parent <vz-tour> to build the navigable data structure.\n *\n * @example\n * ```html\n * <vz-tour-img probe-id=\"lobby\" format=\"MONO_360\" src=\"lobby.jpg\" title=\"Lobby\">\n * <vz-tour-portal to=\"office\" lat=\"10\" lon=\"45\" title=\"Go to Office\" />\n * </vz-tour-img>\n * ```\n */\nexport class VzTourImgElement extends HTMLElementBase {\n static get observedAttributes(): string[] {\n return ['probe-id', 'format', 'src', 'src-left', 'src-right', 'title', 'rotation-y'];\n }\n\n /** Parse this element and its portal children into a TourProbe data object */\n getProbeData(): TourProbe {\n const formatStr = this.getAttribute('format');\n const format = formatStr ? parseFormat(formatStr) : null;\n\n const portals = Array.from(\n this.querySelectorAll('vz-tour-portal') as NodeListOf<VzTourPortalElement>,\n ).map((el) => el.getPortalData());\n\n return {\n probeId: this.getAttribute('probe-id') ?? '',\n title: this.getAttribute('title') ?? undefined,\n format: format as VzFormat,\n src: this.getAttribute('src') ?? '',\n srcLeft: this.getAttribute('src-left') ?? undefined,\n srcRight: this.getAttribute('src-right') ?? undefined,\n rotationY: this.hasAttribute('rotation-y')\n ? parseFloat(this.getAttribute('rotation-y')!)\n : undefined,\n portals,\n };\n }\n}\n\n/** Register the <vz-tour-img> custom element */\nexport function registerTourImg(): void {\n if (!customElements.get('vz-tour-img')) {\n customElements.define('vz-tour-img', VzTourImgElement);\n }\n}\n","import type { TourPortal } from '@vizor-vr/shared';\nimport { HTMLElementBase } from '../core/html-element.js';\n\n/**\n * <vz-tour-portal> - Declarative portal (navigation hotspot) definition.\n * Must be a child of <vz-tour-img>.\n *\n * @example\n * ```html\n * <vz-tour-portal to=\"office\" lat=\"10\" lon=\"45\" title=\"Go to Office\" />\n * ```\n */\nexport class VzTourPortalElement extends HTMLElementBase {\n static get observedAttributes(): string[] {\n return ['to', 'lat', 'lon', 'title', 'distance'];\n }\n\n /** Parse this element's attributes into a TourPortal data object */\n getPortalData(): TourPortal {\n return {\n to: this.getAttribute('to') ?? '',\n title: this.getAttribute('title') ?? undefined,\n lat: this.hasAttribute('lat') ? parseFloat(this.getAttribute('lat')!) : undefined,\n lon: this.hasAttribute('lon') ? parseFloat(this.getAttribute('lon')!) : undefined,\n distance: this.hasAttribute('distance') ? parseFloat(this.getAttribute('distance')!) : undefined,\n };\n }\n}\n\n/** Register the <vz-tour-portal> custom element */\nexport function registerTourPortal(): void {\n if (!customElements.get('vz-tour-portal')) {\n customElements.define('vz-tour-portal', VzTourPortalElement);\n }\n}\n","import {\n SphereGeometry,\n PlaneGeometry,\n MeshBasicMaterial,\n Mesh,\n} from 'three';\nimport type { PerspectiveCamera, Texture } from 'three';\nimport type { StereoMode } from '@vizor-vr/shared';\nimport { SPHERE_DEFAULTS, CAMERA_DEFAULTS } from '@vizor-vr/shared';\nimport type { IProjection } from './projection.js';\n\nexport interface CinemaConfig {\n screenWidth: number;\n screenHeight: number;\n screenDistance: number;\n screenOffsetY: number;\n}\n\nconst DEFAULT_CINEMA: CinemaConfig = {\n screenWidth: 3,\n screenHeight: 1.8,\n screenDistance: 2.5,\n screenOffsetY: 0,\n};\n\n/**\n * Cinema projection: flat video screen inside a panoramic room.\n *\n * Composes two geometries:\n * - Inverted sphere for the room environment (equirectangular)\n * - Flat plane for the video screen positioned at configurable distance/size\n */\nexport class CinemaProjection implements IProjection {\n private meshes: Mesh[] = [];\n private roomMesh: Mesh | null = null;\n private config: CinemaConfig;\n\n constructor(config?: Partial<CinemaConfig>) {\n this.config = { ...DEFAULT_CINEMA, ...config };\n }\n\n createMeshes(videoTexture: Texture, stereoMode: StereoMode): Mesh[] {\n this.dispose();\n\n // Room sphere (dark until room texture is set)\n const roomGeo = new SphereGeometry(\n SPHERE_DEFAULTS.radius,\n SPHERE_DEFAULTS.widthSegments,\n SPHERE_DEFAULTS.heightSegments,\n );\n roomGeo.scale(-1, 1, 1);\n const roomMat = new MeshBasicMaterial({ color: 0x111111 });\n this.roomMesh = new Mesh(roomGeo, roomMat);\n this.roomMesh.layers.set(0); // Visible to both eyes\n\n // Screen plane with video texture\n // Position along +X axis (CameraController lon=0 faces +X)\n // Rotate -90° around Y so plane faces back toward camera at origin\n const screenGeo = new PlaneGeometry(this.config.screenWidth, this.config.screenHeight);\n const screenMat = new MeshBasicMaterial({ map: videoTexture });\n const screenMesh = new Mesh(screenGeo, screenMat);\n screenMesh.position.set(this.config.screenDistance, this.config.screenOffsetY, 0);\n screenMesh.rotation.y = -Math.PI / 2;\n\n if (stereoMode === 'mono') {\n screenMesh.layers.set(0);\n this.meshes = [this.roomMesh, screenMesh];\n } else {\n // Stereo: create two screen meshes with UV remapping\n const screenGeoR = new PlaneGeometry(this.config.screenWidth, this.config.screenHeight);\n const screenMatR = new MeshBasicMaterial({ map: videoTexture });\n const screenMeshR = new Mesh(screenGeoR, screenMatR);\n screenMeshR.position.copy(screenMesh.position);\n screenMeshR.rotation.copy(screenMesh.rotation);\n\n if (stereoMode === 'sbs') {\n this.remapUVsHorizontal(screenGeo, 0, 0.5);\n this.remapUVsHorizontal(screenGeoR, 0.5, 1);\n } else if (stereoMode === 'tb') {\n this.remapUVsVertical(screenGeo, 0.5, 1);\n this.remapUVsVertical(screenGeoR, 0, 0.5);\n }\n\n screenMesh.layers.set(1); // Left eye\n screenMeshR.layers.set(2); // Right eye\n this.meshes = [this.roomMesh, screenMesh, screenMeshR];\n }\n\n return this.meshes;\n }\n\n /** Set the room equirectangular texture */\n setRoomTexture(texture: Texture): void {\n if (this.roomMesh) {\n (this.roomMesh.material as MeshBasicMaterial).map = texture;\n (this.roomMesh.material as MeshBasicMaterial).color.set(0xffffff);\n (this.roomMesh.material as MeshBasicMaterial).needsUpdate = true;\n }\n }\n\n configureCamera(camera: PerspectiveCamera): void {\n camera.fov = CAMERA_DEFAULTS.fov;\n camera.near = 0.1;\n camera.far = SPHERE_DEFAULTS.radius * 2;\n camera.position.set(0, 0, 0);\n camera.updateProjectionMatrix();\n }\n\n update(_camera: PerspectiveCamera, _time: number): void {\n // No per-frame updates needed\n }\n\n dispose(): void {\n for (const mesh of this.meshes) {\n mesh.geometry.dispose();\n (mesh.material as MeshBasicMaterial).dispose();\n }\n this.meshes = [];\n this.roomMesh = null;\n }\n\n private remapUVsHorizontal(geo: PlaneGeometry, uMin: number, uMax: number): void {\n const uv = geo.attributes.uv!;\n for (let i = 0; i < uv.count; i++) {\n const u = uv.getX(i);\n uv.setX(i, uMin + u * (uMax - uMin));\n }\n uv.needsUpdate = true;\n }\n\n private remapUVsVertical(geo: PlaneGeometry, vMin: number, vMax: number): void {\n const uv = geo.attributes.uv!;\n for (let i = 0; i < uv.count; i++) {\n const v = uv.getY(i);\n uv.setY(i, vMin + v * (vMax - vMin));\n }\n uv.needsUpdate = true;\n }\n}\n","import { TextureLoader, VideoTexture, SRGBColorSpace, LinearFilter } from 'three';\nimport { VzFormat } from '@vizor-vr/shared';\nimport { VzMediaElement } from '../core/media-element.js';\nimport { CinemaProjection } from '../renderer/projections/cinema-projection.js';\nimport { resolveFeatureGate } from '../license/feature-gate.js';\n\n/**\n * <vz-cinema> - Virtual cinema: flat video on a screen inside a panoramic room.\n *\n * @example\n * ```html\n * <vz-cinema\n * room-src=\"cinema-room.jpg\"\n * screen-width=\"3\"\n * screen-height=\"1.8\"\n * screen-distance=\"2.5\"\n * style=\"width:100%; aspect-ratio:16/9\"\n * >\n * <source src=\"movie.mp4\" type=\"video/mp4\" />\n * </vz-cinema>\n * ```\n */\nexport class VzCinemaElement extends VzMediaElement {\n private cinemaProjection: CinemaProjection | null = null;\n private videoDataReady = false;\n /** Guard so the projection (cinema or flat fallback) is only built once. */\n private projectionSetup = false;\n\n static get observedAttributes(): string[] {\n return [\n ...super.observedAttributes,\n 'room-src', 'room-format',\n 'screen-width', 'screen-height', 'screen-distance', 'screen-offset-y',\n ];\n }\n\n protected onMediaConnected(): void {\n // Suppress the base class auto-setupVideo — cinema handles its own projection\n this.suppressAutoSetup = true;\n\n // Track video-data readiness. Actual projection setup is deferred until the\n // license also resolves (cinema is a paid feature), so both conditions are\n // reconciled in maybeSetupProjection().\n if (this.videoElement.readyState >= 2) {\n this.videoDataReady = true;\n this.maybeSetupProjection();\n } else {\n this.videoElement.addEventListener('loadeddata', () => {\n this.videoDataReady = true;\n this.maybeSetupProjection();\n }, { once: true });\n }\n }\n\n /**\n * Cinema is a paid feature. The base class runs its own gates (format, webxr,\n * spatial audio) here; we add the cinema gate and (re)attempt projection\n * setup now that the license is known.\n */\n protected override onLicenseResolved(): void {\n super.onLicenseResolved();\n this.maybeSetupProjection();\n }\n\n /**\n * Build the projection once BOTH the video data is ready AND the license has\n * resolved. Licensed → the cinema room projection. Unlicensed → graceful\n * downgrade to flat (mono) playback of the same clip.\n */\n private maybeSetupProjection(): void {\n if (this.projectionSetup) return;\n if (!this.videoDataReady) return;\n if (!this.license) return; // wait for license before deciding\n\n this.projectionSetup = true;\n\n if (resolveFeatureGate('cinema', this.hasFeature('cinema')).action === 'allow') {\n this.setupCinemaProjection();\n } else {\n this.setupFlatFallback();\n }\n }\n\n /**\n * Graceful downgrade when the tier lacks cinema: render the clip as an\n * ordinary flat video instead of projecting it onto a screen inside a room.\n */\n private setupFlatFallback(): void {\n this.renderer.setupVideo(this.videoElement, VzFormat.MONO_FLAT);\n }\n\n protected onMediaDisconnected(): void {\n this.cinemaProjection = null;\n this.projectionSetup = false;\n this.videoDataReady = false;\n }\n\n private setupCinemaProjection(): void {\n this.cinemaProjection = new CinemaProjection({\n screenWidth: this.getNumAttr('screen-width', 3),\n screenHeight: this.getNumAttr('screen-height', 1.8),\n screenDistance: this.getNumAttr('screen-distance', 2.5),\n screenOffsetY: this.getNumAttr('screen-offset-y', 0),\n });\n\n // Create a VideoTexture for the screen\n const videoTexture = new VideoTexture(this.videoElement);\n videoTexture.colorSpace = SRGBColorSpace;\n videoTexture.minFilter = LinearFilter;\n videoTexture.magFilter = LinearFilter;\n videoTexture.generateMipmaps = false;\n\n const stereoMode = this.state.get('stereoMode');\n this.renderer.setCustomProjection(this.cinemaProjection, videoTexture, stereoMode);\n\n // Cinema is always panoramic (user looks around inside the room sphere)\n const cc = this.renderer.getCameraController();\n cc.setPanoramic(true);\n\n // Load room texture if specified\n const roomSrc = this.getAttribute('room-src');\n if (roomSrc) {\n const loader = new TextureLoader();\n loader.load(roomSrc, (texture) => {\n texture.colorSpace = SRGBColorSpace;\n this.cinemaProjection?.setRoomTexture(texture);\n });\n }\n }\n\n private getNumAttr(name: string, defaultVal: number): number {\n const val = this.getAttribute(name);\n if (val === null) return defaultVal;\n const num = parseFloat(val);\n return isNaN(num) ? defaultVal : num;\n }\n}\n\n/** Register the <vz-cinema> custom element */\nexport function registerCinema(): void {\n if (!customElements.get('vz-cinema')) {\n customElements.define('vz-cinema', VzCinemaElement);\n }\n}\n","import { VzMediaElement } from '../core/media-element.js';\n\n/**\n * <vz-live> - Live streaming VR video element.\n *\n * Supports flat + 360° live HLS/DASH streams.\n * Automatically configures the player for live mode:\n * - Sets `isLive = true` state\n * - Shows LIVE indicator instead of time display\n * - Disables seek bar scrubbing (or shows DVR window)\n * - Auto-seeks to live edge on load\n *\n * @example\n * ```html\n * <vz-live title=\"Live Concert\" format=\"MONO_360\" style=\"width: 100%; aspect-ratio: 16/9;\">\n * <source src=\"https://stream.example.com/live.m3u8\" type=\"application/x-mpegURL\" />\n * </vz-live>\n * ```\n */\nexport class VzLiveElement extends VzMediaElement {\n private liveEdgeInterval: ReturnType<typeof setInterval> | null = null;\n\n /** Live edge threshold in seconds — within this range = \"at live edge\" */\n private static readonly LIVE_EDGE_THRESHOLD = 10;\n\n protected onMediaConnected(): void {\n // Mark as live in state\n this.state.batch({\n isLive: true,\n isAtLiveEdge: true,\n });\n\n // Auto-play on load for live streams\n this.videoElement.addEventListener('canplay', () => {\n this.seekToLiveEdge();\n }, { once: true });\n\n // Monitor live edge position\n this.liveEdgeInterval = setInterval(() => {\n this.checkLiveEdge();\n }, 2000);\n }\n\n protected onMediaDisconnected(): void {\n if (this.liveEdgeInterval) {\n clearInterval(this.liveEdgeInterval);\n this.liveEdgeInterval = null;\n }\n }\n\n /**\n * Seek to the live edge of the stream.\n */\n seekToLiveEdge(): void {\n const duration = this.videoElement.duration;\n if (duration && isFinite(duration)) {\n // For DVR-enabled streams, duration is available\n this.videoElement.currentTime = duration;\n } else if (this.videoElement.seekable.length > 0) {\n // Seek to the end of the seekable range\n const end = this.videoElement.seekable.end(this.videoElement.seekable.length - 1);\n this.videoElement.currentTime = end;\n }\n this.state.set('isAtLiveEdge', true);\n }\n\n /**\n * Check if we're at the live edge by comparing currentTime to seekable end.\n */\n private checkLiveEdge(): void {\n if (!this.videoElement || this.videoElement.paused) return;\n\n const seekable = this.videoElement.seekable;\n if (seekable.length === 0) {\n this.state.set('isAtLiveEdge', true);\n return;\n }\n\n const end = seekable.end(seekable.length - 1);\n const current = this.videoElement.currentTime;\n const behindBy = end - current;\n\n this.state.set('isAtLiveEdge', behindBy <= VzLiveElement.LIVE_EDGE_THRESHOLD);\n }\n}\n\n/** Register the <vz-live> custom element */\nexport function registerLive(): void {\n if (!customElements.get('vz-live')) {\n customElements.define('vz-live', VzLiveElement);\n }\n}\n","/**\n * Playlist prev/next controls injected into the active player's control bar.\n * Also shows an item counter \"2 / 5\".\n */\n\nimport { ICON_PLAYLIST_PREV, ICON_PLAYLIST_NEXT } from './icons.js';\nimport { setStaticHtml } from '../utils/static-html.js';\n\nconst ICON_PLAYLIST_LIST = `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"8\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"8\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"8\" y1=\"18\" x2=\"21\" y2=\"18\"/><line x1=\"3\" y1=\"6\" x2=\"3.01\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"3.01\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"3.01\" y2=\"18\"/></svg>`;\n\nexport class PlaylistControls {\n private prevBtn: HTMLButtonElement;\n private nextBtn: HTMLButtonElement;\n private counterEl: HTMLSpanElement;\n private panelBtn: HTMLButtonElement | null = null;\n private injectedInto: HTMLElement;\n private injectedRight: HTMLElement | null;\n\n constructor(\n controlsLeft: HTMLElement,\n controlsRight: HTMLElement | null,\n currentIndex: number,\n totalItems: number,\n onPrev: () => void,\n onNext: () => void,\n onTogglePanel: (() => void) | null,\n ) {\n this.injectedInto = controlsLeft;\n this.injectedRight = controlsRight;\n\n // Previous button\n this.prevBtn = document.createElement('button');\n this.prevBtn.className = 'vz-btn vz-playlist-prev';\n setStaticHtml(this.prevBtn, ICON_PLAYLIST_PREV);\n this.prevBtn.title = 'Previous';\n this.prevBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n onPrev();\n });\n\n // Next button\n this.nextBtn = document.createElement('button');\n this.nextBtn.className = 'vz-btn vz-playlist-next';\n setStaticHtml(this.nextBtn, ICON_PLAYLIST_NEXT);\n this.nextBtn.title = 'Next';\n this.nextBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n onNext();\n });\n\n // Counter display: \"2 / 5\"\n this.counterEl = document.createElement('span');\n this.counterEl.className = 'vz-time vz-playlist-counter';\n this.counterEl.textContent = `${currentIndex + 1} / ${totalItems}`;\n\n // Insert after the play button (first child)\n const firstChild = controlsLeft.children[0];\n if (firstChild?.nextSibling) {\n controlsLeft.insertBefore(this.prevBtn, firstChild.nextSibling);\n controlsLeft.insertBefore(this.nextBtn, this.prevBtn.nextSibling);\n controlsLeft.insertBefore(this.counterEl, this.nextBtn.nextSibling);\n } else {\n controlsLeft.appendChild(this.prevBtn);\n controlsLeft.appendChild(this.nextBtn);\n controlsLeft.appendChild(this.counterEl);\n }\n\n // Panel toggle button (right side, only if panel mode is expandable)\n if (onTogglePanel && controlsRight) {\n this.panelBtn = document.createElement('button');\n this.panelBtn.className = 'vz-btn vz-playlist-panel-toggle';\n setStaticHtml(this.panelBtn, ICON_PLAYLIST_LIST);\n this.panelBtn.title = 'Playlist';\n this.panelBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n onTogglePanel();\n });\n // Insert before the first right-side control\n const firstRight = controlsRight.children[0];\n if (firstRight) {\n controlsRight.insertBefore(this.panelBtn, firstRight);\n } else {\n controlsRight.appendChild(this.panelBtn);\n }\n }\n }\n\n /** Update the counter display */\n update(currentIndex: number, totalItems: number): void {\n this.counterEl.textContent = `${currentIndex + 1} / ${totalItems}`;\n }\n\n /** Remove injected controls */\n dispose(): void {\n this.prevBtn.remove();\n this.nextBtn.remove();\n this.counterEl.remove();\n this.panelBtn?.remove();\n }\n}\n","/**\n * Expandable sidebar panel showing all playlist items.\n * Slides in from the right, overlaid on top of the player.\n */\n\nimport type { PlaylistItem } from '../elements/vz-playlist.js';\n\nexport class PlaylistPanel {\n private panel: HTMLDivElement;\n private listEl: HTMLDivElement;\n private isOpen = false;\n private onSelect: (index: number) => void;\n private shadowRoot: ShadowRoot;\n\n constructor(\n shadowRoot: ShadowRoot,\n items: PlaylistItem[],\n activeIndex: number,\n onSelect: (index: number) => void,\n ) {\n this.shadowRoot = shadowRoot;\n this.onSelect = onSelect;\n\n // Inject panel CSS\n const style = document.createElement('style');\n style.textContent = PANEL_CSS;\n shadowRoot.appendChild(style);\n\n // Create panel container\n this.panel = document.createElement('div');\n this.panel.className = 'vz-playlist-panel';\n\n // Header\n const header = document.createElement('div');\n header.className = 'vz-playlist-panel-header';\n\n const title = document.createElement('span');\n title.className = 'vz-playlist-panel-title';\n title.textContent = 'Playlist';\n\n const closeBtn = document.createElement('button');\n closeBtn.className = 'vz-playlist-panel-close';\n closeBtn.textContent = '×'; // multiplication sign as close ×\n closeBtn.title = 'Close';\n closeBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.close();\n });\n\n header.appendChild(title);\n header.appendChild(closeBtn);\n this.panel.appendChild(header);\n\n // List\n this.listEl = document.createElement('div');\n this.listEl.className = 'vz-playlist-panel-list';\n this.panel.appendChild(this.listEl);\n\n // Render items\n this.renderItems(items, activeIndex);\n\n // Add to shadow DOM\n shadowRoot.appendChild(this.panel);\n\n // Prevent pointer events from reaching the player container\n this.panel.addEventListener('pointerdown', (e) => e.stopPropagation());\n this.panel.addEventListener('pointermove', (e) => e.stopPropagation());\n }\n\n /** Update items and active index */\n update(items: PlaylistItem[], activeIndex: number): void {\n this.renderItems(items, activeIndex);\n }\n\n /** Set the active item highlight */\n setActive(index: number): void {\n const children = this.listEl.children;\n for (let i = 0; i < children.length; i++) {\n children[i]?.classList.toggle('active', i === index);\n }\n }\n\n /** Toggle panel open/closed */\n toggle(): void {\n if (this.isOpen) {\n this.close();\n } else {\n this.open();\n }\n }\n\n /** Open the panel */\n open(): void {\n this.panel.classList.add('open');\n this.isOpen = true;\n }\n\n /** Close the panel */\n close(): void {\n this.panel.classList.remove('open');\n this.isOpen = false;\n }\n\n /** Clean up */\n dispose(): void {\n this.panel.remove();\n }\n\n private renderItems(items: PlaylistItem[], activeIndex: number): void {\n this.listEl.replaceChildren();\n\n for (let i = 0; i < items.length; i++) {\n const item = items[i]!;\n const row = document.createElement('div');\n row.className = 'vz-playlist-panel-item';\n if (i === activeIndex) row.classList.add('active');\n\n // Index number\n const num = document.createElement('span');\n num.className = 'vz-playlist-panel-num';\n num.textContent = String(i + 1);\n\n // Thumbnail placeholder (uses poster or format icon)\n const thumb = document.createElement('div');\n thumb.className = 'vz-playlist-panel-thumb';\n if (item.poster) {\n thumb.style.backgroundImage = `url(${item.poster})`;\n } else {\n // Show format text if no poster\n thumb.textContent = item.format ? item.format.replace('MONO_', '').replace('STEREO_', 'S:') : '360';\n }\n\n // Title\n const title = document.createElement('span');\n title.className = 'vz-playlist-panel-item-title';\n title.textContent = item.title;\n\n row.appendChild(num);\n row.appendChild(thumb);\n row.appendChild(title);\n\n row.addEventListener('click', (e) => {\n e.stopPropagation();\n this.onSelect(i);\n });\n\n this.listEl.appendChild(row);\n }\n }\n}\n\nconst PANEL_CSS = `\n .vz-playlist-panel {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: 280px;\n max-width: 70%;\n background: rgba(15, 15, 25, 0.95);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n z-index: 25;\n transform: translateX(100%);\n transition: transform 0.3s ease;\n display: flex;\n flex-direction: column;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n color: #fff;\n user-select: none;\n -webkit-user-select: none;\n }\n\n .vz-playlist-panel.open {\n transform: translateX(0);\n }\n\n .vz-playlist-panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n flex-shrink: 0;\n }\n\n .vz-playlist-panel-title {\n font-size: 14px;\n font-weight: 600;\n letter-spacing: 0.02em;\n }\n\n .vz-playlist-panel-close {\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.6);\n font-size: 22px;\n cursor: pointer;\n padding: 0 4px;\n line-height: 1;\n transition: color 0.15s;\n }\n\n .vz-playlist-panel-close:hover {\n color: #fff;\n }\n\n .vz-playlist-panel-list {\n flex: 1;\n overflow-y: auto;\n padding: 4px 0;\n }\n\n .vz-playlist-panel-list::-webkit-scrollbar {\n width: 4px;\n }\n\n .vz-playlist-panel-list::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .vz-playlist-panel-list::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.15);\n border-radius: 2px;\n }\n\n .vz-playlist-panel-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 16px;\n cursor: pointer;\n transition: background 0.12s;\n }\n\n .vz-playlist-panel-item:hover {\n background: rgba(255, 255, 255, 0.08);\n }\n\n .vz-playlist-panel-item.active {\n background: rgba(255, 255, 255, 0.12);\n }\n\n .vz-playlist-panel-item.active .vz-playlist-panel-num {\n color: var(--vz-primary, #f43f5e);\n }\n\n .vz-playlist-panel-num {\n width: 20px;\n text-align: center;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.4);\n font-variant-numeric: tabular-nums;\n flex-shrink: 0;\n }\n\n .vz-playlist-panel-thumb {\n width: 48px;\n height: 28px;\n border-radius: 3px;\n background: rgba(255, 255, 255, 0.06);\n background-size: cover;\n background-position: center;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 9px;\n color: rgba(255, 255, 255, 0.3);\n letter-spacing: 0.05em;\n text-transform: uppercase;\n flex-shrink: 0;\n overflow: hidden;\n }\n\n .vz-playlist-panel-item-title {\n font-size: 13px;\n color: rgba(255, 255, 255, 0.85);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n min-width: 0;\n }\n\n .vz-playlist-panel-item.active .vz-playlist-panel-item-title {\n color: #fff;\n font-weight: 500;\n }\n`;\n","/**\n * <vz-playlist> - Container element managing a queue of <vz-video> players.\n *\n * Only one child video is active (visible/playing) at a time.\n * Auto-advances when the current video ends.\n *\n * @example\n * ```html\n * <vz-playlist autoplay loop-playlist>\n * <vz-video title=\"Video 1\" format=\"MONO_360\">\n * <source src=\"video1.mp4\" type=\"video/mp4\" />\n * </vz-video>\n * <vz-video title=\"Video 2\" format=\"MONO_FLAT\">\n * <source src=\"video2.mp4\" type=\"video/mp4\" />\n * </vz-video>\n * </vz-playlist>\n *\n * <!-- With expandable sidebar -->\n * <vz-playlist panel=\"expandable\" autoplay>\n * ...\n * </vz-playlist>\n * ```\n */\n\nimport { getFeaturesForTier } from '@vizor-vr/shared';\nimport type { LicenseTier } from '@vizor-vr/shared';\nimport { PlaylistControls } from '../controls/playlist-controls.js';\nimport { PlaylistPanel } from '../controls/playlist-panel.js';\nimport { HTMLElementBase } from '../core/html-element.js';\nimport { resolveFeatureGate } from '../license/feature-gate.js';\n\nexport interface PlaylistItem {\n index: number;\n element: HTMLElement;\n title: string;\n format: string;\n poster: string;\n}\n\nexport class VzPlaylistElement extends HTMLElementBase {\n private shadow: ShadowRoot;\n private items: PlaylistItem[] = [];\n private currentIndex = -1;\n private playlistControls: PlaylistControls | null = null;\n private playlistPanel: PlaylistPanel | null = null;\n private endedHandler: (() => void) | null = null;\n private connected = false;\n /**\n * True once a child player reports a license tier without the `playlist`\n * feature. The queue degrades to playing only the first item as a single\n * clip: auto-advance and prev/next navigation are disabled and the multi-item\n * controls/panel are removed.\n */\n private playlistLocked = false;\n private licenseListener: ((e: Event) => void) | null = null;\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: 'open' });\n }\n\n static get observedAttributes(): string[] {\n return ['autoplay', 'loop-playlist', 'panel'];\n }\n\n // --- Attribute accessors ---\n\n get autoplay(): boolean {\n return this.hasAttribute('autoplay');\n }\n\n set autoplay(val: boolean) {\n if (val) {\n this.setAttribute('autoplay', '');\n } else {\n this.removeAttribute('autoplay');\n }\n }\n\n get loopPlaylist(): boolean {\n return this.hasAttribute('loop-playlist');\n }\n\n set loopPlaylist(val: boolean) {\n if (val) {\n this.setAttribute('loop-playlist', '');\n } else {\n this.removeAttribute('loop-playlist');\n }\n }\n\n get panelMode(): 'none' | 'expandable' {\n const raw = this.getAttribute('panel');\n return raw === 'expandable' ? 'expandable' : 'none';\n }\n\n connectedCallback(): void {\n this.connected = true;\n\n // Apply host styling\n this.style.display = 'block';\n this.style.position = 'relative';\n this.style.overflow = 'hidden';\n if (!this.style.width) this.style.width = '100%';\n\n // Inject styles\n this.injectStyles();\n\n // Create wrapper\n const wrapper = document.createElement('div');\n wrapper.className = 'vz-playlist-wrapper';\n this.shadow.appendChild(wrapper);\n\n // Slot for light DOM children\n const slot = document.createElement('slot');\n wrapper.appendChild(slot);\n\n // Parse children\n this.parseItems();\n\n // The playlist queue is a paid feature. Child players run their own license\n // checks and bubble a composed `vz-license` event; the tier it reports maps\n // (via the shared TIER_FEATURES table) to whether `playlist` is licensed.\n // When it is not, degrade to first-item-only playback.\n this.licenseListener = (e: Event) => {\n const detail = (e as CustomEvent).detail as { tier?: LicenseTier } | undefined;\n if (!detail?.tier) return;\n const features = getFeaturesForTier(detail.tier);\n if (resolveFeatureGate('playlist', features.playlist).action !== 'allow') {\n this.applyPlaylistLock();\n }\n };\n this.addEventListener('vz-license', this.licenseListener);\n\n // Create playlist panel if configured\n if (this.panelMode === 'expandable') {\n this.playlistPanel = new PlaylistPanel(\n this.shadow,\n this.items,\n this.currentIndex,\n (index) => this.goTo(index),\n );\n }\n\n // Observe slot changes for dynamic children\n slot.addEventListener('slotchange', () => {\n this.parseItems();\n if (this.currentIndex < 0 && this.items.length > 0) {\n this.goTo(0);\n }\n this.playlistPanel?.update(this.items, this.currentIndex);\n });\n\n // Show first item\n if (this.items.length > 0) {\n this.goTo(0);\n }\n }\n\n disconnectedCallback(): void {\n this.connected = false;\n this.detachEndedListener();\n if (this.licenseListener) {\n this.removeEventListener('vz-license', this.licenseListener);\n this.licenseListener = null;\n }\n this.playlistControls?.dispose();\n this.playlistControls = null;\n this.playlistPanel?.dispose();\n this.playlistPanel = null;\n }\n\n attributeChangedCallback(_name: string, _oldVal: string | null, _newVal: string | null): void {\n // Panel change would require re-render (unusual at runtime, ignore for now)\n }\n\n // --- Public API ---\n\n /** Navigate to item at index */\n goTo(index: number): void {\n if (index < 0 || index >= this.items.length) return;\n // Unlicensed graceful downgrade: the queue is pinned to the first item.\n if (this.playlistLocked && index !== 0) return;\n\n const prevIndex = this.currentIndex;\n this.currentIndex = index;\n\n // Hide all items, show current\n for (let i = 0; i < this.items.length; i++) {\n const el = this.items[i]!.element;\n if (i === index) {\n el.style.display = '';\n el.removeAttribute('hidden');\n } else {\n el.style.display = 'none';\n el.setAttribute('hidden', '');\n // Pause hidden videos\n if (typeof (el as any).pause === 'function') {\n (el as any).pause();\n }\n }\n }\n\n // Attach ended listener to the new active element\n this.detachEndedListener();\n this.attachEndedListener();\n\n // Inject/update playlist controls into the active player\n this.injectControls();\n\n // Update panel highlight\n this.playlistPanel?.setActive(index);\n\n // Dispatch change event\n const item = this.items[index]!;\n this.dispatchEvent(\n new CustomEvent('vz-playlist-change', {\n bubbles: true,\n composed: true,\n detail: {\n index,\n title: item.title,\n total: this.items.length,\n },\n }),\n );\n\n // Auto-play if configured and not the initial load\n if (this.autoplay && prevIndex >= 0) {\n const activeEl = this.items[index]!.element;\n if (typeof (activeEl as any).play === 'function') {\n // Small delay to let the element connect/render\n requestAnimationFrame(() => {\n (activeEl as any).play().catch(() => {\n // Autoplay may be blocked by browser policy\n });\n });\n }\n }\n }\n\n /** Go to next item */\n next(): void {\n if (this.playlistLocked) return; // single-clip fallback — no navigation\n if (this.currentIndex < this.items.length - 1) {\n this.goTo(this.currentIndex + 1);\n } else if (this.loopPlaylist) {\n this.goTo(0);\n }\n }\n\n /** Go to previous item */\n previous(): void {\n if (this.playlistLocked) return; // single-clip fallback — no navigation\n if (this.currentIndex > 0) {\n this.goTo(this.currentIndex - 1);\n } else if (this.loopPlaylist) {\n this.goTo(this.items.length - 1);\n }\n }\n\n /**\n * Graceful downgrade when the tier lacks the playlist feature: keep the first\n * item playing as a single clip and disable everything queue-specific —\n * auto-advance, prev/next, the injected controls, and the side panel.\n */\n private applyPlaylistLock(): void {\n if (this.playlistLocked) return;\n this.playlistLocked = true;\n // Stop auto-advancing when the current clip ends.\n this.detachEndedListener();\n // Remove the multi-item prev/next controls and the queue panel.\n this.playlistControls?.dispose();\n this.playlistControls = null;\n this.playlistPanel?.dispose();\n this.playlistPanel = null;\n }\n\n /** Shuffle the playlist order */\n shuffle(): void {\n // Fisher-Yates shuffle on the items array\n for (let i = this.items.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [this.items[i], this.items[j]] = [this.items[j]!, this.items[i]!];\n this.items[i]!.index = i;\n this.items[j]!.index = j;\n }\n\n // Update panel if exists\n this.playlistPanel?.update(this.items, this.currentIndex);\n\n // Find the new index of the currently active element\n const activeEl = this.currentIndex >= 0 ? this.items.find(\n (item) => item.element.style.display !== 'none',\n ) : null;\n if (activeEl) {\n this.currentIndex = this.items.indexOf(activeEl);\n }\n }\n\n /** Get current item index */\n get currentItemIndex(): number {\n return this.currentIndex;\n }\n\n /** Get total item count */\n get itemCount(): number {\n return this.items.length;\n }\n\n /** Get the active player element */\n get activeElement(): HTMLElement | null {\n return this.currentIndex >= 0 ? this.items[this.currentIndex]?.element ?? null : null;\n }\n\n /** Toggle the playlist panel open/closed */\n togglePanel(): void {\n this.playlistPanel?.toggle();\n }\n\n // --- Private ---\n\n private parseItems(): void {\n this.items = [];\n const children = this.querySelectorAll('vz-video, vz-cinema, vz-live');\n let i = 0;\n for (const child of children) {\n this.items.push({\n index: i,\n element: child as HTMLElement,\n title: child.getAttribute('title') ?? `Item ${i + 1}`,\n format: child.getAttribute('format') ?? '',\n poster: child.getAttribute('poster') ?? '',\n });\n i++;\n }\n }\n\n private attachEndedListener(): void {\n if (this.currentIndex < 0) return;\n const activeEl = this.items[this.currentIndex]?.element;\n if (!activeEl) return;\n\n this.endedHandler = () => {\n if (this.currentIndex < this.items.length - 1) {\n this.next();\n } else if (this.loopPlaylist) {\n this.goTo(0);\n } else {\n // Playlist ended\n this.dispatchEvent(\n new CustomEvent('vz-playlist-end', {\n bubbles: true,\n composed: true,\n }),\n );\n }\n };\n\n activeEl.addEventListener('ended', this.endedHandler);\n }\n\n private detachEndedListener(): void {\n if (this.endedHandler && this.currentIndex >= 0) {\n const el = this.items[this.currentIndex]?.element;\n el?.removeEventListener('ended', this.endedHandler);\n }\n this.endedHandler = null;\n }\n\n private injectControls(): void {\n // Clean up previous controls\n this.playlistControls?.dispose();\n this.playlistControls = null;\n\n // Locked to a single clip (unlicensed) — no multi-item controls.\n if (this.playlistLocked) return;\n\n if (this.items.length <= 1) {\n return;\n }\n\n const activeEl = this.items[this.currentIndex]?.element;\n if (!activeEl) return;\n\n // Wait for the element to be ready before injecting controls\n const inject = () => {\n // Access the player's shadow root to find the controls overlay\n const shadow = activeEl.shadowRoot;\n if (!shadow) return;\n\n const controlsLeft = shadow.querySelector('.vz-controls-left');\n const controlsRight = shadow.querySelector('.vz-controls-right');\n if (!controlsLeft) return;\n\n this.playlistControls = new PlaylistControls(\n controlsLeft as HTMLElement,\n controlsRight as HTMLElement | null,\n this.currentIndex,\n this.items.length,\n () => this.previous(),\n () => this.next(),\n this.panelMode === 'expandable' ? () => this.togglePanel() : null,\n );\n };\n\n // The player may not have its shadow DOM ready yet\n if (activeEl.shadowRoot?.querySelector('.vz-controls-left')) {\n inject();\n } else {\n // Wait for vz-ready event\n activeEl.addEventListener('vz-ready', () => inject(), { once: true });\n }\n }\n\n private injectStyles(): void {\n const style = document.createElement('style');\n style.textContent = `\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n\n .vz-playlist-wrapper {\n position: relative;\n width: 100%;\n height: 100%;\n }\n\n ::slotted(vz-video),\n ::slotted(vz-cinema),\n ::slotted(vz-live) {\n width: 100% !important;\n }\n\n ::slotted([hidden]) {\n display: none !important;\n }\n `;\n this.shadow.appendChild(style);\n }\n}\n\n/** Register the <vz-playlist> custom element */\nexport function registerPlaylist(): void {\n if (!customElements.get('vz-playlist')) {\n customElements.define('vz-playlist', VzPlaylistElement);\n }\n}\n","import type { AnnotationData } from '@vizor-vr/shared';\nimport { sanitizeAnnotationHtml } from '../utils/sanitize.js';\nimport { HTMLElementBase } from '../core/html-element.js';\n\n/**\n * <vz-annotation> — child element of vz-video/vz-img for interactive annotations.\n *\n * This is a data-holder element with no shadow DOM. The parent media element's\n * AnnotationManager reads these elements to create 3D sprite markers.\n *\n * @example\n * ```html\n * <vz-video format=\"MONO_360\">\n * <source src=\"video.mp4\" type=\"video/mp4\" />\n * <vz-annotation lat=\"10\" lon=\"45\" time-start=\"5\" time-end=\"15\" title=\"Look here\">\n * <p>Rich HTML content</p>\n * </vz-annotation>\n * </vz-video>\n * ```\n */\nexport class VzAnnotationElement extends HTMLElementBase {\n static get observedAttributes(): string[] {\n return ['lat', 'lon', 'time-start', 'time-end', 'title', 'icon', 'sort-order'];\n }\n\n /** Parse this element's attributes into an AnnotationData object */\n getData(): AnnotationData {\n const lat = this.hasAttribute('lat') ? parseFloat(this.getAttribute('lat')!) : 0;\n const lon = this.hasAttribute('lon') ? parseFloat(this.getAttribute('lon')!) : 0;\n const title = this.getAttribute('title') ?? '';\n const icon = this.getAttribute('icon') ?? undefined;\n const timeStart = this.hasAttribute('time-start')\n ? parseFloat(this.getAttribute('time-start')!)\n : undefined;\n const timeEnd = this.hasAttribute('time-end')\n ? parseFloat(this.getAttribute('time-end')!)\n : undefined;\n const sortOrder = this.hasAttribute('sort-order')\n ? parseInt(this.getAttribute('sort-order')!, 10)\n : undefined;\n\n // Use innerHTML as rich content, sanitized at the source with DOMPurify so\n // stored htmlContent is never an XSS vector regardless of which consumer\n // reads it (the render sink sanitizes again as defense in depth).\n const rawHtml = this.innerHTML.trim();\n const htmlContent = rawHtml ? sanitizeAnnotationHtml(rawHtml) : undefined;\n\n return { lat, lon, timeStart, timeEnd, title, icon, htmlContent, sortOrder };\n }\n}\n\n/** Register the <vz-annotation> custom element */\nexport function registerAnnotation(): void {\n if (!customElements.get('vz-annotation')) {\n customElements.define('vz-annotation', VzAnnotationElement);\n }\n}\n","import { HTMLElementBase } from '../core/html-element.js';\nimport type { RawChapter } from '../chapters/chapter-logic.js';\n\n/**\n * <vz-chapter> — child of vz-video/vz-img declaring a manual chapter marker.\n *\n * A data-holder element with no shadow DOM (like <vz-annotation>). The parent\n * media element reads these to build seek-bar markers + a chapter menu and to\n * fire `vz-chapter-change`. Also emitted by delivery hydration for content-id\n * embeds.\n *\n * @example\n * ```html\n * <vz-video format=\"MONO_360\">\n * <source src=\"tour.mp4\" type=\"video/mp4\" />\n * <vz-chapter start=\"0\" title=\"Lobby\"></vz-chapter>\n * <vz-chapter start=\"42.5\" title=\"Main hall\"></vz-chapter>\n * </vz-video>\n * ```\n */\nexport class VzChapterElement extends HTMLElementBase {\n static get observedAttributes(): string[] {\n return ['start', 'title', 'sort-order', 'chapter-id'];\n }\n\n /** Parse this element's attributes into a RawChapter. */\n getData(): RawChapter {\n const startTime = this.hasAttribute('start') ? parseFloat(this.getAttribute('start')!) : 0;\n const title = this.getAttribute('title') ?? '';\n const sortOrder = this.hasAttribute('sort-order')\n ? parseInt(this.getAttribute('sort-order')!, 10)\n : undefined;\n const id = this.getAttribute('chapter-id') ?? undefined;\n return {\n id,\n title,\n startTime: isNaN(startTime) ? 0 : startTime,\n sortOrder: sortOrder != null && !isNaN(sortOrder) ? sortOrder : undefined,\n };\n }\n}\n\n/** Register the <vz-chapter> custom element. */\nexport function registerChapter(): void {\n if (typeof customElements === 'undefined') return;\n if (!customElements.get('vz-chapter')) {\n customElements.define('vz-chapter', VzChapterElement);\n }\n}\n","import { HTMLElementBase } from '../core/html-element.js';\nimport type { RawHubItem } from '../hub/hub-model.js';\n\n/**\n * <vz-hub-item> — a content entry inside a <vz-hub> lobby. A data-holder element\n * (no shadow DOM, like <vz-annotation>/<vz-chapter>). The parent <vz-hub> reads\n * these to build clickable lobby panels; selecting one plays that source.\n *\n * @example\n * ```html\n * <vz-hub background=\"lobby.jpg\">\n * <vz-hub-item src=\"intro.mp4\" poster=\"intro.jpg\" title=\"Intro\" format=\"MONO_360\"></vz-hub-item>\n * <vz-hub-item src=\"tour.mp4\" poster=\"tour.jpg\" title=\"Factory tour\"></vz-hub-item>\n * </vz-hub>\n * ```\n */\nexport class VzHubItemElement extends HTMLElementBase {\n static get observedAttributes(): string[] {\n return ['src', 'poster', 'title', 'format', 'content-id'];\n }\n\n /** Parse this element's attributes into a RawHubItem. */\n getData(): RawHubItem {\n return {\n src: this.getAttribute('src'),\n poster: this.getAttribute('poster'),\n title: this.getAttribute('title'),\n format: this.getAttribute('format'),\n contentId: this.getAttribute('content-id'),\n };\n }\n}\n\n/** Register the <vz-hub-item> custom element. */\nexport function registerHubItem(): void {\n if (typeof customElements === 'undefined') return;\n if (!customElements.get('vz-hub-item')) {\n customElements.define('vz-hub-item', VzHubItemElement);\n }\n}\n","/**\n * Pure, DOM-free layout math for the vz-hub lobby: distribute N content panels\n * around the viewer as {lat, lon} positions (degrees), so the placement can be\n * unit-tested without a renderer. Panels sit at eye level (lat 0) and are\n * centered on dead-ahead (lon 0); once the arc would wrap past ~330° they\n * spread evenly around a full ring instead.\n */\n\nexport interface HubPanelPosition {\n /** Vertical angle in degrees (0 = eye level). */\n lat: number;\n /** Horizontal angle in degrees, normalized to [-180, 180] (0 = dead ahead). */\n lon: number;\n}\n\nexport interface HubLayoutOptions {\n /** Degrees between adjacent panels in the front-arc layout. */\n spacingDeg?: number;\n /** Eye-level offset for all panels. */\n lat?: number;\n}\n\n/** Normalize an angle in degrees to the range [-180, 180]. */\nexport function normalizeLon(lon: number): number {\n return (((lon + 180) % 360) + 360) % 360 - 180;\n}\n\nexport function hubPanelLayout(count: number, opts: HubLayoutOptions = {}): HubPanelPosition[] {\n if (count <= 0) return [];\n const spacing = opts.spacingDeg ?? 30;\n const lat = opts.lat ?? 0;\n\n const span = (count - 1) * spacing;\n // Once the front arc would exceed ~330°, wrap panels into an even full ring.\n if (span > 330) {\n const step = 360 / count;\n return Array.from({ length: count }, (_, i) => ({ lat, lon: normalizeLon(i * step - 180) }));\n }\n\n // Center the arc on dead-ahead (lon 0).\n const start = -span / 2;\n return Array.from({ length: count }, (_, i) => ({ lat, lon: normalizeLon(start + i * spacing) }));\n}\n","import { Sprite, SpriteMaterial, CanvasTexture, LinearFilter, TextureLoader, SRGBColorSpace } from 'three';\nimport type { Scene, Texture } from 'three';\nimport { latLonToSpherical } from '@vizor-vr/shared';\nimport { hubPanelLayout } from './hub-layout.js';\nimport type { HubItem } from './hub-model.js';\n\nconst PANEL_DISTANCE = 200;\nconst PANEL_SCALE_X = 60;\nconst PANEL_SCALE_Y = 34;\nconst CANVAS_W = 320;\nconst CANVAS_H = 180;\n\n/**\n * Renders clickable lobby panels for vz-hub: one sprite per HubItem, positioned\n * around the viewer via hubPanelLayout on the inside of the panorama sphere.\n * Each sprite starts as a titled placeholder card and, if the item has a poster,\n * swaps to the poster texture once it loads. `userData.portalTo` carries the\n * item index as a string so the shared HotspotInteraction raycaster can drive\n * selection with no changes.\n */\nexport class HubPanelRenderer {\n private sprites: Sprite[] = [];\n private posterTextures: Texture[] = [];\n private scene: Scene;\n private loader = new TextureLoader();\n\n constructor(scene: Scene) {\n this.scene = scene;\n }\n\n /** Build a panel sprite per item and add them to the scene. */\n setItems(items: HubItem[]): void {\n this.clear();\n const positions = hubPanelLayout(items.length);\n items.forEach((item, index) => {\n const sprite = this.createPanelSprite(item, positions[index]!, index);\n this.sprites.push(sprite);\n this.scene.add(sprite);\n });\n }\n\n /** Sprites for raycasting (consumed by HotspotInteraction). */\n getSprites(): Sprite[] {\n return this.sprites;\n }\n\n clear(): void {\n for (const sprite of this.sprites) {\n this.scene.remove(sprite);\n sprite.material.map?.dispose();\n sprite.material.dispose();\n }\n this.sprites = [];\n for (const tex of this.posterTextures) tex.dispose();\n this.posterTextures = [];\n }\n\n dispose(): void {\n this.clear();\n }\n\n private createPanelSprite(item: HubItem, pos: { lat: number; lon: number }, index: number): Sprite {\n const canvas = document.createElement('canvas');\n canvas.width = CANVAS_W;\n canvas.height = CANVAS_H;\n const ctx = canvas.getContext('2d')!;\n\n // Placeholder card with the title (shown until/unless a poster loads).\n ctx.fillStyle = 'rgba(15, 15, 25, 0.9)';\n ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.25)';\n ctx.lineWidth = 3;\n ctx.strokeRect(2, 2, CANVAS_W - 4, CANVAS_H - 4);\n ctx.fillStyle = '#fff';\n ctx.font = 'bold 26px sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(item.title || 'Play', CANVAS_W / 2, CANVAS_H / 2, CANVAS_W - 40);\n\n const texture = new CanvasTexture(canvas);\n texture.minFilter = LinearFilter;\n texture.magFilter = LinearFilter;\n\n const material = new SpriteMaterial({ map: texture, depthTest: false, sizeAttenuation: true });\n const sprite = new Sprite(material);\n sprite.userData.portalTo = String(index);\n sprite.userData.hubIndex = index;\n\n const { phi, theta } = latLonToSpherical(pos.lat, pos.lon);\n sprite.position.set(\n PANEL_DISTANCE * Math.sin(phi) * Math.cos(theta),\n PANEL_DISTANCE * Math.cos(phi),\n PANEL_DISTANCE * Math.sin(phi) * Math.sin(theta),\n );\n sprite.scale.set(PANEL_SCALE_X, PANEL_SCALE_Y, 1);\n\n // Upgrade to the poster thumbnail asynchronously when one is provided.\n if (item.poster) {\n this.loader.load(\n item.poster,\n (tex) => {\n tex.colorSpace = SRGBColorSpace;\n tex.minFilter = LinearFilter;\n tex.magFilter = LinearFilter;\n material.map?.dispose();\n material.map = tex;\n material.needsUpdate = true;\n this.posterTextures.push(tex);\n },\n undefined,\n () => {\n // Keep the placeholder card on load failure.\n },\n );\n }\n\n return sprite;\n }\n}\n","import type { Scene, PerspectiveCamera } from 'three';\nimport { HubPanelRenderer } from './hub-panel-renderer.js';\nimport { HotspotInteraction } from '../hotspots/hotspot-interaction.js';\nimport type { HubItem } from './hub-model.js';\n\n/**\n * Orchestrates hub lobby panels: renders poster panels (HubPanelRenderer) and\n * reuses the shared HotspotInteraction raycaster for click selection, mapping\n * the sprite's index tag back to an item index. Mirrors HotspotManager.\n */\nexport class HubPanelManager {\n private renderer: HubPanelRenderer;\n private interaction: HotspotInteraction;\n\n constructor(\n scene: Scene,\n camera: PerspectiveCamera,\n domElement: HTMLElement,\n onSelect: (index: number) => void,\n ) {\n this.renderer = new HubPanelRenderer(scene);\n this.interaction = new HotspotInteraction(\n camera,\n domElement,\n () => this.renderer.getSprites(),\n (portalTo) => onSelect(Number(portalTo)),\n );\n this.interaction.attach();\n }\n\n setItems(items: HubItem[]): void {\n this.renderer.setItems(items);\n }\n\n clear(): void {\n this.renderer.clear();\n }\n\n dispose(): void {\n this.interaction.dispose();\n this.renderer.dispose();\n }\n}\n","import type { VzEventEmitter } from '../core/event-emitter.js';\nimport type { HubItem } from './hub-model.js';\n\nexport interface HubControllerOptions {\n items: HubItem[];\n events: VzEventEmitter;\n /** Enter an item: swap the lobby scene for that item's playback. */\n onShowItem: (item: HubItem, index: number) => void;\n /** Return to the lobby scene. */\n onShowLobby: () => void;\n}\n\n/**\n * Selection state machine for vz-hub. Tracks whether the viewer is in the lobby\n * (activeIndex -1) or playing an item, drives the show-item/show-lobby callbacks\n * the element wires to the renderer, and fires `vz-hub-select` on entry. Kept\n * separate from the WebGL element so the transitions are unit-tested.\n */\nexport class HubController {\n private items: HubItem[];\n private events: VzEventEmitter;\n private onShowItem: (item: HubItem, index: number) => void;\n private onShowLobby: () => void;\n private activeIndex = -1;\n\n constructor(opts: HubControllerOptions) {\n this.items = opts.items;\n this.events = opts.events;\n this.onShowItem = opts.onShowItem;\n this.onShowLobby = opts.onShowLobby;\n }\n\n isInLobby(): boolean {\n return this.activeIndex === -1;\n }\n\n getActiveIndex(): number {\n return this.activeIndex;\n }\n\n /** Select an item by index: play it and fire vz-hub-select. No-op if invalid or already active. */\n select(index: number): void {\n if (index < 0 || index >= this.items.length) return;\n if (index === this.activeIndex) return;\n this.activeIndex = index;\n const item = this.items[index]!;\n this.onShowItem(item, index);\n this.events.emit('vz-hub-select', {\n id: item.id,\n src: item.src,\n title: item.title,\n index,\n });\n }\n\n /** Return to the lobby. No-op if already there. */\n back(): void {\n if (this.activeIndex === -1) return;\n this.activeIndex = -1;\n this.onShowLobby();\n }\n}\n","/**\n * Pure item model for vz-hub. Turns raw parsed <vz-hub-item> attributes into a\n * normalized, playable HubItem list (stable ids, defaults, drops items with no\n * source) so the parsing is unit-tested rather than done inline in the element.\n */\n\nexport interface RawHubItem {\n src?: string | null;\n poster?: string | null;\n title?: string | null;\n format?: string | null;\n contentId?: string | null;\n}\n\nexport interface HubItem {\n /** Stable id: the content-id if present, else a positional `item-<n>`. */\n id: string;\n /** Playable media URL. */\n src: string;\n /** Thumbnail shown on the lobby panel (null = fall back to a placeholder). */\n poster: string | null;\n title: string;\n /** VzFormat string; defaults to MONO_360. */\n format: string;\n}\n\n/** Heuristic: does a source URL point at a still image (→ vz-img) vs video (→ vz-video)? */\nexport function isImageSource(src: string): boolean {\n const clean = (src.split('?')[0] ?? '').split('#')[0] ?? '';\n return /\\.(jpe?g|png|webp|avif|gif|bmp)$/i.test(clean);\n}\n\nexport function normalizeHubItems(raw: RawHubItem[]): HubItem[] {\n const items: HubItem[] = [];\n raw.forEach((r, i) => {\n const src = r.src?.trim();\n if (!src) return; // an item with nothing to play is dropped\n items.push({\n id: r.contentId?.trim() || `item-${i}`,\n src,\n poster: r.poster?.trim() || null,\n title: r.title ?? '',\n format: r.format?.trim() || 'MONO_360',\n });\n });\n return items;\n}\n","import { TextureLoader, SRGBColorSpace, LinearFilter } from 'three';\nimport type { Texture } from 'three';\nimport { parseFormat } from '@vizor-vr/shared';\nimport type { VzFormat } from '@vizor-vr/shared';\nimport { VzBaseElement } from '../core/base-element.js';\nimport { VzRenderer } from '../renderer/renderer.js';\nimport { ControlsOverlay } from '../controls/controls-overlay.js';\nimport { Compass } from '../controls/compass.js';\nimport { FullscreenButton } from '../controls/fullscreen-button.js';\nimport { VrButton } from '../controls/vr-button.js';\nimport { XRManager } from '../xr/xr-manager.js';\nimport { requestFullscreen, exitFullscreen, isFullscreen, onFullscreenChange } from '../utils/fullscreen.js';\nimport { resolveFeatureGate } from '../license/feature-gate.js';\nimport { HubPanelManager } from '../hub/hub-panel-manager.js';\nimport { HubController } from '../hub/hub-controller.js';\nimport { normalizeHubItems, isImageSource, type HubItem, type RawHubItem } from '../hub/hub-model.js';\nimport type { VzHubItemElement } from './vz-hub-item.js';\n\n/**\n * <vz-hub> — a spatial 360 \"lobby\" that organizes VR content into clickable\n * poster panels. Selecting a panel plays that item in place (an inline\n * vz-video/vz-img with a \"back to lobby\" control); returning restores the\n * gallery. Enterprise-gated: without the license the lobby shows as a static\n * panorama with an upgrade note (graceful downgrade).\n *\n * @example\n * ```html\n * <vz-hub background=\"lobby.jpg\" style=\"width:100%; aspect-ratio:16/9\">\n * <vz-hub-item src=\"intro.mp4\" poster=\"intro.jpg\" title=\"Intro\"></vz-hub-item>\n * <vz-hub-item src=\"tour.mp4\" poster=\"tour.jpg\" title=\"Factory tour\"></vz-hub-item>\n * </vz-hub>\n * ```\n */\nexport class VzHubElement extends VzBaseElement {\n private renderer!: VzRenderer;\n private containerEl!: HTMLDivElement;\n private items: HubItem[] = [];\n private panelManager: HubPanelManager | null = null;\n private controller: HubController | null = null;\n private controlsOverlay: ControlsOverlay | null = null;\n private compass: Compass | null = null;\n private fullscreenButton: FullscreenButton | null = null;\n private vrButton: VrButton | null = null;\n private xrManager: XRManager | null = null;\n private cleanupFullscreen: (() => void) | null = null;\n private loader = new TextureLoader();\n private bgTexture: Texture | null = null;\n private playingEl: HTMLElement | null = null;\n private hubLocked = false;\n\n protected onConnected(): void {\n this.containerEl = document.createElement('div');\n this.containerEl.className = 'vz-container';\n this.shadow.appendChild(this.containerEl);\n\n this.renderer = new VzRenderer(this.shadow, this.containerEl, this.state, this.config);\n\n this.items = this.parseItems();\n\n this.xrManager = new XRManager(\n this.renderer.getThreeRenderer(),\n this.state,\n this.events,\n this.renderer.getCameraController(),\n );\n\n if (!this.config.hideControls) {\n this.controlsOverlay = new ControlsOverlay(\n this.shadow, this.containerEl, this.state, this.config, this.events,\n );\n this.compass = new Compass(this.state, this.renderer);\n this.controlsOverlay.addControl(this.compass.getElement(), 'right');\n this.vrButton = new VrButton(this.state, () => {\n this.xrManager?.toggleXR().catch((err) => {\n this.events.emit('vz-error', { code: 'XR_ERROR', message: String(err) });\n });\n });\n this.controlsOverlay.addControl(this.vrButton.getElement(), 'right');\n this.fullscreenButton = new FullscreenButton(this.state, this);\n this.controlsOverlay.addControl(this.fullscreenButton.getElement(), 'right');\n }\n\n this.cleanupFullscreen = onFullscreenChange(() => {\n this.state.set('fullscreen', isFullscreen());\n this.renderer.resize();\n });\n\n this.controller = new HubController({\n items: this.items,\n events: this.events,\n onShowItem: (item) => this.playItem(item),\n onShowLobby: () => this.returnToLobby(),\n });\n\n this.panelManager = new HubPanelManager(\n this.renderer.getScene(),\n this.renderer.getCamera(),\n this.containerEl,\n (index) => this.controller?.select(index),\n );\n\n this.showLobby();\n\n this.renderer.start();\n this.events.emit('vz-ready');\n }\n\n protected onDisconnected(): void {\n this.cleanupFullscreen?.();\n this.playingEl?.remove();\n this.playingEl = null;\n this.compass?.dispose();\n this.fullscreenButton?.dispose();\n this.vrButton?.dispose();\n this.controlsOverlay?.dispose();\n this.xrManager?.dispose();\n this.panelManager?.dispose();\n this.panelManager = null;\n this.bgTexture?.dispose();\n this.bgTexture = null;\n this.renderer?.dispose();\n }\n\n protected onAttributeChanged(): void {\n // Item/background changes require a reconnect; ignored live.\n }\n\n protected onResize(): void {\n this.renderer?.resize();\n }\n\n /**\n * Hub is an Enterprise feature; WebXR is gated separately. On license resolve:\n * - hub not licensed → lock to a static lobby panorama (panels removed, an\n * upgrade note shown) — graceful downgrade, the background still renders.\n * - webxr not licensed → remove the VR button.\n */\n protected override onLicenseResolved(): void {\n if (resolveFeatureGate('webxr', this.hasFeature('webxr')).action !== 'allow') {\n this.vrButton?.dispose();\n this.vrButton?.getElement().remove();\n this.vrButton = null;\n }\n\n if (resolveFeatureGate('hub', this.hasFeature('hub')).action !== 'allow') {\n this.hubLocked = true;\n this.panelManager?.clear();\n this.playingEl?.remove();\n this.playingEl = null;\n this.showUpgradeOverlay();\n }\n }\n\n toggleFullscreen(): void {\n if (isFullscreen()) exitFullscreen();\n else requestFullscreen(this);\n }\n\n /** Show the lobby: background panorama + clickable panels (unless locked). */\n private showLobby(): void {\n const bg = this.getAttribute('background');\n if (bg) {\n const format = parseFormat(this.getAttribute('background-format') ?? 'MONO_360') ?? ('MONO_360' as VzFormat);\n this.state.set('loading', true);\n this.loader.load(\n bg,\n (texture) => {\n texture.colorSpace = SRGBColorSpace;\n texture.minFilter = LinearFilter;\n this.bgTexture?.dispose();\n this.bgTexture = texture;\n this.renderer.setupImage(texture, format);\n this.state.set('loading', false);\n },\n undefined,\n () => this.state.set('loading', false),\n );\n }\n if (!this.hubLocked) {\n this.panelManager?.setItems(this.items);\n }\n }\n\n /** Play a selected item inline via a child vz-video/vz-img + a back control. */\n private playItem(item: HubItem): void {\n if (this.hubLocked) return;\n this.panelManager?.clear();\n\n const overlay = document.createElement('div');\n overlay.className = 'vz-hub-playing';\n overlay.style.cssText = 'position:absolute;inset:0;z-index:15;background:#000;';\n\n const child = document.createElement(isImageSource(item.src) ? 'vz-img' : 'vz-video');\n child.setAttribute('src', item.src);\n child.setAttribute('format', item.format);\n child.setAttribute('crossorigin', 'anonymous');\n if (isImageSource(item.src) === false) {\n child.setAttribute('muted', '');\n child.setAttribute('autoplay', '');\n }\n // Forward license context so the child resolves the same tier.\n if (this.config.apiKey) child.setAttribute('api-key', this.config.apiKey);\n if (this.config.apiEndpoint) child.setAttribute('api-endpoint', this.config.apiEndpoint);\n if (this.config.licenseKey) child.setAttribute('license-key', this.config.licenseKey);\n child.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;display:block;';\n\n const back = document.createElement('button');\n back.className = 'vz-hub-back';\n back.type = 'button';\n back.textContent = '← Lobby';\n back.style.cssText =\n 'position:absolute;top:12px;left:12px;z-index:20;padding:8px 14px;border:none;' +\n 'border-radius:8px;background:rgba(0,0,0,0.6);color:#fff;font:500 13px system-ui;' +\n 'cursor:pointer;backdrop-filter:blur(8px);';\n back.addEventListener('click', (e) => {\n e.stopPropagation();\n this.controller?.back();\n });\n\n overlay.appendChild(child);\n overlay.appendChild(back);\n this.shadow.appendChild(overlay);\n this.playingEl = overlay;\n }\n\n /** Return to the lobby: tear down the inline player, re-render panels. */\n private returnToLobby(): void {\n this.playingEl?.remove();\n this.playingEl = null;\n if (!this.hubLocked) {\n this.panelManager?.setItems(this.items);\n }\n }\n\n private showUpgradeOverlay(): void {\n if (this.shadow.querySelector('.vz-hub-gate')) return;\n const overlay = document.createElement('div');\n overlay.className = 'vz-hub-gate';\n overlay.style.cssText =\n 'position:absolute;left:0;right:0;bottom:0;padding:10px;text-align:center;z-index:12;' +\n 'background:rgba(0,0,0,0.7);color:#fff;font:500 13px system-ui;';\n overlay.textContent = 'The content hub requires a Vizor Enterprise plan.';\n this.containerEl.appendChild(overlay);\n }\n\n /** Parse <vz-hub-item> children into a normalized item list. */\n private parseItems(): HubItem[] {\n const raw: RawHubItem[] = [];\n this.querySelectorAll('vz-hub-item').forEach((el) => {\n const vzEl = el as VzHubItemElement;\n if (typeof vzEl.getData === 'function') raw.push(vzEl.getData());\n });\n return normalizeHubItems(raw);\n }\n}\n\n/** Register the <vz-hub> custom element. */\nexport function registerHub(): void {\n if (typeof customElements === 'undefined') return;\n if (!customElements.get('vz-hub')) {\n customElements.define('vz-hub', VzHubElement);\n }\n}\n","import { registerVideo } from './elements/vz-video.js';\nimport { registerImg } from './elements/vz-img.js';\nimport { registerTour } from './elements/vz-tour.js';\nimport { registerTourImg } from './elements/vz-tour-img.js';\nimport { registerTourPortal } from './elements/vz-tour-portal.js';\nimport { registerCinema } from './elements/vz-cinema.js';\nimport { registerPlaylist } from './elements/vz-playlist.js';\nimport { registerLive } from './elements/vz-live.js';\nimport { registerAnnotation } from './elements/vz-annotation.js';\nimport { registerChapter } from './elements/vz-chapter.js';\nimport { registerHubItem } from './elements/vz-hub-item.js';\nimport { registerHub } from './elements/vz-hub.js';\n\n/**\n * Auto-register all Vizor custom elements.\n * Used by the CDN script tag build.\n */\nexport function registerAll(): void {\n // No-op outside the browser (SSR/Node) — custom elements need a DOM.\n if (typeof customElements === 'undefined') return;\n // Register child/data elements FIRST. A container element's connectedCallback\n // parses its light-DOM children synchronously, so if a container (vz-video,\n // vz-hub) upgrades before its <vz-annotation>/<vz-chapter>/<vz-hub-item>/\n // <vz-tour-*> children are defined, those children aren't upgraded yet (no\n // getData) and get skipped. Defining children first makes DOM-authored embeds\n // work on the synchronous connect path (the async delivery path was unaffected).\n registerAnnotation();\n registerChapter();\n registerHubItem();\n registerTourPortal();\n registerTourImg();\n // Container elements\n registerVideo();\n registerImg();\n registerTour();\n registerCinema();\n registerPlaylist();\n registerLive();\n registerHub();\n}\n\n// Auto-register when loaded via script tag\nregisterAll();\n"],"names":["hash","FACE_MAP","COLS","ROWS","CANVAS_W","CANVAS_H"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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"}
|