frame academy

Learn how to create immersive, cross-platform webxr sites.

project 5: using audio, video, and 360 video in your webxr sites

Part 2: playing, pausing, and stopping audio

I hope you also took a crack at the challenge for Part 1! If you do the challenges, you'll be building your own immersive website that works on desktop, mobile, or VR.

Remember that if you have questions or want to share your work-in-progress, feel free to ask in our online community, or on Twitter with the hashtag #frameacademy

the sound component

In this part of the project, we'll get the music player we designed in Part 1 working so that it plays, pauses, and stops our audio asset. Hopefully, you've uploaded your own audio file so that you aren't stuck with mine. :)

The first thing we want to do is put the sound component on one of the entities in our scene. The sound component is a core A-Frame component, so you don't need to import it with any external script - it's available to use in any A-Frame project. You can see the documentation for the sound component here.

I'm going to add the sound component to the parent entity for the music player I made in Part 1. In the HTML document for the starter project, it's the entity at line 78 with id="musicpanel". I only need to configure one property for the sound component: src. This is where you set the source for the sound component so it actually has some audio to work with.

You can set the src of the sound component to be the hash sign followed by the id of the audio asset you've preloaded in a-assets. My audio asset in a-assets has id="song" (line 24), so here's what my element on line 78 looks like now with the sound component attached to it:

<a-entity id="musicpanel" position="-14.9 3 -6" rotation="0 90 0"
sound="src: #song">

Tip: Check out some of the other properties you can configure in the documentation for the sound component. There's a property called positional that lets you set whether the audio is spatialized or not so that it sounds like it's coming from a certain position in your scene (by default this property is set to true. There's also a volume property, and you can even control the rate at which the audio gets louder or softer as you move away towards it or away from it!

creating our new component

Now that we have our sound component set up with a src on one of our entities, we're ready to write the components that cause the sound to play, pause, or stop when the user clicks on one of our buttons. This first step will be familiar to you if you're coming from Project 4, but first we want to make a new JavaScript file in Glitch. Click the "New File" button on your Glitch project and give the file the name public/js/audiocontrols.js

In this new JavaScript file, we'll write all of the components we need.

To get the first component started, copy and paste the code you see in the public/js/componentstarter.js file into your new public/js/audiocontrols.js file, but change the name of the component to songplayer. It should look like this:

AFRAME.registerComponent('songplayer', {           
   init: function () {      
   
   }  

});

Awesome. Now we're ready to write our functions. Music to my ears!

writing a musicplay function

We want our code to run as soon as our component loads (fancy word for loads is initializes), so we can put all the code in the curly braces for init.

The first thing we need is a reference to the entity that has the sound component on it. Remember how we did this in the last project with document.querySelector? Let's do it again, using the id of the entity in our HTML with the sound component attached to it. That entity on line 78 has id="musicpanel", so here's how we do it. Inside of init, I'll put:

let audiosource = document.querySelector('#musicpanel');

I named the variable audiosource, but you can give it any name you want.

Next, we need to write a function that has the code that will run as soon as the user clicks the play button in our scene. We're going to make the function the same way we did in Project 4, but this time giving the function the name musicplay, because that's what the function will do - play our audio. You can give it any name you want. First you name the function with let, then give it the parentheses that hold function parameters (this will be empty for now), then => followed by curly brackets. I've left a blank line in the curly braces because that's where the code for the function will go. My function looks like this:

let musicplay = () => {    
 
}

So, what's the code that will actually cause the audiosource to play the audio on its sound component, the code that we put inside our new function? I looked in the documentation for the sound component and saw that in addition to the properties you can configure, it also has methods. A method is a function that is already attached to the component that will perform some action. The names of the three methods make clear what they do: pauseSound, playSound, and stopSound.

For the first component, we're playing the audio, so the method we want to call is playSound. Here's how we can call this method on the sound component that is attached to our audiosource. This code should go inside the curly braces of your musicplay function. I'll break it down below:

audiosource.components.sound.playSound()

Using the dot (.), first we drill down on audiosource to access the components on that entity, then we drill down further to access the sound component, and finally you drill down to call the playSound method that comes included with the component. You want to put that above code in the curly braces of your musicplay function. It's the code that will run as soon as musicplay is called.

Great! Now, although we've defined our musicplay function, we still haven't called it anywhere in our code. We want the musicplay function to run as soon as the user clicks on the play button in our scene, so we want to add an eventListener to our component so that our entity is listening for that "click" event. This should look familiar from Part 4 when we set this up for the sphereexpand function. Outside of musicplay, but still inside of init, you want to have this code that will set up your eventListener to listen for the 'click' event and, once detected, run our musicplay function:

this.el.addEventListener('click', musicplay);

Why am i putting the eventListener on this? It's because I'm going to attach this whole component to the play button entity in my HTML. Remember, this will reference whatever entity your component is attached to. So, if I want this function to run when the user clicks on the play button entity as defined in my HTML, I can attach the component to the play button and use this to refer to it. This is what my whole component looks like now:

AFRAME.registerComponent('songplayer', {      

   init: function () {                
   let audiosource = document.querySelector('#musicpanel');      

   let musicplay = () => {        
      audiosource.components.sound.playSound()        
   }            
   
   this.el.addEventListener('click', musicplay);      
   }
});

The final steps are done back in our HTML. First, make sure the JavaScript file that has our songplayer component in it is imported into my HTML, so I'll put this inside the head element of my HTML document:

<script src="public/js/audiocontrols.js"></script>

Finally, you actually need to attach the component to an entity in your scene. In this case, we want to put it on our play button because that's what the user will click on in order to trigger the event (we want that to be the this in our component that has the eventListener, remember?). So, on line 84 of the HTML, on the entity with id="playbox", attach our new component to the entity. Our component is named songplayer, so it's as easy as adding the songplayer attribute to the entity:

<a-plane id="playbox"  depth=".01" scale=".3 .3 .3" position="-.673 -1.22 .01" shader="flat" width="2" height="2" material="color:#191817; shader: flat" transparent="true" opacity=".9" songplayer>

Try it out: it works! Go up to the music player user interface on the left wall and click the play button:

challenge #2: create and use a songpauser component

In the project I embedded above this challenge, you'll see I've got the songplayer component created and its attached to the playbox entity in my HTML: it works. In the public/js/audiocontrols.js file, I also created a songstopper component, and in my HTML I've attached it to the stopbox entity. So both the play and stop buttons work in my project. In the same JavaScript file, I want you to create a songpauser component, and then make sure its attached to the right HTML entity so that it works in your scene when the user clicks the pause button on the music player user interface.

Tip: feel free to copy and paste one of the other components and then simply change what you need to change! Then just make sure your new component is attached to the right entity in your HTML.

Note: As you can see, you can define as many components as you want in one JavaScript file - you don't need to make a new file for each component. I generally only do this, though, when the components are closely related to each other. Since all of these components have to do with audio, I've put them all in the same audiocontrols.js file.

< go back to part #1
Uploading your assets
Move on to part #3 >
2D video