three-vr-player 0.1.0 → 0.3.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 +7 -1
- package/dist/Player.d.ts +13 -0
- package/dist/{VideoSource-pc2RM7bl.js → VideoSource-CJGIX8gL.js} +72 -64
- package/dist/VideoSource-CJGIX8gL.js.map +1 -0
- package/dist/core/StereoScene.d.ts +3 -0
- package/dist/core/VideoSource.d.ts +2 -0
- package/dist/core.js +1 -1
- package/dist/three-vr-player.js +187 -147
- package/dist/three-vr-player.js.map +1 -1
- package/dist/three-vr-player.standalone.js +37 -37
- package/dist/three-vr-player.standalone.js.map +1 -1
- package/dist/types.d.ts +9 -1
- package/dist/ui/ControlsUI.d.ts +10 -0
- package/package.json +39 -9
- package/dist/VideoSource-pc2RM7bl.js.map +0 -1
package/README.md
CHANGED
|
@@ -78,6 +78,7 @@ new Player(container, {
|
|
|
78
78
|
persistSettings?: boolean; // default false (localStorage)
|
|
79
79
|
shadowDom?: boolean; // default true (style isolation)
|
|
80
80
|
vrButton?: boolean; // default true (shown only when a headset is present)
|
|
81
|
+
nativeFallback?: boolean; // default true — plain 2D <video> fallback on CORS-taint
|
|
81
82
|
});
|
|
82
83
|
```
|
|
83
84
|
|
|
@@ -86,11 +87,12 @@ new Player(container, {
|
|
|
86
87
|
```ts
|
|
87
88
|
player.load(src, { projection? }); player.play(); player.pause();
|
|
88
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
|
|
89
91
|
player.enterVR(); player.dispose();
|
|
90
92
|
player.video; // the underlying <video>
|
|
91
93
|
player.three; // { renderer, scene, camera }
|
|
92
94
|
|
|
93
|
-
player.on('ready'|'play'|'pause'|'ended'|'error'|'timeupdate'|'projectionchange'|'enterxr'|'exitxr', cb);
|
|
95
|
+
player.on('ready'|'play'|'pause'|'ended'|'error'|'timeupdate'|'projectionchange'|'enterxr'|'exitxr'|'fallback', cb);
|
|
94
96
|
```
|
|
95
97
|
|
|
96
98
|
## Headless core
|
|
@@ -107,6 +109,10 @@ To use a cross-origin video as a WebGL texture it must be CORS-clean. If your ho
|
|
|
107
109
|
doesn't send `Access-Control-Allow-Origin`, run the optional [proxy companion](./proxy)
|
|
108
110
|
and pass `proxy: { url, apiPassword }`. CORS-clean sources need no proxy.
|
|
109
111
|
|
|
112
|
+
If a source is neither CORS-clean nor proxied, the player automatically falls back to
|
|
113
|
+
plain **2D `<video>` playback** (no reprojection) and emits a `fallback` event, instead of
|
|
114
|
+
failing on a black screen. Disable with `nativeFallback: false`.
|
|
115
|
+
|
|
110
116
|
## Codecs
|
|
111
117
|
|
|
112
118
|
The browser must be able to decode the file: MP4/WebM with H.264/HEVC/VP9 + AAC/Opus is
|
package/dist/Player.d.ts
CHANGED
|
@@ -16,17 +16,30 @@ export declare class Player {
|
|
|
16
16
|
private readonly listeners;
|
|
17
17
|
private view;
|
|
18
18
|
private readyEmitted;
|
|
19
|
+
private native;
|
|
20
|
+
private loading;
|
|
21
|
+
private proxyConfig?;
|
|
22
|
+
private useProxy;
|
|
23
|
+
private currentSrc?;
|
|
19
24
|
constructor(container: HTMLElement, options?: PlayerOptions);
|
|
20
25
|
private vrSupported;
|
|
21
26
|
load(src: string, o?: {
|
|
22
27
|
projection?: Projection;
|
|
23
28
|
}): Promise<void>;
|
|
29
|
+
/** Swap between WebGL (3D) rendering and plain 2D `<video>` playback. */
|
|
30
|
+
private setNativeFallback;
|
|
24
31
|
play(): Promise<void>;
|
|
25
32
|
pause(): void;
|
|
26
33
|
setProjection(p: Projection): void;
|
|
27
34
|
setSwapEyes(v: boolean): void;
|
|
28
35
|
setFov(deg: number): void;
|
|
29
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;
|
|
30
43
|
enterVR(): Promise<void>;
|
|
31
44
|
get three(): {
|
|
32
45
|
renderer: import('three').WebGLRenderer;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as o from "three";
|
|
2
|
-
import { VRButton as
|
|
3
|
-
function
|
|
2
|
+
import { VRButton as w } from "three/examples/jsm/webxr/VRButton.js";
|
|
3
|
+
function M(a) {
|
|
4
4
|
let e = a;
|
|
5
5
|
try {
|
|
6
6
|
e = new URL(a).pathname;
|
|
@@ -9,21 +9,21 @@ function v(a) {
|
|
|
9
9
|
const s = e.toLowerCase();
|
|
10
10
|
return s.endsWith(".m3u8") ? "hls" : s.endsWith(".mpd") ? "dash" : "progressive";
|
|
11
11
|
}
|
|
12
|
-
const
|
|
12
|
+
const S = {
|
|
13
13
|
progressive: "/proxy/stream",
|
|
14
14
|
hls: "/proxy/hls/manifest.m3u8",
|
|
15
15
|
dash: "/proxy/mpd/manifest.m3u8"
|
|
16
16
|
};
|
|
17
|
-
function
|
|
18
|
-
const s =
|
|
17
|
+
function x(a, e) {
|
|
18
|
+
const s = M(a);
|
|
19
19
|
if (!e || !e.url) return { url: a, format: s };
|
|
20
20
|
const r = e.url.replace(/\/+$/, ""), t = new URLSearchParams();
|
|
21
21
|
t.set("d", a), e.apiPassword && t.set("api_password", e.apiPassword);
|
|
22
22
|
for (const [i, n] of Object.entries(e.headers ?? {}))
|
|
23
23
|
n != null && n !== "" && t.set(`h_${i}`, n);
|
|
24
|
-
return { url: `${r}${
|
|
24
|
+
return { url: `${r}${S[s]}?${t.toString()}`, format: s };
|
|
25
25
|
}
|
|
26
|
-
const
|
|
26
|
+
const h = {
|
|
27
27
|
"180-sbs": { geom: "sphere180", split: "sbs", stereo: !0 },
|
|
28
28
|
"180-mono": { geom: "sphere180", split: "mono", stereo: !1 },
|
|
29
29
|
"360-mono": { geom: "sphere360", split: "mono", stereo: !1 },
|
|
@@ -32,7 +32,7 @@ const c = {
|
|
|
32
32
|
"flat-2d": { geom: "plane", split: "mono", stereo: !1, flat: !0, aspect: "full" },
|
|
33
33
|
"flat-sbs-full": { geom: "plane", split: "sbs", stereo: !0, flat: !0, aspect: "per-eye" },
|
|
34
34
|
"flat-sbs-half": { geom: "plane", split: "sbs", stereo: !0, flat: !0, aspect: "full" }
|
|
35
|
-
},
|
|
35
|
+
}, E = [
|
|
36
36
|
{ value: "180-sbs", label: "180° SBS (VR180)" },
|
|
37
37
|
{ value: "180-mono", label: "180° Mono" },
|
|
38
38
|
{ value: "360-mono", label: "360° Mono" },
|
|
@@ -42,27 +42,27 @@ const c = {
|
|
|
42
42
|
{ value: "flat-sbs-full", label: "Flat 3D — Full SBS" },
|
|
43
43
|
{ value: "flat-sbs-half", label: "Flat 3D — Half SBS" }
|
|
44
44
|
];
|
|
45
|
-
function
|
|
45
|
+
function R(a) {
|
|
46
46
|
var e;
|
|
47
|
-
return !!((e =
|
|
47
|
+
return !!((e = h[a]) != null && e.flat);
|
|
48
48
|
}
|
|
49
|
-
function
|
|
49
|
+
function A(a) {
|
|
50
50
|
const e = String(a).toLowerCase(), s = /(^|[^a-z])(tb|ou|top.?bottom|over.?under)([^a-z]|$)/.test(e), r = /(sbs|side.?by.?side)/.test(e);
|
|
51
51
|
return e.includes("360") ? s ? "360-tb" : r ? "360-sbs" : "360-mono" : e.includes("180") || e.includes("vr180") ? e.includes("mono") ? "180-mono" : "180-sbs" : r ? e.includes("half") ? "flat-sbs-half" : "flat-sbs-full" : null;
|
|
52
52
|
}
|
|
53
|
-
class
|
|
53
|
+
class F {
|
|
54
54
|
constructor(e) {
|
|
55
|
-
this.scene = new o.Scene(), this.meshes = [], this.frameCbs = [], this.
|
|
56
|
-
const
|
|
57
|
-
this.camera.aspect = h / l, this.camera.updateProjectionMatrix(), this.renderer.setSize(h, l, !1);
|
|
58
|
-
};
|
|
59
|
-
const { canvas: s, video: r, projection: t = "180-sbs", swapEyes: i = !1, fov: n = 70, supersampling: u = 1.5 } = e;
|
|
60
|
-
this.canvas = s, this.video = r, this.currentMode = c[t] ? t : "180-sbs", this.currentSwap = i, this.renderer = new o.WebGLRenderer({ canvas: s, antialias: !0 }), this.renderer.setPixelRatio(this.pixelRatioFor(u)), this.renderer.setSize(this.w(), this.h(), !1), this.renderer.xr.enabled = !0, this.camera = new o.PerspectiveCamera(n, this.w() / this.h(), 0.1, 1e3), this.camera.position.set(0, 0, 0), this.camera.layers.enable(1), this.maxAnisotropy = this.renderer.capabilities.getMaxAnisotropy(), this.texture = new o.VideoTexture(r), this.texture.colorSpace = o.SRGBColorSpace, this.texture.minFilter = o.LinearFilter, this.texture.magFilter = o.LinearFilter, this.texture.anisotropy = this.maxAnisotropy, this.applyProjection(this.currentMode, this.currentSwap), r.addEventListener("loadedmetadata", () => {
|
|
61
|
-
c[this.currentMode].flat && this.applyProjection(this.currentMode, this.currentSwap);
|
|
62
|
-
}), this.renderer.setAnimationLoop(() => {
|
|
63
|
-
for (const h of this.frameCbs) h();
|
|
55
|
+
this.scene = new o.Scene(), this.meshes = [], this.frameCbs = [], this.animate = () => {
|
|
56
|
+
for (const c of this.frameCbs) c();
|
|
64
57
|
this.renderer.render(this.scene, this.camera);
|
|
65
|
-
}
|
|
58
|
+
}, this.resize = () => {
|
|
59
|
+
const c = this.w(), d = this.h();
|
|
60
|
+
this.camera.aspect = c / d, this.camera.updateProjectionMatrix(), this.renderer.setSize(c, d, !1);
|
|
61
|
+
};
|
|
62
|
+
const { canvas: s, video: r, projection: t = "180-sbs", swapEyes: i = !1, fov: n = 70, supersampling: l = 1.5 } = e;
|
|
63
|
+
this.canvas = s, this.video = r, this.currentMode = h[t] ? t : "180-sbs", this.currentSwap = i, this.renderer = new o.WebGLRenderer({ canvas: s, antialias: !0 }), this.renderer.setPixelRatio(this.pixelRatioFor(l)), this.renderer.setSize(this.w(), this.h(), !1), this.renderer.xr.enabled = !0, this.camera = new o.PerspectiveCamera(n, this.w() / this.h(), 0.1, 1e3), this.camera.position.set(0, 0, 0), this.camera.layers.enable(1), this.maxAnisotropy = this.renderer.capabilities.getMaxAnisotropy(), this.texture = new o.VideoTexture(r), this.texture.colorSpace = o.SRGBColorSpace, this.texture.minFilter = o.LinearFilter, this.texture.magFilter = o.LinearFilter, this.texture.anisotropy = this.maxAnisotropy, this.applyProjection(this.currentMode, this.currentSwap), r.addEventListener("loadedmetadata", () => {
|
|
64
|
+
h[this.currentMode].flat && this.applyProjection(this.currentMode, this.currentSwap);
|
|
65
|
+
}), this.renderer.setAnimationLoop(this.animate), this.vrButton = w.createButton(this.renderer), this.ro = new ResizeObserver(() => this.resize()), this.ro.observe(s);
|
|
66
66
|
}
|
|
67
67
|
w() {
|
|
68
68
|
return this.canvas.clientWidth || 1;
|
|
@@ -75,10 +75,10 @@ class R {
|
|
|
75
75
|
}
|
|
76
76
|
planeAspect(e) {
|
|
77
77
|
const s = this.video.videoWidth, r = this.video.videoHeight;
|
|
78
|
-
return !s || !r ? 16 / 9 :
|
|
78
|
+
return !s || !r ? 16 / 9 : h[e].aspect === "per-eye" ? s / 2 / r : s / r;
|
|
79
79
|
}
|
|
80
80
|
buildGeometry(e) {
|
|
81
|
-
const s =
|
|
81
|
+
const s = h[e].geom;
|
|
82
82
|
if (s === "sphere180") {
|
|
83
83
|
const n = new o.SphereGeometry(500, 60, 40, -Math.PI, Math.PI, 0, Math.PI);
|
|
84
84
|
return n.scale(-1, 1, 1), n;
|
|
@@ -103,15 +103,15 @@ class R {
|
|
|
103
103
|
this.meshes.length = 0;
|
|
104
104
|
}
|
|
105
105
|
applyProjection(e, s) {
|
|
106
|
-
|
|
107
|
-
const r =
|
|
106
|
+
h[e] || (e = "180-sbs"), this.clearMeshes();
|
|
107
|
+
const r = h[e];
|
|
108
108
|
if (r.stereo) {
|
|
109
109
|
const t = this.buildGeometry(e);
|
|
110
110
|
this.splitUV(t, r.split, "left");
|
|
111
111
|
const i = this.buildGeometry(e);
|
|
112
112
|
this.splitUV(i, r.split, "right");
|
|
113
|
-
const n = new o.Mesh(t, new o.MeshBasicMaterial({ map: this.texture })),
|
|
114
|
-
n.layers.set(s ? 2 : 1),
|
|
113
|
+
const n = new o.Mesh(t, new o.MeshBasicMaterial({ map: this.texture })), l = new o.Mesh(i, new o.MeshBasicMaterial({ map: this.texture }));
|
|
114
|
+
n.layers.set(s ? 2 : 1), l.layers.set(s ? 1 : 2), this.scene.add(n, l), this.meshes.push(n, l);
|
|
115
115
|
} else {
|
|
116
116
|
const t = new o.Mesh(this.buildGeometry(e), new o.MeshBasicMaterial({ map: this.texture }));
|
|
117
117
|
t.layers.set(0), this.scene.add(t), this.meshes.push(t);
|
|
@@ -134,30 +134,36 @@ class R {
|
|
|
134
134
|
return this.currentMode;
|
|
135
135
|
}
|
|
136
136
|
isFlat() {
|
|
137
|
-
return !!
|
|
137
|
+
return !!h[this.currentMode].flat;
|
|
138
138
|
}
|
|
139
139
|
onFrame(e) {
|
|
140
140
|
this.frameCbs.push(e);
|
|
141
141
|
}
|
|
142
|
+
pauseRendering() {
|
|
143
|
+
this.renderer.setAnimationLoop(null);
|
|
144
|
+
}
|
|
145
|
+
resumeRendering() {
|
|
146
|
+
this.renderer.setAnimationLoop(this.animate);
|
|
147
|
+
}
|
|
142
148
|
dispose() {
|
|
143
149
|
this.ro.disconnect(), this.renderer.setAnimationLoop(null), this.texture.dispose(), this.clearMeshes(), this.renderer.dispose();
|
|
144
150
|
}
|
|
145
151
|
}
|
|
146
|
-
const
|
|
147
|
-
function
|
|
152
|
+
const v = Math.PI / 180;
|
|
153
|
+
function y(a, e) {
|
|
148
154
|
return {
|
|
149
155
|
lon: Math.max(-90, Math.min(90, a)),
|
|
150
156
|
lat: Math.max(-85, Math.min(85, e))
|
|
151
157
|
};
|
|
152
158
|
}
|
|
153
|
-
class
|
|
159
|
+
class j {
|
|
154
160
|
constructor(e, s, r = {}) {
|
|
155
161
|
this.camera = e, this.dom = s, this.lon = 0, this.lat = 0, this.dragging = !1, this.px = 0, this.py = 0, this.enabled = !0, this.onDown = (t) => {
|
|
156
162
|
this.enabled && (this.dragging = !0, this.px = t.clientX, this.py = t.clientY, this.capture("setPointerCapture", t.pointerId));
|
|
157
163
|
}, this.onMove = (t) => {
|
|
158
164
|
if (!this.dragging) return;
|
|
159
165
|
const i = t.clientX - this.px, n = t.clientY - this.py;
|
|
160
|
-
this.px = t.clientX, this.py = t.clientY, { lon: this.lon, lat: this.lat } =
|
|
166
|
+
this.px = t.clientX, this.py = t.clientY, { lon: this.lon, lat: this.lat } = y(this.lon - i * 0.15, this.lat + n * 0.15);
|
|
161
167
|
}, this.onUp = (t) => {
|
|
162
168
|
this.dragging = !1, this.capture("releasePointerCapture", t.pointerId);
|
|
163
169
|
}, this.isPresenting = r.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);
|
|
@@ -171,7 +177,7 @@ class F {
|
|
|
171
177
|
}
|
|
172
178
|
update() {
|
|
173
179
|
if (this.isPresenting() || !this.enabled) return;
|
|
174
|
-
const e = (90 - this.lat) *
|
|
180
|
+
const e = (90 - this.lat) * v, s = this.lon * v;
|
|
175
181
|
this.camera.lookAt(
|
|
176
182
|
Math.sin(e) * Math.sin(s),
|
|
177
183
|
Math.cos(e),
|
|
@@ -191,34 +197,36 @@ class F {
|
|
|
191
197
|
this.dom.removeEventListener("pointerdown", this.onDown), this.dom.removeEventListener("pointermove", this.onMove), this.dom.removeEventListener("pointerup", this.onUp), this.dom.removeEventListener("pointercancel", this.onUp);
|
|
192
198
|
}
|
|
193
199
|
}
|
|
194
|
-
class
|
|
200
|
+
class B {
|
|
195
201
|
constructor() {
|
|
196
202
|
this.hls = null;
|
|
197
203
|
}
|
|
198
|
-
async attach(e, s) {
|
|
199
|
-
this.dispose()
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
},
|
|
207
|
-
|
|
204
|
+
async attach(e, s, r = {}) {
|
|
205
|
+
this.dispose();
|
|
206
|
+
const t = r.crossOrigin === void 0 ? "anonymous" : r.crossOrigin;
|
|
207
|
+
t === null ? e.removeAttribute("crossorigin") : e.crossOrigin = t, e.playsInline = !0;
|
|
208
|
+
const { url: i, format: n } = s, l = n === "hls" || n === "dash", c = e.canPlayType("application/vnd.apple.mpegurl") !== "";
|
|
209
|
+
return new Promise((d, m) => {
|
|
210
|
+
const u = () => {
|
|
211
|
+
e.removeEventListener("loadedmetadata", b), e.removeEventListener("error", g);
|
|
212
|
+
}, b = () => {
|
|
213
|
+
u(), d();
|
|
214
|
+
}, g = () => {
|
|
215
|
+
u();
|
|
208
216
|
const p = e.error ? e.error.code : "unknown";
|
|
209
|
-
|
|
217
|
+
m(new Error(`Video failed to load (media error ${p}). Is the source reachable and CORS-clean?`));
|
|
210
218
|
};
|
|
211
|
-
e.addEventListener("loadedmetadata",
|
|
219
|
+
e.addEventListener("loadedmetadata", b), e.addEventListener("error", g), l && !c ? import("hls.js").then(({ default: p }) => {
|
|
212
220
|
if (!p.isSupported()) {
|
|
213
|
-
|
|
221
|
+
u(), m(new Error("HLS/DASH stream needs hls.js, which is unsupported in this browser."));
|
|
214
222
|
return;
|
|
215
223
|
}
|
|
216
|
-
this.hls = new p({ enableWorker: !0 }), this.hls.on(p.Events.ERROR, (
|
|
217
|
-
|
|
218
|
-
}), this.hls.loadSource(
|
|
224
|
+
this.hls = new p({ enableWorker: !0 }), this.hls.on(p.Events.ERROR, (L, f) => {
|
|
225
|
+
f.fatal && (u(), this.dispose(), m(new Error(`HLS fatal error: ${f.type} / ${f.details}`)));
|
|
226
|
+
}), this.hls.loadSource(i), this.hls.attachMedia(e);
|
|
219
227
|
}).catch(() => {
|
|
220
|
-
|
|
221
|
-
}) : (e.src =
|
|
228
|
+
u(), m(new Error('This looks like an HLS/DASH stream but hls.js could not be loaded. Install "hls.js".'));
|
|
229
|
+
}) : (e.src = i, e.load());
|
|
222
230
|
});
|
|
223
231
|
}
|
|
224
232
|
dispose() {
|
|
@@ -226,15 +234,15 @@ class A {
|
|
|
226
234
|
}
|
|
227
235
|
}
|
|
228
236
|
export {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
j as L,
|
|
238
|
+
h as M,
|
|
239
|
+
E as P,
|
|
240
|
+
F as S,
|
|
241
|
+
B as V,
|
|
242
|
+
A as a,
|
|
243
|
+
x as b,
|
|
244
|
+
y as c,
|
|
245
|
+
M as d,
|
|
246
|
+
R as i
|
|
239
247
|
};
|
|
240
|
-
//# sourceMappingURL=VideoSource-
|
|
248
|
+
//# sourceMappingURL=VideoSource-CJGIX8gL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VideoSource-CJGIX8gL.js","sources":["../src/core/proxy.ts","../src/core/projections.ts","../src/core/StereoScene.ts","../src/core/LookControls.ts","../src/core/VideoSource.ts"],"sourcesContent":["export type StreamFormat = 'hls' | 'dash' | 'progressive';\r\n\r\nexport interface ProxyConfig {\r\n url: string;\r\n apiPassword?: string;\r\n headers?: Record<string, string>;\r\n}\r\n\r\n/** Detect streaming format from a URL by extension. */\r\nexport function detectFormat(rawUrl: string): StreamFormat {\r\n let pathname = rawUrl;\r\n try { pathname = new URL(rawUrl).pathname; } catch { /* relative/opaque: use raw */ }\r\n const lower = pathname.toLowerCase();\r\n if (lower.endsWith('.m3u8')) return 'hls';\r\n if (lower.endsWith('.mpd')) return 'dash';\r\n return 'progressive';\r\n}\r\n\r\nconst ENDPOINTS: Record<StreamFormat, string> = {\r\n progressive: '/proxy/stream',\r\n hls: '/proxy/hls/manifest.m3u8',\r\n dash: '/proxy/mpd/manifest.m3u8',\r\n};\r\n\r\n/**\r\n * Build the URL the `<video>`/hls.js should load. When `proxy` is omitted the raw\r\n * URL is returned (it must then be CORS-clean to be used as a WebGL texture).\r\n */\r\nexport function buildProxyUrl(rawUrl: string, proxy?: ProxyConfig): { url: string; format: StreamFormat } {\r\n const format = detectFormat(rawUrl);\r\n if (!proxy || !proxy.url) return { url: rawUrl, format };\r\n const base = proxy.url.replace(/\\/+$/, '');\r\n const params = new URLSearchParams();\r\n params.set('d', rawUrl);\r\n if (proxy.apiPassword) params.set('api_password', proxy.apiPassword);\r\n for (const [name, value] of Object.entries(proxy.headers ?? {})) {\r\n if (value != null && value !== '') params.set(`h_${name}`, value);\r\n }\r\n return { url: `${base}${ENDPOINTS[format]}?${params.toString()}`, format };\r\n}\r\n","import type { Projection } from '../types.js';\r\n\r\nexport type Split = 'mono' | 'sbs' | 'tb';\r\nexport type GeomKind = 'sphere180' | 'sphere360' | 'plane';\r\n\r\nexport interface ModeConfig {\r\n geom: GeomKind;\r\n split: Split;\r\n stereo: boolean;\r\n flat?: boolean;\r\n /** plane only: 'full' = displayW/H, 'per-eye' = (W/2)/H */\r\n aspect?: 'full' | 'per-eye';\r\n}\r\n\r\nexport const MODES: Record<Projection, ModeConfig> = {\r\n '180-sbs': { geom: 'sphere180', split: 'sbs', stereo: true },\r\n '180-mono': { geom: 'sphere180', split: 'mono', stereo: false },\r\n '360-mono': { geom: 'sphere360', split: 'mono', stereo: false },\r\n '360-sbs': { geom: 'sphere360', split: 'sbs', stereo: true },\r\n '360-tb': { geom: 'sphere360', split: 'tb', stereo: true },\r\n 'flat-2d': { geom: 'plane', split: 'mono', stereo: false, flat: true, aspect: 'full' },\r\n 'flat-sbs-full': { geom: 'plane', split: 'sbs', stereo: true, flat: true, aspect: 'per-eye' },\r\n 'flat-sbs-half': { geom: 'plane', split: 'sbs', stereo: true, flat: true, aspect: 'full' },\r\n};\r\n\r\nexport const PROJECTIONS: { value: Projection; label: string }[] = [\r\n { value: '180-sbs', label: '180° SBS (VR180)' },\r\n { value: '180-mono', label: '180° Mono' },\r\n { value: '360-mono', label: '360° Mono' },\r\n { value: '360-sbs', label: '360° SBS' },\r\n { value: '360-tb', label: '360° Top-Bottom' },\r\n { value: 'flat-2d', label: 'Flat 2D (regular movie)' },\r\n { value: 'flat-sbs-full', label: 'Flat 3D — Full SBS' },\r\n { value: 'flat-sbs-half', label: 'Flat 3D — Half SBS' },\r\n];\r\n\r\nexport function isFlatMode(p: Projection): boolean {\r\n return !!MODES[p]?.flat;\r\n}\r\n\r\n/** Guess a projection from a URL / filename; null when nothing recognizable. */\r\nexport function detectProjection(url: string): Projection | null {\r\n const s = String(url).toLowerCase();\r\n const tb = /(^|[^a-z])(tb|ou|top.?bottom|over.?under)([^a-z]|$)/.test(s);\r\n const sbs = /(sbs|side.?by.?side)/.test(s);\r\n if (s.includes('360')) {\r\n if (tb) return '360-tb';\r\n if (sbs) return '360-sbs';\r\n return '360-mono';\r\n }\r\n if (s.includes('180') || s.includes('vr180')) {\r\n return s.includes('mono') ? '180-mono' : '180-sbs';\r\n }\r\n if (sbs) return s.includes('half') ? 'flat-sbs-half' : 'flat-sbs-full';\r\n return null;\r\n}\r\n","import * as THREE from 'three';\r\nimport { VRButton } from 'three/examples/jsm/webxr/VRButton.js';\r\nimport type { Projection } from '../types.js';\r\nimport { MODES } from './projections.js';\r\n\r\n/**\r\n * Maps a video onto the geometry for a chosen projection (inside-out 180/360\r\n * sphere or a flat screen plane) and packs stereo eyes via UV split + WebXR\r\n * layers. Sizes to its canvas's container (not the window), for embedding.\r\n */\r\nexport class StereoScene {\r\n readonly renderer: THREE.WebGLRenderer;\r\n readonly scene = new THREE.Scene();\r\n readonly camera: THREE.PerspectiveCamera;\r\n readonly vrButton: HTMLElement;\r\n readonly maxAnisotropy: number;\r\n\r\n private readonly canvas: HTMLCanvasElement;\r\n private readonly video: HTMLVideoElement;\r\n private readonly texture: THREE.VideoTexture;\r\n private readonly meshes: THREE.Mesh[] = [];\r\n private readonly frameCbs: (() => void)[] = [];\r\n private readonly animate = () => {\r\n for (const cb of this.frameCbs) cb();\r\n this.renderer.render(this.scene, this.camera);\r\n };\r\n private readonly ro: ResizeObserver;\r\n private currentMode: Projection;\r\n private currentSwap: boolean;\r\n\r\n constructor(opts: {\r\n canvas: HTMLCanvasElement; video: HTMLVideoElement;\r\n projection?: Projection; swapEyes?: boolean; fov?: number; supersampling?: number;\r\n }) {\r\n const { canvas, video, projection = '180-sbs', swapEyes = false, fov = 70, supersampling = 1.5 } = opts;\r\n this.canvas = canvas;\r\n this.video = video;\r\n this.currentMode = MODES[projection] ? projection : '180-sbs';\r\n this.currentSwap = swapEyes;\r\n\r\n this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true });\r\n this.renderer.setPixelRatio(this.pixelRatioFor(supersampling));\r\n this.renderer.setSize(this.w(), this.h(), false);\r\n this.renderer.xr.enabled = true;\r\n\r\n this.camera = new THREE.PerspectiveCamera(fov, this.w() / this.h(), 0.1, 1000);\r\n this.camera.position.set(0, 0, 0);\r\n this.camera.layers.enable(1); // desktop shows Layer 1 (left eye) for stereo modes\r\n\r\n this.maxAnisotropy = this.renderer.capabilities.getMaxAnisotropy();\r\n this.texture = new THREE.VideoTexture(video);\r\n this.texture.colorSpace = THREE.SRGBColorSpace;\r\n this.texture.minFilter = THREE.LinearFilter;\r\n this.texture.magFilter = THREE.LinearFilter;\r\n this.texture.anisotropy = this.maxAnisotropy;\r\n\r\n this.applyProjection(this.currentMode, this.currentSwap);\r\n video.addEventListener('loadedmetadata', () => {\r\n if (MODES[this.currentMode].flat) this.applyProjection(this.currentMode, this.currentSwap);\r\n });\r\n\r\n this.renderer.setAnimationLoop(this.animate);\r\n this.vrButton = VRButton.createButton(this.renderer);\r\n\r\n this.ro = new ResizeObserver(() => this.resize());\r\n this.ro.observe(canvas);\r\n }\r\n\r\n private w() { return this.canvas.clientWidth || 1; }\r\n private h() { return this.canvas.clientHeight || 1; }\r\n private pixelRatioFor(ss: number) { return Math.min(window.devicePixelRatio * ss, 4); }\r\n\r\n private planeAspect(mode: Projection) {\r\n const vw = this.video.videoWidth, vh = this.video.videoHeight;\r\n if (!vw || !vh) return 16 / 9;\r\n return MODES[mode].aspect === 'per-eye' ? (vw / 2) / vh : vw / vh;\r\n }\r\n\r\n private buildGeometry(mode: Projection): THREE.BufferGeometry {\r\n const kind = MODES[mode].geom;\r\n if (kind === 'sphere180') { const g = new THREE.SphereGeometry(500, 60, 40, -Math.PI, Math.PI, 0, Math.PI); g.scale(-1, 1, 1); return g; }\r\n if (kind === 'sphere360') { const g = new THREE.SphereGeometry(500, 60, 40); g.scale(-1, 1, 1); return g; }\r\n const h = 2.4, w = h * this.planeAspect(mode);\r\n const g = new THREE.PlaneGeometry(w, h); g.translate(0, 0, -2); return g;\r\n }\r\n\r\n private splitUV(geo: THREE.BufferGeometry, split: string, eye: 'left' | 'right') {\r\n if (split === 'mono') return;\r\n const uv = geo.attributes.uv.array as Float32Array;\r\n for (let i = 0; i < uv.length; i += 2) {\r\n if (split === 'sbs') uv[i] = uv[i] * 0.5 + (eye === 'right' ? 0.5 : 0);\r\n else if (split === 'tb') uv[i + 1] = uv[i + 1] * 0.5 + (eye === 'left' ? 0.5 : 0); // top = left\r\n }\r\n geo.attributes.uv.needsUpdate = true;\r\n }\r\n\r\n private clearMeshes() {\r\n for (const m of this.meshes) { this.scene.remove(m); m.geometry.dispose(); (m.material as THREE.Material).dispose(); }\r\n this.meshes.length = 0;\r\n }\r\n\r\n private applyProjection(mode: Projection, swap: boolean) {\r\n if (!MODES[mode]) mode = '180-sbs';\r\n this.clearMeshes();\r\n const cfg = MODES[mode];\r\n if (!cfg.stereo) {\r\n const m = new THREE.Mesh(this.buildGeometry(mode), new THREE.MeshBasicMaterial({ map: this.texture }));\r\n m.layers.set(0); // mono: desktop + both XR eyes\r\n this.scene.add(m); this.meshes.push(m);\r\n } else {\r\n const gL = this.buildGeometry(mode); this.splitUV(gL, cfg.split, 'left');\r\n const gR = this.buildGeometry(mode); this.splitUV(gR, cfg.split, 'right');\r\n const mL = new THREE.Mesh(gL, new THREE.MeshBasicMaterial({ map: this.texture }));\r\n const mR = new THREE.Mesh(gR, new THREE.MeshBasicMaterial({ map: this.texture }));\r\n mL.layers.set(swap ? 2 : 1); mR.layers.set(swap ? 1 : 2);\r\n this.scene.add(mL, mR); this.meshes.push(mL, mR);\r\n }\r\n this.currentMode = mode; this.currentSwap = swap;\r\n }\r\n\r\n setProjection(p: Projection) { this.applyProjection(p, this.currentSwap); }\r\n setSwapEyes(v: boolean) { this.applyProjection(this.currentMode, v); }\r\n setFov(deg: number) { this.camera.fov = deg; this.camera.updateProjectionMatrix(); }\r\n setSupersampling(ss: number) { this.renderer.setPixelRatio(this.pixelRatioFor(ss)); this.renderer.setSize(this.w(), this.h(), false); }\r\n getProjection() { return this.currentMode; }\r\n isFlat() { return !!MODES[this.currentMode].flat; }\r\n onFrame(cb: () => void) { this.frameCbs.push(cb); }\r\n pauseRendering() { this.renderer.setAnimationLoop(null); }\r\n resumeRendering() { this.renderer.setAnimationLoop(this.animate); }\r\n\r\n resize = () => {\r\n const w = this.w(), h = this.h();\r\n this.camera.aspect = w / h;\r\n this.camera.updateProjectionMatrix();\r\n this.renderer.setSize(w, h, false);\r\n };\r\n\r\n dispose() {\r\n this.ro.disconnect();\r\n this.renderer.setAnimationLoop(null);\r\n this.texture.dispose();\r\n this.clearMeshes();\r\n this.renderer.dispose();\r\n }\r\n}\r\n","import type { Camera } from 'three';\r\n\r\nconst DEG = Math.PI / 180;\r\n\r\n/** Clamp look angles (degrees) to keep the view inside the front hemisphere. */\r\nexport function clampAngles(lon: number, lat: number): { lon: number; lat: number } {\r\n return {\r\n lon: Math.max(-90, Math.min(90, lon)),\r\n lat: Math.max(-85, Math.min(85, lat)),\r\n };\r\n}\r\n\r\n/**\r\n * Pointer/touch drag → camera yaw/pitch, clamped to the front hemisphere.\r\n * Disabled while an XR session drives the head pose, and for flat-screen modes.\r\n */\r\nexport class LookControls {\r\n private lon = 0;\r\n private lat = 0;\r\n private dragging = false;\r\n private px = 0;\r\n private py = 0;\r\n private enabled = true;\r\n private readonly isPresenting: () => boolean;\r\n\r\n constructor(\r\n private readonly camera: Camera,\r\n private readonly dom: HTMLElement,\r\n opts: { isPresenting?: () => boolean } = {},\r\n ) {\r\n this.isPresenting = opts.isPresenting ?? (() => false);\r\n this.dom.addEventListener('pointerdown', this.onDown);\r\n this.dom.addEventListener('pointermove', this.onMove);\r\n this.dom.addEventListener('pointerup', this.onUp);\r\n this.dom.addEventListener('pointercancel', this.onUp);\r\n }\r\n\r\n private capture(fn: 'setPointerCapture' | 'releasePointerCapture', id: number) {\r\n try { this.dom[fn]?.(id); } catch { /* pointer not active */ }\r\n }\r\n\r\n private onDown = (e: PointerEvent) => {\r\n if (!this.enabled) return;\r\n this.dragging = true;\r\n this.px = e.clientX;\r\n this.py = e.clientY;\r\n this.capture('setPointerCapture', e.pointerId);\r\n };\r\n\r\n private onMove = (e: PointerEvent) => {\r\n if (!this.dragging) return;\r\n const dx = e.clientX - this.px;\r\n const dy = e.clientY - this.py;\r\n this.px = e.clientX;\r\n this.py = e.clientY;\r\n ({ lon: this.lon, lat: this.lat } = clampAngles(this.lon - dx * 0.15, this.lat + dy * 0.15));\r\n };\r\n\r\n private onUp = (e: PointerEvent) => {\r\n this.dragging = false;\r\n this.capture('releasePointerCapture', e.pointerId);\r\n };\r\n\r\n update() {\r\n if (this.isPresenting() || !this.enabled) return;\r\n const phi = (90 - this.lat) * DEG;\r\n const theta = this.lon * DEG;\r\n this.camera.lookAt(\r\n Math.sin(phi) * Math.sin(theta),\r\n Math.cos(phi),\r\n -Math.sin(phi) * Math.cos(theta),\r\n );\r\n }\r\n\r\n reset() { this.lon = 0; this.lat = 0; }\r\n\r\n setEnabled(v: boolean) {\r\n this.enabled = v;\r\n if (!v) { this.lon = 0; this.lat = 0; this.camera.lookAt(0, 0, -1); }\r\n }\r\n\r\n getAngles() { return { lon: this.lon, lat: this.lat }; }\r\n\r\n dispose() {\r\n this.dom.removeEventListener('pointerdown', this.onDown);\r\n this.dom.removeEventListener('pointermove', this.onMove);\r\n this.dom.removeEventListener('pointerup', this.onUp);\r\n this.dom.removeEventListener('pointercancel', this.onUp);\r\n }\r\n}\r\n","import type Hls from 'hls.js';\r\nimport type { StreamFormat } from './proxy.js';\r\n\r\n/**\r\n * Attaches a source to a `<video>`. Progressive files go straight on `video.src`;\r\n * HLS/DASH-as-HLS go through hls.js — which is imported dynamically, so consumers\r\n * who never play HLS don't pay for it (and it's an optional dependency).\r\n */\r\nexport class VideoSource {\r\n private hls: Hls | null = null;\r\n\r\n async attach(\r\n video: HTMLVideoElement,\r\n source: { url: string; format: StreamFormat },\r\n opts: { crossOrigin?: string | null } = {},\r\n ): Promise<void> {\r\n this.dispose();\r\n const co = opts.crossOrigin === undefined ? 'anonymous' : opts.crossOrigin;\r\n if (co === null) video.removeAttribute('crossorigin');\r\n else video.crossOrigin = co;\r\n video.playsInline = true;\r\n\r\n const { url, format } = source;\r\n const isHlsLike = format === 'hls' || format === 'dash';\r\n const nativeHls = video.canPlayType('application/vnd.apple.mpegurl') !== '';\r\n\r\n return new Promise<void>((resolve, reject) => {\r\n const cleanup = () => {\r\n video.removeEventListener('loadedmetadata', onLoaded);\r\n video.removeEventListener('error', onError);\r\n };\r\n const onLoaded = () => { cleanup(); resolve(); };\r\n const onError = () => {\r\n cleanup();\r\n const code = video.error ? video.error.code : 'unknown';\r\n reject(new Error(`Video failed to load (media error ${code}). Is the source reachable and CORS-clean?`));\r\n };\r\n video.addEventListener('loadedmetadata', onLoaded);\r\n video.addEventListener('error', onError);\r\n\r\n if (isHlsLike && !nativeHls) {\r\n import('hls.js').then(({ default: HlsCtor }) => {\r\n if (!HlsCtor.isSupported()) {\r\n cleanup();\r\n reject(new Error('HLS/DASH stream needs hls.js, which is unsupported in this browser.'));\r\n return;\r\n }\r\n this.hls = new HlsCtor({ enableWorker: true });\r\n this.hls.on(HlsCtor.Events.ERROR, (_e, data) => {\r\n if (data.fatal) { cleanup(); this.dispose(); reject(new Error(`HLS fatal error: ${data.type} / ${data.details}`)); }\r\n });\r\n this.hls.loadSource(url);\r\n this.hls.attachMedia(video);\r\n }).catch(() => {\r\n cleanup();\r\n reject(new Error('This looks like an HLS/DASH stream but hls.js could not be loaded. Install \"hls.js\".'));\r\n });\r\n } else {\r\n video.src = url;\r\n video.load();\r\n }\r\n });\r\n }\r\n\r\n dispose() {\r\n if (this.hls) { this.hls.destroy(); this.hls = null; }\r\n }\r\n}\r\n"],"names":["detectFormat","rawUrl","pathname","lower","ENDPOINTS","buildProxyUrl","proxy","format","base","params","name","value","MODES","PROJECTIONS","isFlatMode","p","_a","detectProjection","url","s","tb","sbs","StereoScene","opts","THREE","cb","w","h","canvas","video","projection","swapEyes","fov","supersampling","VRButton","ss","mode","vw","vh","kind","g","geo","split","eye","uv","m","swap","cfg","gL","gR","mL","mR","v","deg","DEG","clampAngles","lon","lat","LookControls","camera","dom","e","dx","dy","fn","id","_b","phi","theta","VideoSource","source","co","isHlsLike","nativeHls","resolve","reject","cleanup","onLoaded","onError","code","HlsCtor","_e","data"],"mappings":"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"}
|
|
@@ -16,6 +16,7 @@ export declare class StereoScene {
|
|
|
16
16
|
private readonly texture;
|
|
17
17
|
private readonly meshes;
|
|
18
18
|
private readonly frameCbs;
|
|
19
|
+
private readonly animate;
|
|
19
20
|
private readonly ro;
|
|
20
21
|
private currentMode;
|
|
21
22
|
private currentSwap;
|
|
@@ -42,6 +43,8 @@ export declare class StereoScene {
|
|
|
42
43
|
getProjection(): Projection;
|
|
43
44
|
isFlat(): boolean;
|
|
44
45
|
onFrame(cb: () => void): void;
|
|
46
|
+
pauseRendering(): void;
|
|
47
|
+
resumeRendering(): void;
|
|
45
48
|
resize: () => void;
|
|
46
49
|
dispose(): void;
|
|
47
50
|
}
|
package/dist/core.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as o, M as s, P as t, S as r, V as c, b as d, c as l, d as S, a as i, i as n } from "./VideoSource-
|
|
1
|
+
import { L as o, M as s, P as t, S as r, V as c, b as d, c as l, d as S, a as i, i as n } from "./VideoSource-CJGIX8gL.js";
|
|
2
2
|
export {
|
|
3
3
|
o as LookControls,
|
|
4
4
|
s as MODES,
|