three-vr-player 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -62,6 +62,9 @@ menu in the controls, or set it programmatically:
62
62
  | `flat-sbs-full` | flat screen | left/right (full-width per eye) |
63
63
  | `flat-sbs-half` | flat screen | left/right (half-width per eye) |
64
64
 
65
+ Pick **Off (native player)** in the menu — or call `setProjection('off')` — to disable
66
+ reprojection and play the raw **2D `<video>`** (the same view as the CORS fallback).
67
+
65
68
  ## Options
66
69
 
67
70
  ```ts
package/dist/Player.d.ts CHANGED
@@ -17,6 +17,8 @@ export declare class Player {
17
17
  private view;
18
18
  private readyEmitted;
19
19
  private native;
20
+ private tainted;
21
+ private displayMode;
20
22
  private loading;
21
23
  private proxyConfig?;
22
24
  private useProxy;
@@ -30,7 +32,10 @@ export declare class Player {
30
32
  private setNativeFallback;
31
33
  play(): Promise<void>;
32
34
  pause(): void;
33
- setProjection(p: Projection): void;
35
+ /** Set the display mode: a geometry projection (WebGL 3D) or 'off' for plain 2D `<video>`. */
36
+ setProjection(p: Projection | 'off'): void;
37
+ private applyProjectionGeometry;
38
+ private applyDisplay;
34
39
  setSwapEyes(v: boolean): void;
35
40
  setFov(deg: number): void;
36
41
  setSupersampling(x: number): void;
@@ -38,7 +38,7 @@ const h = {
38
38
  { value: "360-mono", label: "360° Mono" },
39
39
  { value: "360-sbs", label: "360° SBS" },
40
40
  { value: "360-tb", label: "360° Top-Bottom" },
41
- { value: "flat-2d", label: "Flat 2D (regular movie)" },
41
+ { value: "flat-2d", label: "Flat 2D" },
42
42
  { value: "flat-sbs-full", label: "Flat 3D — Full SBS" },
43
43
  { value: "flat-sbs-half", label: "Flat 3D — Half SBS" }
44
44
  ];
@@ -245,4 +245,4 @@ export {
245
245
  M as d,
246
246
  R as i
247
247
  };
248
- //# sourceMappingURL=VideoSource-CJGIX8gL.js.map
248
+ //# sourceMappingURL=VideoSource-BGaVaGUn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VideoSource-BGaVaGUn.js","sources":["../src/core/proxy.ts","../src/core/projections.ts","../src/core/StereoScene.ts","../src/core/LookControls.ts","../src/core/VideoSource.ts"],"sourcesContent":["export type StreamFormat = 'hls' | 'dash' | 'progressive';\r\n\r\nexport interface ProxyConfig {\r\n url: string;\r\n apiPassword?: string;\r\n headers?: Record<string, string>;\r\n}\r\n\r\n/** Detect streaming format from a URL by extension. */\r\nexport function detectFormat(rawUrl: string): StreamFormat {\r\n let pathname = rawUrl;\r\n try { pathname = new URL(rawUrl).pathname; } catch { /* relative/opaque: use raw */ }\r\n const lower = pathname.toLowerCase();\r\n if (lower.endsWith('.m3u8')) return 'hls';\r\n if (lower.endsWith('.mpd')) return 'dash';\r\n return 'progressive';\r\n}\r\n\r\nconst ENDPOINTS: Record<StreamFormat, string> = {\r\n progressive: '/proxy/stream',\r\n hls: '/proxy/hls/manifest.m3u8',\r\n dash: '/proxy/mpd/manifest.m3u8',\r\n};\r\n\r\n/**\r\n * Build the URL the `<video>`/hls.js should load. When `proxy` is omitted the raw\r\n * URL is returned (it must then be CORS-clean to be used as a WebGL texture).\r\n */\r\nexport function buildProxyUrl(rawUrl: string, proxy?: ProxyConfig): { url: string; format: StreamFormat } {\r\n const format = detectFormat(rawUrl);\r\n if (!proxy || !proxy.url) return { url: rawUrl, format };\r\n const base = proxy.url.replace(/\\/+$/, '');\r\n const params = new URLSearchParams();\r\n params.set('d', rawUrl);\r\n if (proxy.apiPassword) params.set('api_password', proxy.apiPassword);\r\n for (const [name, value] of Object.entries(proxy.headers ?? {})) {\r\n if (value != null && value !== '') params.set(`h_${name}`, value);\r\n }\r\n return { url: `${base}${ENDPOINTS[format]}?${params.toString()}`, format };\r\n}\r\n","import type { Projection } from '../types.js';\r\n\r\nexport type Split = 'mono' | 'sbs' | 'tb';\r\nexport type GeomKind = 'sphere180' | 'sphere360' | 'plane';\r\n\r\nexport interface ModeConfig {\r\n geom: GeomKind;\r\n split: Split;\r\n stereo: boolean;\r\n flat?: boolean;\r\n /** plane only: 'full' = displayW/H, 'per-eye' = (W/2)/H */\r\n aspect?: 'full' | 'per-eye';\r\n}\r\n\r\nexport const MODES: Record<Projection, ModeConfig> = {\r\n '180-sbs': { geom: 'sphere180', split: 'sbs', stereo: true },\r\n '180-mono': { geom: 'sphere180', split: 'mono', stereo: false },\r\n '360-mono': { geom: 'sphere360', split: 'mono', stereo: false },\r\n '360-sbs': { geom: 'sphere360', split: 'sbs', stereo: true },\r\n '360-tb': { geom: 'sphere360', split: 'tb', stereo: true },\r\n 'flat-2d': { geom: 'plane', split: 'mono', stereo: false, flat: true, aspect: 'full' },\r\n 'flat-sbs-full': { geom: 'plane', split: 'sbs', stereo: true, flat: true, aspect: 'per-eye' },\r\n 'flat-sbs-half': { geom: 'plane', split: 'sbs', stereo: true, flat: true, aspect: 'full' },\r\n};\r\n\r\nexport const PROJECTIONS: { value: Projection; label: string }[] = [\r\n { value: '180-sbs', label: '180° SBS (VR180)' },\r\n { value: '180-mono', label: '180° Mono' },\r\n { value: '360-mono', label: '360° Mono' },\r\n { value: '360-sbs', label: '360° SBS' },\r\n { value: '360-tb', label: '360° Top-Bottom' },\r\n { value: 'flat-2d', label: 'Flat 2D' },\r\n { value: 'flat-sbs-full', label: 'Flat 3D — Full SBS' },\r\n { value: 'flat-sbs-half', label: 'Flat 3D — Half SBS' },\r\n];\r\n\r\nexport function isFlatMode(p: Projection): boolean {\r\n return !!MODES[p]?.flat;\r\n}\r\n\r\n/** Guess a projection from a URL / filename; null when nothing recognizable. */\r\nexport function detectProjection(url: string): Projection | null {\r\n const s = String(url).toLowerCase();\r\n const tb = /(^|[^a-z])(tb|ou|top.?bottom|over.?under)([^a-z]|$)/.test(s);\r\n const sbs = /(sbs|side.?by.?side)/.test(s);\r\n if (s.includes('360')) {\r\n if (tb) return '360-tb';\r\n if (sbs) return '360-sbs';\r\n return '360-mono';\r\n }\r\n if (s.includes('180') || s.includes('vr180')) {\r\n return s.includes('mono') ? '180-mono' : '180-sbs';\r\n }\r\n if (sbs) return s.includes('half') ? 'flat-sbs-half' : 'flat-sbs-full';\r\n return null;\r\n}\r\n","import * as THREE from 'three';\r\nimport { VRButton } from 'three/examples/jsm/webxr/VRButton.js';\r\nimport type { Projection } from '../types.js';\r\nimport { MODES } from './projections.js';\r\n\r\n/**\r\n * Maps a video onto the geometry for a chosen projection (inside-out 180/360\r\n * sphere or a flat screen plane) and packs stereo eyes via UV split + WebXR\r\n * layers. Sizes to its canvas's container (not the window), for embedding.\r\n */\r\nexport class StereoScene {\r\n readonly renderer: THREE.WebGLRenderer;\r\n readonly scene = new THREE.Scene();\r\n readonly camera: THREE.PerspectiveCamera;\r\n readonly vrButton: HTMLElement;\r\n readonly maxAnisotropy: number;\r\n\r\n private readonly canvas: HTMLCanvasElement;\r\n private readonly video: HTMLVideoElement;\r\n private readonly texture: THREE.VideoTexture;\r\n private readonly meshes: THREE.Mesh[] = [];\r\n private readonly frameCbs: (() => void)[] = [];\r\n private readonly animate = () => {\r\n for (const cb of this.frameCbs) cb();\r\n this.renderer.render(this.scene, this.camera);\r\n };\r\n private readonly ro: ResizeObserver;\r\n private currentMode: Projection;\r\n private currentSwap: boolean;\r\n\r\n constructor(opts: {\r\n canvas: HTMLCanvasElement; video: HTMLVideoElement;\r\n projection?: Projection; swapEyes?: boolean; fov?: number; supersampling?: number;\r\n }) {\r\n const { canvas, video, projection = '180-sbs', swapEyes = false, fov = 70, supersampling = 1.5 } = opts;\r\n this.canvas = canvas;\r\n this.video = video;\r\n this.currentMode = MODES[projection] ? projection : '180-sbs';\r\n this.currentSwap = swapEyes;\r\n\r\n this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true });\r\n this.renderer.setPixelRatio(this.pixelRatioFor(supersampling));\r\n this.renderer.setSize(this.w(), this.h(), false);\r\n this.renderer.xr.enabled = true;\r\n\r\n this.camera = new THREE.PerspectiveCamera(fov, this.w() / this.h(), 0.1, 1000);\r\n this.camera.position.set(0, 0, 0);\r\n this.camera.layers.enable(1); // desktop shows Layer 1 (left eye) for stereo modes\r\n\r\n this.maxAnisotropy = this.renderer.capabilities.getMaxAnisotropy();\r\n this.texture = new THREE.VideoTexture(video);\r\n this.texture.colorSpace = THREE.SRGBColorSpace;\r\n this.texture.minFilter = THREE.LinearFilter;\r\n this.texture.magFilter = THREE.LinearFilter;\r\n this.texture.anisotropy = this.maxAnisotropy;\r\n\r\n this.applyProjection(this.currentMode, this.currentSwap);\r\n video.addEventListener('loadedmetadata', () => {\r\n if (MODES[this.currentMode].flat) this.applyProjection(this.currentMode, this.currentSwap);\r\n });\r\n\r\n this.renderer.setAnimationLoop(this.animate);\r\n this.vrButton = VRButton.createButton(this.renderer);\r\n\r\n this.ro = new ResizeObserver(() => this.resize());\r\n this.ro.observe(canvas);\r\n }\r\n\r\n private w() { return this.canvas.clientWidth || 1; }\r\n private h() { return this.canvas.clientHeight || 1; }\r\n private pixelRatioFor(ss: number) { return Math.min(window.devicePixelRatio * ss, 4); }\r\n\r\n private planeAspect(mode: Projection) {\r\n const vw = this.video.videoWidth, vh = this.video.videoHeight;\r\n if (!vw || !vh) return 16 / 9;\r\n return MODES[mode].aspect === 'per-eye' ? (vw / 2) / vh : vw / vh;\r\n }\r\n\r\n private buildGeometry(mode: Projection): THREE.BufferGeometry {\r\n const kind = MODES[mode].geom;\r\n if (kind === 'sphere180') { const g = new THREE.SphereGeometry(500, 60, 40, -Math.PI, Math.PI, 0, Math.PI); g.scale(-1, 1, 1); return g; }\r\n if (kind === 'sphere360') { const g = new THREE.SphereGeometry(500, 60, 40); g.scale(-1, 1, 1); return g; }\r\n const h = 2.4, w = h * this.planeAspect(mode);\r\n const g = new THREE.PlaneGeometry(w, h); g.translate(0, 0, -2); return g;\r\n }\r\n\r\n private splitUV(geo: THREE.BufferGeometry, split: string, eye: 'left' | 'right') {\r\n if (split === 'mono') return;\r\n const uv = geo.attributes.uv.array as Float32Array;\r\n for (let i = 0; i < uv.length; i += 2) {\r\n if (split === 'sbs') uv[i] = uv[i] * 0.5 + (eye === 'right' ? 0.5 : 0);\r\n else if (split === 'tb') uv[i + 1] = uv[i + 1] * 0.5 + (eye === 'left' ? 0.5 : 0); // top = left\r\n }\r\n geo.attributes.uv.needsUpdate = true;\r\n }\r\n\r\n private clearMeshes() {\r\n for (const m of this.meshes) { this.scene.remove(m); m.geometry.dispose(); (m.material as THREE.Material).dispose(); }\r\n this.meshes.length = 0;\r\n }\r\n\r\n private applyProjection(mode: Projection, swap: boolean) {\r\n if (!MODES[mode]) mode = '180-sbs';\r\n this.clearMeshes();\r\n const cfg = MODES[mode];\r\n if (!cfg.stereo) {\r\n const m = new THREE.Mesh(this.buildGeometry(mode), new THREE.MeshBasicMaterial({ map: this.texture }));\r\n m.layers.set(0); // mono: desktop + both XR eyes\r\n this.scene.add(m); this.meshes.push(m);\r\n } else {\r\n const gL = this.buildGeometry(mode); this.splitUV(gL, cfg.split, 'left');\r\n const gR = this.buildGeometry(mode); this.splitUV(gR, cfg.split, 'right');\r\n const mL = new THREE.Mesh(gL, new THREE.MeshBasicMaterial({ map: this.texture }));\r\n const mR = new THREE.Mesh(gR, new THREE.MeshBasicMaterial({ map: this.texture }));\r\n mL.layers.set(swap ? 2 : 1); mR.layers.set(swap ? 1 : 2);\r\n this.scene.add(mL, mR); this.meshes.push(mL, mR);\r\n }\r\n this.currentMode = mode; this.currentSwap = swap;\r\n }\r\n\r\n setProjection(p: Projection) { this.applyProjection(p, this.currentSwap); }\r\n setSwapEyes(v: boolean) { this.applyProjection(this.currentMode, v); }\r\n setFov(deg: number) { this.camera.fov = deg; this.camera.updateProjectionMatrix(); }\r\n setSupersampling(ss: number) { this.renderer.setPixelRatio(this.pixelRatioFor(ss)); this.renderer.setSize(this.w(), this.h(), false); }\r\n getProjection() { return this.currentMode; }\r\n isFlat() { return !!MODES[this.currentMode].flat; }\r\n onFrame(cb: () => void) { this.frameCbs.push(cb); }\r\n pauseRendering() { this.renderer.setAnimationLoop(null); }\r\n resumeRendering() { this.renderer.setAnimationLoop(this.animate); }\r\n\r\n resize = () => {\r\n const w = this.w(), h = this.h();\r\n this.camera.aspect = w / h;\r\n this.camera.updateProjectionMatrix();\r\n this.renderer.setSize(w, h, false);\r\n };\r\n\r\n dispose() {\r\n this.ro.disconnect();\r\n this.renderer.setAnimationLoop(null);\r\n this.texture.dispose();\r\n this.clearMeshes();\r\n this.renderer.dispose();\r\n }\r\n}\r\n","import type { Camera } from 'three';\r\n\r\nconst DEG = Math.PI / 180;\r\n\r\n/** Clamp look angles (degrees) to keep the view inside the front hemisphere. */\r\nexport function clampAngles(lon: number, lat: number): { lon: number; lat: number } {\r\n return {\r\n lon: Math.max(-90, Math.min(90, lon)),\r\n lat: Math.max(-85, Math.min(85, lat)),\r\n };\r\n}\r\n\r\n/**\r\n * Pointer/touch drag → camera yaw/pitch, clamped to the front hemisphere.\r\n * Disabled while an XR session drives the head pose, and for flat-screen modes.\r\n */\r\nexport class LookControls {\r\n private lon = 0;\r\n private lat = 0;\r\n private dragging = false;\r\n private px = 0;\r\n private py = 0;\r\n private enabled = true;\r\n private readonly isPresenting: () => boolean;\r\n\r\n constructor(\r\n private readonly camera: Camera,\r\n private readonly dom: HTMLElement,\r\n opts: { isPresenting?: () => boolean } = {},\r\n ) {\r\n this.isPresenting = opts.isPresenting ?? (() => false);\r\n this.dom.addEventListener('pointerdown', this.onDown);\r\n this.dom.addEventListener('pointermove', this.onMove);\r\n this.dom.addEventListener('pointerup', this.onUp);\r\n this.dom.addEventListener('pointercancel', this.onUp);\r\n }\r\n\r\n private capture(fn: 'setPointerCapture' | 'releasePointerCapture', id: number) {\r\n try { this.dom[fn]?.(id); } catch { /* pointer not active */ }\r\n }\r\n\r\n private onDown = (e: PointerEvent) => {\r\n if (!this.enabled) return;\r\n this.dragging = true;\r\n this.px = e.clientX;\r\n this.py = e.clientY;\r\n this.capture('setPointerCapture', e.pointerId);\r\n };\r\n\r\n private onMove = (e: PointerEvent) => {\r\n if (!this.dragging) return;\r\n const dx = e.clientX - this.px;\r\n const dy = e.clientY - this.py;\r\n this.px = e.clientX;\r\n this.py = e.clientY;\r\n ({ lon: this.lon, lat: this.lat } = clampAngles(this.lon - dx * 0.15, this.lat + dy * 0.15));\r\n };\r\n\r\n private onUp = (e: PointerEvent) => {\r\n this.dragging = false;\r\n this.capture('releasePointerCapture', e.pointerId);\r\n };\r\n\r\n update() {\r\n if (this.isPresenting() || !this.enabled) return;\r\n const phi = (90 - this.lat) * DEG;\r\n const theta = this.lon * DEG;\r\n this.camera.lookAt(\r\n Math.sin(phi) * Math.sin(theta),\r\n Math.cos(phi),\r\n -Math.sin(phi) * Math.cos(theta),\r\n );\r\n }\r\n\r\n reset() { this.lon = 0; this.lat = 0; }\r\n\r\n setEnabled(v: boolean) {\r\n this.enabled = v;\r\n if (!v) { this.lon = 0; this.lat = 0; this.camera.lookAt(0, 0, -1); }\r\n }\r\n\r\n getAngles() { return { lon: this.lon, lat: this.lat }; }\r\n\r\n dispose() {\r\n this.dom.removeEventListener('pointerdown', this.onDown);\r\n this.dom.removeEventListener('pointermove', this.onMove);\r\n this.dom.removeEventListener('pointerup', this.onUp);\r\n this.dom.removeEventListener('pointercancel', this.onUp);\r\n }\r\n}\r\n","import type Hls from 'hls.js';\r\nimport type { StreamFormat } from './proxy.js';\r\n\r\n/**\r\n * Attaches a source to a `<video>`. Progressive files go straight on `video.src`;\r\n * HLS/DASH-as-HLS go through hls.js — which is imported dynamically, so consumers\r\n * who never play HLS don't pay for it (and it's an optional dependency).\r\n */\r\nexport class VideoSource {\r\n private hls: Hls | null = null;\r\n\r\n async attach(\r\n video: HTMLVideoElement,\r\n source: { url: string; format: StreamFormat },\r\n opts: { crossOrigin?: string | null } = {},\r\n ): Promise<void> {\r\n this.dispose();\r\n const co = opts.crossOrigin === undefined ? 'anonymous' : opts.crossOrigin;\r\n if (co === null) video.removeAttribute('crossorigin');\r\n else video.crossOrigin = co;\r\n video.playsInline = true;\r\n\r\n const { url, format } = source;\r\n const isHlsLike = format === 'hls' || format === 'dash';\r\n const nativeHls = video.canPlayType('application/vnd.apple.mpegurl') !== '';\r\n\r\n return new Promise<void>((resolve, reject) => {\r\n const cleanup = () => {\r\n video.removeEventListener('loadedmetadata', onLoaded);\r\n video.removeEventListener('error', onError);\r\n };\r\n const onLoaded = () => { cleanup(); resolve(); };\r\n const onError = () => {\r\n cleanup();\r\n const code = video.error ? video.error.code : 'unknown';\r\n reject(new Error(`Video failed to load (media error ${code}). Is the source reachable and CORS-clean?`));\r\n };\r\n video.addEventListener('loadedmetadata', onLoaded);\r\n video.addEventListener('error', onError);\r\n\r\n if (isHlsLike && !nativeHls) {\r\n import('hls.js').then(({ default: HlsCtor }) => {\r\n if (!HlsCtor.isSupported()) {\r\n cleanup();\r\n reject(new Error('HLS/DASH stream needs hls.js, which is unsupported in this browser.'));\r\n return;\r\n }\r\n this.hls = new HlsCtor({ enableWorker: true });\r\n this.hls.on(HlsCtor.Events.ERROR, (_e, data) => {\r\n if (data.fatal) { cleanup(); this.dispose(); reject(new Error(`HLS fatal error: ${data.type} / ${data.details}`)); }\r\n });\r\n this.hls.loadSource(url);\r\n this.hls.attachMedia(video);\r\n }).catch(() => {\r\n cleanup();\r\n reject(new Error('This looks like an HLS/DASH stream but hls.js could not be loaded. Install \"hls.js\".'));\r\n });\r\n } else {\r\n video.src = url;\r\n video.load();\r\n }\r\n });\r\n }\r\n\r\n dispose() {\r\n if (this.hls) { this.hls.destroy(); this.hls = null; }\r\n }\r\n}\r\n"],"names":["detectFormat","rawUrl","pathname","lower","ENDPOINTS","buildProxyUrl","proxy","format","base","params","name","value","MODES","PROJECTIONS","isFlatMode","p","_a","detectProjection","url","s","tb","sbs","StereoScene","opts","THREE","cb","w","h","canvas","video","projection","swapEyes","fov","supersampling","VRButton","ss","mode","vw","vh","kind","g","geo","split","eye","uv","m","swap","cfg","gL","gR","mL","mR","v","deg","DEG","clampAngles","lon","lat","LookControls","camera","dom","e","dx","dy","fn","id","_b","phi","theta","VideoSource","source","co","isHlsLike","nativeHls","resolve","reject","cleanup","onLoaded","onError","code","HlsCtor","_e","data"],"mappings":"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"}
package/dist/core.js CHANGED
@@ -1,4 +1,4 @@
1
- import { L as o, M as s, P as t, S as r, V as c, b as d, c as l, d as S, a as i, i as n } from "./VideoSource-CJGIX8gL.js";
1
+ import { L as o, M as s, P as t, S as r, V as c, b as d, c as l, d as S, a as i, i as n } from "./VideoSource-BGaVaGUn.js";
2
2
  export {
3
3
  o as LookControls,
4
4
  s as MODES,
@@ -1,92 +1,111 @@
1
- import { P as V, V as D, S as J, L as W, a as q, b as Y } from "./VideoSource-CJGIX8gL.js";
2
- import { M as tt, c as et, d as st, i as it } from "./VideoSource-CJGIX8gL.js";
3
- function L(l) {
1
+ import { P as Y, V as _, S as H, L as K, a as Q, b as X } from "./VideoSource-BGaVaGUn.js";
2
+ import { M as rt, c as at, d as pt, i as lt } from "./VideoSource-BGaVaGUn.js";
3
+ function $(l) {
4
4
  if (!Number.isFinite(l) || l < 0) return "0:00";
5
- const t = Math.floor(l % 60), s = Math.floor(l / 60 % 60), e = Math.floor(l / 3600), a = String(t).padStart(2, "0");
6
- return e > 0 ? `${e}:${String(s).padStart(2, "0")}:${a}` : `${s}:${a}`;
5
+ const t = Math.floor(l % 60), e = Math.floor(l / 60 % 60), s = Math.floor(l / 3600), p = String(t).padStart(2, "0");
6
+ return s > 0 ? `${s}:${String(e).padStart(2, "0")}:${p}` : `${e}:${p}`;
7
7
  }
8
- function o(l, t = {}, s = []) {
9
- const e = document.createElement(l), { class: a, ...c } = t;
10
- a && (e.className = a), Object.assign(e, c);
11
- for (const d of s) e.append(d);
12
- return e;
8
+ function o(l, t = {}, e = []) {
9
+ const s = document.createElement(l), { class: p, ...c } = t;
10
+ p && (s.className = p), Object.assign(s, c);
11
+ for (const u of e) s.append(u);
12
+ return s;
13
13
  }
14
- class _ {
15
- constructor(t, s) {
14
+ class Z {
15
+ constructor(t, e) {
16
16
  this.nodes = [], this.disposers = [];
17
- const e = s.video, a = o("button", { class: "tvp-btn tvp-play", title: "Play/Pause", textContent: "▶" }), c = o("input", { class: "tvp-seek", type: "range", min: "0", max: "1000", value: "0" }), d = o("span", { class: "tvp-time", textContent: "0:00 / 0:00" }), n = o("button", { class: "tvp-btn tvp-mute", title: "Volume", textContent: "🔊" }), h = o("input", { class: "tvp-volume", type: "range", min: "0", max: "1", step: "0.01", value: "1" }), m = o("div", { class: "tvp-volpopup", hidden: !0 }, [h]), R = o("span", { class: "tvp-volwrap" }, [n, m]), j = o("span", { class: "tvp-vrslot" }), x = o("button", { class: "tvp-btn tvp-projbtn", title: "Projection", textContent: "🌐" }), u = o("div", { class: "tvp-projmenu", hidden: !0 });
18
- for (const i of V) {
19
- const r = o("button", { textContent: i.label });
20
- r.dataset.mode = i.value, u.append(r);
17
+ const s = e.video, p = o("button", { class: "tvp-btn tvp-play", title: "Play/Pause", textContent: "▶" }), c = o("input", { class: "tvp-seek", type: "range", min: "0", max: "1000", value: "0" }), u = o("span", { class: "tvp-time", textContent: "0:00 / 0:00" }), r = o("button", { class: "tvp-btn tvp-mute", title: "Volume", textContent: "🔊" }), v = o("input", { class: "tvp-volume", type: "range", min: "0", max: "1", step: "0.01", value: "1" }), f = o("div", { class: "tvp-volpopup", hidden: !0 }, [v]), G = o("span", { class: "tvp-volwrap" }, [r, f]), z = o("span", { class: "tvp-vrslot" }), b = o("button", { class: "tvp-btn tvp-projbtn", title: "Projection", textContent: "🌐" }), d = o("div", { class: "tvp-projmenu", hidden: !0 });
18
+ for (const i of Y) {
19
+ const n = o("button", { textContent: i.label });
20
+ n.dataset.mode = i.value, d.append(n);
21
21
  }
22
- const B = o("span", { class: "tvp-projwrap" }, [x, u]), C = o("button", { class: "tvp-btn tvp-settingsbtn", title: "Settings", textContent: "⚙" }), F = o("button", { class: "tvp-btn tvp-fullscreen", title: "Fullscreen", textContent: "⛶" }), $ = o("footer", { class: "tvp-controls" }, [a, c, d, R, j, B, C, F]), b = o("input", { type: "checkbox", checked: s.initial.swapEyes }), f = o("input", { type: "range", min: "30", max: "100", step: "1", value: String(s.initial.fov) }), y = o("span", { textContent: String(s.initial.fov) }), w = o("input", { type: "range", min: "1", max: "2", step: "0.25", value: String(s.initial.supersampling) }), z = o("span", { textContent: String(s.initial.supersampling) }), k = o("input", { type: "checkbox", checked: s.proxy.enabled }), S = o("input", { type: "text", value: s.proxy.url, placeholder: "http://localhost:8888", spellcheck: !1 }), P = o("input", { type: "password", value: s.proxy.apiPassword, placeholder: "API password" }), N = o("section", { class: "tvp-settings" }, [
23
- o("label", { class: "row" }, [b, "Swap eyes (if depth looks wrong)"]),
24
- o("label", {}, [T("Field of view (zoom): ", y, "°"), f]),
25
- o("label", {}, [T("Supersampling: ", z, "× (sharpness)"), w]),
22
+ const M = o("button", { textContent: "Off (native player)" });
23
+ M.dataset.mode = "off", d.append(o("hr", { class: "tvp-sep" }), M);
24
+ const J = o("span", { class: "tvp-projwrap" }, [b, d]), k = o("button", { class: "tvp-btn tvp-settingsbtn", title: "Settings", textContent: "⚙" }), O = o("button", { class: "tvp-btn tvp-fullscreen", title: "Fullscreen", textContent: "" }), S = o("footer", { class: "tvp-controls" }, [p, c, u, G, z, J, k, O]), P = o("input", { type: "checkbox", checked: e.initial.swapEyes }), x = o("input", { type: "range", min: "30", max: "100", step: "1", value: String(e.initial.fov) }), j = o("span", { textContent: String(e.initial.fov) }), E = o("input", { type: "range", min: "1", max: "2", step: "0.25", value: String(e.initial.supersampling) }), A = o("span", { textContent: String(e.initial.supersampling) }), C = o("input", { type: "checkbox", checked: e.proxy.enabled }), F = o("input", { type: "text", value: e.proxy.url, placeholder: "http://localhost:8888", spellcheck: !1 }), N = o("input", { type: "password", value: e.proxy.apiPassword, placeholder: "API password" }), g = o("section", { class: "tvp-settings" }, [
25
+ o("label", { class: "row" }, [P, "Swap eyes (if depth looks wrong)"]),
26
+ o("label", {}, [D("Field of view (zoom): ", j, "°"), x]),
27
+ o("label", {}, [D("Supersampling: ", A, "× (sharpness)"), E]),
26
28
  o("hr", { class: "tvp-sep" }),
27
- o("label", { class: "row" }, [k, "Use CORS proxy"]),
28
- o("label", {}, ["Proxy URL", S]),
29
- o("label", {}, ["API password", P])
30
- ]), U = o("div", { class: "tvp-toast" });
31
- this.nodes.push($, N, U);
29
+ o("label", { class: "row" }, [C, "Use CORS proxy"]),
30
+ o("label", {}, ["Proxy URL", F]),
31
+ o("label", {}, ["API password", N])
32
+ ]), W = o("div", { class: "tvp-toast" });
33
+ this.nodes.push(S, g, W);
32
34
  for (const i of this.nodes) t.append(i);
33
- const p = (i, r, v, g) => {
34
- i.addEventListener(r, v, g), this.disposers.push(() => i.removeEventListener(r, v, g));
35
- }, O = (i) => c.style.setProperty("--seek", `${i}%`);
36
- p(a, "click", () => {
37
- e.paused ? e.play() : e.pause();
38
- }), p(e, "play", () => {
39
- a.textContent = "⏸";
40
- }), p(e, "pause", () => {
41
- a.textContent = "";
42
- }), p(e, "timeupdate", () => {
43
- if (e.duration) {
44
- const i = e.currentTime / e.duration * 100;
45
- c.value = String(i * 10), O(i), d.textContent = `${L(e.currentTime)} / ${L(e.duration)}`;
35
+ const a = (i, n, h, m) => {
36
+ i.addEventListener(n, h, m), this.disposers.push(() => i.removeEventListener(n, h, m));
37
+ }, I = (i) => c.style.setProperty("--seek", `${i}%`), y = () => {
38
+ f.hidden = !0, d.hidden = !0, g.classList.remove("open");
39
+ };
40
+ a(p, "click", () => {
41
+ s.paused ? s.play() : s.pause();
42
+ }), a(s, "play", () => {
43
+ p.textContent = "";
44
+ }), a(s, "pause", () => {
45
+ p.textContent = "▶";
46
+ }), a(s, "timeupdate", () => {
47
+ if (s.duration) {
48
+ const i = s.currentTime / s.duration * 100;
49
+ c.value = String(i * 10), I(i), u.textContent = `${$(s.currentTime)} / ${$(s.duration)}`;
46
50
  }
47
- }), p(c, "input", () => {
48
- O(Number(c.value) / 10), e.duration && (e.currentTime = Number(c.value) / 1e3 * e.duration);
49
- }), p(n, "click", (i) => {
50
- i.stopPropagation(), m.hidden = !m.hidden;
51
- }), p(h, "input", () => {
52
- e.volume = Number(h.value), e.muted = e.volume === 0, n.textContent = e.muted ? "🔇" : "🔊";
51
+ }), a(c, "input", () => {
52
+ I(Number(c.value) / 10), s.duration && (s.currentTime = Number(c.value) / 1e3 * s.duration);
53
+ }), a(r, "click", (i) => {
54
+ i.stopPropagation();
55
+ const n = f.hidden;
56
+ y(), f.hidden = !n;
57
+ }), a(v, "input", () => {
58
+ s.volume = Number(v.value), s.muted = s.volume === 0, r.textContent = s.muted ? "🔇" : "🔊";
53
59
  });
54
- const A = () => {
55
- const i = s.getProjection();
56
- let r = "Projection";
57
- u.querySelectorAll("button").forEach((v) => {
58
- const g = v.dataset.mode === i;
59
- v.classList.toggle("active", g), g && (r = `Projection: ${v.textContent}`);
60
- }), x.title = r;
60
+ const B = () => {
61
+ const i = e.getProjection();
62
+ let n = "Projection";
63
+ d.querySelectorAll("button").forEach((h) => {
64
+ const m = h.dataset.mode === i;
65
+ h.classList.toggle("active", m), m && (n = `Projection: ${h.textContent}`);
66
+ }), b.title = n;
61
67
  };
62
- p(x, "click", (i) => {
63
- i.stopPropagation(), u.hidden = !u.hidden;
64
- }), p(u, "click", (i) => {
65
- const r = i.target.closest("button[data-mode]");
66
- r && (s.setProjection(r.dataset.mode), A(), u.hidden = !0);
67
- }), A(), p(C, "click", (i) => {
68
- i.stopPropagation(), N.classList.toggle("open");
69
- }), p(b, "change", () => s.setSwapEyes(b.checked)), p(f, "input", () => {
70
- const i = Number(f.value);
71
- y.textContent = String(i), s.setFov(i);
72
- }), p(w, "input", () => {
73
- const i = Number(w.value);
74
- z.textContent = String(i), s.setSupersampling(i);
68
+ a(b, "click", (i) => {
69
+ i.stopPropagation();
70
+ const n = d.hidden;
71
+ y(), d.hidden = !n;
72
+ }), a(d, "click", (i) => {
73
+ const n = i.target.closest("button[data-mode]");
74
+ n && (e.setProjection(n.dataset.mode), B(), d.hidden = !0);
75
+ }), B(), a(k, "click", (i) => {
76
+ i.stopPropagation();
77
+ const n = !g.classList.contains("open");
78
+ y(), g.classList.toggle("open", n);
79
+ }), a(P, "change", () => e.setSwapEyes(P.checked)), a(x, "input", () => {
80
+ const i = Number(x.value);
81
+ j.textContent = String(i), e.setFov(i);
82
+ }), a(E, "input", () => {
83
+ const i = Number(E.value);
84
+ A.textContent = String(i), e.setSupersampling(i);
75
85
  });
76
- const E = () => s.setProxy({ url: S.value.trim(), apiPassword: P.value, enabled: k.checked });
77
- p(k, "change", E), p(S, "change", E), p(P, "change", E), p(s.surface, "wheel", (i) => {
86
+ const T = () => e.setProxy({ url: F.value.trim(), apiPassword: N.value, enabled: C.checked });
87
+ a(C, "change", T), a(F, "change", T), a(N, "change", T), a(e.surface, "wheel", (i) => {
78
88
  i.preventDefault();
79
- const r = Math.max(30, Math.min(100, Number(f.value) + Math.sign(i.deltaY) * 3));
80
- f.value = String(r), y.textContent = String(r), s.setFov(r);
81
- }, { passive: !1 }), p(F, "click", () => {
82
- var r, v;
83
- const i = s.fullscreenTarget;
84
- document.fullscreenElement ? (v = document.exitFullscreen) == null || v.call(document) : (r = i.requestFullscreen) == null || r.call(i);
85
- }), p(document, "click", (i) => {
86
- const r = i.composedPath();
87
- !m.hidden && !r.includes(m) && !r.includes(n) && (m.hidden = !0), !u.hidden && !r.includes(u) && !r.includes(x) && (u.hidden = !0);
88
- }), s.vrSupported().then((i) => {
89
- i && (Object.assign(s.vrButton.style, { position: "static", left: "auto", bottom: "auto", transform: "none", margin: "0" }), j.append(s.vrButton));
89
+ const n = Math.max(30, Math.min(100, Number(x.value) + Math.sign(i.deltaY) * 3));
90
+ x.value = String(n), j.textContent = String(n), e.setFov(n);
91
+ }, { passive: !1 }), a(O, "click", () => {
92
+ var n, h;
93
+ const i = e.fullscreenTarget;
94
+ document.fullscreenElement ? (h = document.exitFullscreen) == null || h.call(document) : (n = i.requestFullscreen) == null || n.call(i);
95
+ }), a(document, "click", (i) => {
96
+ const n = i.composedPath(), h = (m) => n.includes(m);
97
+ [f, r, d, b, g, k].some(h) || y();
98
+ });
99
+ let w = 0;
100
+ const q = () => !f.hidden || !d.hidden || g.classList.contains("open"), R = () => {
101
+ q() || S.classList.add("tvp-hidden");
102
+ }, L = () => {
103
+ S.classList.remove("tvp-hidden"), clearTimeout(w), w = window.setTimeout(R, 5e3);
104
+ };
105
+ a(e.fullscreenTarget, "pointermove", L), a(e.fullscreenTarget, "pointerdown", L), a(e.fullscreenTarget, "pointerleave", () => {
106
+ clearTimeout(w), R();
107
+ }), this.disposers.push(() => clearTimeout(w)), L(), e.vrSupported().then((i) => {
108
+ i && (Object.assign(e.vrButton.style, { position: "static", left: "auto", bottom: "auto", transform: "none", margin: "0" }), z.append(e.vrButton));
90
109
  });
91
110
  }
92
111
  dispose() {
@@ -94,82 +113,82 @@ class _ {
94
113
  for (const t of this.nodes) t.remove();
95
114
  }
96
115
  }
97
- function T(l, t, s) {
98
- const e = document.createDocumentFragment();
99
- return e.append(l, t, s), e;
116
+ function D(l, t, e) {
117
+ const s = document.createDocumentFragment();
118
+ return s.append(l, t, e), s;
100
119
  }
101
- const I = ":host,:host *,.tvp,.tvp *{box-sizing:border-box}.tvp-canvas{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;display:block;touch-action:none;cursor:grab}.tvp-canvas:active{cursor:grabbing}.tvp-video{display:none}.tvp-controls{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;gap:10px;padding:12px;background:linear-gradient(transparent,#000c);z-index:5;font:14px system-ui,Segoe UI,Roboto,sans-serif;color:#e8eaed}.tvp-btn{padding:4px;border:0;background:none;color:#e8eaed;cursor:pointer;font-size:22px;line-height:1}.tvp-btn:hover{opacity:.7}.tvp-seek{flex:1;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:14px;background:transparent;cursor:pointer;margin:0}.tvp-seek::-webkit-slider-runnable-track{height:4px;border-radius:2px;background:linear-gradient(to right,#4f8cff var(--seek,0%),#3a4150 var(--seek,0%))}.tvp-seek::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-top:-4px;width:12px;height:12px;border-radius:50%;background:#fff;cursor:pointer}.tvp-seek::-moz-range-track{height:4px;border-radius:2px;background:#3a4150}.tvp-seek::-moz-range-progress{height:4px;border-radius:2px;background:#4f8cff}.tvp-seek::-moz-range-thumb{width:12px;height:12px;border:0;border-radius:50%;background:#fff;cursor:pointer}.tvp-time{font-variant-numeric:tabular-nums;min-width:96px;text-align:center;color:#cfd6e2}.tvp-volwrap,.tvp-projwrap{position:relative;display:inline-flex}.tvp-volpopup{position:absolute;bottom:calc(100% + 10px);left:50%;transform:translate(-50%);width:40px;height:132px;background:#121418eb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid #2a2f3a;border-radius:10px;z-index:8;box-shadow:0 8px 24px #0009}.tvp-volpopup[hidden]{display:none}.tvp-volume{position:absolute;top:50%;left:50%;width:104px;height:14px;margin:0;transform:translate(-50%,-50%) rotate(-90deg);-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;cursor:pointer}.tvp-volume::-webkit-slider-runnable-track{height:4px;border-radius:2px;background:#3a4150}.tvp-volume::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-top:-4px;width:12px;height:12px;border-radius:50%;background:#fff;cursor:pointer}.tvp-volume::-moz-range-track{height:4px;border-radius:2px;background:#3a4150}.tvp-volume::-moz-range-thumb{width:12px;height:12px;border:0;border-radius:50%;background:#fff;cursor:pointer}.tvp-projmenu{position:absolute;bottom:calc(100% + 8px);right:0;min-width:210px;background:#121418eb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid #2a2f3a;border-radius:10px;padding:6px;display:flex;flex-direction:column;gap:2px;z-index:8;box-shadow:0 8px 24px #0009}.tvp-projmenu[hidden]{display:none}.tvp-projmenu button{text-align:left;background:transparent;border:0;color:#e8eaed;padding:8px 10px;border-radius:7px;cursor:pointer;font:inherit;white-space:nowrap}.tvp-projmenu button:hover{background:#252b39}.tvp-projmenu button.active{background:#4f8cff;color:#fff}.tvp-vrslot button{position:static!important}.tvp-settings{position:absolute;bottom:62px;right:10px;width:300px;max-width:calc(100% - 20px);max-height:70%;overflow-y:auto;background:#121418eb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid #2a2f3a;border-radius:12px;padding:14px;display:none;flex-direction:column;gap:12px;z-index:6;font:14px system-ui,Segoe UI,Roboto,sans-serif;color:#e8eaed}.tvp-settings.open{display:flex}.tvp-settings label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:#aab2c0}.tvp-settings label.row{flex-direction:row;align-items:center;gap:8px;color:#e8eaed;font-size:13px}.tvp-settings input[type=range]{width:100%;accent-color:#4f8cff}.tvp-settings input[type=text],.tvp-settings input[type=password]{width:100%;padding:7px 9px;border-radius:7px;border:1px solid #333;background:#151821;color:#e8eaed;font:inherit}.tvp-sep{width:100%;border:0;border-top:1px solid #2a2f3a;margin:2px 0}.tvp-toast{position:absolute;left:50%;bottom:74px;transform:translate(-50%);max-width:80%;padding:10px 16px;border-radius:10px;background:#1b1f2aed;border:1px solid #2a2f3a;color:#e8eaed;opacity:0;pointer-events:none;transition:opacity .2s;z-index:7;font:13px system-ui}.tvp-toast.show{opacity:1}", M = "three-vr-player:settings";
102
- class H {
103
- constructor(t, s = {}) {
104
- var c, d;
105
- this.source = new D(), this.listeners = /* @__PURE__ */ new Map(), this.readyEmitted = !1, this.native = !1, this.loading = !1, this.useProxy = !1, this.opts = s, this.proxyConfig = s.proxy, this.useProxy = !!s.proxy;
106
- const e = s.persistSettings ? this.loadSettings() : null;
120
+ const U = ":host,:host *,.tvp,.tvp *{box-sizing:border-box}.tvp-canvas{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;display:block;touch-action:none;cursor:grab}.tvp-canvas:active{cursor:grabbing}.tvp-video{display:none}.tvp-controls{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;gap:10px;padding:12px;background:linear-gradient(transparent,#000c);z-index:5;font:14px system-ui,Segoe UI,Roboto,sans-serif;color:#e8eaed;transition:opacity .3s}.tvp-controls.tvp-hidden{opacity:0;pointer-events:none}.tvp-btn{padding:4px;border:0;background:none;color:#e8eaed;cursor:pointer;font-size:22px;line-height:1}.tvp-btn:hover{opacity:.7}.tvp-seek{flex:1;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:14px;background:transparent;cursor:pointer;margin:0}.tvp-seek::-webkit-slider-runnable-track{height:4px;border-radius:2px;background:linear-gradient(to right,#4f8cff var(--seek,0%),#3a4150 var(--seek,0%))}.tvp-seek::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-top:-4px;width:12px;height:12px;border-radius:50%;background:#fff;cursor:pointer}.tvp-seek::-moz-range-track{height:4px;border-radius:2px;background:#3a4150}.tvp-seek::-moz-range-progress{height:4px;border-radius:2px;background:#4f8cff}.tvp-seek::-moz-range-thumb{width:12px;height:12px;border:0;border-radius:50%;background:#fff;cursor:pointer}.tvp-time{font-variant-numeric:tabular-nums;min-width:96px;text-align:center;color:#cfd6e2}.tvp-volwrap,.tvp-projwrap{position:relative;display:inline-flex}.tvp-volpopup{position:absolute;bottom:calc(100% + 10px);left:50%;transform:translate(-50%);width:40px;height:132px;background:#121418eb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid #2a2f3a;border-radius:10px;z-index:8;box-shadow:0 8px 24px #0009}.tvp-volpopup[hidden]{display:none}.tvp-volume{position:absolute;top:50%;left:50%;width:104px;height:14px;margin:0;transform:translate(-50%,-50%) rotate(-90deg);-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;cursor:pointer}.tvp-volume::-webkit-slider-runnable-track{height:4px;border-radius:2px;background:#3a4150}.tvp-volume::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-top:-4px;width:12px;height:12px;border-radius:50%;background:#fff;cursor:pointer}.tvp-volume::-moz-range-track{height:4px;border-radius:2px;background:#3a4150}.tvp-volume::-moz-range-thumb{width:12px;height:12px;border:0;border-radius:50%;background:#fff;cursor:pointer}.tvp-projmenu{position:absolute;bottom:calc(100% + 8px);right:0;min-width:210px;background:#121418eb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid #2a2f3a;border-radius:10px;padding:6px;display:flex;flex-direction:column;gap:2px;z-index:8;box-shadow:0 8px 24px #0009}.tvp-projmenu[hidden]{display:none}.tvp-projmenu button{text-align:left;background:transparent;border:0;color:#e8eaed;padding:8px 10px;border-radius:7px;cursor:pointer;font:inherit;white-space:nowrap}.tvp-projmenu button:hover{background:#252b39}.tvp-projmenu button.active{background:#4f8cff;color:#fff}.tvp-vrslot button{position:static!important}.tvp-settings{position:absolute;bottom:62px;right:10px;width:300px;max-width:calc(100% - 20px);max-height:70%;overflow-y:auto;background:#121418eb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid #2a2f3a;border-radius:12px;padding:14px;display:none;flex-direction:column;gap:12px;z-index:6;font:14px system-ui,Segoe UI,Roboto,sans-serif;color:#e8eaed}.tvp-settings.open{display:flex}.tvp-settings label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:#aab2c0}.tvp-settings label.row{flex-direction:row;align-items:center;gap:8px;color:#e8eaed;font-size:13px}.tvp-settings input[type=range]{width:100%;accent-color:#4f8cff}.tvp-settings input[type=text],.tvp-settings input[type=password]{width:100%;padding:7px 9px;border-radius:7px;border:1px solid #333;background:#151821;color:#e8eaed;font:inherit}.tvp-sep{width:100%;border:0;border-top:1px solid #2a2f3a;margin:2px 0}.tvp-toast{position:absolute;left:50%;bottom:74px;transform:translate(-50%);max-width:80%;padding:10px 16px;border-radius:10px;background:#1b1f2aed;border:1px solid #2a2f3a;color:#e8eaed;opacity:0;pointer-events:none;transition:opacity .2s;z-index:7;font:13px system-ui}.tvp-toast.show{opacity:1}", V = "three-vr-player:settings";
121
+ class tt {
122
+ constructor(t, e = {}) {
123
+ var c, u;
124
+ this.source = new _(), this.listeners = /* @__PURE__ */ new Map(), this.readyEmitted = !1, this.native = !1, this.tainted = !1, this.loading = !1, this.useProxy = !1, this.opts = e, this.proxyConfig = e.proxy, this.useProxy = !!e.proxy;
125
+ const s = e.persistSettings ? this.loadSettings() : null;
107
126
  this.view = {
108
- projection: s.projection ?? (e == null ? void 0 : e.projection) ?? "180-sbs",
109
- swapEyes: s.swapEyes ?? (e == null ? void 0 : e.swapEyes) ?? !1,
110
- fov: s.fov ?? (e == null ? void 0 : e.fov) ?? 70,
111
- supersampling: s.supersampling ?? (e == null ? void 0 : e.supersampling) ?? 1.5
112
- }, this.wrap = document.createElement("div"), this.wrap.className = "tvp", this.wrap.style.cssText = "position:relative;width:100%;height:100%;overflow:hidden;", t.appendChild(this.wrap);
113
- let a;
114
- if (s.shadowDom !== !1) {
115
- const n = this.wrap.attachShadow({ mode: "open" }), h = document.createElement("style");
116
- h.textContent = I, n.appendChild(h), a = n;
127
+ projection: e.projection ?? (s == null ? void 0 : s.projection) ?? "180-sbs",
128
+ swapEyes: e.swapEyes ?? (s == null ? void 0 : s.swapEyes) ?? !1,
129
+ fov: e.fov ?? (s == null ? void 0 : s.fov) ?? 70,
130
+ supersampling: e.supersampling ?? (s == null ? void 0 : s.supersampling) ?? 1.5
131
+ }, this.displayMode = this.view.projection, this.wrap = document.createElement("div"), this.wrap.className = "tvp", this.wrap.style.cssText = "position:relative;width:100%;height:100%;overflow:hidden;", t.appendChild(this.wrap);
132
+ let p;
133
+ if (e.shadowDom !== !1) {
134
+ const r = this.wrap.attachShadow({ mode: "open" }), v = document.createElement("style");
135
+ v.textContent = U, r.appendChild(v), p = r;
117
136
  } else {
118
137
  if (!document.getElementById("tvp-styles")) {
119
- const n = document.createElement("style");
120
- n.id = "tvp-styles", n.textContent = I, document.head.appendChild(n);
138
+ const r = document.createElement("style");
139
+ r.id = "tvp-styles", r.textContent = U, document.head.appendChild(r);
121
140
  }
122
- a = this.wrap;
141
+ p = this.wrap;
123
142
  }
124
- this.canvas = document.createElement("canvas"), this.canvas.className = "tvp-canvas", this.video = document.createElement("video"), this.video.className = "tvp-video", this.video.playsInline = !0, s.crossOrigin !== null && (this.video.crossOrigin = s.crossOrigin ?? "anonymous"), a.append(this.canvas, this.video), this.scene = new J({
143
+ this.canvas = document.createElement("canvas"), this.canvas.className = "tvp-canvas", this.video = document.createElement("video"), this.video.className = "tvp-video", this.video.playsInline = !0, e.crossOrigin !== null && (this.video.crossOrigin = e.crossOrigin ?? "anonymous"), p.append(this.canvas, this.video), this.scene = new H({
125
144
  canvas: this.canvas,
126
145
  video: this.video,
127
146
  projection: this.view.projection,
128
147
  swapEyes: this.view.swapEyes,
129
148
  fov: this.view.fov,
130
149
  supersampling: this.view.supersampling
131
- }), this.look = new W(this.scene.camera, this.canvas, { isPresenting: () => this.scene.renderer.xr.isPresenting }), this.scene.onFrame(() => this.look.update()), this.look.setEnabled(!this.scene.isFlat()), s.controls !== !1 && (this.ui = new _(a, {
150
+ }), this.look = new K(this.scene.camera, this.canvas, { isPresenting: () => this.scene.renderer.xr.isPresenting }), this.scene.onFrame(() => this.look.update()), this.look.setEnabled(!this.scene.isFlat()), e.controls !== !1 && (this.ui = new Z(p, {
132
151
  video: this.video,
133
152
  surface: this.canvas,
134
153
  fullscreenTarget: this.wrap,
135
154
  vrButton: this.scene.vrButton,
136
155
  vrSupported: () => this.vrSupported(),
137
- getProjection: () => this.scene.getProjection(),
138
- setProjection: (n) => this.setProjection(n),
139
- setSwapEyes: (n) => this.setSwapEyes(n),
140
- setFov: (n) => this.setFov(n),
141
- setSupersampling: (n) => this.setSupersampling(n),
156
+ getProjection: () => this.displayMode,
157
+ setProjection: (r) => this.setProjection(r),
158
+ setSwapEyes: (r) => this.setSwapEyes(r),
159
+ setFov: (r) => this.setFov(r),
160
+ setSupersampling: (r) => this.setSupersampling(r),
142
161
  initial: { swapEyes: this.view.swapEyes, fov: this.view.fov, supersampling: this.view.supersampling },
143
- proxy: { url: ((c = this.proxyConfig) == null ? void 0 : c.url) ?? "", apiPassword: ((d = this.proxyConfig) == null ? void 0 : d.apiPassword) ?? "", enabled: this.useProxy },
144
- setProxy: (n) => this.setProxy(n)
162
+ proxy: { url: ((c = this.proxyConfig) == null ? void 0 : c.url) ?? "", apiPassword: ((u = this.proxyConfig) == null ? void 0 : u.apiPassword) ?? "", enabled: this.useProxy },
163
+ setProxy: (r) => this.setProxy(r)
145
164
  })), this.video.addEventListener("play", () => this.emit("play")), this.video.addEventListener("pause", () => this.emit("pause")), this.video.addEventListener("ended", () => this.emit("ended")), this.video.addEventListener("timeupdate", () => this.emit("timeupdate", this.video.currentTime)), this.video.addEventListener("error", () => {
146
165
  this.loading || this.emit("error", this.video.error);
147
166
  }), this.video.addEventListener("loadedmetadata", () => {
148
167
  this.readyEmitted || (this.readyEmitted = !0, this.emit("ready"));
149
- }), this.scene.renderer.xr.addEventListener("sessionstart", () => this.emit("enterxr")), this.scene.renderer.xr.addEventListener("sessionend", () => this.emit("exitxr")), s.src && this.load(s.src, { projection: s.projection });
168
+ }), this.scene.renderer.xr.addEventListener("sessionstart", () => this.emit("enterxr")), this.scene.renderer.xr.addEventListener("sessionend", () => this.emit("exitxr")), e.src && this.load(e.src, { projection: e.projection });
150
169
  }
151
170
  vrSupported() {
152
171
  return this.opts.vrButton === !1 || !navigator.xr ? Promise.resolve(!1) : navigator.xr.isSessionSupported("immersive-vr").catch(() => !1);
153
172
  }
154
- async load(t, s = {}) {
173
+ async load(t, e = {}) {
155
174
  this.currentSrc = t;
156
- const e = s.projection ?? (this.opts.autoDetect !== !1 ? q(t) : null);
157
- e && this.setProjection(e);
158
- const { url: a, format: c } = Y(t, this.useProxy ? this.proxyConfig : void 0), d = this.opts.crossOrigin === void 0 ? "anonymous" : this.opts.crossOrigin;
175
+ const s = e.projection ?? (this.opts.autoDetect !== !1 ? Q(t) : null);
176
+ s && this.applyProjectionGeometry(s), this.displayMode !== "off" && (this.displayMode = this.view.projection);
177
+ const { url: p, format: c } = X(t, this.useProxy ? this.proxyConfig : void 0), u = this.opts.crossOrigin === void 0 ? "anonymous" : this.opts.crossOrigin;
159
178
  this.loading = !0;
160
179
  try {
161
- await this.source.attach(this.video, { url: a, format: c }, { crossOrigin: d }), this.setNativeFallback(!1), await this.video.play().catch(() => {
180
+ await this.source.attach(this.video, { url: p, format: c }, { crossOrigin: u }), this.tainted = !1, this.applyDisplay(), await this.video.play().catch(() => {
162
181
  });
163
- } catch (n) {
164
- if (this.opts.nativeFallback !== !1 && c === "progressive" && d !== null)
182
+ } catch (r) {
183
+ if (this.opts.nativeFallback !== !1 && c === "progressive" && u !== null)
165
184
  try {
166
- this.setNativeFallback(!0), await this.source.attach(this.video, { url: a, format: c }, { crossOrigin: null }), await this.video.play().catch(() => {
185
+ this.tainted = !0, this.setNativeFallback(!0), await this.source.attach(this.video, { url: p, format: c }, { crossOrigin: null }), await this.video.play().catch(() => {
167
186
  }), this.emit("fallback");
168
187
  return;
169
- } catch (h) {
170
- throw this.setNativeFallback(!1), this.emit("error", h), h;
188
+ } catch (v) {
189
+ throw this.tainted = !1, this.setNativeFallback(!1), this.emit("error", v), v;
171
190
  }
172
- throw this.setNativeFallback(!1), this.emit("error", n), n;
191
+ throw this.setNativeFallback(!1), this.emit("error", r), r;
173
192
  } finally {
174
193
  this.loading = !1;
175
194
  }
@@ -193,8 +212,19 @@ class H {
193
212
  pause() {
194
213
  this.video.pause();
195
214
  }
215
+ /** Set the display mode: a geometry projection (WebGL 3D) or 'off' for plain 2D `<video>`. */
196
216
  setProjection(t) {
197
- this.scene.setProjection(t), this.look.setEnabled(!this.scene.isFlat()), this.look.reset(), this.view.projection = t, this.persist(), this.emit("projectionchange", t);
217
+ if (t === "off") {
218
+ this.displayMode = "off", this.setNativeFallback(!0), this.emit("projectionchange", t);
219
+ return;
220
+ }
221
+ this.displayMode = t, this.applyProjectionGeometry(t), this.tainted && this.currentSrc ? this.load(this.currentSrc) : this.setNativeFallback(!1), this.emit("projectionchange", t);
222
+ }
223
+ applyProjectionGeometry(t) {
224
+ this.scene.setProjection(t), this.look.setEnabled(!this.scene.isFlat()), this.look.reset(), this.view.projection = t, this.persist();
225
+ }
226
+ applyDisplay() {
227
+ this.setNativeFallback(this.displayMode === "off");
198
228
  }
199
229
  setSwapEyes(t) {
200
230
  this.scene.setSwapEyes(t), this.view.swapEyes = t, this.persist();
@@ -210,35 +240,35 @@ class H {
210
240
  this.proxyConfig = t.url ? { url: t.url, apiPassword: t.apiPassword || void 0 } : void 0, this.useProxy = t.enabled, this.currentSrc && this.load(this.currentSrc);
211
241
  }
212
242
  async enterVR() {
213
- var s;
243
+ var e;
214
244
  const t = this.scene.vrButton;
215
- (s = t == null ? void 0 : t.click) == null || s.call(t);
245
+ (e = t == null ? void 0 : t.click) == null || e.call(t);
216
246
  }
217
247
  get three() {
218
248
  return { renderer: this.scene.renderer, scene: this.scene.scene, camera: this.scene.camera };
219
249
  }
220
- on(t, s) {
221
- let e = this.listeners.get(t);
222
- return e || (e = /* @__PURE__ */ new Set(), this.listeners.set(t, e)), e.add(s), this;
250
+ on(t, e) {
251
+ let s = this.listeners.get(t);
252
+ return s || (s = /* @__PURE__ */ new Set(), this.listeners.set(t, s)), s.add(e), this;
223
253
  }
224
- off(t, s) {
225
- var e;
226
- return (e = this.listeners.get(t)) == null || e.delete(s), this;
254
+ off(t, e) {
255
+ var s;
256
+ return (s = this.listeners.get(t)) == null || s.delete(e), this;
227
257
  }
228
- emit(t, s) {
229
- var e;
230
- (e = this.listeners.get(t)) == null || e.forEach((a) => a(s));
258
+ emit(t, e) {
259
+ var s;
260
+ (s = this.listeners.get(t)) == null || s.forEach((p) => p(e));
231
261
  }
232
262
  persist() {
233
263
  if (this.opts.persistSettings)
234
264
  try {
235
- localStorage.setItem(M, JSON.stringify(this.view));
265
+ localStorage.setItem(V, JSON.stringify(this.view));
236
266
  } catch {
237
267
  }
238
268
  }
239
269
  loadSettings() {
240
270
  try {
241
- const t = localStorage.getItem(M);
271
+ const t = localStorage.getItem(V);
242
272
  return t ? JSON.parse(t) : null;
243
273
  } catch {
244
274
  return null;
@@ -249,12 +279,12 @@ class H {
249
279
  (t = this.ui) == null || t.dispose(), this.look.dispose(), this.source.dispose(), this.scene.dispose(), this.video.remove(), this.canvas.remove(), this.wrap.remove(), this.listeners.clear();
250
280
  }
251
281
  }
252
- class K extends HTMLElement {
282
+ class et extends HTMLElement {
253
283
  static get observedAttributes() {
254
284
  return ["src", "projection"];
255
285
  }
256
286
  connectedCallback() {
257
- this.player || (this.style.display || (this.style.display = "block"), this.player = new H(this, {
287
+ this.player || (this.style.display || (this.style.display = "block"), this.player = new tt(this, {
258
288
  src: this.getAttribute("src") ?? void 0,
259
289
  projection: this.getAttribute("projection") ?? void 0,
260
290
  controls: this.hasAttribute("controls"),
@@ -265,39 +295,39 @@ class K extends HTMLElement {
265
295
  proxy: this.getAttribute("proxy-url") ? { url: this.getAttribute("proxy-url"), apiPassword: this.getAttribute("proxy-password") ?? void 0 } : void 0
266
296
  }));
267
297
  }
268
- attributeChangedCallback(t, s, e) {
269
- !this.player || e == null || (t === "src" && this.player.load(e), t === "projection" && this.player.setProjection(e));
298
+ attributeChangedCallback(t, e, s) {
299
+ !this.player || s == null || (t === "src" && this.player.load(s), t === "projection" && this.player.setProjection(s));
270
300
  }
271
301
  disconnectedCallback() {
272
302
  var t;
273
303
  (t = this.player) == null || t.dispose(), this.player = void 0;
274
304
  }
275
305
  numAttr(t) {
276
- const s = this.getAttribute(t);
277
- return s == null ? void 0 : Number(s);
306
+ const e = this.getAttribute(t);
307
+ return e == null ? void 0 : Number(e);
278
308
  }
279
309
  /** The underlying Player instance (for programmatic control). */
280
310
  get api() {
281
311
  return this.player;
282
312
  }
283
313
  }
284
- function G(l = "three-video") {
285
- typeof customElements < "u" && !customElements.get(l) && customElements.define(l, K);
314
+ function st(l = "three-video") {
315
+ typeof customElements < "u" && !customElements.get(l) && customElements.define(l, et);
286
316
  }
287
- G();
317
+ st();
288
318
  export {
289
- W as LookControls,
290
- tt as MODES,
291
- V as PROJECTIONS,
292
- H as Player,
293
- J as StereoScene,
294
- K as ThreeVideoElement,
295
- D as VideoSource,
296
- Y as buildProxyUrl,
297
- et as clampAngles,
298
- st as detectFormat,
299
- q as detectProjection,
300
- it as isFlatMode,
301
- G as registerWebComponent
319
+ K as LookControls,
320
+ rt as MODES,
321
+ Y as PROJECTIONS,
322
+ tt as Player,
323
+ H as StereoScene,
324
+ et as ThreeVideoElement,
325
+ _ as VideoSource,
326
+ X as buildProxyUrl,
327
+ at as clampAngles,
328
+ pt as detectFormat,
329
+ Q as detectProjection,
330
+ lt as isFlatMode,
331
+ st as registerWebComponent
302
332
  };
303
333
  //# sourceMappingURL=three-vr-player.js.map