three-vr-player 0.4.0 → 0.6.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.
@@ -1,109 +1,134 @@
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
- if (!Number.isFinite(l) || l < 0) return "0:00";
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}`;
1
+ import { P as X, V as Z, S as tt, L as et, a as st, b as it } from "./VideoSource-CMM_vlRP.js";
2
+ import { M as dt, c as ht, d as ut, i as vt } from "./VideoSource-CMM_vlRP.js";
3
+ function J(c) {
4
+ if (!Number.isFinite(c) || c < 0) return "0:00";
5
+ const t = Math.floor(c % 60), e = Math.floor(c / 60 % 60), s = Math.floor(c / 3600), a = String(t).padStart(2, "0");
6
+ return s > 0 ? `${s}:${String(e).padStart(2, "0")}:${a}` : `${e}:${a}`;
7
7
  }
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);
8
+ function o(c, t = {}, e = []) {
9
+ const s = document.createElement(c), { class: a, ...d } = t;
10
+ a && (s.className = a), Object.assign(s, d);
11
11
  for (const u of e) s.append(u);
12
12
  return s;
13
13
  }
14
- class X {
14
+ class ot {
15
15
  constructor(t, e) {
16
16
  this.nodes = [], this.disposers = [];
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);
17
+ const s = e.video, a = o("button", { class: "tvp-btn tvp-play", title: "Play/Pause", textContent: "▶" }), d = o("input", { class: "tvp-seek", type: "range", min: "0", max: "1000", value: "0" }), u = o("span", { class: "tvp-time", textContent: "0:00 / 0:00" }), n = o("button", { class: "tvp-btn tvp-mute", title: "Volume", textContent: "🔊" }), m = o("input", { class: "tvp-volume", type: "range", min: "0", max: "1", step: "0.01", value: "1" }), g = o("div", { class: "tvp-volpopup", hidden: !0 }, [m]), _ = o("span", { class: "tvp-volwrap" }, [n, g]), y = o("button", { class: "tvp-btn tvp-vrbtn", title: "Enter VR", textContent: "🥽", hidden: !0 }), k = o("button", { class: "tvp-btn tvp-projbtn", title: "Projection", textContent: "🌐" }), h = o("div", { class: "tvp-projmenu", hidden: !0 });
18
+ for (const i of X) {
19
+ const r = o("button", { textContent: i.label });
20
+ r.dataset.mode = i.value, h.append(r);
21
21
  }
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]),
22
+ const O = o("button", { textContent: "Off (native player)" });
23
+ O.dataset.mode = "off", h.append(o("hr", { class: "tvp-sep" }), O);
24
+ const H = o("span", { class: "tvp-projwrap" }, [k, h]), j = o("button", { class: "tvp-btn tvp-settingsbtn", title: "Settings", textContent: "⚙" }), A = o("button", { class: "tvp-btn tvp-fullscreen", title: "Fullscreen", textContent: "" }), C = o("footer", { class: "tvp-controls" }, [a, d, u, _, y, H, j, A]), F = o("input", { type: "checkbox", checked: e.initial.swapEyes }), w = o("input", { type: "range", min: "30", max: "100", step: "1", value: String(e.initial.fov) }), T = o("span", { textContent: String(e.initial.fov) }), R = o("input", { type: "range", min: "1", max: "2", step: "0.25", value: String(e.initial.supersampling) }), I = o("span", { textContent: String(e.initial.supersampling) }), L = o("input", { type: "checkbox", checked: e.proxy.enabled }), V = 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" }), b = o("section", { class: "tvp-settings" }, [
25
+ o("label", { class: "row" }, [F, "Swap eyes (if depth looks wrong)"]),
26
+ o("label", {}, [W("Field of view (zoom): ", T, "°"), w]),
27
+ o("label", {}, [W("Supersampling: ", I, "× (sharpness)"), R]),
26
28
  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);
29
+ o("label", { class: "row" }, [L, "Use CORS proxy"]),
30
+ o("label", {}, ["Proxy URL", V]),
31
+ o("label", {}, ["API password", N])
32
+ ]), S = o("div", { class: "tvp-toast" });
33
+ this.nodes.push(C, b, S);
32
34
  for (const i of this.nodes) t.append(i);
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");
35
+ const p = (i, r, l, v) => {
36
+ i.addEventListener(r, l, v), this.disposers.push(() => i.removeEventListener(r, l, v));
37
+ }, $ = (i) => d.style.setProperty("--seek", `${i}%`), E = () => {
38
+ g.hidden = !0, h.hidden = !0, b.classList.remove("open");
37
39
  };
38
- a(p, "click", () => {
40
+ let B = 0;
41
+ const K = (i) => {
42
+ S.textContent = i, S.classList.add("show"), clearTimeout(B), B = window.setTimeout(() => S.classList.remove("show"), 4500);
43
+ }, f = (i, r) => {
44
+ let l = !1, v = -1e9;
45
+ const G = (x) => {
46
+ v = x.timeStamp, r(x);
47
+ };
48
+ p(i, "pointerdown", () => {
49
+ l = !0;
50
+ }), p(i, "pointercancel", () => {
51
+ l = !1;
52
+ }), p(i, "pointerup", (x) => {
53
+ l && (l = !1, G(x));
54
+ }), p(i, "click", (x) => {
55
+ x.timeStamp - v > 700 && G(x);
56
+ });
57
+ };
58
+ f(a, () => {
39
59
  s.paused ? s.play() : s.pause();
40
- }), a(s, "play", () => {
41
- p.textContent = "⏸";
42
- }), a(s, "pause", () => {
43
- p.textContent = "▶";
44
- }), a(s, "timeupdate", () => {
60
+ }), p(s, "play", () => {
61
+ a.textContent = "⏸";
62
+ }), p(s, "pause", () => {
63
+ a.textContent = "▶";
64
+ }), p(s, "timeupdate", () => {
45
65
  if (s.duration) {
46
66
  const i = s.currentTime / s.duration * 100;
47
- c.value = String(i * 10), I(i), u.textContent = `${R(s.currentTime)} / ${R(s.duration)}`;
67
+ d.value = String(i * 10), $(i), u.textContent = `${J(s.currentTime)} / ${J(s.duration)}`;
48
68
  }
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 ? "🔇" : "🔊";
69
+ }), p(d, "input", () => {
70
+ $(Number(d.value) / 10), s.duration && (s.currentTime = Number(d.value) / 1e3 * s.duration);
71
+ }), f(n, () => {
72
+ const i = g.hidden;
73
+ E(), g.hidden = !i;
74
+ }), p(m, "input", () => {
75
+ s.volume = Number(m.value), s.muted = s.volume === 0, n.textContent = s.muted ? "🔇" : "🔊";
57
76
  });
58
- const M = () => {
77
+ const D = () => {
59
78
  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;
79
+ let r = "Projection";
80
+ h.querySelectorAll("button").forEach((l) => {
81
+ const v = l.dataset.mode === i;
82
+ l.classList.toggle("active", v), v && (r = `Projection: ${l.textContent}`);
83
+ }), k.title = r;
65
84
  };
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);
85
+ f(k, () => {
86
+ const i = h.hidden;
87
+ E(), h.hidden = !i;
88
+ }), f(h, (i) => {
89
+ const r = i.target.closest("button[data-mode]");
90
+ r && (e.setProjection(r.dataset.mode), D(), h.hidden = !0);
91
+ }), D(), f(j, () => {
92
+ const i = !b.classList.contains("open");
93
+ E(), b.classList.toggle("open", i);
94
+ }), p(F, "change", () => e.setSwapEyes(F.checked)), p(w, "input", () => {
95
+ const i = Number(w.value);
96
+ T.textContent = String(i), e.setFov(i);
97
+ }), p(R, "input", () => {
98
+ const i = Number(R.value);
99
+ I.textContent = String(i), e.setSupersampling(i);
83
100
  });
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) => {
101
+ const z = () => e.setProxy({ url: V.value.trim(), apiPassword: N.value, enabled: L.checked });
102
+ p(L, "change", z), p(V, "change", z), p(N, "change", z), p(e.surface, "wheel", (i) => {
86
103
  i.preventDefault();
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;
104
+ const r = Math.max(30, Math.min(100, Number(w.value) + Math.sign(i.deltaY) * 3));
105
+ w.value = String(r), T.textContent = String(r), e.setFov(r);
106
+ }, { passive: !1 }), f(A, () => {
107
+ var r, l;
91
108
  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();
109
+ document.fullscreenElement ? (l = document.exitFullscreen) == null || l.call(document) : (r = i.requestFullscreen) == null || r.call(i);
110
+ }), p(document, "pointerdown", (i) => {
111
+ const r = i.composedPath(), l = (v) => r.includes(v);
112
+ [g, n, h, k, b, j].some(l) || E();
96
113
  });
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);
114
+ let P = 0;
115
+ const Q = () => !g.hidden || !h.hidden || b.classList.contains("open"), U = () => {
116
+ Q() || C.classList.add("tvp-hidden");
117
+ }, M = () => {
118
+ C.classList.remove("tvp-hidden"), clearTimeout(P), P = window.setTimeout(U, 5e3);
102
119
  };
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));
120
+ p(e.fullscreenTarget, "pointermove", M), p(e.fullscreenTarget, "pointerdown", M), p(e.fullscreenTarget, "pointerleave", () => {
121
+ clearTimeout(P), U();
122
+ }), this.disposers.push(() => clearTimeout(P)), M(), e.vrSupported().then((i) => {
123
+ y.hidden = !i;
124
+ }), f(y, () => {
125
+ if (e.isPresenting()) {
126
+ e.exitVR();
127
+ return;
128
+ }
129
+ e.enterVR().catch((i) => K(i instanceof Error ? i.message : "Could not enter VR."));
130
+ }), e.onVrChange((i) => {
131
+ y.classList.toggle("active", i), y.title = i ? "Exit VR" : "Enter VR";
107
132
  });
108
133
  }
109
134
  dispose() {
@@ -111,82 +136,89 @@ class X {
111
136
  for (const t of this.nodes) t.remove();
112
137
  }
113
138
  }
114
- function $(l, t, e) {
139
+ function W(c, t, e) {
115
140
  const s = document.createDocumentFragment();
116
- return s.append(l, t, e), s;
141
+ return s.append(c, t, e), s;
117
142
  }
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 {
143
+ const q = ":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-btn.active{color:#4f8cff}.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}", Y = "three-vr-player:settings";
144
+ class nt {
120
145
  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;
146
+ var d, u;
147
+ this.source = new Z(), 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;
123
148
  const s = e.persistSettings ? this.loadSettings() : null;
124
149
  this.view = {
125
150
  projection: e.projection ?? (s == null ? void 0 : s.projection) ?? "180-sbs",
126
151
  swapEyes: e.swapEyes ?? (s == null ? void 0 : s.swapEyes) ?? !1,
127
152
  fov: e.fov ?? (s == null ? void 0 : s.fov) ?? 70,
128
153
  supersampling: e.supersampling ?? (s == null ? void 0 : s.supersampling) ?? 1.5
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);
130
- let p;
154
+ }, 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);
155
+ let a;
131
156
  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;
157
+ const n = this.wrap.attachShadow({ mode: "open" }), m = document.createElement("style");
158
+ m.textContent = q, n.appendChild(m), a = n;
134
159
  } else {
135
160
  if (!document.getElementById("tvp-styles")) {
136
- const r = document.createElement("style");
137
- r.id = "tvp-styles", r.textContent = U, document.head.appendChild(r);
161
+ const n = document.createElement("style");
162
+ n.id = "tvp-styles", n.textContent = q, document.head.appendChild(n);
138
163
  }
139
- p = this.wrap;
164
+ a = this.wrap;
140
165
  }
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({
166
+ 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"), a.append(this.canvas, this.video), this.scene = new tt({
142
167
  canvas: this.canvas,
143
168
  video: this.video,
144
169
  projection: this.view.projection,
145
170
  swapEyes: this.view.swapEyes,
146
171
  fov: this.view.fov,
147
172
  supersampling: this.view.supersampling
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, {
173
+ }), this.look = new et(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 ot(a, {
149
174
  video: this.video,
150
175
  surface: this.canvas,
151
176
  fullscreenTarget: this.wrap,
152
- vrButton: this.scene.vrButton,
153
177
  vrSupported: () => this.vrSupported(),
154
- getProjection: () => this.scene.getProjection(),
155
- setProjection: (r) => this.setProjection(r),
156
- setSwapEyes: (r) => this.setSwapEyes(r),
157
- setFov: (r) => this.setFov(r),
158
- setSupersampling: (r) => this.setSupersampling(r),
178
+ enterVR: () => this.enterVR(),
179
+ exitVR: () => this.scene.exitVR(),
180
+ isPresenting: () => this.scene.renderer.xr.isPresenting,
181
+ onVrChange: (n) => {
182
+ this.on("enterxr", () => n(!0)), this.on("exitxr", () => n(!1));
183
+ },
184
+ getProjection: () => this.displayMode,
185
+ setProjection: (n) => this.setProjection(n),
186
+ setSwapEyes: (n) => this.setSwapEyes(n),
187
+ setFov: (n) => this.setFov(n),
188
+ setSupersampling: (n) => this.setSupersampling(n),
159
189
  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)
190
+ proxy: { url: ((d = this.proxyConfig) == null ? void 0 : d.url) ?? "", apiPassword: ((u = this.proxyConfig) == null ? void 0 : u.apiPassword) ?? "", enabled: this.useProxy },
191
+ setProxy: (n) => this.setProxy(n)
162
192
  })), 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", () => {
163
193
  this.loading || this.emit("error", this.video.error);
164
194
  }), this.video.addEventListener("loadedmetadata", () => {
165
195
  this.readyEmitted || (this.readyEmitted = !0, this.emit("ready"));
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 });
196
+ }), this.scene.renderer.xr.addEventListener("sessionstart", () => this.emit("enterxr")), this.scene.renderer.xr.addEventListener("sessionend", () => this.emit("exitxr")), this.vrSupported().then((n) => {
197
+ n && this.scene.offerVR();
198
+ }), e.src && this.load(e.src, { projection: e.projection });
167
199
  }
168
200
  vrSupported() {
169
201
  return this.opts.vrButton === !1 || !navigator.xr ? Promise.resolve(!1) : navigator.xr.isSessionSupported("immersive-vr").catch(() => !1);
170
202
  }
171
203
  async load(t, e = {}) {
172
204
  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;
205
+ const s = e.projection ?? (this.opts.autoDetect !== !1 ? st(t) : null);
206
+ s && this.applyProjectionGeometry(s), this.displayMode !== "off" && (this.displayMode = this.view.projection);
207
+ const { url: a, format: d } = it(t, this.useProxy ? this.proxyConfig : void 0), u = this.opts.crossOrigin === void 0 ? "anonymous" : this.opts.crossOrigin;
176
208
  this.loading = !0;
177
209
  try {
178
- await this.source.attach(this.video, { url: p, format: c }, { crossOrigin: u }), this.setNativeFallback(!1), await this.video.play().catch(() => {
210
+ await this.source.attach(this.video, { url: a, format: d }, { crossOrigin: u }), this.tainted = !1, this.applyDisplay(), await this.video.play().catch(() => {
179
211
  });
180
- } catch (r) {
181
- if (this.opts.nativeFallback !== !1 && c === "progressive" && u !== null)
212
+ } catch (n) {
213
+ if (this.opts.nativeFallback !== !1 && d === "progressive" && u !== null)
182
214
  try {
183
- this.setNativeFallback(!0), await this.source.attach(this.video, { url: p, format: c }, { crossOrigin: null }), await this.video.play().catch(() => {
215
+ this.tainted = !0, this.setNativeFallback(!0), await this.source.attach(this.video, { url: a, format: d }, { crossOrigin: null }), await this.video.play().catch(() => {
184
216
  }), this.emit("fallback");
185
217
  return;
186
- } catch (v) {
187
- throw this.setNativeFallback(!1), this.emit("error", v), v;
218
+ } catch (m) {
219
+ throw this.tainted = !1, this.setNativeFallback(!1), this.emit("error", m), m;
188
220
  }
189
- throw this.setNativeFallback(!1), this.emit("error", r), r;
221
+ throw this.setNativeFallback(!1), this.emit("error", n), n;
190
222
  } finally {
191
223
  this.loading = !1;
192
224
  }
@@ -210,8 +242,19 @@ class Z {
210
242
  pause() {
211
243
  this.video.pause();
212
244
  }
245
+ /** Set the display mode: a geometry projection (WebGL 3D) or 'off' for plain 2D `<video>`. */
213
246
  setProjection(t) {
214
- this.scene.setProjection(t), this.look.setEnabled(!this.scene.isFlat()), this.look.reset(), this.view.projection = t, this.persist(), this.emit("projectionchange", t);
247
+ if (t === "off") {
248
+ this.displayMode = "off", this.setNativeFallback(!0), this.emit("projectionchange", t);
249
+ return;
250
+ }
251
+ this.displayMode = t, this.applyProjectionGeometry(t), this.tainted && this.currentSrc ? this.load(this.currentSrc) : this.setNativeFallback(!1), this.emit("projectionchange", t);
252
+ }
253
+ applyProjectionGeometry(t) {
254
+ this.scene.setProjection(t), this.look.setEnabled(!this.scene.isFlat()), this.look.reset(), this.view.projection = t, this.persist();
255
+ }
256
+ applyDisplay() {
257
+ this.setNativeFallback(this.displayMode === "off");
215
258
  }
216
259
  setSwapEyes(t) {
217
260
  this.scene.setSwapEyes(t), this.view.swapEyes = t, this.persist();
@@ -226,10 +269,13 @@ class Z {
226
269
  setProxy(t) {
227
270
  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
271
  }
272
+ /** Enter immersive VR. Rejects if the WebXR session request fails (e.g. no headset,
273
+ * or a non-secure origin) — the built-in controls surface the reason as a toast. */
229
274
  async enterVR() {
230
- var e;
231
- const t = this.scene.vrButton;
232
- (e = t == null ? void 0 : t.click) == null || e.call(t);
275
+ await this.scene.enterVR();
276
+ }
277
+ exitVR() {
278
+ this.scene.exitVR();
233
279
  }
234
280
  get three() {
235
281
  return { renderer: this.scene.renderer, scene: this.scene.scene, camera: this.scene.camera };
@@ -244,18 +290,18 @@ class Z {
244
290
  }
245
291
  emit(t, e) {
246
292
  var s;
247
- (s = this.listeners.get(t)) == null || s.forEach((p) => p(e));
293
+ (s = this.listeners.get(t)) == null || s.forEach((a) => a(e));
248
294
  }
249
295
  persist() {
250
296
  if (this.opts.persistSettings)
251
297
  try {
252
- localStorage.setItem(V, JSON.stringify(this.view));
298
+ localStorage.setItem(Y, JSON.stringify(this.view));
253
299
  } catch {
254
300
  }
255
301
  }
256
302
  loadSettings() {
257
303
  try {
258
- const t = localStorage.getItem(V);
304
+ const t = localStorage.getItem(Y);
259
305
  return t ? JSON.parse(t) : null;
260
306
  } catch {
261
307
  return null;
@@ -266,12 +312,12 @@ class Z {
266
312
  (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();
267
313
  }
268
314
  }
269
- class tt extends HTMLElement {
315
+ class rt extends HTMLElement {
270
316
  static get observedAttributes() {
271
317
  return ["src", "projection"];
272
318
  }
273
319
  connectedCallback() {
274
- this.player || (this.style.display || (this.style.display = "block"), this.player = new Z(this, {
320
+ this.player || (this.style.display || (this.style.display = "block"), this.player = new nt(this, {
275
321
  src: this.getAttribute("src") ?? void 0,
276
322
  projection: this.getAttribute("projection") ?? void 0,
277
323
  controls: this.hasAttribute("controls"),
@@ -298,23 +344,23 @@ class tt extends HTMLElement {
298
344
  return this.player;
299
345
  }
300
346
  }
301
- function et(l = "three-video") {
302
- typeof customElements < "u" && !customElements.get(l) && customElements.define(l, tt);
347
+ function at(c = "three-video") {
348
+ typeof customElements < "u" && !customElements.get(c) && customElements.define(c, rt);
303
349
  }
304
- et();
350
+ at();
305
351
  export {
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
352
+ et as LookControls,
353
+ dt as MODES,
354
+ X as PROJECTIONS,
355
+ nt as Player,
356
+ tt as StereoScene,
357
+ rt as ThreeVideoElement,
358
+ Z as VideoSource,
359
+ it as buildProxyUrl,
360
+ ht as clampAngles,
361
+ ut as detectFormat,
362
+ st as detectProjection,
363
+ vt as isFlatMode,
364
+ at as registerWebComponent
319
365
  };
320
366
  //# sourceMappingURL=three-vr-player.js.map