Hello developer. Login with your existing account. New to Vodafone Developer? Register your account.

+ Login or create an account

Audio Code Notes

Introduction

The Vodafone developer widget samples series demonstrates how to use the JIL 1.2.2/WAC 1.0 widget APIs. These notes document the Audio sample widget.

The JIL/WAC audio API enables widgets to play many common types of audio files. The API provides a basic player interface including:

  • play, pause, resume, and stop functions
  • allows repeat play
  • supports playing of local and remote files, i.e. audio streaming

Using the JIL/WAC audio API you can create dedicated audio player apps as well as adding sound capabilities to your JIL apps and games.

The Audio sample widget implements a simple MP3 player app for local files with track selection, play and pause, and an elapsed time display. You can reuse the sample code as a starting point for your own projects.

Supported audio formats may vary between phones, but all phones should support MP3, and may also support MP4 (i.e. AAC/M4A) and Ogg Vorbis, as well as WAV and AMR.

Download the installable widget package and source files here - audio.zip (8.0 MB)

You can find more samples in this series at Widgets Getting Started .

JIL APIs used

The Audio widget uses the following JIL APIs:

  • Widget.Multimedia.AudioPlayer

JIL Features declared

The Audio widget declares the following JIL features:

Required features:
  • http://jil.org/jil/api/1.1/widget
  • http://jil.org/jil/api/1.1/device
  • http://jil.org/jil/api/1.1/multimedia
  • http://jil.org/jil/api/1.1/audioplayer
Optional features:

None.

Compatibility

  • Android: Yes
  • Symbian: No
  • Nokia N8: Yes (but see Note below)

Note: Runtimes for the latest Nokia Symbian phones may refuse to play audio files not packaged with the widget.

Overview

The Audio sample widget demonstrates how to use the JIL/WAC audio API to open and play audio files, and to pause, resume, and stop playback. The audio API is based on the AudioPlayer object, which is implemented by the JIL widget runtime. The AudioPlayer provides a simple-to-use player interface, as well as a callback method onStateChange() that widgets can use to track playback progress.

On launch, the widget displays a "Play" button; an elapsed play time display; and a track playlist.

Tapping "Play" plays the currently selected track from the playlist; tapping a track title in the playlist selects the track and plays it immediately, halting any track that is currently playing and toggling the "Play" button to "Pause". The selected track title is displayed in a side-scrolling marquee header. The timer displays the elapsed play time.

The screen shot below shows the Audio sample widget running on the HTC Desire:

The JIL/WAC audio API

The JIL/WAC audio API is based on the Widget.Multimedia.AudioPlayer object. You don't need to create your own instance of the AudioPlayer, instead simply invoke the static object which is implemented by the widget runtime, after first registering a callback method implementation with the player. To play a file, you must first open it. You do not need to close the file after playing; but you must stop the player if it is already playing before you can open a new file to play. The basic sequence of calls is therefore:

  • Register the callback method implementation by setting the value of Widget.Multimedia.AudioPlayer.onStateChange, for example: Widget.Multimedia.AudioPlayer.onStateChange = app._onAudioStateChange;
  • Implement the callback method to respond to state changes relevant to your app. You can ignore state changes you do not intend to respond to, for example if you only want to play one-off sound effects you may not care about intermediate states.
  • Stop the player in case it is already playing by calling Widget.Multimedia.AudioPlayer.stop().
  • Open the file to play by calling Widget.Multimedia.AudioPlayer.open(<String> fileUrl), passing the file URL as a string.
  • Play the file, passing the number of desired repetitions as parameter to Widget.Multimedia.AudioPlayer.play(<Number> playQty).
  • Depending on your app functionality, call Widget.Multimedia.AudioPlayer.pause() and Widget.Multimedia.AudioPlayer.resume() during playback to pause and resume playback.

The AudioPlayer.open() method abstracts all details of the physical file interface, and any codec, protocol, buffering, and caching requirements, which are handled transparently by the runtime; simply provide a file URL with appropriate syntax, invoking one of the supported protocols: file://, http://, https://, or rtsp://.

If no protocol is specified, the widget runtime interprets the URL as a simple file name. It will first search the media files in the widget package for the file; if the file is not found, the file:// specifier will be assumed and the local file system will be searched for the file. If the file:// specifier is given, the search will start in the local file system instead of the widget package. Supported protocols may vary between networks and/or runtime implementations, so handle errors appropriately.

If you need to control the player, for example to pause and resume playback, use the callback notifications to drive your app state management; see The state machine model, below. The sample provides a complete implementation of the callback, which you can adapt for your own apps. If you just need a fire-and-forget interface for in-app sounds then a minimal implementation will be sufficient.

