Getting Started With Howler.js in React
By: Madeline Higgins
Tags: Howler, React, Games, Web Development, Music

Howler.js is a popular audio manipulation library that gives developers the power of the Web Audio API with the simplicity of HTML5 Audio. It’s particularly useful for games and sound effects, but it’s great for any browser-based audio that involves multiple sources playing independently.

Getting Howler set up can be a little tricky if you’re new to browser audio. Let’s take a look at getting started with Howler and game sounds using traditional React class components.

A brief overview of web audio challenges

Audio has a complicated history on the web. Before the HTML5 <audio> element was created, you couldn’t reliably play sound across different browsers without Adobe flash player (a plugin). Getting it set up in HTML looks like this:

<audio src='great_song.mp3'></audio>

It can also take multiple source options:

<audio controls>

<source src='great_song.webm' type='audio/webm' >

<source src='great_song.mp3' type='audio/mp3' >

</audio>

You have to include the controls keywords if you want to control playback. You can also use pure javascript:

const audio = new Audio()

audio.src = 'great_song.mp3'

audio.play()

The setup here is pretty straightforward. The downside is that the audio element has limited capabilities when it comes to sophisticated sound design — particularly in terms of controlling time precisely and playing multiple sounds at once.

The release of the Web Audio API marked a big advancement in web audio. It builds on HTML5 audio to represent audio elements as nodes on a directed graph known as the audio context, enabling more complex audio manipulation. Each sound represents a source node with a specific flow to its ‘destination’ (output).

<audio src='source_audio.mp3'></audio> const AudioContext = window.AudioContext

const audioContext = new AudioContext() const audioElem = document.querySelector(audio)

const channel = audioContext.createMediaElementSource(audioElem) channel.connect(audioContext.destination)

We instantiate the audio context and get a reference to our source sound. Then we connect the source to the global destination. There are many types of nodes in the Web Audio API. If we want to start manipulating volume, for example, we would create a new volume node and connect it to the graph between the audio node source and the destination (acting as an intermediary to change the volume before it reaches the destination). Such tools enable a lot more power for developers, with the tradeoff of more boilerplate code.

Like any good library, Howler takes the best of both technologies and wraps them into a convenient package. It relies primarily on the Web Audio API with fallback support for HTML5 audio wherever the API isn’t supported, while allowing developers to manually enable HTML5 audio when necessary (often for larger files, like background music). It takes a minimum of two file format options and implements whichever works best for a client’s browser — without needing to download both file types first. Howler manages Chrome’s much-lamented autoplay policy — which prevents sound from playing until a user interacts with a site — by unlocking autoplay when the page loads. It also takes care of boilerplate audio context setup and direct DOM manipulation, which makes it easy to plug in to your React application.

Getting started

Howler enables control of the global audio context through the Howler object, or you can control a group of sounds using a Howl. You can also control individual sprite sounds within a Howl. Most of your work with Howler will probably be within Howl objects. We’ll first look at how you can use a Howl object to apply sound effects using sound sprites before exploring background music.

Setting up the overhead

Howler obscures a lot of the initial Web Audio API setup, like creating the Audio Context and setting up buffers. All we have to do in React is import a Howl object with the appropriate sounds and press play (we’ll get to that shortly). But first, if you’re new to web audio (particularly the concept of audio sprites), there’s a bit of background to cover. If not, feel free to skip to the implementing Howler in React section.

Much game audio on the web is organized around the idea of sound ‘sprites.’ Similar to image sprite sheets, sounds are compiled into a single file and referenced by their location in the file to reduce the network load of multiple small files being requested repeatedly. But Howler doesn’t provide any mechanisms for sprite creation, so we have to handle that ourselves. We can use the ffmpeg wrapper audiosprite to create the sprite file and .json object that Howler will reference.

Ffmpeg converts audio and video files into different file types. You can install it using Homebrew, or go to the website and download the appropriate version for your device (the static 64 bit download for mac is fine for Mac users with OS 10.7 or later). Here’s a more in-depth explanation that might be helpful for Windows users.

