A Completely Immersive 360 Virtual Reality Experience

Michael McAnally
13 min readAug 26, 2020

--

Mind Palace 360VR app in the Chrome browser

This article is some what deprecated since recent changes to the browsers and a-frame/three.js have broken some of the code base upon which I wrote the application. Here is a more updated article:

========================================

Important Code Update:

Check it out here.

Be sure to have a VR headset readily available to get the full experience while tethered to a fast internet connection.

HOW TO USE: Use your controller left thumb stick or pad to move the interface and orbs (spheres) closer to you for teleport with your right hand trigger button and the laser pointer. Then select the green play button to view the coastal video. You can also play open source music (Mozart) by toggle selecting the purple music box

Also notice the shadow of the tripod for the 360 camera on the rock in the screen shot above and the VR button in the bottom right hand corner used to enter VR with your headset.

Entering WebXR in 2020 is still a little cumbersome, but its come a long way over the last three years. Lets now talk about how I did this and why?

First I wanted to experiment in VR and get a really immersive experience out of it that would help me remember places I had been and any associated memories. To do that and be as realistic as possible, I turned to using a 360 degree camera.

After experimenting with 360 degree video I realized the bandwidth required for a full moving 360 experience was prohibitive because it was highly dependent on having a really fast internet connection.

That was initially disappointing, but I did come up with a compromising solution to reduce the amount of data transferred and to provide for more VR locations as well. The only upside is that once its cached in the browser it usually loads really quickly the second and succeeding times.

Since I already had a regular 2D camera and phone which can shoot video, I thought maybe I might try combining this media with the 360 degree camera still shots and see what happens?

Success! Or I believe so . . .

Using A-Frame and a little HTML and JavaScript I was able to create a WebXR immersive experience inside a browser! Details about how it works follow.

In this code example (kept in one file on purpose), I use HTML5, JavaScript, A-Frame and a lot of media 360 degree photos, thumbnails, .mp4, .mp3, .wav, .png, .jpg files. All taken together these make both the immersive experience and the VR user interface. The code is fairly well commented, so lets break it down.

First I’m going to assume you have basic HTML and JavaScript knowledge and get right to the meat of how we do the VR. I use the latest A-frame repositories:

<script src="aframe-master/dist/aframe-v1.0.4.min.js"></script>
<script src="aframe-rounded-master/dist/aframe-rounded-component.min.js"></script>
<script src="superframe-master/components/text-geometry/dist/aframe-text-geometry-component.min.js"></script>
<script src="aframe-extras-master/dist/aframe-extras.min.js"></script>

A-Frame

Our base for: Virtual Reality Made Simple, Declarative HTML, Entity-Component Architecture, Documentation can be found here.

A-Frame Rounded Component

A-Frame Text Geometry

A-Frame Extras

Now that we have what we need to do VR in HTML, we should note the basic structure of A-Frame. First everything is found within <scene></scene> tags. Our assets, the media we are going to use, is found within <assets></assets> tags inside the <scene> tags.

<a-assets timeout="30000" ><!-- mixin used to animate selected  orbs  -->
<a-mixin id="marble" scale=".35 .35 .35" material="color: white" animation__rotation="startEvents: mouseenter; pauseEvents: mouseleave; resumeEvents: mouseenter; property: rotation; to: 0 360 0; loop: true; dur: 10000" animation__mouseenter="startEvents: mouseenter; pauseEvents: mouseleave; resumeEvents: mouseenter; property: components.material.material.color; type: color; to: white; dur: 500; " animation__mouseleave="property: components.material.material.color; type: color; to: gray; startEvents: mouseleave; dur: 500;" shadow ></a-mixin>
<!-- Replace with 360 images of your choosing -->
<img crossorigin="anonymous" id="orb1" src="assets/360/image/SAM_101_0101.jpg">
<img crossorigin="anonymous" id="orb2" src="assets/360/image/SAM_101_0152.jpg">
<img crossorigin="anonymous" id="orb3" src="assets/360/image/SAM_101_0370.jpg">
<img crossorigin="anonymous" id="orb4" src="assets/360/image/SAM_101_0128.jpg">