Supported audio formats include (but note that some devices may not support all formats):

  • Standard multimedia formats for high quality audio recording including MP3, MP4 (i.e. AAC/M4A) and the open Ogg Vorbis standard
  • The mobile voice standard AMR format
  • Uncompressed WAV files

For small file size sound effects and samples, for example for games, WAV may offer the best performance since it avoids a decompression step and loads quickly. For typical multimedia apps playing music or podcasts, the standard multimedia formats offer the best trade off between quality and file size.

The AudioPlayer.play() method takes a number parameter playQty that specifies the number of desired repetitions. If 0 is specified, the file will not be played. For repeated sounds, for example looping samples, specifying the playQty avoids file loading and unloading time and therefore improves latency.

All method calls result in an exception with the type ExceptionTypes.UNSUPPORTED if the widget runtime does not support the method.

The open() and play() methods throw an exception with the type ExceptionTypes.INVALID_PARAMETER if any parameter is invalid or missing.

As well as the AudioPlayer itself, the Multimedia object includes the following additional related properties and methods:

  • Widget.Multimedia.isAudioPlaying indicates if there is any audio playing
  • Widget.Multimedia.getVolume() gets the current audio volume
  • Widget.Multimedia.stopAll() stops all currently playing multimedia files including audio files

The state machine model

The AudioPlayer defines a state machine that describes the current playback state: opened, playing, stopped, paused, and completed. The AudioPlayer transitions between states as part of the play cycle. State changes are reported to the onStateChange() callback; see audio.js below. See the JIL or WAC 1.0 API reference for the complete state machine diagram and transition chart – for links, see Find out more, below.

The runtime ignores calls that require "redundant" transitions, for example calling play() when the file is already playing, except when they are "impossible", in which case an exception is thrown – for example calling play() without first calling open(). Your app should handle exceptions to catch any such cases or other unsupported functionality.

Code notes

The Audio sample widget is a simple, but complete, example of an MP3 player application that uses the JIL audio APIs to play tracks from a playlist.

The sample consists of the following source code files:

  • config.xml
  • audio.html
  • audio.css
  • audio.js
  • common/css/base.css
  • common/css/forms.css
  • common/css/layout.css

MP3 music files are also included in the widget package.

The notes below walk through the code in more detail.

config.xml

Every widget must provide a config.xml configuration file that defines the widget package and is used at install time by the runtime.

For a JIL widget, this is also where you should declare the JIL features your widget uses, including required features and optional features. Declaring a feature initialises the appropriate runtime object; if you don't declare the feature, the object value may be undefined. Because the widget and device objects are implicitly required to access any JIL API, it is good practice to always declare both features as required="true", even though some runtimes may initialise these objects. It is a design choice whether you declare additional JIL APIs your widget uses as required or optional:

  • Declaring a feature as required will cause installation to fail if the device does not support that specific feature
  • Declaring a feature as optional will enable installation even if the device does not support that specific feature, but at runtime the API object will be undefined. Trying to access the API will lead to an undefined or unsupported exception.

The Audio sample widget declares JIL features as follows:

<?xml version="1.0" encoding="utf-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" ...>
    <name>audio</name>
    ...
    <feature name="http://jil.org/jil/api/1.1/widget" required="true"/>

    <feature name="http://jil.org/jil/api/1.1/device" required="true"/>
    <description>Example audio widget.</description>
    <feature name="http://jil.org/jil/api/1.1/multimedia" required="true"/>
    <feature name="http://jil.org/jil/api/1.1/audioplayer" required="true"/>
</widget>

For more general comments on the structure and contents of config.xml for JIL widgets, see the Basic Application sample widget code notes.

audio.html

The HTML page uses the HTML5 DOCTYPE declaration <!DOCTYPE html>. The code is straightforward: it loads page styles using style sheet <link> tags, defines all page elements, and finally loads the JavaScript code using <script> tags.

Page elements include:

  • Fixed position header and footer <div> elements
  • A <form> element that defines the main player view and contains UI elements including:
    • The elapsed time display progress-meter
    • The play button
  • A <ul> list element that contains the playlist

The filenames of the playlist audio files are embedded as anchor URLs in the list items, for example:

<ul class="listing nav" id="nav-song-list">
    <li><a href="sense.mp3" title="Tenpenny Joke - Sense">Sense</a></li>
    ....
</ul>

This is a good example of how widgets can use HTML elements to store app data; structural elements are real data containers, and not simply presentation elements. Instead of managing a separate playlist data structure, the playlist is embedded directly into HTML, and DOM methods are used to manipulate it.

The Play/Pause button is declared as a custom button using the type="button" attribute/value assignment.

The player form declaration action="#" is meaningless, but satisfies the syntactic requirement that an HTML form must always declare an action attribute.

