forked from EXT/VR180-Web-Player
Compare commits
11 Commits
2D
...
fix/quest-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
928fafa290 | ||
|
|
4183ae2530 | ||
|
|
d869f75e1e | ||
|
|
b559ea6ebf | ||
|
|
85baf3cd79 | ||
|
|
0a8cb8196c | ||
|
|
5087c3cbb2 | ||
|
|
0ef4ca56a5 | ||
|
|
ffb29bc4ec | ||
|
|
090ad5f315 | ||
|
|
957f1af8b0 |
39
README.md
39
README.md
@@ -1,43 +1,38 @@
|
|||||||
# VR180 Web Player
|
# VR180 Web Player
|
||||||
A web-based video player for 180 degree, 3D video.
|
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? You could build an app and deal with app stores. You could jump through some hoops to put it on YouTube but it will be limited to Meta headsets. Or you can use this web player and put it on your website.
|
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
|
||||||
Add the player script `<script type="module" src="vr180-player.js"></script>` before the closing body tag and use this HTML snippet:
|
1. Drop the `vr180player` directory in the root level of your website.
|
||||||
|
2. Link to the player CSS file `<link rel="stylesheet" href="vr180player/vr180-player.css">`.
|
||||||
|
3. Add the player script `<script type="module" src="vr180player/vr180-player.js"></script>` before the closing body tag.
|
||||||
|
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>
|
||||||
<source src="sbs-video.mp4" type="video/mp4">
|
<source src="sbs-video.mp4" type="video/mp4">
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
<button id="playBtn">Play</button>
|
|
||||||
```
|
```
|
||||||
This creates a button on your page. When VR is available, the button will be active and clicking it will begin the immersive experience.
|
|
||||||
|
|
||||||
<img src="https://github.com/user-attachments/assets/05db6208-6d42-48fa-a0da-55de41f35e6d" width=50%>
|
## How it works
|
||||||
|
When the webpage loads, the video file is embeded normally, with a play button positioned over the poster frame. When the user clicks play, the player script checks if `navigator.xr` exists. If it does, a VR experience is initiated. If not, it builds a rectilinear, 2D view of your video and plays it that way.
|
||||||
|
|
||||||
*Example Button*
|
**VR on Apple Vision Pro**
|
||||||
|

|
||||||
|
|
||||||
Once the video is playing, you can bring up video controls. When the video is over, you'll automatically exit the experience.
|
**2D in Safari on Mac**
|
||||||
|
|
||||||

