three-vr-player 0.2.0 → 0.4.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
@@ -87,6 +87,7 @@ new Player(container, {
87
87
  ```ts
88
88
  player.load(src, { projection? }); player.play(); player.pause();
89
89
  player.setProjection(p); player.setSwapEyes(b); player.setFov(deg); player.setSupersampling(x);
90
+ player.setProxy({ url, apiPassword?, enabled }); // also exposed as a toggle in the ⚙ settings
90
91
  player.enterVR(); player.dispose();
91
92
  player.video; // the underlying <video>
92
93
  player.three; // { renderer, scene, camera }
package/dist/Player.d.ts CHANGED
@@ -18,6 +18,9 @@ export declare class Player {
18
18
  private readyEmitted;
19
19
  private native;
20
20
  private loading;
21
+ private proxyConfig?;
22
+ private useProxy;
23
+ private currentSrc?;
21
24
  constructor(container: HTMLElement, options?: PlayerOptions);
22
25
  private vrSupported;
23
26
  load(src: string, o?: {
@@ -31,6 +34,12 @@ export declare class Player {
31
34
  setSwapEyes(v: boolean): void;
32
35
  setFov(deg: number): void;
33
36
  setSupersampling(x: number): void;
37
+ /** Update the CORS proxy config / toggle it, and reload the current source if any. */
38
+ setProxy(p: {
39
+ url: string;
40
+ apiPassword?: string;
41
+ enabled: boolean;
42
+ }): void;
34
43
  enterVR(): Promise<void>;
35
44
  get three(): {
36
45
  renderer: import('three').WebGLRenderer;
@@ -1,86 +1,109 @@
1
- import { P as I, V as R, S as $, L as V, a as D, b as U } from "./VideoSource-CJGIX8gL.js";
2
- import { M as G, c as Q, d as X, i as Z } from "./VideoSource-CJGIX8gL.js";
3
- function z(l) {
1
+ import { P as Y, V as _, S as H, L as K, a as G, b as Q } from "./VideoSource-CJGIX8gL.js";
2
+ import { M as nt, c as rt, d as at, i as pt } from "./VideoSource-CJGIX8gL.js";
3
+ function R(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 n(l, t = {}, s = []) {
9
- const e = document.createElement(l), { class: a, ...o } = t;
10
- a && (e.className = a), Object.assign(e, o);
11
- for (const c of s) e.append(c);
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 J {
15
- constructor(t, s) {
14
+ class X {
15
+ constructor(t, e) {
16
16
  this.nodes = [], this.disposers = [];
17
- const e = s.video, a = n("button", { class: "tvp-btn tvp-play", title: "Play/Pause", textContent: "▶" }), o = n("input", { class: "tvp-seek", type: "range", min: "0", max: "1000", value: "0" }), c = n("span", { class: "tvp-time", textContent: "0:00 / 0:00" }), h = n("button", { class: "tvp-btn tvp-mute", title: "Volume", textContent: "🔊" }), v = n("input", { class: "tvp-volume", type: "range", min: "0", max: "1", step: "0.01", value: "1" }), m = n("div", { class: "tvp-volpopup", hidden: !0 }, [v]), L = n("span", { class: "tvp-volwrap" }, [h, m]), k = n("span", { class: "tvp-vrslot" }), b = n("button", { class: "tvp-btn tvp-projbtn", title: "Projection", textContent: "🌐" }), d = n("div", { class: "tvp-projmenu", hidden: !0 });
18
- for (const i of I) {
19
- const r = n("button", { textContent: i.label });
20
- r.dataset.mode = i.value, d.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]), D = o("span", { class: "tvp-volwrap" }, [r, f]), N = o("span", { class: "tvp-vrslot" }), b = o("button", { class: "tvp-btn tvp-projbtn", title: "Projection", textContent: "🌐" }), h = 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, h.append(n);
21
21
  }
22
- const T = n("span", { class: "tvp-projwrap" }, [b, d]), S = n("button", { class: "tvp-btn tvp-settingsbtn", title: "Settings", textContent: "⚙" }), E = n("button", { class: "tvp-btn tvp-fullscreen", title: "Fullscreen", textContent: "⛶" }), M = n("footer", { class: "tvp-controls" }, [a, o, c, L, k, T, S, E]), x = n("input", { type: "checkbox", checked: s.initial.swapEyes }), f = n("input", { type: "range", min: "30", max: "100", step: "1", value: String(s.initial.fov) }), y = n("span", { textContent: String(s.initial.fov) }), w = n("input", { type: "range", min: "1", max: "2", step: "0.25", value: String(s.initial.supersampling) }), j = n("span", { textContent: String(s.initial.supersampling) }), C = n("section", { class: "tvp-settings" }, [
23
- n("label", { class: "row" }, [x, "Swap eyes (if depth looks wrong)"]),
24
- n("label", {}, [N("Field of view (zoom): ", y, "°"), f]),
25
- n("label", {}, [N("Supersampling: ", j, "× (sharpness)"), w])
26
- ]), B = n("div", { class: "tvp-toast" });
27
- this.nodes.push(M, C, B);
22
+ const J = o("span", { class: "tvp-projwrap" }, [b, h]), 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, D, N, 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) }), E = o("span", { textContent: String(e.initial.fov) }), j = 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 }), T = o("input", { type: "password", value: e.proxy.apiPassword, placeholder: "API password" }), g = o("section", { class: "tvp-settings" }, [
23
+ o("label", { class: "row" }, [P, "Swap eyes (if depth looks wrong)"]),
24
+ o("label", {}, [$("Field of view (zoom): ", E, "°"), x]),
25
+ o("label", {}, [$("Supersampling: ", A, "× (sharpness)"), j]),
26
+ o("hr", { class: "tvp-sep" }),
27
+ o("label", { class: "row" }, [C, "Use CORS proxy"]),
28
+ o("label", {}, ["Proxy URL", F]),
29
+ o("label", {}, ["API password", T])
30
+ ]), W = o("div", { class: "tvp-toast" });
31
+ this.nodes.push(S, g, W);
28
32
  for (const i of this.nodes) t.append(i);
29
- const p = (i, r, u, g) => {
30
- i.addEventListener(r, u, g), this.disposers.push(() => i.removeEventListener(r, u, g));
31
- }, P = (i) => o.style.setProperty("--seek", `${i}%`);
32
- p(a, "click", () => {
33
- e.paused ? e.play() : e.pause();
34
- }), p(e, "play", () => {
35
- a.textContent = "⏸";
36
- }), p(e, "pause", () => {
37
- a.textContent = "";
38
- }), p(e, "timeupdate", () => {
39
- if (e.duration) {
40
- const i = e.currentTime / e.duration * 100;
41
- o.value = String(i * 10), P(i), c.textContent = `${z(e.currentTime)} / ${z(e.duration)}`;
33
+ const a = (i, n, d, m) => {
34
+ i.addEventListener(n, d, m), this.disposers.push(() => i.removeEventListener(n, d, m));
35
+ }, I = (i) => c.style.setProperty("--seek", `${i}%`), y = () => {
36
+ f.hidden = !0, h.hidden = !0, g.classList.remove("open");
37
+ };
38
+ a(p, "click", () => {
39
+ s.paused ? s.play() : s.pause();
40
+ }), a(s, "play", () => {
41
+ p.textContent = "";
42
+ }), a(s, "pause", () => {
43
+ p.textContent = "▶";
44
+ }), a(s, "timeupdate", () => {
45
+ if (s.duration) {
46
+ const i = s.currentTime / s.duration * 100;
47
+ c.value = String(i * 10), I(i), u.textContent = `${R(s.currentTime)} / ${R(s.duration)}`;
42
48
  }
43
- }), p(o, "input", () => {
44
- P(Number(o.value) / 10), e.duration && (e.currentTime = Number(o.value) / 1e3 * e.duration);
45
- }), p(h, "click", (i) => {
46
- i.stopPropagation(), m.hidden = !m.hidden;
47
- }), p(v, "input", () => {
48
- e.volume = Number(v.value), e.muted = e.volume === 0, h.textContent = e.muted ? "🔇" : "🔊";
49
+ }), a(c, "input", () => {
50
+ I(Number(c.value) / 10), s.duration && (s.currentTime = Number(c.value) / 1e3 * s.duration);
51
+ }), a(r, "click", (i) => {
52
+ i.stopPropagation();
53
+ const n = f.hidden;
54
+ y(), f.hidden = !n;
55
+ }), a(v, "input", () => {
56
+ s.volume = Number(v.value), s.muted = s.volume === 0, r.textContent = s.muted ? "🔇" : "🔊";
49
57
  });
50
- const F = () => {
51
- const i = s.getProjection();
52
- let r = "Projection";
53
- d.querySelectorAll("button").forEach((u) => {
54
- const g = u.dataset.mode === i;
55
- u.classList.toggle("active", g), g && (r = `Projection: ${u.textContent}`);
56
- }), b.title = r;
58
+ const M = () => {
59
+ const i = e.getProjection();
60
+ let n = "Projection";
61
+ h.querySelectorAll("button").forEach((d) => {
62
+ const m = d.dataset.mode === i;
63
+ d.classList.toggle("active", m), m && (n = `Projection: ${d.textContent}`);
64
+ }), b.title = n;
57
65
  };
58
- p(b, "click", (i) => {
59
- i.stopPropagation(), d.hidden = !d.hidden;
60
- }), p(d, "click", (i) => {
61
- const r = i.target.closest("button[data-mode]");
62
- r && (s.setProjection(r.dataset.mode), F(), d.hidden = !0);
63
- }), F(), p(S, "click", (i) => {
64
- i.stopPropagation(), C.classList.toggle("open");
65
- }), p(x, "change", () => s.setSwapEyes(x.checked)), p(f, "input", () => {
66
- const i = Number(f.value);
67
- y.textContent = String(i), s.setFov(i);
68
- }), p(w, "input", () => {
69
- const i = Number(w.value);
70
- j.textContent = String(i), s.setSupersampling(i);
71
- }), p(s.surface, "wheel", (i) => {
66
+ a(b, "click", (i) => {
67
+ i.stopPropagation();
68
+ const n = h.hidden;
69
+ y(), h.hidden = !n;
70
+ }), a(h, "click", (i) => {
71
+ const n = i.target.closest("button[data-mode]");
72
+ n && (e.setProjection(n.dataset.mode), M(), h.hidden = !0);
73
+ }), M(), a(k, "click", (i) => {
74
+ i.stopPropagation();
75
+ const n = !g.classList.contains("open");
76
+ y(), g.classList.toggle("open", n);
77
+ }), a(P, "change", () => e.setSwapEyes(P.checked)), a(x, "input", () => {
78
+ const i = Number(x.value);
79
+ E.textContent = String(i), e.setFov(i);
80
+ }), a(j, "input", () => {
81
+ const i = Number(j.value);
82
+ A.textContent = String(i), e.setSupersampling(i);
83
+ });
84
+ const L = () => e.setProxy({ url: F.value.trim(), apiPassword: T.value, enabled: C.checked });
85
+ a(C, "change", L), a(F, "change", L), a(T, "change", L), a(e.surface, "wheel", (i) => {
72
86
  i.preventDefault();
73
- const r = Math.max(30, Math.min(100, Number(f.value) + Math.sign(i.deltaY) * 3));
74
- f.value = String(r), y.textContent = String(r), s.setFov(r);
75
- }, { passive: !1 }), p(E, "click", () => {
76
- var r, u;
77
- const i = s.fullscreenTarget;
78
- document.fullscreenElement ? (u = document.exitFullscreen) == null || u.call(document) : (r = i.requestFullscreen) == null || r.call(i);
79
- }), p(document, "click", (i) => {
80
- const r = i.composedPath();
81
- !m.hidden && !r.includes(m) && !r.includes(h) && (m.hidden = !0), !d.hidden && !r.includes(d) && !r.includes(b) && (d.hidden = !0);
82
- }), s.vrSupported().then((i) => {
83
- i && (Object.assign(s.vrButton.style, { position: "static", left: "auto", bottom: "auto", transform: "none", margin: "0" }), k.append(s.vrButton));
87
+ const n = Math.max(30, Math.min(100, Number(x.value) + Math.sign(i.deltaY) * 3));
88
+ x.value = String(n), E.textContent = String(n), e.setFov(n);
89
+ }, { passive: !1 }), a(O, "click", () => {
90
+ var n, d;
91
+ const i = e.fullscreenTarget;
92
+ document.fullscreenElement ? (d = document.exitFullscreen) == null || d.call(document) : (n = i.requestFullscreen) == null || n.call(i);
93
+ }), a(document, "click", (i) => {
94
+ const n = i.composedPath(), d = (m) => n.includes(m);
95
+ [f, r, h, b, g, k].some(d) || y();
96
+ });
97
+ let w = 0;
98
+ const q = () => !f.hidden || !h.hidden || g.classList.contains("open"), B = () => {
99
+ q() || S.classList.add("tvp-hidden");
100
+ }, z = () => {
101
+ S.classList.remove("tvp-hidden"), clearTimeout(w), w = window.setTimeout(B, 5e3);
102
+ };
103
+ a(e.fullscreenTarget, "pointermove", z), a(e.fullscreenTarget, "pointerdown", z), a(e.fullscreenTarget, "pointerleave", () => {
104
+ clearTimeout(w), B();
105
+ }), this.disposers.push(() => clearTimeout(w)), z(), e.vrSupported().then((i) => {
106
+ i && (Object.assign(e.vrButton.style, { position: "static", left: "auto", bottom: "auto", transform: "none", margin: "0" }), N.append(e.vrButton));
84
107
  });
85
108
  }
86
109
  dispose() {
@@ -88,78 +111,82 @@ class J {
88
111
  for (const t of this.nodes) t.remove();
89
112
  }
90
113
  }
91
- function N(l, t, s) {
92
- const e = document.createDocumentFragment();
93
- return e.append(l, t, s), e;
114
+ function $(l, t, e) {
115
+ const s = document.createDocumentFragment();
116
+ return s.append(l, t, e), s;
94
117
  }
95
- const O = ":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-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}", A = "three-vr-player:settings";
96
- class W {
97
- constructor(t, s = {}) {
98
- this.source = new R(), this.listeners = /* @__PURE__ */ new Map(), this.readyEmitted = !1, this.native = !1, this.loading = !1, this.opts = s;
99
- const e = s.persistSettings ? this.loadSettings() : null;
118
+ 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";
119
+ class Z {
120
+ constructor(t, e = {}) {
121
+ var c, u;
122
+ this.source = new _(), this.listeners = /* @__PURE__ */ new Map(), this.readyEmitted = !1, this.native = !1, this.loading = !1, this.useProxy = !1, this.opts = e, this.proxyConfig = e.proxy, this.useProxy = !!e.proxy;
123
+ const s = e.persistSettings ? this.loadSettings() : null;
100
124
  this.view = {
101
- projection: s.projection ?? (e == null ? void 0 : e.projection) ?? "180-sbs",
102
- swapEyes: s.swapEyes ?? (e == null ? void 0 : e.swapEyes) ?? !1,
103
- fov: s.fov ?? (e == null ? void 0 : e.fov) ?? 70,
104
- supersampling: s.supersampling ?? (e == null ? void 0 : e.supersampling) ?? 1.5
125
+ projection: e.projection ?? (s == null ? void 0 : s.projection) ?? "180-sbs",
126
+ swapEyes: e.swapEyes ?? (s == null ? void 0 : s.swapEyes) ?? !1,
127
+ fov: e.fov ?? (s == null ? void 0 : s.fov) ?? 70,
128
+ supersampling: e.supersampling ?? (s == null ? void 0 : s.supersampling) ?? 1.5
105
129
  }, 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);
106
- let a;
107
- if (s.shadowDom !== !1) {
108
- const o = this.wrap.attachShadow({ mode: "open" }), c = document.createElement("style");
109
- c.textContent = O, o.appendChild(c), a = o;
130
+ let p;
131
+ if (e.shadowDom !== !1) {
132
+ const r = this.wrap.attachShadow({ mode: "open" }), v = document.createElement("style");
133
+ v.textContent = U, r.appendChild(v), p = r;
110
134
  } else {
111
135
  if (!document.getElementById("tvp-styles")) {
112
- const o = document.createElement("style");
113
- o.id = "tvp-styles", o.textContent = O, document.head.appendChild(o);
136
+ const r = document.createElement("style");
137
+ r.id = "tvp-styles", r.textContent = U, document.head.appendChild(r);
114
138
  }
115
- a = this.wrap;
139
+ p = this.wrap;
116
140
  }
117
- 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 $({
141
+ 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({
118
142
  canvas: this.canvas,
119
143
  video: this.video,
120
144
  projection: this.view.projection,
121
145
  swapEyes: this.view.swapEyes,
122
146
  fov: this.view.fov,
123
147
  supersampling: this.view.supersampling
124
- }), this.look = new V(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 J(a, {
148
+ }), 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 X(p, {
125
149
  video: this.video,
126
150
  surface: this.canvas,
127
151
  fullscreenTarget: this.wrap,
128
152
  vrButton: this.scene.vrButton,
129
153
  vrSupported: () => this.vrSupported(),
130
154
  getProjection: () => this.scene.getProjection(),
131
- setProjection: (o) => this.setProjection(o),
132
- setSwapEyes: (o) => this.setSwapEyes(o),
133
- setFov: (o) => this.setFov(o),
134
- setSupersampling: (o) => this.setSupersampling(o),
135
- initial: { swapEyes: this.view.swapEyes, fov: this.view.fov, supersampling: this.view.supersampling }
155
+ setProjection: (r) => this.setProjection(r),
156
+ setSwapEyes: (r) => this.setSwapEyes(r),
157
+ setFov: (r) => this.setFov(r),
158
+ setSupersampling: (r) => this.setSupersampling(r),
159
+ initial: { swapEyes: this.view.swapEyes, fov: this.view.fov, supersampling: this.view.supersampling },
160
+ proxy: { url: ((c = this.proxyConfig) == null ? void 0 : c.url) ?? "", apiPassword: ((u = this.proxyConfig) == null ? void 0 : u.apiPassword) ?? "", enabled: this.useProxy },
161
+ setProxy: (r) => this.setProxy(r)
136
162
  })), 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", () => {
137
163
  this.loading || this.emit("error", this.video.error);
138
164
  }), this.video.addEventListener("loadedmetadata", () => {
139
165
  this.readyEmitted || (this.readyEmitted = !0, this.emit("ready"));
140
- }), 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 });
166
+ }), 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 });
141
167
  }
142
168
  vrSupported() {
143
169
  return this.opts.vrButton === !1 || !navigator.xr ? Promise.resolve(!1) : navigator.xr.isSessionSupported("immersive-vr").catch(() => !1);
144
170
  }
145
- async load(t, s = {}) {
146
- const e = s.projection ?? (this.opts.autoDetect !== !1 ? D(t) : null);
147
- e && this.setProjection(e);
148
- const { url: a, format: o } = U(t, this.opts.proxy), c = this.opts.crossOrigin === void 0 ? "anonymous" : this.opts.crossOrigin;
171
+ async load(t, e = {}) {
172
+ this.currentSrc = t;
173
+ const s = e.projection ?? (this.opts.autoDetect !== !1 ? G(t) : null);
174
+ s && this.setProjection(s);
175
+ const { url: p, format: c } = Q(t, this.useProxy ? this.proxyConfig : void 0), u = this.opts.crossOrigin === void 0 ? "anonymous" : this.opts.crossOrigin;
149
176
  this.loading = !0;
150
177
  try {
151
- await this.source.attach(this.video, { url: a, format: o }, { crossOrigin: c }), this.setNativeFallback(!1), await this.video.play().catch(() => {
178
+ await this.source.attach(this.video, { url: p, format: c }, { crossOrigin: u }), this.setNativeFallback(!1), await this.video.play().catch(() => {
152
179
  });
153
- } catch (h) {
154
- if (this.opts.nativeFallback !== !1 && o === "progressive" && c !== null)
180
+ } catch (r) {
181
+ if (this.opts.nativeFallback !== !1 && c === "progressive" && u !== null)
155
182
  try {
156
- this.setNativeFallback(!0), await this.source.attach(this.video, { url: a, format: o }, { crossOrigin: null }), await this.video.play().catch(() => {
183
+ this.setNativeFallback(!0), await this.source.attach(this.video, { url: p, format: c }, { crossOrigin: null }), await this.video.play().catch(() => {
157
184
  }), this.emit("fallback");
158
185
  return;
159
186
  } catch (v) {
160
187
  throw this.setNativeFallback(!1), this.emit("error", v), v;
161
188
  }
162
- throw this.setNativeFallback(!1), this.emit("error", h), h;
189
+ throw this.setNativeFallback(!1), this.emit("error", r), r;
163
190
  } finally {
164
191
  this.loading = !1;
165
192
  }
@@ -195,36 +222,40 @@ class W {
195
222
  setSupersampling(t) {
196
223
  this.scene.setSupersampling(t), this.view.supersampling = t, this.persist();
197
224
  }
225
+ /** Update the CORS proxy config / toggle it, and reload the current source if any. */
226
+ setProxy(t) {
227
+ this.proxyConfig = t.url ? { url: t.url, apiPassword: t.apiPassword || void 0 } : void 0, this.useProxy = t.enabled, this.currentSrc && this.load(this.currentSrc);
228
+ }
198
229
  async enterVR() {
199
- var s;
230
+ var e;
200
231
  const t = this.scene.vrButton;
201
- (s = t == null ? void 0 : t.click) == null || s.call(t);
232
+ (e = t == null ? void 0 : t.click) == null || e.call(t);
202
233
  }
203
234
  get three() {
204
235
  return { renderer: this.scene.renderer, scene: this.scene.scene, camera: this.scene.camera };
205
236
  }
206
- on(t, s) {
207
- let e = this.listeners.get(t);
208
- return e || (e = /* @__PURE__ */ new Set(), this.listeners.set(t, e)), e.add(s), this;
237
+ on(t, e) {
238
+ let s = this.listeners.get(t);
239
+ return s || (s = /* @__PURE__ */ new Set(), this.listeners.set(t, s)), s.add(e), this;
209
240
  }
210
- off(t, s) {
211
- var e;
212
- return (e = this.listeners.get(t)) == null || e.delete(s), this;
241
+ off(t, e) {
242
+ var s;
243
+ return (s = this.listeners.get(t)) == null || s.delete(e), this;
213
244
  }
214
- emit(t, s) {
215
- var e;
216
- (e = this.listeners.get(t)) == null || e.forEach((a) => a(s));
245
+ emit(t, e) {
246
+ var s;
247
+ (s = this.listeners.get(t)) == null || s.forEach((p) => p(e));
217
248
  }
218
249
  persist() {
219
250
  if (this.opts.persistSettings)
220
251
  try {
221
- localStorage.setItem(A, JSON.stringify(this.view));
252
+ localStorage.setItem(V, JSON.stringify(this.view));
222
253
  } catch {
223
254
  }
224
255
  }
225
256
  loadSettings() {
226
257
  try {
227
- const t = localStorage.getItem(A);
258
+ const t = localStorage.getItem(V);
228
259
  return t ? JSON.parse(t) : null;
229
260
  } catch {
230
261
  return null;
@@ -235,12 +266,12 @@ class W {
235
266
  (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();
236
267
  }
237
268
  }
238
- class q extends HTMLElement {
269
+ class tt extends HTMLElement {
239
270
  static get observedAttributes() {
240
271
  return ["src", "projection"];
241
272
  }
242
273
  connectedCallback() {
243
- this.player || (this.style.display || (this.style.display = "block"), this.player = new W(this, {
274
+ this.player || (this.style.display || (this.style.display = "block"), this.player = new Z(this, {
244
275
  src: this.getAttribute("src") ?? void 0,
245
276
  projection: this.getAttribute("projection") ?? void 0,
246
277
  controls: this.hasAttribute("controls"),
@@ -251,39 +282,39 @@ class q extends HTMLElement {
251
282
  proxy: this.getAttribute("proxy-url") ? { url: this.getAttribute("proxy-url"), apiPassword: this.getAttribute("proxy-password") ?? void 0 } : void 0
252
283
  }));
253
284
  }
254
- attributeChangedCallback(t, s, e) {
255
- !this.player || e == null || (t === "src" && this.player.load(e), t === "projection" && this.player.setProjection(e));
285
+ attributeChangedCallback(t, e, s) {
286
+ !this.player || s == null || (t === "src" && this.player.load(s), t === "projection" && this.player.setProjection(s));
256
287
  }
257
288
  disconnectedCallback() {
258
289
  var t;
259
290
  (t = this.player) == null || t.dispose(), this.player = void 0;
260
291
  }
261
292
  numAttr(t) {
262
- const s = this.getAttribute(t);
263
- return s == null ? void 0 : Number(s);
293
+ const e = this.getAttribute(t);
294
+ return e == null ? void 0 : Number(e);
264
295
  }
265
296
  /** The underlying Player instance (for programmatic control). */
266
297
  get api() {
267
298
  return this.player;
268
299
  }
269
300
  }
270
- function Y(l = "three-video") {
271
- typeof customElements < "u" && !customElements.get(l) && customElements.define(l, q);
301
+ function et(l = "three-video") {
302
+ typeof customElements < "u" && !customElements.get(l) && customElements.define(l, tt);
272
303
  }
273
- Y();
304
+ et();
274
305
  export {
275
- V as LookControls,
276
- G as MODES,
277
- I as PROJECTIONS,
278
- W as Player,
279
- $ as StereoScene,
280
- q as ThreeVideoElement,
281
- R as VideoSource,
282
- U as buildProxyUrl,
283
- Q as clampAngles,
284
- X as detectFormat,
285
- D as detectProjection,
286
- Z as isFlatMode,
287
- Y as registerWebComponent
306
+ K as LookControls,
307
+ nt as MODES,
308
+ Y as PROJECTIONS,
309
+ Z as Player,
310
+ H as StereoScene,
311
+ tt as ThreeVideoElement,
312
+ _ as VideoSource,
313
+ Q as buildProxyUrl,
314
+ rt as clampAngles,
315
+ at as detectFormat,
316
+ G as detectProjection,
317
+ pt as isFlatMode,
318
+ et as registerWebComponent
288
319
  };
289
320
  //# sourceMappingURL=three-vr-player.js.map