<!-- Replace with 360 thumbnails corresponding to the 360 images above to be wrapped around orbs -->
<img crossorigin="anonymous" id="orbthumb1" src="assets/360/image/thumb/SAM_101_0101_thumb.jpg">
<img crossorigin="anonymous" id="orbthumb2" src="assets/360/image/thumb/SAM_101_0152_thumb.jpg">
<img crossorigin="anonymous" id="orbthumb3" src="assets/360/image/thumb/SAM_101_0370_thumb.jpg">
<img crossorigin="anonymous" id="orbthumb4" src="assets/360/image/thumb/SAM_101_0128_thumb.jpg">
<!-- Replace MP4 video with your own -->
<video crossorigin="anonymous" id="video-src" src="assets/video/MP_beach.mp4"></video>

<!-- Our font -->
<a-asset-item id="optimer_bold" src="assets/fonts/optimer_bold.typeface.json"></a-asset-item>
<!-- Controls for the video player -->
<img crossorigin="anonymous" src="assets/img/play2.png" id="play" >
<img crossorigin="anonymous" src="assets/img/pause.png" id="pause" >
<img crossorigin="anonymous" src="assets/img/volume-normal.png" id="volume-normal" >
<img crossorigin="anonymous" src="assets/img/volume-mute.png" id="volume-mute" >
<img crossorigin="anonymous" src="assets/img/seek-back.png" id="seek-back" >
<!-- Music Box -->
<img crossorigin="anonymous" id="music-image_on" src="assets/img/music.png">
</a-assets>

To customize the code and replace the 360 images with ones from your own 360 camera, change the src address for orb1-orb5.

Remember to create the thumbnails as well and replace the src address for orbthumb1-orbthumb4. The thumbnails can be resized from the large images in a photoshop like tool to 512 w X 256 h. Try adding the word _thumb to the file name and put it in a separate named directory folder to avoid confusion. Also please note that the 360-degree images and corresponding thumbnails are images that get wrapped around the orbs and you while inside VR to give the complete interface and environmental immersive experience.

Thumbnail sizes in Photoshop

Now replace the mp4, video-src with the address of your video. Something associated with at least one of the main orbs (preferably orb1). For example I chose the coastline video because it showed motion and sound of the surf on the beach, creating an additive affect to the overall experience.

<!-- Replace labels and speak info for the orbs if you want them  -->
<a-entity id="OrbName_place1" class="clickable" position="-1.39624 -0.598 -4.5" rotation="-24.697 0 0" text-geometry="value: Rocky Beach; size: 0.10; font: #optimer_bold" material="color: #F4A460" onclick="playBlip();speakInfo('Beach at Lands End with San Francisco Golden Gate Bridge.');"></a-entity>
<a-entity id="OrbName_place2" class="clickable" position="0.61722 -0.598 -4.5" rotation="-24.697 0 0" text-geometry="value: Pier & Bridge; size: 0.10; font: #optimer_bold" material="color: #F4A460" onclick="playBlip();speakInfo('Pier and Bay Bridge.');"></a-entity>
<a-entity id="OrbName_place3" material="color: #F4A460" class="clickable" position="-0.89313 -1.11 -4" rotation="-24.697 0 0" text-geometry="value: Street Level; size: 0.10; font: #optimer_bold" onclick="playBlip();speakInfo('Street level at Salesforce Tower.');"></a-entity>
<a-entity id="OrbName_place4" material="color: #F4A460" class="clickable" position="0.1523 -1.11 -4" rotation="-24.697 0 0" text-geometry="value: Downtown; size: 0.10; font: #optimer_bold" onclick="playBlip();speakInfo('Downtown San Francisco on Market Street.');"></a-entity>

