From 69511e4549348d59340af6cdc4c78f29c9f7c6f8 Mon Sep 17 00:00:00 2001 From: Aiden <68633820+awils27@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:20:55 +1000 Subject: [PATCH] Custom player --- README.md | 2 +- test-pages/demo.css | 82 ++++++++++++++++++ test-pages/index.html | 1 + test-pages/local-media-picker.js | 140 +++++++++++++++++++++++++++++++ test-pages/test-local-media.html | 58 +++++++++++++ 5 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 test-pages/local-media-picker.js create mode 100644 test-pages/test-local-media.html diff --git a/README.md b/README.md index cea9692..d7e13c7 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ When the page loads, the script binds every `[data-vr-web-launcher]` on the page - Control icons are embedded from [Lucide](https://lucide.dev/) SVG definitions, so no PNG icon assets are required. ## Demo -Run `npm run build`, then open `test-pages/index.html` through a local web server. The test hub is a gallery with launchers for flat 3D image, VR180 3D image, image carousels, flat 3D video, and VR180 3D video. +Run `npm run build`, then open `test-pages/index.html` through a local web server. The test hub is a gallery with launchers for flat 3D image, VR180 3D image, image carousels, flat 3D video, VR180 3D video, and a local file picker for browser-selected image/video media. For local experimentation, run: diff --git a/test-pages/demo.css b/test-pages/demo.css index 6efa4c9..52134de 100644 --- a/test-pages/demo.css +++ b/test-pages/demo.css @@ -204,6 +204,88 @@ a { font-weight: 650; } +.demo-local-panel { + display: grid; + gap: 16px; + width: min(100%, 760px); + padding: 18px; + border: 1px solid #d5d5cf; + border-radius: 8px; + background: #fff; +} + +.demo-field { + display: grid; + gap: 6px; + font-weight: 650; +} + +.demo-field input, +.demo-field select { + width: 100%; + min-height: 42px; + border: 1px solid #c7c7c0; + border-radius: 6px; + background: #fff; + color: inherit; + font: inherit; +} + +.demo-field input { + padding: 8px; +} + +.demo-field select { + padding: 0 10px; +} + +.demo-local-name { + margin: 0; + color: #606058; + font-size: 0.95rem; +} + +.demo-local-preview { + display: grid; + place-items: center; + width: 100%; + aspect-ratio: 16 / 9; + border-radius: 6px; + background: #111; + color: #d8d8d8; + overflow: hidden; +} + +.demo-local-preview img, +.demo-local-preview video { + display: block; + width: 100%; + height: 100%; + object-fit: contain; +} + +.demo-local-preview p { + margin: 0; + padding: 16px; + text-align: center; +} + +.demo-local-launch { + min-height: 44px; + border: 0; + border-radius: 6px; + background: #151515; + color: #fff; + font: inherit; + font-weight: 750; + cursor: pointer; +} + +.demo-local-launch:disabled { + background: #9a9a92; + cursor: not-allowed; +} + @media (max-width: 640px) { .demo-page { padding: 20px; diff --git a/test-pages/index.html b/test-pages/index.html index 1d5b9d6..6018799 100644 --- a/test-pages/index.html +++ b/test-pages/index.html @@ -111,6 +111,7 @@ VR180 image carousel 3D video VR180 video + Local media

Image tests use files in ../media/. Video tests expect ../media/sbs-video.mp4.

diff --git a/test-pages/local-media-picker.js b/test-pages/local-media-picker.js new file mode 100644 index 0000000..2485f68 --- /dev/null +++ b/test-pages/local-media-picker.js @@ -0,0 +1,140 @@ +const fileInput = document.querySelector('[data-local-media-file]'); +const launchButton = document.querySelector('[data-local-media-launch]'); +const projectionSelect = document.querySelector('[data-local-media-projection]'); +const preview = document.querySelector('[data-local-media-preview]'); +const fileName = document.querySelector('[data-local-media-name]'); + +let activeObjectUrl = ''; + +fileInput?.addEventListener('change', () => { + const file = fileInput.files?.[0]; + if (!file) { + clearLocalMedia(); + return; + } + + const mediaType = getMediaType(file); + if (!mediaType) { + clearLocalMedia(); + setPreviewMessage('Choose an image or video file.'); + return; + } + + if (activeObjectUrl) { + URL.revokeObjectURL(activeObjectUrl); + } + + activeObjectUrl = URL.createObjectURL(file); + updateLauncher(file, mediaType, activeObjectUrl); + renderPreview(file, mediaType, activeObjectUrl); +}); + +projectionSelect?.addEventListener('change', () => { + if (launchButton && projectionSelect) { + launchButton.dataset.projection = projectionSelect.value; + } +}); + +window.addEventListener('pagehide', () => { + if (activeObjectUrl) { + URL.revokeObjectURL(activeObjectUrl); + } +}); + +function clearLocalMedia() { + if (activeObjectUrl) { + URL.revokeObjectURL(activeObjectUrl); + activeObjectUrl = ''; + } + + if (launchButton) { + launchButton.disabled = true; + delete launchButton.dataset.src; + delete launchButton.dataset.mediaType; + delete launchButton.dataset.type; + delete launchButton.dataset.title; + } + + if (fileName) { + fileName.textContent = 'No file selected'; + } + + setPreviewMessage('Preview will appear here.'); +} + +function getMediaType(file) { + if (file.type.startsWith('image/')) { + return 'image'; + } + + if (file.type.startsWith('video/')) { + return 'video'; + } + + const extension = file.name.split('.').pop()?.toLowerCase(); + if (['avif', 'gif', 'jpeg', 'jpg', 'png', 'webp'].includes(extension)) { + return 'image'; + } + + if (['m4v', 'mov', 'mp4', 'ogv', 'webm'].includes(extension)) { + return 'video'; + } + + return ''; +} + +function updateLauncher(file, mediaType, objectUrl) { + if (!launchButton || !projectionSelect) { + return; + } + + launchButton.disabled = false; + launchButton.dataset.src = objectUrl; + launchButton.dataset.mediaType = mediaType; + launchButton.dataset.projection = projectionSelect.value; + launchButton.dataset.title = file.name; + + if (file.type) { + launchButton.dataset.type = file.type; + } else { + delete launchButton.dataset.type; + } + + if (fileName) { + fileName.textContent = `${file.name} (${mediaType})`; + } +} + +function renderPreview(file, mediaType, objectUrl) { + if (!preview) { + return; + } + + preview.replaceChildren(); + + if (mediaType === 'image') { + const image = document.createElement('img'); + image.src = objectUrl; + image.alt = file.name; + preview.appendChild(image); + return; + } + + const video = document.createElement('video'); + video.src = objectUrl; + video.controls = true; + video.muted = true; + video.playsInline = true; + video.preload = 'metadata'; + preview.appendChild(video); +} + +function setPreviewMessage(message) { + if (!preview) { + return; + } + + const placeholder = document.createElement('p'); + placeholder.textContent = message; + preview.replaceChildren(placeholder); +} diff --git a/test-pages/test-local-media.html b/test-pages/test-local-media.html new file mode 100644 index 0000000..bf340b6 --- /dev/null +++ b/test-pages/test-local-media.html @@ -0,0 +1,58 @@ + + + + + + Local Media Test + + + +
+
+
+
+

Local Media

+

Select a local SBS image or video, choose the projection, then launch it.

+
+ Back +
+ +

Checking immersive WebXR support...

+ +
+ + + + +

No file selected

+ +
+

Preview will appear here.

+
+ + +
+
+
+ + + + +