Building a Simple MP3 Audio Player in Vue.js

Muhammad At-Tauhidi
6 min readNov 15, 2020

--

I recently rebuilt the platform for my language learning site One Month Lingo using Laravel 7 and Vue.js.

For the old site, I had simply been using the default HTML <audio></audio> tag to play mp3 audio, but for the rebuild I wanted to create a custom audio player that fit the look and feel of the new site design.

Pros and cons of using the HTML <audio></audio> tag

If you are just looking for the quickest way to add an MP3 player to your site, you can use the HTML <audio></audio> tag to add an HTML5 audio player to your site using just a few lines of HTML like this:

<audio controls>
<source src="https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_5MG.mp3" type="audio/mpeg">
Your browser does not support the audio tag.
</audio>

Including the “controls” option automatically generates an audio player that looks something like this (depending on your browser):

Default HTML Audio Player in Chrome Desktop

The advantage of using the default HTML audio player is that it makes it ridiculously easy to add a usable audio player to your site. The <audio></audio> tag is also supported by nearly every modern browser, so you don’t have to worry about compatability on older browsers, mobile browsers, etc.

But the downside is that it is nearly impossible to style the look and feel of the style player. Even worse, every browser renders the player somewhat differently, making it nearly impossible to reliably customize the player to match your site.

Building a custom audio player in Vue.js

Since I was already using Vue.js for most of the frontend for my site, I decided to build my own Mp3 audio player as a Vue component.

At the highest level, the basic steps for building the audio player will be to:

  • Add the default <audio></audio> tag to the page (hidden with style=“display:none”)
  • design the buttons, etc for the player in HTML and CSS
  • create methods in javascript to trigger audio events on the HTML Audio/Video DOM

Using the HTML Audio/Video DOM

The HTML Audio/Video DOM contains all of the methods, properties and events that we need to control the playback of audio on the page. As a quick example of how it works, the toggleAudio() method shown below can be used to toggle audio playback between play and paused states.

<audio id="audio-player">
<source src="https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_5MG.mp3" type="audio/mpeg">
Your browser does not support the audio tag.
</audio>
...toggleAudio() {
var audio = document.getElementById("audio-player");
if (audio.paused) {
audio.play();
} else {
audio.pause();
}
}

Using this, we can create a simple Play/Pause button in Vue by attaching our toggleAudio() method to a button @click event like so:

<button @click="toggleAudio()"> Play/Pause </button>

That’s pretty much all you need to create a very simple play/pause button in Vue.

Obviously, there are several other features that we would want to include in order to provide users with a usable, full-featured audio player:

  • An audio progress slider that shows the current audio playback position
  • A numerical display that shows the audio duration and elapsed playback time
  • The abilty to drag the playback slider to move the current audio playback to a new position
  • A “Loading…” indicator that hides the playback controls until the audio file has been loaded to the page

You can see a working demo of my Vue AudioPlayer component here:

Click to view Vue Mp3 Audio Player Demo

Here’s a slightly modified version of the code for the main AudioPlayer.vue component:

To use this component, you’ll need to import the AudioPlayer component and register it in your project’s main.js:

import AudioPlayer from './AudioPlayer.vue'Vue.component('audio-player', AudioPlayer);

Finally, to add the player to a page, include the <audio-player></audio-player> tag, while passing the url for your mp3 file and an id for the audio element as props:

<audio-player url="https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_5MG.mp3" playerid="audio-player" > </audio-player>

Note that I am using Tailwind CSS to define some of the styles used here. If you want to use this straight out of the box, you’ll need to have Tailwind installed in your project. For the demo, I’m simply using the CDN version of Tailwind using one line of code inserted into the <head> block of my index.html:

<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">

Making the Audio Player reactive to the audio play state

The trickiest part of this component is making it reactive to changes in the audio position as the audio plays. We need to capture these changes in order to show the progress bar that moves along with the audio playback. Going in the other direction, we also want to audio play position to change whenever the user drags the slider to a new position.

This sounds a lot like the familiar two-way data binding concept that reactive javascript frameworks like Vue and React are known for. However, we run into a bit of a snag here because there is no way to bind the input and display values directly to the Audio/Video DOM object.

To get around this, I created a ‘playbackTime’ property in the Vue component and set up a combination of an event listener and a Vue watcher to automatically keep the audio object’s ‘currentTime’ property in sync with the AudioPlayer component’s local ‘playbackTime’ variable:

To update the playbackTime property in time with the audio playback position, I added an event listener on the Audio/Video DOM “timeupdate” event. The timeupdate event automatically fires a few times per second while the audio is playing. Our event listener waits for the timeupdate event to fire, and then calls a method that sets the value of our component’s ‘playbackTime’ property equal to the ‘currentTime’ value of the mp3 file. This keeps our local playbackTime property in sync with the audio playback time (minus a few milliseconds — more on that in a minute).

To make the audio position respond to changes in user input, we basically do the same thing in reverse. We can create a draggable slider using an <input> element that is bound to the ‘playbackTime’ property via v-model. We then add a Vue watcher on the ‘playbackTime’ property that updates the value of audio.currentTime whenever the value of the local playbackTime property changes in response to user input. With this in place, the current audio position will be automatically updated whenver the user drags the slider to a new position.

Replacing the default ‘timeupdate’ event

Please note that the default audio ‘timeupdate’ event can be a bit wonky and the actual time it takes to fire can be unpredictable.

From the official docs:

“The event frequency is dependant on the system load, but will be thrown between about 4Hz and 66Hz (assuming the event handlers don’t take longer than 250ms to run).”

This wide range in variability makes it hard to ensure a consistent experience for all users. In some cases this can result in very slow updates, which makes updates to our progress bar appear very jumpy instead of smooth.

For this reason I am using a replacement to the default timeupdate event. I have inlcuded this in the demo code as freqtimeupdate.js. To use this, you’ll need to import the freqtimeupdate.js file in your main.js and replace the two references to ‘timeupdate’ AudioPlayer.vue to ‘freqtimeupdate’. By default, freqtimeupdate will fire every 100ms (10 times per second), which is enough to ensure a smooth and consistent UI for our progress bar.

To see the full working code, you can download the demo code from Github

--

--

Responses (1)