Now change the title labels right after the value: and before the ; and then the text to speech synthesis descriptions after speakInfo(‘ and before ‘); to match the orbs subject matter. We are almost done.

<body>
<button id="playButton" type="button">Play Music</button>
<audio id="playAudio" autoplay loop>
<source src="https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/MozartALittleNightMusic.mp3" type="audio/mpeg">
</audio>

You can change the .mp3 background music to tie it all together with something open source or to which you own the rights. This can be found near the <body> html tag in the source.

Now this is a complete listing of the code which can be found on Github with the other media files. You need to provide your own video.mp4 and music.mp3 files since Github says they are too large to upload, -sorry. You’ll find the address URLs on line 94 and line 72 respectively in the source. Just replace mine with addresses to yours. Please try to keep the files stored in the /video and /mp3 directories under /assets to keep media type separation neat.

You should be able to host this code very nicely on your own server on the internet as long as it has an SSL certificate, that is https.

8 Orb version

Here is a recent updated DEMO if you haven’t checked it out already and in addition I included a very large example with up to 8 orbs. Now that is really pushing it to the limit! But if you have the bandwidth here it is . . . This is really more suited to a LAN/WAN or localhost with Node.js.

Finally, some future thoughts, given the state of VR in 2020 and internet bandwidth requirements this is really a tethered experience. However, with the 5G rollout and a future headset more powerful than the Oculus Quest, which I’m sure will eventually happen; combined with a fully realized, mature and adopted WebXR specification, you might be able to change out the orbs dynamically from the server-side without ever leaving the HTML page.

In fact if I get a little sci-fi, which I tend to do, I can imagine a full immersive 360VR experience album with multiple interactive observers each wearing a headset in different geo-locations that would leave any old style homegrown movie-slideshow in the dust. What an experience that would be!

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width,shrink-to-fit=no,user-scalable=no,maximum-scale=1,minimum-scale=1">
<title>Mind Palace 360VR (Heavy graphic asset loading, please wait)</title>
<meta name="description" content="This is a 360 VR Mind Palace immersive UI. By Michael McAnally, August 25, 2020."></meta>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="gray-translucent" />
<!-- A-frame component libraries, look them up on Github -->
<script src="aframe-master/dist/aframe-v1.0.4.min.js"></script>
<script src="aframe-rounded-master/dist/aframe-rounded-component.min.js"></script>
<script src="superframe-master/components/text-geometry/dist/aframe-text-geometry-component.min.js"></script>
<script src="aframe-extras-master/dist/aframe-extras.min.js"></script>
<script type="text/javascript">
var Speech = true;
var audio1 = new Audio('assets/wav/action.wav');
var audio2 = new Audio('assets/wav/swoosh.wav');
function speakInfo(narration) {
var audio_msg = new SpeechSynthesisUtterance(narration);
if (Speech === true) {
window.speechSynthesis.speak(audio_msg);
}
}
function changeOrb(orb_num) {// change our orb sky
document.getElementById('orbSky').setAttribute('material', 'src: #orb' + orb_num.toString());
}
// audio https://stackoverflow.com/questions/47921013/play-sound-on-click-in-a-frame?answertab=active#tab-top
AFRAME.registerComponent('audiohandler', {
init:function() {
let playing = true;
let audio = document.querySelector("#playAudio");
this.el.addEventListener('click', () => {
if(!playing) {
audio.play();
} else {
audio.pause();
audio.currentTime = 0;
}
playing = !playing;
});
}
})
function playSwoosh() {
audio2.play();
}
function playBlip() {
audio1.play();
}
function playSound() {//alert("TEST Sound playing functional!!!");
}
</script>
</head>
<body>
<button id="playButton" type="button">Play Music</button>
<audio id="playAudio" autoplay loop>
<source src="https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/MozartALittleNightMusic.mp3" type="audio/mpeg">
</audio>
<a-scene background="color: #FAFAFA">
<a-assets timeout="30000" >
<!-- mixin used to animate selected orbs -->
<a-mixin id="marble" scale=".35 .35 .35" material="color: white" animation__rotation="startEvents: mouseenter; pauseEvents: mouseleave; resumeEvents: mouseenter; property: rotation; to: 0 360 0; loop: true; dur: 10000" animation__mouseenter="startEvents: mouseenter; pauseEvents: mouseleave; resumeEvents: mouseenter; property: components.material.material.color; type: color; to: white; dur: 500; " animation__mouseleave="property: components.material.material.color; type: color; to: gray; startEvents: mouseleave; dur: 500;" shadow ></a-mixin>
<!-- Replace with 360 images of your choosing -->
<img crossorigin="anonymous" id="orb1" src="assets/360/image/SAM_101_0101.jpg">
<img crossorigin="anonymous" id="orb2" src="assets/360/image/SAM_101_0152.jpg">
<img crossorigin="anonymous" id="orb3" src="assets/360/image/SAM_101_0370.jpg">
<img crossorigin="anonymous" id="orb4" src="assets/360/image/SAM_101_0128.jpg">

