three-vr-player 0.9.0 → 0.10.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/dist/{VideoSource-BXMPMJzW.js → VideoSource-DHj5tBsK.js} +167 -121
- package/dist/VideoSource-DHj5tBsK.js.map +1 -0
- package/dist/core/StereoScene.d.ts +8 -0
- package/dist/core/VRControls.d.ts +8 -2
- package/dist/core/projections.d.ts +2 -0
- package/dist/core/vr-panel-layout.d.ts +4 -1
- package/dist/core.js +1 -1
- package/dist/three-vr-player.js +15 -15
- package/dist/three-vr-player.js.map +1 -1
- package/dist/three-vr-player.standalone.js +45 -45
- package/dist/three-vr-player.standalone.js.map +1 -1
- package/package.json +1 -1
- package/dist/VideoSource-BXMPMJzW.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as a from "three";
|
|
2
|
-
function
|
|
2
|
+
function Q(h) {
|
|
3
3
|
let e = h;
|
|
4
4
|
try {
|
|
5
5
|
e = new URL(h).pathname;
|
|
@@ -8,21 +8,21 @@ function D(h) {
|
|
|
8
8
|
const t = e.toLowerCase();
|
|
9
9
|
return t.endsWith(".m3u8") ? "hls" : t.endsWith(".mpd") ? "dash" : "progressive";
|
|
10
10
|
}
|
|
11
|
-
const
|
|
11
|
+
const D = {
|
|
12
12
|
progressive: "/proxy/stream",
|
|
13
13
|
hls: "/proxy/hls/manifest.m3u8",
|
|
14
14
|
dash: "/proxy/mpd/manifest.m3u8"
|
|
15
15
|
};
|
|
16
|
-
function
|
|
17
|
-
const t =
|
|
16
|
+
function Y(h, e) {
|
|
17
|
+
const t = Q(h);
|
|
18
18
|
if (!e || !e.url) return { url: h, format: t };
|
|
19
19
|
const i = e.url.replace(/\/+$/, ""), s = new URLSearchParams();
|
|
20
20
|
s.set("d", h), e.apiPassword && s.set("api_password", e.apiPassword);
|
|
21
21
|
for (const [r, n] of Object.entries(e.headers ?? {}))
|
|
22
22
|
n != null && n !== "" && s.set(`h_${r}`, n);
|
|
23
|
-
return { url: `${i}${
|
|
23
|
+
return { url: `${i}${D[t]}?${s.toString()}`, format: t };
|
|
24
24
|
}
|
|
25
|
-
const
|
|
25
|
+
const S = {
|
|
26
26
|
"180-sbs": { geom: "sphere180", split: "sbs", stereo: !0 },
|
|
27
27
|
"180-mono": { geom: "sphere180", split: "mono", stereo: !1 },
|
|
28
28
|
"360-mono": { geom: "sphere360", split: "mono", stereo: !1 },
|
|
@@ -34,7 +34,7 @@ const b = {
|
|
|
34
34
|
"flat-2d": { geom: "plane", split: "mono", stereo: !1, flat: !0, aspect: "full" },
|
|
35
35
|
"flat-sbs-full": { geom: "plane", split: "sbs", stereo: !0, flat: !0, aspect: "per-eye" },
|
|
36
36
|
"flat-sbs-half": { geom: "plane", split: "sbs", stereo: !0, flat: !0, aspect: "full" }
|
|
37
|
-
},
|
|
37
|
+
}, G = [
|
|
38
38
|
{ value: "180-sbs", label: "180° SBS (VR180)" },
|
|
39
39
|
{ value: "180-mono", label: "180° Mono" },
|
|
40
40
|
{ value: "360-mono", label: "360° Mono" },
|
|
@@ -45,23 +45,34 @@ const b = {
|
|
|
45
45
|
{ value: "flat-2d", label: "Flat 2D" },
|
|
46
46
|
{ value: "flat-sbs-full", label: "Flat 3D — Full SBS" },
|
|
47
47
|
{ value: "flat-sbs-half", label: "Flat 3D — Half SBS" }
|
|
48
|
-
]
|
|
49
|
-
|
|
48
|
+
], V = {
|
|
49
|
+
"180-sbs": "180° SBS",
|
|
50
|
+
"180-mono": "180° Mono",
|
|
51
|
+
"360-mono": "360° Mono",
|
|
52
|
+
"360-sbs": "360° SBS",
|
|
53
|
+
"360-tb": "360° TB",
|
|
54
|
+
"fisheye190-sbs": "Fisheye SBS",
|
|
55
|
+
"fisheye190-mono": "Fisheye Mono",
|
|
56
|
+
"flat-2d": "Flat 2D",
|
|
57
|
+
"flat-sbs-full": "Flat SBS",
|
|
58
|
+
"flat-sbs-half": "Flat SBS½"
|
|
59
|
+
};
|
|
60
|
+
function Z(h) {
|
|
50
61
|
var e;
|
|
51
|
-
return !!((e =
|
|
62
|
+
return !!((e = S[h]) != null && e.flat);
|
|
52
63
|
}
|
|
53
|
-
function
|
|
64
|
+
function J(h) {
|
|
54
65
|
const e = String(h).toLowerCase(), t = /(^|[^a-z])(tb|ou|top.?bottom|over.?under)([^a-z]|$)/.test(e), i = /(sbs|side.?by.?side)/.test(e);
|
|
55
66
|
return /(fisheye|mkx200|rf52|vrca220)/.test(e) ? e.includes("mono") ? "fisheye190-mono" : "fisheye190-sbs" : e.includes("360") ? t ? "360-tb" : i ? "360-sbs" : "360-mono" : e.includes("180") || e.includes("vr180") ? e.includes("mono") ? "180-mono" : "180-sbs" : i ? e.includes("half") ? "flat-sbs-half" : "flat-sbs-full" : null;
|
|
56
67
|
}
|
|
57
|
-
function
|
|
68
|
+
function k(h) {
|
|
58
69
|
if (!Number.isFinite(h) || h < 0) return "0:00";
|
|
59
70
|
const e = Math.floor(h % 60), t = Math.floor(h / 60 % 60), i = Math.floor(h / 3600), s = String(e).padStart(2, "0");
|
|
60
71
|
return i > 0 ? `${i}:${String(t).padStart(2, "0")}:${s}` : `${t}:${s}`;
|
|
61
72
|
}
|
|
62
|
-
const
|
|
63
|
-
function
|
|
64
|
-
const h =
|
|
73
|
+
const y = 1024, w = 340;
|
|
74
|
+
function O() {
|
|
75
|
+
const h = y, e = w, t = 48;
|
|
65
76
|
return {
|
|
66
77
|
width: h,
|
|
67
78
|
height: e,
|
|
@@ -74,24 +85,28 @@ function Q() {
|
|
|
74
85
|
play: { x: h / 2 - 44, y: 150, w: 88, h: 88 },
|
|
75
86
|
volIcon: { x: t, y: 178, w: 44, h: 44 },
|
|
76
87
|
volBar: { x: t + 60, y: 192, w: 200, h: 16 },
|
|
88
|
+
// Projection stepper on the right: ◀ [label] ▶
|
|
89
|
+
projPrev: { x: 592, y: 178, w: 46, h: 46 },
|
|
90
|
+
projNext: { x: h - 94, y: 178, w: 46, h: 46 },
|
|
91
|
+
projLabel: { x: 646, y: 178, w: h - 94 - 646, h: 46 },
|
|
77
92
|
seekBar: { x: t, y: 286, w: h - t * 2, h: 14 },
|
|
78
93
|
timeCur: { x: t, y: 262 },
|
|
79
94
|
timeDur: { x: h - t, y: 262 }
|
|
80
95
|
};
|
|
81
96
|
}
|
|
82
97
|
const C = (h) => h < 0 ? 0 : h > 1 ? 1 : h;
|
|
83
|
-
function
|
|
98
|
+
function x(h, e, t, i = 0, s = 0) {
|
|
84
99
|
return e >= h.x - i && e <= h.x + h.w + i && t >= h.y - s && t <= h.y + h.h + s;
|
|
85
100
|
}
|
|
86
|
-
function
|
|
87
|
-
return
|
|
101
|
+
function W(h, e, t = O()) {
|
|
102
|
+
return x(t.recenter, h, e) ? { region: "recenter" } : x(t.passthrough, h, e) ? { region: "passthrough" } : x(t.exit, h, e) ? { region: "exit" } : x(t.projPrev, h, e) ? { region: "projPrev" } : x(t.projNext, h, e) ? { region: "projNext" } : x(t.play, h, e) ? { region: "play" } : x(t.volIcon, h, e) ? { region: "volume" } : x(t.volBar, h, e, 12, 26) ? { region: "volume", value: C((h - t.volBar.x) / t.volBar.w) } : x(t.seekBar, h, e, 12, 26) ? { region: "seek", value: C((h - t.seekBar.x) / t.seekBar.w) } : null;
|
|
88
103
|
}
|
|
89
|
-
const
|
|
90
|
-
class
|
|
104
|
+
const R = "#4f8cff", P = "#e8eaed", L = "#aab2c0", U = [4, 5], A = 8e3, N = 0.05, _ = 0.02, I = 1500, T = 0.15, q = 0.012, z = 0.012, H = 0.08;
|
|
105
|
+
class X {
|
|
91
106
|
// re-lock the panel to the head pose for a few frames after summon
|
|
92
107
|
constructor(e) {
|
|
93
|
-
this.layout =
|
|
94
|
-
const t = 1, i = t *
|
|
108
|
+
this.layout = O(), this.raycaster = new a.Raycaster(), this.tmpMatrix = new a.Matrix4(), this.tmpVec = new a.Vector3(), this.tmpQuat = new a.Quaternion(), this.prevQuat = [], this.prevPos = [], this.revealCooldownUntil = 0, this.grabbing = null, this.grabStartQuat = new a.Quaternion(), this.prevGrabQuat = new a.Quaternion(), this.grabQuat = new a.Quaternion(), this.deltaQuat = new a.Quaternion(), this.grabEuler = new a.Euler(0, 0, 0, "YXZ"), this.controllers = [], this.lasers = [], this.connected = [], this.cleanups = [], this.visible = !1, this.hover = null, this.paintKey = "", this.idleAt = 0, this.togglePrev = !1, this.placeFrames = 0, this.renderer = e.renderer, this.scene = e.scene, this.actions = e.actions, this.canvas = document.createElement("canvas"), this.canvas.width = y, this.canvas.height = w, this.ctx = this.canvas.getContext("2d"), this.texture = new a.CanvasTexture(this.canvas), this.texture.colorSpace = a.SRGBColorSpace, this.texture.minFilter = a.LinearFilter;
|
|
109
|
+
const t = 1, i = t * w / y;
|
|
95
110
|
this.panel = new a.Mesh(
|
|
96
111
|
new a.PlaneGeometry(t, i),
|
|
97
112
|
new a.MeshBasicMaterial({ map: this.texture, transparent: !0, depthTest: !1, side: a.DoubleSide })
|
|
@@ -103,13 +118,13 @@ class z {
|
|
|
103
118
|
for (let n = 0; n < 2; n++) {
|
|
104
119
|
const l = this.renderer.xr.getController(n), o = this.makeLaser();
|
|
105
120
|
o.visible = !1, l.add(o), this.scene.add(l), this.controllers.push(l), this.lasers.push(o), this.connected.push(n < r);
|
|
106
|
-
const c = () => this.onSelect(n),
|
|
107
|
-
this.connected[n] = !(
|
|
121
|
+
const c = () => this.onSelect(n), f = () => this.onGrabStart(n), u = () => this.onGrabEnd(n), g = (d) => {
|
|
122
|
+
this.connected[n] = !(d != null && d.data) || d.data.targetRayMode === "tracked-pointer";
|
|
108
123
|
}, v = () => {
|
|
109
124
|
this.connected[n] = !1;
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
|
|
125
|
+
}, p = l;
|
|
126
|
+
p.addEventListener("selectstart", c), p.addEventListener("squeezestart", f), p.addEventListener("squeezeend", u), p.addEventListener("connected", g), p.addEventListener("disconnected", v), this.cleanups.push(() => {
|
|
127
|
+
p.removeEventListener("selectstart", c), p.removeEventListener("squeezestart", f), p.removeEventListener("squeezeend", u), p.removeEventListener("connected", g), p.removeEventListener("disconnected", v);
|
|
113
128
|
});
|
|
114
129
|
}
|
|
115
130
|
this.paint(!0);
|
|
@@ -140,7 +155,7 @@ class z {
|
|
|
140
155
|
const o = this.rayHit(n);
|
|
141
156
|
o.point ? (r.visible = !0, r.scale.z = o.distance, i = o.point, o.hit && (t = o.hit.region)) : r.visible = !1;
|
|
142
157
|
}
|
|
143
|
-
i ? (this.cursor.position.copy(i), this.cursor.visible = !0) : this.cursor.visible = !1, t && (this.idleAt = e +
|
|
158
|
+
i ? (this.cursor.position.copy(i), this.cursor.visible = !0) : this.cursor.visible = !1, t && (this.idleAt = e + A), this.hover = t, this.paint(!1);
|
|
144
159
|
}
|
|
145
160
|
/** Ray from a controller against the panel: the region under it (if any) and
|
|
146
161
|
* how far the laser reaches. */
|
|
@@ -149,13 +164,13 @@ class z {
|
|
|
149
164
|
const t = this.raycaster.intersectObject(this.panel, !1);
|
|
150
165
|
if (!t.length) return { hit: null, distance: 5, point: null };
|
|
151
166
|
const i = t[0];
|
|
152
|
-
return { hit: i.uv ?
|
|
167
|
+
return { hit: i.uv ? W(i.uv.x * y, (1 - i.uv.y) * w, this.layout) : null, distance: i.distance, point: i.point };
|
|
153
168
|
}
|
|
154
169
|
onSelect(e) {
|
|
155
170
|
if (!this.visible || !this.connected[e]) return;
|
|
156
171
|
const { hit: t } = this.rayHit(this.controllers[e]);
|
|
157
172
|
if (t) {
|
|
158
|
-
switch (this.idleAt =
|
|
173
|
+
switch (this.idleAt = E() + A, t.region) {
|
|
159
174
|
case "play":
|
|
160
175
|
this.actions.togglePlay();
|
|
161
176
|
break;
|
|
@@ -165,6 +180,12 @@ class z {
|
|
|
165
180
|
case "passthrough":
|
|
166
181
|
this.actions.passthroughAvailable() && this.actions.togglePassthrough();
|
|
167
182
|
break;
|
|
183
|
+
case "projPrev":
|
|
184
|
+
this.actions.cycleProjection(-1);
|
|
185
|
+
break;
|
|
186
|
+
case "projNext":
|
|
187
|
+
this.actions.cycleProjection(1);
|
|
188
|
+
break;
|
|
168
189
|
case "recenter":
|
|
169
190
|
this.actions.recenter(), this.placeFrames = 12;
|
|
170
191
|
break;
|
|
@@ -186,26 +207,28 @@ class z {
|
|
|
186
207
|
const i = this.controllers[t], s = this.tmpVec.setFromMatrixPosition(i.matrixWorld);
|
|
187
208
|
if (!this.connected[t] || s.lengthSq() <= 1e-6) continue;
|
|
188
209
|
const r = this.tmpQuat.setFromRotationMatrix(i.matrixWorld), n = this.prevQuat[t], l = this.prevPos[t];
|
|
189
|
-
n && l && !this.visible && e > this.revealCooldownUntil && (r.angleTo(n) >
|
|
210
|
+
n && l && !this.visible && e > this.revealCooldownUntil && (r.angleTo(n) > N || s.distanceTo(l) > _) && this.show(), this.prevQuat[t] || (this.prevQuat[t] = new a.Quaternion()), this.prevPos[t] || (this.prevPos[t] = new a.Vector3()), this.prevQuat[t].copy(r), this.prevPos[t].copy(s);
|
|
190
211
|
}
|
|
191
212
|
}
|
|
192
|
-
/** Right thumbstick
|
|
193
|
-
* zoom in)
|
|
213
|
+
/** Right thumbstick, applied every frame it's held: X zooms the content fov (push right =
|
|
214
|
+
* zoom in), Y tilts the content pitch (push up = tilt up). The grip grab-rotate also does
|
|
215
|
+
* pitch/yaw; the two coexist. */
|
|
194
216
|
handleThumbstick() {
|
|
195
217
|
const e = this.renderer.xr.getSession();
|
|
196
|
-
if (e)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
218
|
+
if (!e) return;
|
|
219
|
+
const t = (i) => Math.sign(i) * (Math.abs(i) - T) / (1 - T);
|
|
220
|
+
for (const i of e.inputSources) {
|
|
221
|
+
if (i.handedness !== "right" || !i.gamepad) continue;
|
|
222
|
+
const s = i.gamepad.axes, r = s.length >= 4 ? s[2] ?? 0 : s[0] ?? 0, n = s.length >= 4 ? s[3] ?? 0 : s[1] ?? 0;
|
|
223
|
+
Math.abs(r) > T && this.actions.adjustZoom(t(r) * q), Math.abs(n) > T && this.actions.adjustTilt(-t(n) * z);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
203
226
|
}
|
|
204
227
|
onGrabStart(e) {
|
|
205
228
|
this.connected[e] && (this.grabbing = e, this.controllers[e].getWorldQuaternion(this.grabStartQuat), this.prevGrabQuat.copy(this.grabStartQuat));
|
|
206
229
|
}
|
|
207
230
|
onGrabEnd(e) {
|
|
208
|
-
this.grabbing === e && (this.grabbing = null, this.revealCooldownUntil =
|
|
231
|
+
this.grabbing === e && (this.grabbing = null, this.revealCooldownUntil = E() + I, this.controllers[e].getWorldQuaternion(this.grabQuat).angleTo(this.grabStartQuat) < H && this.toggle());
|
|
209
232
|
}
|
|
210
233
|
/** While the grip is held, apply the controller's frame-to-frame rotation to the
|
|
211
234
|
* content's yaw and pitch — a 1:1 grab-and-turn. Roll is ignored. */
|
|
@@ -223,7 +246,7 @@ class z {
|
|
|
223
246
|
let t = !1;
|
|
224
247
|
for (const i of (e == null ? void 0 : e.inputSources) ?? []) {
|
|
225
248
|
const s = i.gamepad;
|
|
226
|
-
s &&
|
|
249
|
+
s && U.some((r) => {
|
|
227
250
|
var n;
|
|
228
251
|
return (n = s.buttons[r]) == null ? void 0 : n.pressed;
|
|
229
252
|
}) && (t = !0);
|
|
@@ -234,12 +257,12 @@ class z {
|
|
|
234
257
|
this.visible ? this.hide() : this.show();
|
|
235
258
|
}
|
|
236
259
|
show() {
|
|
237
|
-
this.place(), this.placeFrames = 12, this.visible = !0, this.panel.visible = !0, this.idleAt =
|
|
260
|
+
this.place(), this.placeFrames = 12, this.visible = !0, this.panel.visible = !0, this.idleAt = E() + A, this.paint(!0);
|
|
238
261
|
}
|
|
239
262
|
hide() {
|
|
240
263
|
this.visible = !1, this.panel.visible = !1, this.cursor.visible = !1;
|
|
241
264
|
for (const e of this.lasers) e.visible = !1;
|
|
242
|
-
this.revealCooldownUntil =
|
|
265
|
+
this.revealCooldownUntil = E() + I;
|
|
243
266
|
}
|
|
244
267
|
/** World-lock the panel in front of the current head pose (the "recenter"). Uses
|
|
245
268
|
* yaw only (so head pitch/roll at summon doesn't skew it), drops it into the lower
|
|
@@ -262,33 +285,35 @@ class z {
|
|
|
262
285
|
}
|
|
263
286
|
/** Repaint the panel only when something visible changed (state or hover). */
|
|
264
287
|
paint(e) {
|
|
265
|
-
const t = this.actions.currentTime(), i = this.actions.duration(), s = this.actions.volume(), r = this.actions.muted(), n = this.actions.passthroughAvailable() && this.actions.passthroughEnabled(), l = [this.actions.isPlaying(), Math.floor(t), Math.floor(i), s.toFixed(2), r, this.hover, this.actions.title(), this.actions.passthroughAvailable(), n].join("|");
|
|
288
|
+
const t = this.actions.currentTime(), i = this.actions.duration(), s = this.actions.volume(), r = this.actions.muted(), n = this.actions.passthroughAvailable() && this.actions.passthroughEnabled(), l = [this.actions.isPlaying(), Math.floor(t), Math.floor(i), s.toFixed(2), r, this.hover, this.actions.title(), this.actions.passthroughAvailable(), n, this.actions.projectionLabel()].join("|");
|
|
266
289
|
if (!e && l === this.paintKey) return;
|
|
267
290
|
this.paintKey = l;
|
|
268
291
|
const o = this.ctx, c = this.layout;
|
|
269
|
-
o.clearRect(0, 0,
|
|
270
|
-
const
|
|
271
|
-
if (
|
|
272
|
-
o.font = '600 30px system-ui,"Segoe UI",Roboto,sans-serif', o.fillStyle =
|
|
273
|
-
const
|
|
274
|
-
o.fillText(this.ellipsize(
|
|
292
|
+
o.clearRect(0, 0, y, w), this.roundRect(0, 0, y, w, 28), o.fillStyle = "rgba(22,24,30,0.84)", o.fill(), o.lineWidth = 2, o.strokeStyle = "rgba(255,255,255,0.08)", o.stroke();
|
|
293
|
+
const f = this.actions.title();
|
|
294
|
+
if (f) {
|
|
295
|
+
o.font = '600 30px system-ui,"Segoe UI",Roboto,sans-serif', o.fillStyle = P, o.textAlign = "center", o.textBaseline = "middle";
|
|
296
|
+
const d = 2 * c.exit.x - 48 - y;
|
|
297
|
+
o.fillText(this.ellipsize(f, d), y / 2, c.title.y + c.title.h / 2);
|
|
275
298
|
}
|
|
276
|
-
if (this.drawIconButton(c.recenter, this.hover === "recenter", !1, (
|
|
277
|
-
const
|
|
278
|
-
this.drawIconButton(c.passthrough,
|
|
299
|
+
if (this.drawIconButton(c.recenter, this.hover === "recenter", !1, (d, m, b) => this.iconRecenter(d, m, b)), this.drawIconButton(c.exit, this.hover === "exit", !1, (d, m, b) => this.iconExit(d, m, b)), this.actions.passthroughAvailable()) {
|
|
300
|
+
const d = this.actions.passthroughEnabled();
|
|
301
|
+
this.drawIconButton(c.passthrough, d, this.hover === "passthrough", (m, b, M) => this.iconEye(m, b, !d, M));
|
|
279
302
|
}
|
|
280
|
-
const
|
|
281
|
-
o.beginPath(), o.arc(
|
|
282
|
-
const
|
|
283
|
-
this.drawBar(c.volBar,
|
|
284
|
-
const v =
|
|
285
|
-
|
|
303
|
+
const u = { x: c.play.x + c.play.w / 2, y: c.play.y + c.play.h / 2 };
|
|
304
|
+
o.beginPath(), o.arc(u.x, u.y, 44, 0, Math.PI * 2), o.fillStyle = this.hover === "play" ? R : "rgba(255,255,255,0.14)", o.fill(), o.fillStyle = "#fff", this.actions.isPlaying() ? (o.fillRect(u.x - 13, u.y - 16, 9, 32), o.fillRect(u.x + 4, u.y - 16, 9, 32)) : (o.beginPath(), o.moveTo(u.x - 12, u.y - 17), o.lineTo(u.x - 12, u.y + 17), o.lineTo(u.x + 18, u.y), o.closePath(), o.fill()), this.drawSpeaker(c.volIcon, r);
|
|
305
|
+
const g = r ? 0 : Math.max(0, Math.min(1, s));
|
|
306
|
+
this.drawBar(c.volBar, g, this.hover === "volume");
|
|
307
|
+
const v = c.projLabel.x + c.projLabel.w / 2;
|
|
308
|
+
o.fillStyle = L, o.font = "600 15px system-ui,sans-serif", o.textAlign = "center", o.textBaseline = "alphabetic", o.fillText("PROJECTION", v, c.projPrev.y - 8), this.drawIconButton(c.projPrev, this.hover === "projPrev", !1, (d, m, b) => this.iconArrow(d, m, -1, b)), this.drawIconButton(c.projNext, this.hover === "projNext", !1, (d, m, b) => this.iconArrow(d, m, 1, b)), o.fillStyle = P, o.font = "600 26px system-ui,sans-serif", o.textAlign = "center", o.textBaseline = "middle", o.fillText(this.ellipsize(this.actions.projectionLabel(), c.projLabel.w - 12), v, c.projLabel.y + c.projLabel.h / 2);
|
|
309
|
+
const p = i > 0 ? Math.max(0, Math.min(1, t / i)) : 0;
|
|
310
|
+
this.drawBar(c.seekBar, p, this.hover === "seek", !0), o.fillStyle = L, o.font = "22px system-ui,sans-serif", o.textBaseline = "alphabetic", o.textAlign = "left", o.fillText(k(t), c.timeCur.x, c.timeCur.y), o.textAlign = "right", o.fillText(i > 0 ? k(i) : "--:--", c.timeDur.x, c.timeDur.y), this.texture.needsUpdate = !0;
|
|
286
311
|
}
|
|
287
312
|
/** A rounded-square icon button: accent fill when `filled`, white border when `hover`; the
|
|
288
313
|
* icon is drawn centred in `filled ? white : TEXT`. */
|
|
289
314
|
drawIconButton(e, t, i, s) {
|
|
290
315
|
const r = this.ctx;
|
|
291
|
-
this.roundRect(e.x, e.y, e.w, e.h, 14), r.fillStyle = t ?
|
|
316
|
+
this.roundRect(e.x, e.y, e.w, e.h, 14), r.fillStyle = t ? R : "rgba(255,255,255,0.12)", r.fill(), i && (r.lineWidth = 3, r.strokeStyle = "rgba(255,255,255,0.9)", r.stroke()), s(e.x + e.w / 2, e.y + e.h / 2, t ? "#fff" : P);
|
|
292
317
|
}
|
|
293
318
|
/** Recenter — a reticle (ring + centre dot + crosshair ticks). */
|
|
294
319
|
iconRecenter(e, t, i) {
|
|
@@ -300,6 +325,11 @@ class z {
|
|
|
300
325
|
const r = this.ctx, n = 16, l = 10;
|
|
301
326
|
r.strokeStyle = s, r.fillStyle = s, r.lineWidth = 2.5, r.lineCap = "round", r.beginPath(), r.moveTo(e - n, t), r.quadraticCurveTo(e, t - l * 1.7, e + n, t), r.quadraticCurveTo(e, t + l * 1.7, e - n, t), r.stroke(), r.beginPath(), r.arc(e, t, 4, 0, Math.PI * 2), r.fill(), i && (r.beginPath(), r.moveTo(e - n, t + l), r.lineTo(e + n, t - l), r.stroke());
|
|
302
327
|
}
|
|
328
|
+
/** A solid triangle pointing left (dir −1) or right (dir +1) — the projection stepper arrows. */
|
|
329
|
+
iconArrow(e, t, i, s) {
|
|
330
|
+
const r = this.ctx, n = 8, l = 10;
|
|
331
|
+
r.fillStyle = s, r.beginPath(), r.moveTo(e + i * n, t), r.lineTo(e - i * n, t - l), r.lineTo(e - i * n, t + l), r.closePath(), r.fill();
|
|
332
|
+
}
|
|
303
333
|
/** Exit — a door frame with an arrow pointing out to the right. */
|
|
304
334
|
iconExit(e, t, i) {
|
|
305
335
|
const s = this.ctx, r = e - 15, n = 11, l = 22, o = e + 16;
|
|
@@ -307,11 +337,11 @@ class z {
|
|
|
307
337
|
}
|
|
308
338
|
drawBar(e, t, i, s = !1) {
|
|
309
339
|
const r = this.ctx, n = e.h / 2;
|
|
310
|
-
this.roundRect(e.x, e.y, e.w, e.h, n), r.fillStyle = "rgba(255,255,255,0.22)", r.fill(), t > 0 && (this.roundRect(e.x, e.y, e.w * t, e.h, n), r.fillStyle =
|
|
340
|
+
this.roundRect(e.x, e.y, e.w, e.h, n), r.fillStyle = "rgba(255,255,255,0.22)", r.fill(), t > 0 && (this.roundRect(e.x, e.y, e.w * t, e.h, n), r.fillStyle = R, r.fill()), (s || i) && (r.beginPath(), r.arc(e.x + e.w * t, e.y + e.h / 2, i ? 12 : 9, 0, Math.PI * 2), r.fillStyle = "#fff", r.fill());
|
|
311
341
|
}
|
|
312
342
|
drawSpeaker(e, t) {
|
|
313
343
|
const i = this.ctx, s = e.x, r = e.y + e.h / 2;
|
|
314
|
-
i.fillStyle = t ?
|
|
344
|
+
i.fillStyle = t ? L : P, i.beginPath(), i.moveTo(s, r - 7), i.lineTo(s + 10, r - 7), i.lineTo(s + 20, r - 16), i.lineTo(s + 20, r + 16), i.lineTo(s + 10, r + 7), i.lineTo(s, r + 7), i.closePath(), i.fill(), i.strokeStyle = t ? "#e06a6a" : P, i.lineWidth = 3, t ? (i.beginPath(), i.moveTo(s + 26, r - 9), i.lineTo(s + 40, r + 9), i.moveTo(s + 40, r - 9), i.lineTo(s + 26, r + 9), i.stroke()) : (i.beginPath(), i.arc(s + 24, r, 8, -Math.PI / 3, Math.PI / 3), i.stroke(), i.beginPath(), i.arc(s + 24, r, 15, -Math.PI / 3, Math.PI / 3), i.stroke());
|
|
315
345
|
}
|
|
316
346
|
dispose() {
|
|
317
347
|
for (const e of this.cleanups) e();
|
|
@@ -322,14 +352,14 @@ class z {
|
|
|
322
352
|
this.scene.remove(this.panel), this.panel.geometry.dispose(), this.panel.material.dispose(), this.scene.remove(this.cursor), this.cursor.geometry.dispose(), this.cursor.material.dispose(), this.texture.dispose();
|
|
323
353
|
}
|
|
324
354
|
}
|
|
325
|
-
function
|
|
355
|
+
function E() {
|
|
326
356
|
return typeof performance < "u" ? performance.now() : 0;
|
|
327
357
|
}
|
|
328
|
-
class
|
|
358
|
+
class K {
|
|
329
359
|
constructor(e) {
|
|
330
360
|
this.scene = new a.Scene(), this.meshes = [], this.rig = new a.Group(), this.tilt = 0, this.fov = 1, this.builtFov = 1, this.frameCbs = [], this.alphaFisheye = !1, this.inPassthroughSession = !1, this.passthroughOn = !0, this.animate = (o) => {
|
|
331
361
|
var c;
|
|
332
|
-
for (const
|
|
362
|
+
for (const f of this.frameCbs) f();
|
|
333
363
|
(c = this.vrControls) == null || c.update(o ?? 0), this.video.readyState >= this.video.HAVE_CURRENT_DATA && (this.texture.needsUpdate = !0), this.renderer.render(this.scene, this.camera);
|
|
334
364
|
}, this.vrSessionInit = { optionalFeatures: ["local-floor"] }, this.arSessionInit = { optionalFeatures: ["local-floor"] }, this.vrTitle = "", this.resize = () => {
|
|
335
365
|
if (this.renderer.xr.isPresenting) return;
|
|
@@ -337,8 +367,8 @@ class Z {
|
|
|
337
367
|
this.camera.aspect = o / c, this.camera.updateProjectionMatrix(), this.renderer.setSize(o, c, !1);
|
|
338
368
|
};
|
|
339
369
|
const { canvas: t, video: i, projection: s = "180-sbs", swapEyes: r = !1, fov: n = 70, supersampling: l = 1.5 } = e;
|
|
340
|
-
this.canvas = t, this.video = i, this.currentMode =
|
|
341
|
-
|
|
370
|
+
this.canvas = t, this.video = i, this.currentMode = S[s] ? s : "180-sbs", this.currentSwap = r, this.renderer = new a.WebGLRenderer({ canvas: t, antialias: !0, alpha: !0 }), this.renderer.setClearColor(0, 1), this.renderer.setPixelRatio(this.pixelRatioFor(l)), this.renderer.setSize(this.w(), this.h(), !1), this.renderer.xr.enabled = !0, this.camera = new a.PerspectiveCamera(n, this.w() / this.h(), 0.1, 1e3), this.camera.position.set(0, 0, 0), this.maxAnisotropy = this.renderer.capabilities.getMaxAnisotropy(), this.texture = new a.VideoTexture(i), this.texture.colorSpace = a.SRGBColorSpace, this.texture.minFilter = a.LinearFilter, this.texture.magFilter = a.LinearFilter, this.texture.anisotropy = 1, this.rig.rotation.order = "YXZ", this.scene.add(this.rig), this.applyProjection(this.currentMode, this.currentSwap), i.addEventListener("loadedmetadata", () => {
|
|
371
|
+
S[this.currentMode].flat && this.applyProjection(this.currentMode, this.currentSwap);
|
|
342
372
|
}), this.renderer.setAnimationLoop(this.animate), this.renderer.xr.addEventListener("sessionstart", () => {
|
|
343
373
|
var c;
|
|
344
374
|
const o = (c = this.renderer.xr.getSession()) == null ? void 0 : c.environmentBlendMode;
|
|
@@ -350,7 +380,7 @@ class Z {
|
|
|
350
380
|
}
|
|
351
381
|
buildVRControls() {
|
|
352
382
|
const e = this.video;
|
|
353
|
-
this.vrControls = new
|
|
383
|
+
this.vrControls = new X({
|
|
354
384
|
renderer: this.renderer,
|
|
355
385
|
scene: this.scene,
|
|
356
386
|
actions: {
|
|
@@ -380,7 +410,9 @@ class Z {
|
|
|
380
410
|
passthroughAvailable: () => this.inPassthroughSession,
|
|
381
411
|
// any AR session, not just alpha content
|
|
382
412
|
passthroughEnabled: () => this.passthroughOn,
|
|
383
|
-
togglePassthrough: () => this.setPassthrough(!this.passthroughOn)
|
|
413
|
+
togglePassthrough: () => this.setPassthrough(!this.passthroughOn),
|
|
414
|
+
projectionLabel: () => this.projectionLabel(),
|
|
415
|
+
cycleProjection: (t) => this.cycleProjection(t)
|
|
384
416
|
}
|
|
385
417
|
});
|
|
386
418
|
}
|
|
@@ -410,10 +442,10 @@ class Z {
|
|
|
410
442
|
}
|
|
411
443
|
planeAspect(e) {
|
|
412
444
|
const t = this.video.videoWidth, i = this.video.videoHeight;
|
|
413
|
-
return !t || !i ? 16 / 9 :
|
|
445
|
+
return !t || !i ? 16 / 9 : S[e].aspect === "per-eye" ? t / 2 / i : t / i;
|
|
414
446
|
}
|
|
415
447
|
buildGeometry(e) {
|
|
416
|
-
const t =
|
|
448
|
+
const t = S[e].geom, i = this.fov;
|
|
417
449
|
if (t === "sphere180") {
|
|
418
450
|
const l = Math.PI * i, o = Math.PI * i, c = new a.SphereGeometry(50, 64, 32, -l / 2, l, Math.PI / 2 - o / 2, o);
|
|
419
451
|
return c.scale(-1, 1, 1), c;
|
|
@@ -435,21 +467,21 @@ class Z {
|
|
|
435
467
|
*/
|
|
436
468
|
buildFisheyeDome(e = 95) {
|
|
437
469
|
const r = a.MathUtils.degToRad(e), n = [], l = [], o = [];
|
|
438
|
-
for (let
|
|
439
|
-
const
|
|
440
|
-
for (let
|
|
441
|
-
const
|
|
442
|
-
n.push(
|
|
470
|
+
for (let u = 0; u <= 64; u++) {
|
|
471
|
+
const g = u / 64, v = g * r, p = g * 0.5, d = Math.sin(v), m = Math.cos(v);
|
|
472
|
+
for (let b = 0; b <= 128; b++) {
|
|
473
|
+
const M = b / 128 * Math.PI * 2, j = Math.cos(M), F = Math.sin(M);
|
|
474
|
+
n.push(d * j * 50, d * F * 50, -m * 50), l.push(0.5 + p * j, 0.5 + p * F);
|
|
443
475
|
}
|
|
444
476
|
}
|
|
445
477
|
const c = 129;
|
|
446
|
-
for (let
|
|
447
|
-
for (let
|
|
448
|
-
const v =
|
|
449
|
-
o.push(v,
|
|
478
|
+
for (let u = 0; u < 64; u++)
|
|
479
|
+
for (let g = 0; g < 128; g++) {
|
|
480
|
+
const v = u * c + g, p = v + 1, d = v + c, m = d + 1;
|
|
481
|
+
o.push(v, d, p, p, d, m);
|
|
450
482
|
}
|
|
451
|
-
const
|
|
452
|
-
return
|
|
483
|
+
const f = new a.BufferGeometry();
|
|
484
|
+
return f.setAttribute("position", new a.Float32BufferAttribute(n, 3)), f.setAttribute("uv", new a.Float32BufferAttribute(l, 2)), f.setIndex(o), f.computeVertexNormals(), f;
|
|
453
485
|
}
|
|
454
486
|
/**
|
|
455
487
|
* Transparent material for fisheye content with a DeoVR-style packed alpha matte. Built by
|
|
@@ -509,7 +541,7 @@ varying vec2 vDiscUv;
|
|
|
509
541
|
/** Shift the shared video texture for the eye/half being drawn. Runs from the mesh's
|
|
510
542
|
* onBeforeRender, so it's called once per eye per frame (the reference approach). */
|
|
511
543
|
updateStereoUV(e) {
|
|
512
|
-
const t = this.texture, i =
|
|
544
|
+
const t = this.texture, i = S[this.currentMode].split;
|
|
513
545
|
let s = !0;
|
|
514
546
|
if (this.renderer.xr.isPresenting) {
|
|
515
547
|
const n = e, l = this.renderer.xr.getCamera().cameras;
|
|
@@ -528,16 +560,16 @@ varying vec2 vDiscUv;
|
|
|
528
560
|
this.meshes.length = 0;
|
|
529
561
|
}
|
|
530
562
|
applyProjection(e, t) {
|
|
531
|
-
|
|
563
|
+
S[e] || (e = "180-sbs"), this.clearMeshes(), this.currentMode = e, this.currentSwap = t, this.alphaUniforms = void 0;
|
|
532
564
|
let i;
|
|
533
|
-
if (
|
|
565
|
+
if (S[e].geom === "fisheye" && this.alphaFisheye)
|
|
534
566
|
i = this.makeFisheyeAlphaMaterial();
|
|
535
567
|
else {
|
|
536
|
-
const r =
|
|
568
|
+
const r = S[e].geom === "fisheye" ? a.DoubleSide : a.FrontSide;
|
|
537
569
|
i = new a.MeshBasicMaterial({ map: this.texture, side: r });
|
|
538
570
|
}
|
|
539
571
|
const s = new a.Mesh(this.buildGeometry(e), i);
|
|
540
|
-
s.layers.set(0),
|
|
572
|
+
s.layers.set(0), S[e].geom === "sphere180" && (s.rotation.y = Math.PI / 2), s.onBeforeRender = (r, n, l) => this.updateStereoUV(l), this.rig.add(s), this.meshes.push(s);
|
|
541
573
|
}
|
|
542
574
|
/** Pitch the video content up/down (radians, accumulated and clamped). */
|
|
543
575
|
adjustTilt(e) {
|
|
@@ -564,6 +596,20 @@ varying vec2 vDiscUv;
|
|
|
564
596
|
setProjection(e) {
|
|
565
597
|
this.applyProjection(e, this.currentSwap);
|
|
566
598
|
}
|
|
599
|
+
/** Player registers this so an in-VR projection change flows back through Player — keeping its
|
|
600
|
+
* state, persistence, and the DOM controls in sync. Falls back to a direct geometry swap. */
|
|
601
|
+
setProjectionRequester(e) {
|
|
602
|
+
this.projectionRequester = e;
|
|
603
|
+
}
|
|
604
|
+
/** Short label of the current projection, for the in-VR stepper. */
|
|
605
|
+
projectionLabel() {
|
|
606
|
+
return V[this.currentMode] ?? this.currentMode;
|
|
607
|
+
}
|
|
608
|
+
/** Step the projection by dir (±1) through the PROJECTIONS list (wraps around). */
|
|
609
|
+
cycleProjection(e) {
|
|
610
|
+
const t = G.map((r) => r.value), i = Math.max(0, t.indexOf(this.currentMode)), s = t[(i + e + t.length) % t.length];
|
|
611
|
+
(this.projectionRequester ?? ((r) => this.setProjection(r)))(s);
|
|
612
|
+
}
|
|
567
613
|
setSwapEyes(e) {
|
|
568
614
|
this.applyProjection(this.currentMode, e);
|
|
569
615
|
}
|
|
@@ -577,7 +623,7 @@ varying vec2 vDiscUv;
|
|
|
577
623
|
return this.currentMode;
|
|
578
624
|
}
|
|
579
625
|
isFlat() {
|
|
580
|
-
return !!
|
|
626
|
+
return !!S[this.currentMode].flat;
|
|
581
627
|
}
|
|
582
628
|
onFrame(e) {
|
|
583
629
|
this.frameCbs.push(e);
|
|
@@ -622,21 +668,21 @@ varying vec2 vDiscUv;
|
|
|
622
668
|
this.ro.disconnect(), this.renderer.setAnimationLoop(null), (e = this.vrControls) == null || e.dispose(), this.texture.dispose(), this.clearMeshes(), this.renderer.dispose();
|
|
623
669
|
}
|
|
624
670
|
}
|
|
625
|
-
const
|
|
626
|
-
function
|
|
671
|
+
const B = Math.PI / 180;
|
|
672
|
+
function $(h, e) {
|
|
627
673
|
return {
|
|
628
674
|
lon: Math.max(-90, Math.min(90, h)),
|
|
629
675
|
lat: Math.max(-85, Math.min(85, e))
|
|
630
676
|
};
|
|
631
677
|
}
|
|
632
|
-
class
|
|
678
|
+
class ee {
|
|
633
679
|
constructor(e, t, i = {}) {
|
|
634
680
|
this.camera = e, this.dom = t, this.lon = 0, this.lat = 0, this.dragging = !1, this.px = 0, this.py = 0, this.enabled = !0, this.onDown = (s) => {
|
|
635
681
|
this.enabled && (this.dragging = !0, this.px = s.clientX, this.py = s.clientY, this.capture("setPointerCapture", s.pointerId));
|
|
636
682
|
}, this.onMove = (s) => {
|
|
637
683
|
if (!this.dragging) return;
|
|
638
684
|
const r = s.clientX - this.px, n = s.clientY - this.py;
|
|
639
|
-
this.px = s.clientX, this.py = s.clientY, { lon: this.lon, lat: this.lat } =
|
|
685
|
+
this.px = s.clientX, this.py = s.clientY, { lon: this.lon, lat: this.lat } = $(this.lon - r * 0.15, this.lat + n * 0.15);
|
|
640
686
|
}, this.onUp = (s) => {
|
|
641
687
|
this.dragging = !1, this.capture("releasePointerCapture", s.pointerId);
|
|
642
688
|
}, this.isPresenting = i.isPresenting ?? (() => !1), this.dom.addEventListener("pointerdown", this.onDown), this.dom.addEventListener("pointermove", this.onMove), this.dom.addEventListener("pointerup", this.onUp), this.dom.addEventListener("pointercancel", this.onUp);
|
|
@@ -650,7 +696,7 @@ class K {
|
|
|
650
696
|
}
|
|
651
697
|
update() {
|
|
652
698
|
if (this.isPresenting() || !this.enabled) return;
|
|
653
|
-
const e = (90 - this.lat) *
|
|
699
|
+
const e = (90 - this.lat) * B, t = this.lon * B;
|
|
654
700
|
this.camera.lookAt(
|
|
655
701
|
Math.sin(e) * Math.sin(t),
|
|
656
702
|
Math.cos(e),
|
|
@@ -670,7 +716,7 @@ class K {
|
|
|
670
716
|
this.dom.removeEventListener("pointerdown", this.onDown), this.dom.removeEventListener("pointermove", this.onMove), this.dom.removeEventListener("pointerup", this.onUp), this.dom.removeEventListener("pointercancel", this.onUp);
|
|
671
717
|
}
|
|
672
718
|
}
|
|
673
|
-
class
|
|
719
|
+
class te {
|
|
674
720
|
constructor() {
|
|
675
721
|
this.hls = null;
|
|
676
722
|
}
|
|
@@ -679,26 +725,26 @@ class J {
|
|
|
679
725
|
const s = i.crossOrigin === void 0 ? "anonymous" : i.crossOrigin;
|
|
680
726
|
s === null ? e.removeAttribute("crossorigin") : e.crossOrigin = s, e.playsInline = !0;
|
|
681
727
|
const { url: r, format: n } = t, l = n === "hls" || n === "dash", o = e.canPlayType("application/vnd.apple.mpegurl") !== "";
|
|
682
|
-
return new Promise((c,
|
|
683
|
-
const
|
|
684
|
-
e.removeEventListener("loadedmetadata",
|
|
685
|
-
},
|
|
686
|
-
|
|
728
|
+
return new Promise((c, f) => {
|
|
729
|
+
const u = () => {
|
|
730
|
+
e.removeEventListener("loadedmetadata", g), e.removeEventListener("error", v);
|
|
731
|
+
}, g = () => {
|
|
732
|
+
u(), c();
|
|
687
733
|
}, v = () => {
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
|
|
734
|
+
u();
|
|
735
|
+
const p = e.error ? e.error.code : "unknown";
|
|
736
|
+
f(new Error(`Video failed to load (media error ${p}). Is the source reachable and CORS-clean?`));
|
|
691
737
|
};
|
|
692
|
-
e.addEventListener("loadedmetadata",
|
|
693
|
-
if (!
|
|
694
|
-
|
|
738
|
+
e.addEventListener("loadedmetadata", g), e.addEventListener("error", v), l && !o ? import("hls.js").then(({ default: p }) => {
|
|
739
|
+
if (!p.isSupported()) {
|
|
740
|
+
u(), f(new Error("HLS/DASH stream needs hls.js, which is unsupported in this browser."));
|
|
695
741
|
return;
|
|
696
742
|
}
|
|
697
|
-
this.hls = new
|
|
698
|
-
|
|
743
|
+
this.hls = new p({ enableWorker: !0 }), this.hls.on(p.Events.ERROR, (d, m) => {
|
|
744
|
+
m.fatal && (u(), this.dispose(), f(new Error(`HLS fatal error: ${m.type} / ${m.details}`)));
|
|
699
745
|
}), this.hls.loadSource(r), this.hls.attachMedia(e);
|
|
700
746
|
}).catch(() => {
|
|
701
|
-
|
|
747
|
+
u(), f(new Error('This looks like an HLS/DASH stream but hls.js could not be loaded. Install "hls.js".'));
|
|
702
748
|
}) : (e.src = r, e.load());
|
|
703
749
|
});
|
|
704
750
|
}
|
|
@@ -707,16 +753,16 @@ class J {
|
|
|
707
753
|
}
|
|
708
754
|
}
|
|
709
755
|
export {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
756
|
+
ee as L,
|
|
757
|
+
S as M,
|
|
758
|
+
G as P,
|
|
759
|
+
K as S,
|
|
760
|
+
te as V,
|
|
761
|
+
J as a,
|
|
762
|
+
Y as b,
|
|
763
|
+
$ as c,
|
|
764
|
+
Q as d,
|
|
765
|
+
k as f,
|
|
766
|
+
Z as i
|
|
721
767
|
};
|
|
722
|
-
//# sourceMappingURL=VideoSource-
|
|
768
|
+
//# sourceMappingURL=VideoSource-DHj5tBsK.js.map
|