audio.js

The audio.js code performs the following main functions:

  • Initialises the app, creating DOM element references that will be used later e.g. the Play/Pause button, playlist, and timer display
  • Initialises the main page elements including registering listener methods for timer updates and AudioPlayer state changes
  • Implements the registered listener methods

The code follows recommended practice by encapsulating all app functionality within a global app object. See the Basic Application sample code notes for more about how to structure your widget JavaScript code, as well as DOM handling and use of local objects to reduce the scope chain.

Much of the code in audio.js implements UI functionality including the elapsed play time display, Play/Pause button handling, and song selection from the playlist. Playback state changes reported to the _onAudioStateChange() callback method are used to manage the timer display and toggle the button text. The current state is stored in the app.playState variable:

// Listener for state changes of the Widget.Multimedia.AudioPlayer object.
_onAudioStateChange: function(state) {
    app.playState = state; // store the current state
    ...
    switch (state) {
        case "playing": // playing just started
	    ...
            break;
        case "opened": // an audio file has been opened and is ready play
	buttonPlay.disabled = false;
	    ...
    };
}

UI controls are initialised in the _initControls() method which adds event listeners to the Pause/Play button container and the playlist element. The two listener methods, which are called back by the widget runtime when either the button or the playlist receives a click event, are _onControlClicked() and _onSongListClicked().

The _onControlClicked() method invokes the audio API and implements the pause/play logic for the UI control; depending on the current playState, when the Play/Pause button is clicked, the AudioPlayer is called to resume(), pause(), or play() the opened audio file:

//  Event listener for the player controls. Uses event delegation.
_onControlClicked: function(event) {
    var target = event.target;
    if (target.name === "control") { // is the clicked element a control?
        if (target.value === "play") { // is the clicked element the play/pause button?
            if (target.disabled) { // do nothing if the button is disabled
                return;
            }
            switch (app.playState) {
                // If the player is paused, resume playing.
                case "paused":
                    Widget.Multimedia.AudioPlayer.resume();
                    break;
                // If the player is playing, pause it.
                case "playing":
                    Widget.Multimedia.AudioPlayer.pause();
                    break;
                // In all other cases start playback
                default: // opened, stopped, completed
                    // the `play()` method takes the number of desired
                    // repetitions as parameter.
                    Widget.Multimedia.AudioPlayer.play(1);
            }
        }
	// other controls could be added here
    }
}

The _onSongListClicked() method sets the track to play based on the user selection. The file URL string value is extracted from the selected element and passed to _playFile(). Note that the string value of the URL is extracted, not the URL reference object. The HTML <marquee> element is used to sidescroll the track title in the page header:

// Event listener for the song list
_onSongListClicked: function(event) {
    var target = event.target;
    // Don't open the file
    event.preventDefault();
        // Querying target.href would return the absolute URL,
        // getAttribute returns the value of the attribute.
    var songUrl = target.getAttribute("href");
    // Title to display
    var title = target.getAttribute("title");
    if (songUrl) {
        // Place a nice 90ies-style marquee in the header displaying
        // the song title
        app.dom.title.innerHTML = "\<marquee\>" + util.escapeHTML(title)
           + "\</marquee\>";
        // Play the song
        app._playFile(songUrl, true);
    }
}

The _playFile() method encapsulates the AudioPlayer initialisation open() and play() calls, to ensure that the player is in a good state before play() is called for the first time:

// Opens an audio file and optionally starts playback.
_playFile: function(fileName, autoPlay) {
    // Stop the audio player first to reach a state from which
    // open() can be called.
    Widget.Multimedia.AudioPlayer.stop();
    // Stop and reset the timer
    app.timer.stop();
    // Open the file to play
    Widget.Multimedia.AudioPlayer.open(fileName);
    if (autoPlay) {
        // TODO: remove timeout. Needed as workaround for opera bug
        // where "opened" fires after "playing"
        setTimeout(function() {
            // The `play()` method takes the number of desired
            // repetitions as parameter.
            Widget.Multimedia.AudioPlayer.play(1);}, 100);
    }
}

audio.css

The audio.css style sheet defines custom styles for the Play/Pause button, and for the elapsed play timer. Other button styles are set in the common forms.css style sheet.

Common

The Audio sample uses the common style sheets common/css/base.css, common/css/forms.css, and common/css/layout.css.

The base.css and layout.css styles, including the use of media queries and the fixed header and footer layouts, are discussed in the Basic Application sample code notes.

The forms.css style sheet contains basic button and label styles.

Concluding remarks

As the Audio sample demonstrates, the JIL audio API is easy to use and makes it possible to implement basic audio player functionality in apps, as well as supporting more general playing of samples and sounds.