<!-- Replace with 360 thumbnails corresponding to the 360 images above to be wrapped around orbs -->
<img crossorigin="anonymous" id="orbthumb1" src="assets/360/image/thumb/SAM_101_0101_thumb.jpg">
<img crossorigin="anonymous" id="orbthumb2" src="assets/360/image/thumb/SAM_101_0152_thumb.jpg">
<img crossorigin="anonymous" id="orbthumb3" src="assets/360/image/thumb/SAM_101_0370_thumb.jpg">
<img crossorigin="anonymous" id="orbthumb4" src="assets/360/image/thumb/SAM_101_0128_thumb.jpg">
<!-- Replace MP4 video with your own -->
<video crossorigin="anonymous" id="video-src" src="assets/video/MP_beach.mp4"></video>

<!-- Our font -->
<a-asset-item id="optimer_bold" src="assets/fonts/optimer_bold.typeface.json"></a-asset-item>
<!-- Controls for the video player -->
<img crossorigin="anonymous" src="assets/img/play2.png" id="play" >
<img crossorigin="anonymous" src="assets/img/pause.png" id="pause" >
<img crossorigin="anonymous" src="assets/img/volume-normal.png" id="volume-normal" >
<img crossorigin="anonymous" src="assets/img/volume-mute.png" id="volume-mute" >
<img crossorigin="anonymous" src="assets/img/seek-back.png" id="seek-back" >
<!-- Music Box -->
<img crossorigin="anonymous" id="music-image_on" src="assets/img/music.png">
</a-assets>
<a-entity id="mouseCursor" cursor="rayOrigin: mouse"></a-entity>
<a-sky id="orbSky" material="src: #orb1" rotation="0 -90 0" ></a-sky>
<!-- Title of the Mind Palace -->
<a-entity id="NodeName" position="-1.04751 -2.14237 -3.55524" rotation="-39.4 0 0" text-geometry="value: Mind Palace 360VR; opacity: .5; size: .175; font: #optimer_bold" material="color: #F4A460"></a-entity>
<!-- Camera, Basic movement and selection -->
<a-entity id="cameraRig" movement-controls="" position="0 0 5" rotation="0 0 0">
<!-- camera -->
<a-entity id="head" camera="active: true" look-controls="pointerLockEnabled: true" position="0 1.6 0" ></a-entity>
<a-entity class="leftController" hand-controls="hand: left; handModelStyle: lowPoly; color: #15ACCF" tracked-controls vive-controls="hand: left" oculus-touch-controls="hand: left" windows-motion-controls="hand: left" visible="true"></a-entity>