Creating sprites

Once you have ffmpeg installed, you’re ready to use audiosprite: npm install audiosprite . From the root level of your project in the command line, cd into your /public folder, make a folder called effects ( mkdir effects ), and cd into that. Place any desired sound effect samples in this folder. We won’t cover where you’d source such sounds here, though your options range from original effects produced in a digital audio workstation (DAW) to sounds sourced from sample packs — even voice memos or found sounds on a cell phone. Audacity is a somewhat archaic but free DAW that can be useful in this circumstance.

Once your sounds are assembled, it’s time to create sprites. My files are called ‘correct_sound.mp3,’ ‘incorrect_sound.mp3’, and ‘win_sound.mp3’.

audiosprite -e ‘webm,mp3’ -f ‘howler’ -o effects correct_sound.mp3 incorrect_sound.mp3 win_sound.mp3

See a full rundown of audiosprite flags here. In the above code, -e stands for export, and specifies what file formats should be exported (you have to give at least two options). Webm is the preferred format (it delivers the best quality at the smallest size), with a fallback to mp3. Howler will look at the generated files and choose which works best for the user’s browser. -f is for format (howler). -o is for output, the name of the generated . json file (in this case, effects). Then list each file that you want to be a part of the sprite object. Make sure you’re running this command from inside the effects folder (where the samples should be located).

You’ll see the program start running. It shouldn’t take very long — if it starts to take more than a few seconds, your output file is probably getting too big (it’ll still work, but slowly). If so, you might want to consider restructuring to use more Howl objects.

A successful audiosprite conversion

You should now have a effects.json file available in your sounds folder. Run cat effects.json to see the sprite object. You should see a list of the file names you specified, each followed by an array of two numbers:

