Two-Minute Tutorial 1 Part 2

TMT1 P2 Add PhoneGap Accelerometer API & More jQuery Mobile UI to Media Player

This Two-Minute Tutorial (TMT1 Part2) for MDS AppLaud adds to the simple media player created in Part 1 (TMT1P1). Another mode of controlling the media player (Flip Mode) is added using the PhoneGap Accelerometer API. Additional jQuery Mobile features are used for the new UI, plus an example of overriding jQuery Mobile’s css formatting.

Prerequisites and Prep

  • Installation of the latest MDS AppLaud plugin, see Get Started
  • Completion of TMT1Part1 – TMT1 Part2 extends the app created in Part 1

Part 2 Description

  • Use jQuery Mobile for new Flip Mode UI
  • Use the PhoneGap Accelerometer API to detect device position change
  • All code provided in complete files as attachments at the end of this page

1. Add New UI Elements for Flip Mode

For review, the screen from the app created in Part 1 is shown below:

The first change to index.html reflects the new app’s features. Change the header text to:

    <h1>Audio Player with 2 Modes</h1>

Offering another mode of playing media should still be visually simple. In this new version the user will be able to control the media player in either manual mode or the new flip mode, but not both at the same time. Simplifying the logic behind switching modes will help simplify the underlying javascript. Review of the UI from Part 1 is below. The manual controls (Start/Pause/Stop) will remain unchanged.

The new UI for flip mode will be added in this section. The .hide() and .show() methods will be used to hide or show the controls depending on which mode the user has selected. The current and total media lengths will be moved to a footer (in the next section). This way, the header, footer, and new navbar (to switch between manual and flip modes) will be identical regardless of the mode selected, providing a consistent look.

In TMT1 Part 1, the page’s div only content area in index.html was already given an id:

    <div data-role=”content” id=”content-manual” data-theme=”a”>

A second content area, on the same page, will be created with a different id: id=”content-flip”. These two content areas will toggle between hide/show depending on the user’s selection, so that only one will show at a time.

Create the new, second content area with the following html immediately after the first content’s closing div, and before the page’s closing div in index.html:

    <div data-role=”content” id=”content-flip” data-theme=”a”>

<p>Switch <strong>ON</strong> to start Flip Control mode.</p>

<p>Flip phone face down to start music, face up to pause music.</p>

<div data-role=”fieldcontain” data-theme=”e”>
<label for=”pauseslider” data-theme=”e”>Start/Stop Flip Control Mode</label>
<select name=”pauseslider” id=”pauseslider” data-role=”slider” data-theme=”e”>
<option value=”off” data-theme=”e”>OFF</option>
<option value=”on” data-theme=”e”>ON</option>
</select>
</div>

</div> <!– /content-flip –>

The jQuery Mobile slider code in the content area shown above will be discussed in a later section. Now we have two content areas appearing on the same page.

To enable switching between modes, place the following navbar implementation after the header closing div (data-role=”header”) and before the first content div in index.html. Placing it after (out of) the header area allows us to separately control the color (data-theme=”c”) so that it can look different from the header.

    <div data-role=”navbar” data-theme=”c”>
<ul>
<li><a id=”nav-manual”>Manual Control</a></li>
<li><a id=”nav-flip”>Flip Control</a></li>
</ul>
</div><!– /navbar –>