<a-entity class="rightController" hand-controls="hand: right; handModelStyle: lowPoly; color: #15ACCF" tracked-controls vive-controls="hand: right" oculus-touch-controls="hand: right" windows-motion-controls="hand: right" laser-controls raycaster="showLine: true; far: 30; interval: 0; objects: .clickable, a-link;" line="color: limegreen; opacity: 0.5" visible="true"></a-entity>
</a-entity><!-- This lets us play music if browser allows it, enable audio -->
<a-box id="playButton" class="clickable" position="1.23745 -1.6294 -4.10106" rotation="-27.121 0 0" material="src: #music-image_on" scale="0.25 0.25 0.25" onclick="playBlip();" audiohandler shadow ></a-box>
<!-- Video Label -->
<a-entity id="VideoLabel" class="clickable" position="-0.47682 -0.598 -4.5" rotation="-24.697 0 0" text-geometry="value: Coastline Video; opacity: .5; size: 0.10; font: #optimer_bold" onclick="playBlip();speakInfo('To Play Video Select The Green Play Button. Other Video Controls Will Appear.');" material="color: #00FF00"></a-entity>
<!-- Replace labels and speak info for the orbs if you want them -->
<a-entity id="OrbName_place1" class="clickable" position="-1.39624 -0.598 -4.5" rotation="-24.697 0 0" text-geometry="value: Rocky Beach; size: 0.10; font: #optimer_bold" material="color: #F4A460" onclick="playBlip();speakInfo('Beach at Lands End with San Francisco Golden Gate Bridge.');"></a-entity>
<a-entity id="OrbName_place2" class="clickable" position="0.61722 -0.598 -4.5" rotation="-24.697 0 0" text-geometry="value: Pier & Bridge; size: 0.10; font: #optimer_bold" material="color: #F4A460" onclick="playBlip();speakInfo('Pier and Bay Bridge.');"></a-entity>
<a-entity id="OrbName_place3" material="color: #F4A460" class="clickable" position="-0.89313 -1.11 -4" rotation="-24.697 0 0" text-geometry="value: Street Level; size: 0.10; font: #optimer_bold" onclick="playBlip();speakInfo('Street level at Salesforce Tower.');"></a-entity>
<a-entity id="OrbName_place4" material="color: #F4A460" class="clickable" position="0.1523 -1.11 -4" rotation="-24.697 0 0" text-geometry="value: Downtown; size: 0.10; font: #optimer_bold" onclick="playBlip();speakInfo('Downtown San Francisco on Market Street.');"></a-entity>
<!-- Actual orbs and their placement -->
<a-sphere id="orb_place1" class="clickable" mixin="marble" position="-1 -1 -4.5" rotation="0 85 0" material="src: #orbthumb1" onclick="playSwoosh();changeOrb(1);" ></a-sphere>
<a-sphere id="orb_place2" class="clickable" mixin="marble" position="1 -1 -4.5" rotation="0 -75 0" material="src: #orbthumb2" onclick="playSwoosh();changeOrb(2);" ></a-sphere>
<a-sphere id="orb_place3" class="clickable" mixin="marble" position="-.5 -1.5 -4" rotation="0 -75 0" material="src: #orbthumb3" onclick="playSwoosh();changeOrb(3);" ></a-sphere>
<a-sphere id="orb_place4" class="clickable" mixin="marble" position=".5 -1.5 -4" rotation="0 45 0" material="src: #orbthumb4" onclick="playSwoosh();changeOrb(4);" ></a-sphere>
<!-- Translucent base for orbs -->
<a-rounded radius="0.1" top-left-radius="0.6" top-right-radius="0.6" bottom-left-radius="0.6" bottom-right-radius="0.6" position="-1.62926 -2.20165 -4.13398" scale="0.661 0.218 0.00001" rotation="-50 0 0" width="5" height="8" color="#657383" opacity=".35" shadow="" rounded=""></a-rounded>
<!-- Controls for display of video screen, seems to work nicely, javascript below </scene> tag below -->