{

"urls": [

"effects.webm",

"effects.mp3"

],

"sprite": {

"correct_sound": [

0,

2063.673469387755

],

"incorrect_sound": [

4000,

2063.673469387755

],

"win_sound": [

8000,

4519.183673469389

]

}

The array positions represent where the sound starts on the sprite sheet and its duration. You can now reference this object in your program with Howler.

If you run into errors while generating the files with audiosprite, or some sprites aren’t being created, double check that you’re running audiosprite from within the correct directory ( /effects ) and that the file names and formats match what you’ve typed exactly. Also make sure to cd out of this folder and back to the root when you want to commit the added changes to git.

Implementing Howler in React

npm install howler

Let’s create a new sound object. To do this in a React class component, we’ll want to assign the sound object as part of the constructor.

import React from 'react'

import {Howl} from ‘howler’ class Game extends React.Component {

constructor() {

super()

this.soundEffects = new Howl({

src: [effects/effects.webm’, effects/effects.mp3’],

sprite: {

correct_sound: [0, 2063.673469387755],

incorrect_sound: [4000, 2063.673469387755],

win_sound: [8000, 4519.183673469389]

}

})

}



render() {

...

}

}

The src specifies which file will act as the sound source and should match the name we specified with the -o flag. It takes the file format options we gave with the -e flag in order of preference. Copy the array of times directly from the effects.json in your terminal. This will ensure that your sounds start and stop at the right points in the larger file.

To get effects to play in response to particular user actions, we can write a simple method on our Game class (or tie the sound effect into existing functionality if we have it).

handleCorrect = () => {

this.soundEffects.play('correct_sound')

}

Then we attach this function to the onClick of the relevant JSX elements in the render portion of the component. This process is simple for one-off sound effects that don’t need to be captured and referenced again (though we can do that too, as we’ll soon see).

render() {

return (

<div onClick={this.handleCorrect}>Correct item!</div>

)

}

It’s important to note that I used an arrow function when writing my handleCorrect method, so the this context in the above code is implicitly bound to component. Don’t forget to bind the function in the constructor if you use a function declaration. Read more on the this keyword here.

Background music

Managing background music is also pretty straightforward. We probably don’t want to use a sprite sheet for background music. If the sprite sheet file becomes too large, it takes a long time to load and defeats the purpose of a sprite sheet. Instead, we can create new Howl objects for different background tracks in their associated components.

cd out of your effects file and back into the /public file. Make a directory called music and add any music files you want in there. We’ll still run audiosprite on music files to generate the desired file types, but we won’t need to use the generated .json object. I’m using audiosprite again because I want to convert my mp3 to webm (a higher quality format), but it isn’t required if we aren’t creating sprites — it would still work fine if we dragged an mp3 into the folder and set the src equal to its path.

We’ll follow the same process of dragging the desired files into our music folder (mine is called ‘game_theme.wav’) and running an audiosprite command:

audiosprite -e ‘webm,mp3’ -f ‘howler’ -o theme game_theme.wav

Then set up the object in the constructor of whatever component will control the music:

import React from 'react'

import {Howl} from ‘howler’ class Game extends React.Component {

constructor() {

super()

this.music = new Howl({

src: [music/theme.webm’, music/theme.mp3’],

html5: true,

loop: true

})

}



render() {

...

}

}

As mentioned earlier, setting HTML to true ensures that our music is handled appropriately for its size and desired functionality. We also set looping to true so the sound keeps playing in the background while a user interacts with our app.

We initiate the music when the component mounts:

componentDidMount() {

this.music.play()

}

And pause when it unmounts:

componentWillUnmount() {

this.music.stop()

}

For a smoother experience, we probably want to fade the music instead of stopping it abruptly:

componentWillUnmount() {

this.music.fade(this.music.volume(), 0, 1000)

this.music.stop()

}

The above code tells Howler to fade from the current volume to zero over a duration of 1000 milliseconds, then stop what’s playing so it doesn’t continue silently in the background.

We can capture and manipulate individual sounds within a sprite object, too— if for some reason we wanted to make a small sprite object with tiny music loops, or capture and control longer-playing effects. Let’s see how.

class Game extends React.Component {

constructor() {

super()

this.soundEffects = new Howl({

src: [effects/effects.webm’, effects/effects.mp3’],

sprite: {

'correct_sound': [0, 2063.673469387755],

'incorrect_sound': [4000, 2063.673469387755],

'win_sound': [8000, 4519.183673469389]

}

})

this.source = 0

} render() {

...

}

}

This time, we also initiate a property called this.source in the constructor so we can capture the sound that’s playing. Any time we invoke this.sounds.play() , an audio ID is returned as an integer. We want to capture this ID so we can have some control over it. So we declare the source with an initial value of 0. If we want the sound to start playing as soon as the component mounts, we can do as before:

this.source = this.effects.play('win_sound')

This saves the audio ID of that specific instance of a sound; without it, we won’t be able to pause or otherwise control the sound we’ve started (running this.effects.pause('win_sound') won’t stop the sound — we have to run this.effects.pause(this.source) ).

Once we have access to the audio ID, we’re free to manipulate it as we wish. We can seek, change the rate, volume, and more. We just have to pass this.source as an argument when we want to control that sound. We can perform the same fade in our previous example by passing the source as the fourth argument.

componentWillUnmount() {

this.effects.fade(this.effects.volume(), 0, 1000, this.source)

this.effects.stop()

}

Most of what we’ve done here is utilizing the full HTML5 audio spec with the precision of the Web Audio API, plus some added functionality in the form of fading. Howler enables much more complexity by offering third-party plugin support to enable 3D sound/spatial audio, and more.

When might you NOT want to use Howler?

Howler is particularly convenient for controlling multiple independent sounds (i.e. game sounds, or a DJing app). Music production-based apps, which rely on more music theory structure and precisely timed sounds within a single timeline, might have more success with a library like Tone.js.

I once spent a day trying to force Tone to smoothly control multiple independent audio playback rates in a DJing app before realizing it wasn’t the right tool for that use case. Picking the right tool requires understanding how your project’s needs intersect with the challenges of the underlying technologies. Implementing it often involves sifting through blog posts for use cases semi-relevant to your specific struggle. I hope this blog post was relevant for your case!