Note that each item in the navbar is given an id (id=”nav-manual” and id=”nav-flip”). These unique id’s on each list item allow easy implementation of their tap handlers. To add the javascript for switching between the two modes, add the following to main.js at the end, but inside of the $(‘#page-home’).live(‘pageinit’) function:

    // Start with Manual selected and Flip Mode hidden
$(‘#nav-manual’).focus();
$(‘#content-flip’).hide();$(‘#nav-manual’).live(‘tap’, function() {
$(‘#content-flip’).hide();
$(‘#content-manual’).show();
});$(‘#nav-flip’).live(‘tap’, function() {
$(‘#content-manual’).hide();
$(‘#content-flip’).show();
});

The first two lines of code set the starting (default) state so the navbar focus will be on Manual Control, the manual content is shown by default, and the flip mode content will be hidden. The rest of the code implements the functions to be called when either navbar button is tapped. A tap on the Manual Control button hides the flip content and shows the manual content; a tap on the Flip Control button hides the manual content and shows the flip content. Only one content area will be shown at a time.

At this point, we can do a sanity check and run the app. The nav bar should appear under the header in grey, the manual mode content shows, and upon tapping Flip Control, the flip mode content will show while the manual mode buttons disappear.

2. Move the Current and Total Media Length data into a Footer

Although possibly non-standard, moving the media length data into a fixed footer produces a look and layout close to what I wanted.

Start by deleting the current and total media code in index.html from the manual mode content area:

    <div class=”ui-grid-a”>
<div class=”ui-block-a”> Current: <span id=”audio_position”>0 sec</span></div>
<div class=”ui-block-b”>Total: <span id=media_dur>0</span> sec</div>
</div><!– /grid-a –>

Insert the following footer implementation after the content areas, and before the page’s final div (last div in the page, NOT included in either content area):

<div data-role=”footer” data-theme=”e”>
<div data-role=”navbar”>
<ul>
<li><a> Current: <span id=”audio_position”>0 sec</span></a></li>
<li><a>Total: <span id=media_dur>0</span> sec</a></li>
</ul>
</div><!– /navbar –>
</div> <!– /footer –>

Note that we are using a navbar with two list items as the footer. As the UI defines one page with two toggled contents, a single footer for either mode simplifies display of the current media position and length. Whether in manual or flip mode, the media length data will be updated in one and the same place.

3. Make some adjustments to css

If you were to run the app at this point, you might see a few things you don’t like.

  • The full header text is not shown
  • Everything is bunched near the top
  • The footer floats after the content in a different place depending on mode

Through a not-too-unpleasant review of jQuery’s css file (jquery.mobile/jquery.mobile-1.0a2.css) and trial and error, I found that altering the following rules fixed the problems listed above.

.ui-header .ui-title {
margin-right:20px;
margin-left:20px;
}.ui-content { padding: 25px 15px; }.ui-page .ui-footer { position: absolute; bottom: 0px; left: 0px; }

An easy way to manage css override rules is in a separate css file. Copy and import the attached file (tmt1-2-override.css) into /assets/www directory. Add the following line to index.html after jQuery’s css file link:

<link rel=”stylesheet” href=”tmt1-2-override.css” type=”text/css”>

Run the app to verify the changes were picked up:

Note: we can’t allow the header text to be this wide when using a Back button or other button in the header as the buttons will be positioned on top of the text.

4. Add functionality in javascript to implement Flip Mode

The new Flip Mode will use the PhoneGap Accelerometer API to detect when the device is flipped over. When Flip Mode is selected by the user (using flip switch described below), the media player will start playing when flipped face down and pause when flipped face up. My Motorola Droid has a speaker on the back, so I am using this scheme.

Review the slider html code we already added to the flip content:

        <div data-role=”fieldcontain” data-theme=”e”>
<label for=”pauseslider” data-theme=”e”>Start/Stop Flip Control Mode</label>
<select name=”pauseslider” id=”pauseslider” data-role=”slider” data-theme=”e”>
<option value=”off” data-theme=”e”>OFF</option>
<option value=”on” data-theme=”e”>ON</option>
</select>
</div>

The slider has in id (id=”pauseslider”) which we will use in javascript to implement the action to take on a change event. When the user flips it to the ON position, the accelerometer will be used to detect the device flip and determine whether to play (face down) or pause (face up). When the user flips the switch to OFF, the media player will stop.

To help track which mode we are in, add the following global var declaration near the beginning of main.js:

var lastFlipVal = “off”;

The var lastFlipVal is initialized to “off” since we are starting the player in manual mode.

Add the block of code show below to the end of the $(‘#page-home’).live(‘pageinit’) function in main.js:

$(‘#pauseslider’).change(function() {
if (lastFlipVal !== $(this).val()) {
var newFlipVal = $(this).val();
if (newFlipVal === “off”) {
stopAudio();
}
lastFlipVal = newFlipVal;
setFlipper(newFlipVal);
} // else a false alarm, no change in on/off
});

When the user taps or swipes the switch, the slider gets a change event and can compare the current value of the switch to the new value (“on” or “off”). At the time of this writing, jQuery Mobile may report false positives (change event, but still same value), so we explicitly check the value each time. If the value has changed from “on” to “off”, we call stopAudio. For a change in either direction, we record the new flip value in the global var lastFlipVal, then pass this value (“on” or “off”) to the function that will start the accelerometer: setFlipper().

Add the following implementation of setFlipper and updateAcceleration before the $(‘#page-home’).live(‘pageinit’) function in main.js:

var accelWatch = null;
var options = { ‘frequency’ : 1900 };
var lastZ = null;function updateAcceleration(a) {
var changeThreshhold = 12;if (lastZ !== null) {  // not first time
var deltaZ = Math.abs(lastZ – a.z);
if (deltaZ > changeThreshhold) {
if (lastZ > 0) { $(“#playaudio”).trigger(‘tap’); }
else { pauseAudio(); }
lastZ = null;
return;
}
}
lastZ = a.z;
}

var setFlipper = function(state) {
if (state === “off”) {
if (accelWatch) {
navigator.accelerometer.clearWatch(accelWatch);
accelWatch = null;
}
} else {
accelWatch = navigator.accelerometer.watchAcceleration(
updateAcceleration,
function(ex) {
alert(“accel fail (” + ex.name + “: ” + ex.message + “)”);
},
options);
}
};

Referring to the PhoneGap Accelerometer documentation, we will provide 1) a success callback function, 2) a fail callback function, and 3) an option structure including a frequency value (in msec), to the watchAcceleration function. When I flip my phone at a regular speed, a frequency of just under 2 seconds works well to detect the face up to face down motion. It might also take just under 2 seconds for the music to start or pause because of this frequency.

The accelerometer provides the device’s acceleration, measured along three axes (x, y, z), every 1900 msecs. Acceleration data is passed the success callback function we provided, updateAcceleration(). For the face up to face down motion, the most acceleration happens only along the z axis (a.z in our function), so that is the only change we measure. We don’t want to trigger the media player based on other motions of the device in any case, so we ignore the x and y data (a.x and a.y).

The success function, updateAcceleration, checks for change greater than changeThreshhold to determine if it’s big enough to take action: play or pause the media. The play audio javascript takes a parameter (src), so we will invoke the play action by programmatically triggering a tap event on the playaudio button. If the device movement determines we are to pause, call pauseAudio. If the motion was not enough to do anything, record the current position for use in the next invocation.

In order to prevent the user from switching to manual mode while using flip mode, make use of the global var lastFlipVal, which has a value of “on” or “off” depending on the mode (manual or flip). Add the following comment and if statement to the tap handler definition for the nav-manual button. To show context, the entire block is shown below.

    $(‘#nav-manual’).live(‘tap’, function() {
// if flip is ON, don’t change pages
if (lastFlipVal === “on”) {
alert(“Set Flip Control OFF to switch to manual control.”);
return;
}
$(‘#content-flip’).hide();
$(‘#content-manual’).show();
stopAudio();
});

This is a simple, brute force way to maintain clarity of state: the user can’t switch to manual mode without flipping the switch to off, which in turn stops the media player.

The final bit of code to add (remember: all source files attached to this page!) just resets the text fields to 0 when the stopAudio function is called. Add these 2 lines to the end of stopAudio():

    setAudioPosition(“0”);
$(“#media_dur”).html(“0”);

5. Sit back, run the app, and listen to some music!

Try out manual mode, verifying the current time is paused when paused or reset on stop. Change to flip mode, flip the switch and turn your device face down. To make this app work optimally on your device, play with the frequency or changeThreshhold values.