|
You can drag the 2D video around to see things outside the frame.
|
||||||
|
<img width="1000" height="793" alt="2d" src="https://github.com/user-attachments/assets/094d30b7-7175-44ba-a700-d333196f8bb3" />
|
||||||
|
|
||||||
## Video Format
|
## Video Format
|
||||||
**The player only supports 2:1, side-by-side video using either H.264 or HEVC in an mp4 file.** It does not support over-under, MV-HEVC, or .aivu.
|
**The player only supports 2:1, side-by-side video using either H.264 or HEVC in an mp4 file.** It does not support over-under, MV-HEVC, APMP, or .aivu.
|
||||||
|
|
||||||
## Features
|
## Support
|
||||||
Tapping anywhere will bring up the controls. Without interaction they will go away in 10 seconds. Tapping outside of the controls will close them right away.
|
I'm not a developer and I might not be able to help you if you run into problems, want to customize this, or add new features (not that I won't try). I'm releasing this with the [unlicense](https://unlicense.org/) so you're free to do anything at all with it. That said, if you have ideas or want to contribute code, I'd love to [hear](mailto:hello@michaelverdi.com) from you.
|
||||||
- Play/Pause
|
|
||||||
- Rewind 15 seconds
|
|
||||||
- Skip 15 seconds
|
|
||||||
- Mute/Unmute
|
|
||||||
- Seek
|
|
||||||
- Exit VR
|
|
||||||
|
|
||||||
## Future
|
|
||||||
I'm not a developer. I used AI to help create this and give me the ability to post immersive videos on my website. I'm unlikely to be able to help you if you run into problems, want to customize this, or add new features. I'm releasing this with the [unlicense](https://unlicense.org/) so you're free to do anything at all with it. That said, if you have ideas or want to contribute code, I'd love to [hear](mailto:hello@michaelverdi.com) from you.
|
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
**Test it out in a headset!**
|
**Test it out!**
|
||||||
Open [https://verdi.github.io/VR180-Web-Player/](https://verdi.github.io/VR180-Web-Player/) in a browser on your headset and then click the Enter VR button.
|
Open [https://verdi.github.io/VR180-Web-Player/](https://verdi.github.io/VR180-Web-Player/) in a browser on your headset (or another device). Or check out this short film I made -> [Blandscape](https://michaelverdi.com/blandscape)
|
||||||
|
|||||||
@@ -15,11 +15,6 @@
|
|||||||
max-width: 750px;
|
max-width: 750px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
video {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
aspect-ratio: 16/9;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
BIN
sbs-video.mp4
BIN
sbs-video.mp4
Binary file not shown.
@@ -4,6 +4,12 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
}
|
||||||
|
|
||||||
#playBtn {
|
#playBtn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.176.0/build/three.module.js';
|
import * as THREE from 'https://unpkg.com/three/build/three.module.js';
|
||||||
|
|
||||||
let scene, camera, renderer, video, videoTexture, sphereMaterial;
|
let scene, camera, renderer, video, videoTexture, sphereMaterial;
|
||||||
let vr180Mesh;
|
let vr180Mesh;
|
||||||
@@ -428,25 +428,20 @@ function init() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const xrCamera = renderer.xr.getCamera();
|
// Use view matrix eye offset for reliable stereo detection
|
||||||
|
// This works consistently across Quest Browser updates and Safari/VisionOS
|
||||||
|
// Left eye has negative X offset, right eye has positive X offset
|
||||||
|
const viewMatrix = activeCamera.matrixWorldInverse;
|
||||||
|
const eyeOffsetX = viewMatrix.elements[12];
|
||||||
|
|
||||||
if (xrCamera && xrCamera.cameras && xrCamera.cameras.length >= 2) {
|
if (eyeOffsetX < 0) {
|
||||||
if (activeCamera === xrCamera.cameras[0]) {
|
// Left eye - show left half of SBS video
|
||||||
material.map.offset.x = 0;
|
material.map.offset.x = 0;
|
||||||
} else if (activeCamera === xrCamera.cameras[1]) {
|
|
||||||
material.map.offset.x = 0.5;
|
|
||||||
} else {
|
} else {
|
||||||
material.map.offset.x = 0;
|
// Right eye - show right half of SBS video
|
||||||
|
material.map.offset.x = 0.5;
|
||||||
}
|
}
|
||||||
material.map.repeat.x = 0.5;
|
material.map.repeat.x = 0.5;
|
||||||
} else {
|
|
||||||
const projMatrixEl8 = activeCamera.projectionMatrix.elements[8];
|
|
||||||
if (projMatrixEl8 < -0.0001) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize 2D camera
|
// Initialize 2D camera
|
||||||
@@ -1436,9 +1431,7 @@ function start2DMode() {
|
|||||||
|
|
||||||
// Position the canvas to match the video element
|
// Position the canvas to match the video element
|
||||||
const canvas = renderer.domElement;
|
const canvas = renderer.domElement;
|
||||||
canvas.style.position = 'absolute';
|
canvas.style.position = 'relative';
|
||||||
canvas.style.top = '0';
|
|
||||||
canvas.style.left = '0';
|
|
||||||
canvas.style.width = '100%';
|
canvas.style.width = '100%';
|
||||||
canvas.style.height = 'auto';
|
canvas.style.height = 'auto';
|
||||||
canvas.style.aspectRatio = '16/9';
|
canvas.style.aspectRatio = '16/9';
|
||||||
@@ -1733,6 +1726,12 @@ function renderXR(timestamp, frame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
// Ensure video texture is synchronized with render loop
|
||||||
|
// This prevents glitches from texture update timing issues on Quest browsers
|
||||||
|
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