forked from EXT/VR180-Web-Player
Compare commits
7 Commits
revert-1-f
...
fix/quest-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40e3711925 | ||
|
|
c41ad12b32 | ||
|
|
dbe6e5b1d9 | ||
|
|
626b7f451b | ||
|
|
a56e36eaf6 | ||
|
|
bebcb3d355 | ||
|
|
f4fb9cf6bb |
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
index.html export-ignore
|
||||||
|
README.md export-ignore
|
||||||
|
sbs-video.mp4 export-ignore
|
||||||
|
poster.jpg export-ignore
|
||||||
@@ -4,10 +4,9 @@ A web-based video player for 180 degree, 3D video.
|
|||||||
Got an immersive video you want people to see with the Apple Vision Pro or Meta Quest headsets? Now you can put it on your website just like any other video! People will see the immersive 3D video if they have a capable headset or they'll get a 2D version on other devices.
|
Got an immersive video you want people to see with the Apple Vision Pro or Meta Quest headsets? Now you can put it on your website just like any other video! People will see the immersive 3D video if they have a capable headset or they'll get a 2D version on other devices.
|
||||||
|
|
||||||
## How to use it
|
## How to use it
|
||||||
1. Drop the `vr180player` directory in the root level of your website.
|
1. Link to the player CSS file `<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Verdi/VR180-Web-Player@v1.0.1/vr180player/vr180-player.css">`.
|
||||||
2. Link to the player CSS file `<link rel="stylesheet" href="vr180player/vr180-player.css">`.
|
2. Add the player script `<script type="module" src="https://cdn.jsdelivr.net/gh/Verdi/VR180-Web-Player@v1.0.1/vr180player/vr180-player.js"></script>` before the closing body tag.
|
||||||
3. Add the player script `<script type="module" src="vr180player/vr180-player.js"></script>` before the closing body tag.
|
3. And use this HTML snippet to embed your video:
|
||||||
4. And use this HTML snippet to embed your video:
|
|
||||||
```
|
```
|
||||||
<div id="vr-container">
|
<div id="vr-container">
|
||||||
<video id="vr180" poster="poster.jpg" title="Demo Video" crossOrigin="anonymous" playsinline>
|
<video id="vr180" poster="poster.jpg" title="Demo Video" crossOrigin="anonymous" playsinline>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||||
<title>VR180 Web Player</title>
|
<title>VR180 Web Player</title>
|
||||||
<link rel="stylesheet" href="vr180player/vr180-player.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Verdi/VR180-Web-Player@v1.0.1/vr180player/vr180-player.css">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
@@ -28,6 +28,6 @@
|
|||||||
<!-- UI elements will be dynamically inserted here by JavaScript -->
|
<!-- UI elements will be dynamically inserted here by JavaScript -->
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script type="module" src="vr180player/vr180-player.js"></script>
|
<script type="module" src="https://cdn.jsdelivr.net/gh/Verdi/VR180-Web-Player@v1.0.1/vr180player/vr180-player.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as THREE from 'https://unpkg.com/three/build/three.module.js';
|
import * as THREE from 'https://unpkg.com/three/build/three.module.js';
|
||||||
|
const _playerBase = new URL('.', import.meta.url).href;
|
||||||
|
|
||||||
let scene, camera, renderer, video, videoTexture, sphereMaterial;
|
let scene, camera, renderer, video, videoTexture, sphereMaterial;
|
||||||
let vr180Mesh;
|
let vr180Mesh;
|
||||||
@@ -111,7 +112,7 @@ function createPlayButton() {
|
|||||||
playButton.setAttribute('aria-label', 'Play video');
|
playButton.setAttribute('aria-label', 'Play video');
|
||||||
|
|
||||||
const playImg = document.createElement('img');
|
const playImg = document.createElement('img');
|
||||||
playImg.src = 'vr180player/images/play.png';
|
playImg.src = _playerBase + 'images/play.png';
|
||||||
playImg.alt = 'Play';
|
playImg.alt = 'Play';
|
||||||
|
|
||||||
playButton.appendChild(playImg);
|
playButton.appendChild(playImg);
|
||||||
@@ -429,24 +430,41 @@ function init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const xrCamera = renderer.xr.getCamera();
|
const xrCamera = renderer.xr.getCamera();
|
||||||
|
let isLeftEye = true; // Default to left eye
|
||||||
|
|
||||||
if (xrCamera && xrCamera.cameras && xrCamera.cameras.length >= 2) {
|
if (xrCamera && xrCamera.cameras && xrCamera.cameras.length >= 2) {
|
||||||
|
// Method 1: Direct camera reference comparison
|
||||||
if (activeCamera === xrCamera.cameras[0]) {
|
if (activeCamera === xrCamera.cameras[0]) {
|
||||||
material.map.offset.x = 0;
|
isLeftEye = true;
|
||||||
} else if (activeCamera === xrCamera.cameras[1]) {
|
} else if (activeCamera === xrCamera.cameras[1]) {
|
||||||
material.map.offset.x = 0.5;
|
isLeftEye = false;
|
||||||
} else {
|
} else {
|
||||||
material.map.offset.x = 0;
|
// Method 2: View matrix position (matrixWorldInverse.elements[12] is the X translation)
|
||||||
|
const viewMatrixX = activeCamera.matrixWorldInverse.elements[12];
|
||||||
|
const leftCamX = xrCamera.cameras[0].matrixWorldInverse.elements[12];
|
||||||
|
const rightCamX = xrCamera.cameras[1].matrixWorldInverse.elements[12];
|
||||||
|
|
||||||
|
const diffToLeft = Math.abs(viewMatrixX - leftCamX);
|
||||||
|
const diffToRight = Math.abs(viewMatrixX - rightCamX);
|
||||||
|
|
||||||
|
if (diffToLeft < 0.001 || diffToLeft < diffToRight) {
|
||||||
|
isLeftEye = true;
|
||||||
|
} else if (diffToRight < 0.001) {
|
||||||
|
isLeftEye = false;
|
||||||
|
} else {
|
||||||
|
// Method 3: Projection matrix asymmetry (elements[8] indicates eye offset)
|
||||||
|
const projMatrixEl8 = activeCamera.projectionMatrix.elements[8];
|
||||||
|
isLeftEye = projMatrixEl8 <= 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
material.map.repeat.x = 0.5;
|
|
||||||
} else {
|
} else {
|
||||||
|
// Fallback when xrCamera.cameras is not available
|
||||||
const projMatrixEl8 = activeCamera.projectionMatrix.elements[8];
|
const projMatrixEl8 = activeCamera.projectionMatrix.elements[8];
|
||||||
if (projMatrixEl8 < -0.0001) {
|
isLeftEye = projMatrixEl8 <= 0;
|
||||||
material.map.offset.x = 0; material.map.repeat.x = 0.5;
|
|
||||||
} else if (projMatrixEl8 > 0.0001) {
|
|
||||||
material.map.offset.x = 0.5; material.map.repeat.x = 0.5;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
material.map.offset.x = isLeftEye ? 0 : 0.5;
|
||||||
|
material.map.repeat.x = 0.5;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize 2D camera
|
// Initialize 2D camera
|
||||||
@@ -1731,6 +1749,10 @@ function renderXR(timestamp, frame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
// Sync video texture before render to ensure frame consistency
|
||||||
|
if (videoTexture && video && !video.paused && !video.ended) {
|
||||||
|
videoTexture.needsUpdate = true;
|
||||||
|
}
|
||||||
handleControllerInteractions();
|
handleControllerInteractions();
|
||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user