<!-- MEDIAS HOLDER -->
<a-sound id="alert-sound" src="src: url(assets/wav/action.wav)" autoplay="false" position="0 0 0"></a-sound>
<a-video id="video-screen" src="#video-src" position="0.00193 1.02935 -5.4166" rotation="0 0 0" scale="0.564 0.697 1" width="8" height="4" rotation="0 0 0" visible="false"></a-video>
<!-- END MEDIAS HOLDER -->
<!-- CONTROLS -->
<a-image class="clickable" id="control-back" width="0.4" height="0.4" src="#seek-back" position="-0.37171 -0.92581 -4.49178" rotation="0 0 0" visible="true" scale="0.85 0.85 0.85"></a-image>
<a-image class="clickable" id="control-play" width="0.4" height="0.4" src="#play" position="0.03166 -0.92581 -4.49178" rotation="0 0 0"></a-image>
<a-image class="clickable" id="control-volume" width="0.4" height="0.4" src="#volume-mute" position="0.42174 -0.92581 -4.49178" rotation="0 0 0" visible="true" scale="0.75 0.75 0.75"></a-image>
<!-- END CONTROLS -->
<!-- PROGRESSBAR -->
<a-entity id="progress-bar" geometry="primitive:plane;height:0.1;width:4" material="opacity:0;transparent:true;visible:false" position="0.03516 -0.5536 -5.48963" rotation="0 0 0">
<a-plane id="progress-bar-track" width="4" height="0.1" color="gray" position="" opacity="0.2" visible="false" material="" geometry=""></a-plane>
<a-plane id="progress-bar-fill" width="3.0772968174269693" height="0.1" color="#7198e5" position="-0.4613515912865154 0 0.01438" geometry="" visible="false" material=""></a-plane>
</a-entity>
<!-- END PROGRESSBAR -->
</a-scene><!-- A Video Player Script (still works, when browser permissions enabled) -->
<script type="text/javascript">
//Google Code for un-audio mute
// Existing code unchanged.
window.onload = function() {
var context = new AudioContext();
// Setup all nodes
}// One-liner to resume playback when user interacted with the page.
document.querySelector('button').addEventListener('click', function() {
context.resume().then(() => {
console.log('Playback resumed successfully');
});
});
var AVideoPlayer = function() {
// Vals
this.duration = 0;
this.current_progress = 0;
this.progressWidth = 4;
this.paused = true;
// Elems
this.elProgressBar = null;
this.elProgressTrack = null;
this.elProgressFill = null;
this.elAlertSound = null;
this.elVideo = null;
this.elVideoScreen = null;
this.elControlBack = null;
this.elControlPlay = null;
this.elControlVolume = null;
this._initElements = function() {
this.elProgressBar = document.getElementById('progress-bar');
this.elProgressTrack = document.getElementById('progress-bar-track');
this.elProgressFill = document.getElementById('progress-bar-fill');
this.elAlertSound = document.getElementById('alert-sound');
this.elVideo = document.getElementById('video-src');
this.elVideoScreen = document.getElementById('video-screen');
this.elControlBack = document.getElementById('control-back');
this.elControlPlay = document.getElementById('control-play');
this.elControlVolume = document.getElementById('control-volume');
}
/**
* PROGRESS
*/
this.setProgress = function(progress) {
var new_progress = this.progressWidth*progress;
this._setProgressWidth(new_progress);
var progress_coord = this._getProgressCoord();
if (progress_coord != undefined) {
progress_coord.x = -(this.progressWidth-new_progress)/2;
this._setProgressCoord(progress_coord);
}
}
this._getProgressCoord = function() {
return AFRAME.utils.coordinates.parse(this.elProgressFill.getAttribute("position"))
}
this._getProgressWidth = function() {
return parseFloat(this.elProgressFill.getAttribute("width"));
}
this._setProgressCoord = function(coord) {
this.elProgressFill.setAttribute("position", coord);
}
this._setProgressWidth = function(width) {
this.elProgressFill.setAttribute("width", width);
}
/*
* UI SETTERS
*/
this.isProgressBarVisible = function(isVisible) {
this.elProgressTrack.setAttribute("visible", isVisible);
this.elProgressFill.setAttribute("visible", isVisible);
}
this.isControlVisible = function(isVisible) {
this.elControlBack.setAttribute("visible", isVisible);
this.elControlVolume.setAttribute("visible", isVisible);
this.elVideoScreen.setAttribute("visible", isVisible);
}
/*
* EVENTS
*/
this._addPlayerEvents = function() {
var that = this;
this.elVideo.pause();
this.elVideo.onplay = function() {
that.duration = this.duration;
}
this.elVideo.ontimeupdate = function() {
if (that.duration > 0) {
that.current_progress = this.currentTime/that.duration;
}
that.setProgress(that.current_progress);
}
}
this._addControlsEvent = function() {
var that = this;
this.elControlPlay.addEventListener('click', function () {
that._playAudioAlert();
if (that.elVideo.paused) {
this.setAttribute('src', '#pause');
that.elVideo.play();
that.paused = false;
that.isProgressBarVisible(true);
that.isControlVisible(true);
} else {
this.setAttribute('src', '#play');
that.elVideo.pause();
that.paused = true;
that.isProgressBarVisible(false);
that.isControlVisible(false);
}
});
this.elControlVolume.addEventListener('click', function () {
that._playAudioAlert();
if (that.elVideo.muted) {
that.elVideo.muted = false;
this.setAttribute('src', '#volume-normal');
} else {
that.elVideo.muted = true;
this.setAttribute('src', '#volume-mute');
}
});
this.elControlBack.addEventListener('click', function () {
that._playAudioAlert();
that.elVideo.currentTime = 0;
});
}
this._addProgressEvent = function() {
var that = this;
this.elProgressBar.addEventListener('click', function (e) {
if (e.detail == undefined || e.detail.intersection == undefined || that.duration === 0) {
return;
}
let seekedPosition = (e.detail.intersection.point.x+(that.progressWidth/2))/that.progressWidth;
try {
let seekedTime = seekedPosition*that.duration;
that.elVideo.currentTime = seekedTime;
} catch (e) {
}
});
}
this._playAudioAlert = function() {
if (this.elAlertSound.components !== undefined && this.elAlertSound.components.sound !== undefined) {
this.elAlertSound.components.sound.playSound();
}
}
/**
* MOBILE PATCH TO PLAY VIDEO
*/
this._mobileFriendly = function() {
if (AFRAME.utils.device.isMobile()) {
var that = this;
let video_permission = document.getElementById('video-permission');
let video_permission_button = document.getElementById('video-permission-button');
video_permission.style.display = 'block';
video_permission_button.addEventListener("click", function() {
video_permission.style.display = 'none';
that.elVideo.play();
that.elVideo.pause();
}, false);
}
}
this.init = function() {
this._initElements();
//this._determinateProgressWidth();
this.setProgress(this.current_progress);
this._addPlayerEvents();
this._addControlsEvent();
this._addProgressEvent();
this._mobileFriendly();
}
this.init();
}
let scene = document.querySelector('a-scene');
var cursor = document.querySelector('a-cursor');
/**
* CINEMA MODE
*/
scene.lightOff = function() {
scene.islightOn = true;
scene.removeAttribute('animation__fogback');
scene.setAttribute('animation__fog', "property: fog.color; to: #0c192a; dur: 800; easing: easeInQuad;");
}
scene.lightOn = function() {
scene.islightOn = false;
scene.removeAttribute('animation__fog');
scene.setAttribute('animation__fogback', "property: fog.color; to: #dbdedb; dur: 800");
}
/**
* AVideoPlayer
*/
var videoPlayer = new AVideoPlayer();
document.querySelector('#control-play').addEventListener('click', function () {
if (videoPlayer.paused) {
scene.lightOn()
} else {
scene.lightOff();
}
});
</script>
</body>
</html>

--

--

Michael McAnally
Michael McAnally

Written by Michael McAnally

Temporary gathering of sentient stardust. Free thinker, evolving human, writer, coder, artist, humanitarian. Semi-retired, doctorate level researcher.

Responses (1)