AngularJS Directive and Service for HTML5 Audio

PUBLISHED ON MAR 9, 2016 — ANGULARJS, HTML, JAVASCRIPT

While working on a project with my good friend we had the need to play audio playlists for a website. Originally we were using jPlayer but this seemed like overkill for what we needed. Plus, with our site built on the AngularJS framework, I wanted a more modular component like the rest of our code.

As such, I decided to create an AngularJS implementation to handle HTML5 audio - a service for handling the audio functionality that is needed by our website, and a directive for placing the player on our page and allowing user interaction.

The first thing to do was to set up our directive so we can start to see our player elements on the page.

All we need initially is pretty much an empty directive:

angular.module('app').directive('myPlayer', function() {
  return {
        restrict: "E",
        scope: {},
        templateUrl: "./player.html",
        link: function (scope, element, attrs) {

      }
    };
});

This just gives us a clean slate to start adding in our functionality. As you can see we’ve set a templateUrl, so we now need to create the html for that:

<div>
    <a>
        <span class="glyphicon glyphicon-step-backward"></span>
    </a>
    <a>
        <span class="glyphicon glyphicon-play"></span>
    </a>
    <a>
        <span class="glyphicon glyphicon-pause"></span>
    </a>
    <a>
        <span class="glyphicon glyphicon-step-forward"></span>
    </a>
</div>

This adds now functionality as it stands, but now we can insert the directive into our page and, using the glyphicons provided by Bootstrap, see basically what an audio player should look like.

Now we’re going to need a service so that we can actually have our player do something. First, we need to create the service so we can start adding things to it:

myApp.service("PlayerService", function () {

});

This just gives us a service that we can inject into our directive called PlayerService, but as it stands it doesn’t do anything. We’re going to want our service to return an object, that way we can set it on the scope of our directive so it can be more easily called from the template. We also need to add the properties and functions to give us some basic functionality - we’ll start with the basics such as playing, pausing and setting the tracks.

On the service create an object (I’ve just called it myPlayer in the example) and have the service return this object.

In this object create variables for the current list of tracks (for the demo I’ve filled this with some freely available tracks from another website), the current index that we’re playing and a value for whether or not the player is currently paused. We’ll need a reference to the audio object so that it can be used, so add that too.

We’re also going to need functions for playing and pausing audio.

Our play and pause functions are very basic - the play function simply plays the currently set audio, while the pause function will simply pause or play the audio element based on the variable we added:

angular.module('app').service('PlayerService', function(AudioFactory) {
  var myPlayer = {
    
    isPaused: true,
    // Sample tracks, to be replaced by the audio files you actually want to use
    trackList: [
      'http://www.stephaniequinn.com/Music/Allegro%20from%20Duet%20in%20C%20Major.mp3',
      'http://www.stephaniequinn.com/Music/Pachelbel%20-%20Canon%20in%20D%20Major.mp3', 
      'http://www.stephaniequinn.com/Music/Vivaldi%20-%20Spring%20from%20Four%20Seasons.mp3'
    ],
    currentIndex: 0,
     
    play: function () {
                myPlayer.isPaused = false;
                AudioFactory.src = myPlayer.trackList[myPlayer.currentIndex];
                AudioFactory.play();
            },
     
    pause: function () {
                myPlayer.isPaused = !myPlayer.isPaused;
                if (myPlayer.isPaused) {
                    AudioFactory.pause();
                } else {
                    AudioFactory.play();
                }
            },
  };
   
  return myPlayer;
});

I’ve set the source of the audio player to the current track (defaulting to the first) in the tracklist for this example. How you set the playlist and which song to play is up to you, based on what you’re wanting to achieve. For example, in my own work I’ve created a playlist with the option to play any track on the playlist - as such this takes in the playlist and the index of the track as parameters, and I set the source in the play function.

As you can see from this code, we’ve also injected an AudioFactory into our service, so we need to create that:

angular.module('app').factory('AudioFactory', function($document) {
  var audio = $document[0].createElement('audio');
  return audio;
});

The factory is very basic, simply creating and returning a HTML audio element, but doing this not only gives us an easy way of calling functions on this element from our service, it also makes it much easier to implement unit tests on our code (I’ll cover this in a later post).

Now we can inject this service into our directive and add a reference to it on the scope, so our directive will now look like this:

angular.module('app').directive('myPlayer', function(PlayerService) {
  return {
        restrict: "E",
        scope: {},
        templateUrl: "./player.html",
        link: function (scope, element, attrs) {
          scope.myPlayerService = PlayerService;
      }
    };
});

Now we’ve done that we can add calls to the service in our directive’s template, so your play and pause buttons will look like this:

<a>
    <span class="glyphicon glyphicon-play" ng-click="myPlayerService.play()"></span>
</a>
<a>
    <span class="glyphicon glyphicon-pause" ng-click="myPlayerService.pause()"></span>
</a>

If you run this, you can now see that clicking these buttons will do as advertised - playing when you click play, and click pause will pause the audio.

This doesn’t give users much control over the audio, so let’s add the ability to skip forwards and backwards.

To do this, we first need to add two more functions to our service, the next and previous functions. These will simply set the source of the audio accordingly, and call the play function to start the track:

previous: function () {
            if (myPlayer.currentIndex > 0) {
                myPlayer.currentIndex--;
                myPlayer.play();
            }
          },
 
next: function () {
            if (myPlayer.currentIndex < myPlayer.trackList.length) {
                myPlayer.currentIndex++;
                myPlayer.play();
            }
          }

Now we need to add a way to call these from our directive the same way we did with play and pause. From the original template we already have the buttons in place, we just need to add the click handlers to call the functions, making the template now look like this:

<div>
    <a>
        <span class="glyphicon glyphicon-step-backward" ng-click="myPlayerService.previous()"></span>
    </a>
    <a>
        <span class="glyphicon glyphicon-play" ng-click="myPlayerService.play()"></span>
    </a>
    <a>
        <span class="glyphicon glyphicon-pause" ng-click="myPlayerService.pause()"></span>
    </a>
    <a>
        <span class="glyphicon glyphicon-step-forward" ng-click="myPlayerService.next()"></span>
    </a>
</div>

And that’s all there is to it. These can now be easily re-used across projects, and (I think, at least) are nice and clean to read and use, and can be easily extended to add extra functionality or be tailored to your needs.

It’s quite easy from here to handle other aspects you might be interested in - such as displaying and interacting with a playlist, handling volume controls, a seek bar for the current track etc.

A working Plunk of the code can be found here: https://plnkr.co/LfuABr

As always, feel free to let me know if I’ve gotten anything wrong or of any way to improve things!

TAGS: ANGULARJS, AUDIO, HTML
comments powered by Disqus