Creating a Simple Audio Player with SoundManager 2

    Aurelio De Rosa
    Share

    Two very useful features introduced in HTML5 are the audio and video elements, which allow audio and video to be played natively in the browser. Before this innovation, people wanting to offer video or audio on their websites had to rely on third party software like Flash.

    Fortunately for us, audio and video have solved this problem, at least partially. I say partially for two main reasons. The first is that not all the browsers that developers are asked to support have implemented these elements, like some old mobile browsers and old versions of Internet Explorer. The second reason is that browsers haven’t reached agreement on the formats to implement, so today we have different browsers supporting different formats. This happened due to “political” reasons rather than technical ones, but whatever the cause, we have to face these two issues.

    With this in mind, if you want to use either of these elements in a website, not only do you have to provide different formats for each audio or video you want to play, but you also need to offer a fallback for browsers not supporting audio and video. To achieve this goal, you can either use the native methods and then install a library that offers you a Flash-based player with its own features and APIs, or you can use an all-in-one library that exposes the same APIs and provides a native solution or a Flash-based solution depending on the capabilities of the browser.

    In this article, we’ll look into the second option, by discussing one such all-in-one library called SoundManager 2.

    What is SoundManager 2?

    As described on the SoundManager 2 website, this library provides simple, reliable cross-platform audio under a single JavaScript API. SoundManager 2 is compatible with an incredible number of browsers, and I bet with all of those you have to support. To give you an idea, here is the list of the tested browsers and platforms:

    • Firefox (all versions), Windows/Mac
    • Safari 1.3+ (Mac) / All Windows versions
    • Mobile Webkit: iOS 4.0 devices, iPad 3.2 (original iPad iOS release) and newer
    • Android (2.3+, confirmed on 2.3.3.)
    • Google Chrome (all versions/OSes)
    • Internet Explorer 5.0+, Windows
    • Opera 9.10 (slightly buggy, 9.5+ ideal), Windows/Mac
    • Netscape 8.0+, Windows/Mac
    • Firefox 1.5+, Linux (Flash 9 beta).

    Can you believe it? Even Internet Explorer 5 is supported!

    SoundManager 2 wraps and extends both the HTML Audio and Flash Audio APIs, providing a single, unified sound API to JavaScript. The API is consistent regardless of the technology working under the hood to play sound.

    Now that you have an idea of what this library is and why you may want to use it, instead of listing the methods and properties available, I want to guide you through the creation of a small project developed using SoundManager 2.

    Creating a Simple Audio Player with SoundManager 2

    In this section, we’ll develop a simple yet functional audio player using HTML, CSS, and JavaScript with the support of SoundManager 2. To keep things as simple as possible, our player will allow a user to play a given audio file that we’ll hard code into the source. You can easily modify the source to allow users to choose what audio they want to play, perhaps using a select menu.

    Without further ado, let’s start writing the markup that powers our player.

    The Markup

    Our player gives a user the ability to:

    • play and stop an audio file
    • pause and resume an audio file
    • turn up and down the volume
    • move the current position of the audio file 30 seconds back and forth
    • know the duration of the file audio and the time elapsed since its start
    • know the current volume.

    Based on this list, you can see that we need six buttons: play/pause/resume, stop, volume up, volume down, move back, and move forth. The first button, “play”, performs various tasks based on the state of the audio file. If the audio file is playing, the button allows the user to pause the audio; if the audio file is paused, the button allows the audio to be resumed; and if the audio file is stopped or hasn’t been played yet, the button allows playing of the audio from the start. Each of the buttons will be associated with an icon so that our ideal user will have a pleasant experience.

    In addition to the buttons, we need three more elements to show the time elapsed, the total duration of the audio file, and the volume of the player (initialized to 100).

    A possible implementation of these requirements is shown below:

    <div class="player">
       <div class="player__audio-info">
          <div>
             Played
             <span class="player__time-elapsed">-</span> of
             <span class="player__time-total">-</span>
             <button class="player__previous button button--small">Move back</button>
             <button class="player__next button button--small">Move forth</button>
          </div>
          <div>
             Volume: <span class="player__volume-info">100</span>
             <button class="player__volume-down button button--small">Volume down</button>
             <button class="player__volume-up button button--small">Volume up</button>
          </div>
       </div>
       <button class="player__play button button--large">Play</button>
       <button class="player__stop button button--large">Stop</button>
    </div>

    As you can see, I’ve used the BEM notation to name the classes used to style the elements of the markup. Some of you may argue that for such a simple example it’s overkill. While this is surely true, I’m a strong believer that good habits start by using a technology or a methodology with simple examples and then building upon them. My opinion is that starting with a complex project is not ideal for someone who has just started learning. This explains why I’ve used it in this project.

    In addition to the BEM notation, you may have observed that I’ve employed the button element to mark up the buttons. This may seem pretty obvious, but it isn’t. Many developers, in this situation, would have used a elements or spans; but a elements should lead the user to somewhere, while a button is the element to use when an element should do something. And our player needs to do something.

    Now that we have the markup in place, let’s style it.

    Adding a Bit of Style

    I’ll keep the styles for this project pretty simple. The player will have a gray background, and a black border to highlight its boundaries. I’ll also “reset” the default style of the buttons so that they won’t look like typical buttons but will show the associated icons instead. Lastly, to switch from the “play” button to the “pause” button, I’ll create an is-playing class that clearly marks the state of the audio file by changing the icon displayed.

    The complete styles for our player are shown below:

    .player
    {
       display: inline-block;
       width: 300px;
       padding: 5px;
       background-color: #E3E3E3;
       border: 1px solid #000000;
    }
    
    .player span
    {
       font-weight: bold;
    }
    
    .button
    {
       text-indent: 200%;
       white-space: nowrap;
       overflow: hidden;
       border: none;
       padding: 0;
       background: rgba(255,255,255,0);
       cursor: pointer;
       vertical-align: bottom;
    }
    
    .button--small
    {
       width: 19px;
       height: 19px;
    }
    
    .button--large
    {
       width: 48px;
       height: 48px;
    }
    
    .player__audio-info
    {
       padding-bottom: 5px;
       border-bottom: 1px dotted #000000;
    }
    
    .player__audio-info div + div
    {
       margin-top: 10px;
    }
    
    .player__volume-info
    {
       display: inline-block;
       width: 1.5em;
    }
    
    .player__play
    {
       background-image: url("http://i60.tinypic.com/14mbep2.png");
    }
    
    .player__play.is-playing
    {
       background-image: url("http://i57.tinypic.com/idyhd2.png");
    }
    
    .player__stop
    {
       background-image: url("http://i61.tinypic.com/35mehdz.png");
    }
    
    .player__previous
    {
       background-image: url("http://i60.tinypic.com/sdihc5.png");
    }
    
    .player__next
    {
       background-image: url("http://i57.tinypic.com/2s1nm77.png");
    }
    
    .player__volume-down
    {
       background-image: url("http://i60.tinypic.com/331nom0.png");
    }
    
    .player__volume-up
    {
       background-image: url("http://i60.tinypic.com/ekkc1t.png");
    }

    Developing the Behavior

    We’ve finally arrived at the core of our project, the business logic. As you’ll see, it isn’t very complex, but in order to have more maintainable code, we’ll create a support function called formatMilliseconds, and an object called player. As the name implies, the function is used to convert a given amount of milliseconds into a string. More specifically, the string will be formatted as “H:MM:SS:mmm” as we’ll use it to show the total duration of the audio file and the time elapsed. The player object will be used to store the elements of the player so that we don’t have to retrieve them every time. This allows us to improve the performance of our project. Finally, we’ll use an audio variable to store the instance of the object that represents our audio file, created using SoundManager 2.

    The function and the variables we’ve just described are shown below:

    function formatMilliseconds(milliseconds) {
       var hours = Math.floor(milliseconds / 3600000);
       milliseconds = milliseconds % 3600000;
       var minutes = Math.floor(milliseconds / 60000);
       milliseconds = milliseconds % 60000;
       var seconds = Math.floor(milliseconds / 1000);
       milliseconds = Math.floor(milliseconds % 1000);
    
       return (hours > 0 ? hours : '0') + ':' +
          (minutes < 10 ? '0' : '') + minutes + ':' +
          (seconds < 10 ? '0' : '') + seconds + ':' +
          (milliseconds < 100 ? '0' : '') + (milliseconds < 10 ? '0' : '') + milliseconds;
    }
    
    var player = {
       btnPlay: document.querySelector('.player__play'),
       btnStop: document.querySelector('.player__stop'),
       btnPrevious: document.querySelector('.player__previous'),
       btnNext: document.querySelector('.player__next'),
       btnVolumeDown: document.querySelector('.player__volume-down'),
       btnVolumeUp: document.querySelector('.player__volume-up'),
       timeElapsed: document.querySelector('.player__time-elapsed'),
       timeTotal: document.querySelector('.player__time-total'),
       volume: document.querySelector('.player__volume-info')
    };
    var audio = null;

    At this point, we have to create a new object that represents our audio file, which means we have to assign a value to our audio variable. We’ll do that by using the createSound() method provided by the library. It allows us to define several properties, but the most important are id, which assigns an identifier to the audio file, and url, where you can set the URL to the audio file.

    The creation of this object is performed inside an anonymous function that is executed when the ready event of the library is fired, which means the library has performed all its actions and is ready to be used. We can specify what to do when the ready event is fired, and other settings, by passing an object literal to the setup() method.

    This is also where you should point to the Flash-based player of SoundManager 2, to use as a fallback. It’s set in the code below:

    soundManager.setup({
       useFastPolling: true,
       useHighPerformance: true,
       onready: function() {
          audio = soundManager.createSound({
             id: 'audio',
             url: 'http://freshly-ground.com/data/audio/mpc/20090119%20-%20Untitled%20Groove.mp3',
             whileloading: function() {
                player.timeTotal.textContent = formatMilliseconds(audio.durationEstimate);
             },
             whileplaying: function() {
                player.timeElapsed.textContent = formatMilliseconds(audio.position);
             },
             onload: function() {
                player.timeTotal.textContent = formatMilliseconds(audio.duration);
             },
             onfinish: function() {
                var event;
                try {
                   // Internet Explorer does not like this statement
                   event = new Event('click');
                } catch (ex) {
                   event = document.createEvent('MouseEvent');
                   event.initEvent('click', true, false);
                }
                player.btnStop.dispatchEvent(event);
             }
          });
       }
    });

    Once we have instantiated the object that represents the audio file, we have to add an event listener to each of the buttons of our player. Here is where our player object comes into play. Using it, we can refer to the buttons and the other elements of the player without performing a new selection every time. This is also where the SoundManager 2 library shows how easy it is to use.For example, let’s say that you want to play the audio: what method do you think the library exposes? play(), of course! And what if we want to stop the audio? For that we have stop(). Now, what if we want to know if the audio file is paused or not? The library provides a Boolean property called paused. For the total duration, we have a duration property instead. Very easy, isn’t it?

    To change the volume and to move the current position of the audio we have two methods: setVolume() and setPosition(). Each of them accepts a single number that updates the value you want to change. For example, if you want to set the volume to 50 (the scale ranges from 0 to 100), you can write:

    audio.setVolume(50);

    If you want to move the position to 10 seconds from the start you can write:

    audio.setPosition(10000);

    The value provided is 10000 because the method accepts milliseconds.

    The remaining part of code that implements the features we have described is presented below:

    player.btnPlay.addEventListener('click', function() {
       if (audio === null) {
         return;
       }
       
       if (audio.playState === 0 || audio.paused === true) {
         audio.play();
         this.classList.add('is-playing');
       } else {
         audio.pause();
         this.classList.remove('is-playing');
       }
    });
    
    player.btnStop.addEventListener('click', function() {
       if (audio === null) {
         return;
       }
    
       audio.stop();
       document.querySelector('.player__time-elapsed').textContent = formatMilliseconds(0);
       player.btnPlay.classList.remove('is-playing');
    });
    
    player.btnVolumeDown.addEventListener('click', function() {
       if (audio === null) {
         return;
       }
    
       var volume = audio.volume - 10 < 0 ? 0 : audio.volume - 10;
       audio.setVolume(volume);
       player.volume.textContent = volume;
    });
    
    player.btnVolumeUp.addEventListener('click', function() {
       if (audio === null) {
         return;
       }
    
       var volume = audio.volume + 10 > 100 ? 100 : audio.volume + 10;
       audio.setVolume(volume);
       player.volume.textContent = volume;
    });
    
    player.btnPrevious.addEventListener('click', function() {
       if (audio === null) {
         return;
       }
    
       var position = audio.position - 30000 < 0 ? 0 : audio.position - 30000;
       audio.setPosition(position);
       player.timeElapsed.textContent = formatMilliseconds(audio.position);
    });
    
    player.btnNext.addEventListener('click', function() {
       if (audio === null) {
         return;
       }
    
       var position = audio.position + 30000 > audio.duration ? audio.duration : audio.position + 30000;
       if (position === audio.duration) {
          var event;
          try {
             // Internet Explorer does not like this statement
             event = new Event('click');
          } catch (ex) {
             event = document.createEvent('MouseEvent');
             event.initEvent('click', true, false);
          }
          player.btnStop.dispatchEvent(event);
       } else {
          audio.setPosition(position);
          player.timeElapsed.textContent = formatMilliseconds(audio.position);   
       }
    });

    The Result

    We’ve completed our task, but before we can see the player in action, we have to include the SoundManager 2 library. You can do that by downloading the library and all its files from the SoundManager 2 website, or alternatively from a CDN.

    Remember that, in order to have the Flash-based player as a fallback, you have to include the SWF file that comes with the SoundManager 2 library. Once you have done that, you are ready to see the player live.

    The result of our project is shown below in the following JSFiddle:

    Conclusion

    In this tutorial, I’ve described SoundManager 2 – a library that allows you to use a unique set of APIs to deal with browsers that support the audio element and its API and those that don’t. As you have seen, SoundManager 2 supports an incredible number of browsers (including Internet Explorer 5!), so you can reliably use it in your projects.

    We’ve put some of SoundManager 2’s methods into action by creating a simple player that can perform basic tasks like playing and pausing an audio file, modifying the volume, and moving the audio back and forth. If you want to learn more about SoundManager 2, I suggest you read its extensive documentation. I hope you liked the library and the demo, and that you’ll share your opinions with us.