Previously I played with the Accelerometer API make a "roll the ball" labyrinth game.  While I found the accelerometer API easy to implement, my experiments with the Media API proved less fruitful.  For a while I was afraid I would be unable to show anything at all, but after a considerable amount of fiddling (and Googling), I was able to get it working.   

The Media API contains function for capturing audio and video as well playing back audio.  The first thing I did was add some fun sound effects to my "roll the ball" game.  And secondly, I made an Adobe ColdFusion Sound Board.  But first, I had to get it working.

Compile Errors

I started on this API like rest.  Right click on  your project, and choose properties. Then ColdFusion Mobile Project, Phonegap tab, New button, and finally features.  I ticked the checkbox for "Media" and saved.

When I went to compile my project, the PhoneGap Status view in Builder came back with the message "error".   All errors I had received so far on compilation were thrown from my local ColdFusion installation and the message could be found in the console output.  After a bit of poking, I took to build.phonegap.com and found it has a log you can access from the website of your build process.

Here is what I found:

-compile:
    [javac] Compiling 32 source files to /project/bin/classes
    [javac] /project/src/org/apache/cordova/mediacapture/Capture.java:32: cannot find symbol
    [javac] symbol  : class LocalFilesystemURL

    [javac] location: package org.apache.cordova.file
    [javac] import org.apache.cordova.file.LocalFilesystemURL;
    [javac]                               ^
    [javac] /project/src/org/apache/cordova/mediacapture/Capture.java:449: cannot find symbol
    [javac] symbol  : class LocalFilesystemURL

    [javac] location: class org.apache.cordova.mediacapture.Capture
    [javac]         LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(fp.getAbsolutePath());
    [javac]         ^
    [javac] /project/src/org/apache/cordova/mediacapture/Capture.java:449: cannot find symbol
    [javac] symbol  : method filesystemURLforLocalPath(java.lang.String)

    [javac] location: class org.apache.cordova.file.FileUtils
    [javac]         LocalFilesystemURL url = filePlugin.filesystemURLforLocalPath(fp.getAbsolutePath());
    [javac]                                            ^
    [javac] Note: Some input files use or override a deprecated API.
    [javac] Note: Recompile with -Xlint:deprecation for details.
    [javac] Note: /project/src/org/apache/cordova/mediacapture/Capture.java uses unchecked or unsafe operations.
    [javac] Note: Recompile with -Xlint:unchecked for details.
    [javac] 3 errors

BUILD FAILED
/home/ec2-user/android-sdk/tools/ant/build.xml:720: The following error occurred while executing this line:
/home/ec2-user/android-sdk/tools/ant/build.xml:734: Compile failed; see the compiler error output for details.

So The takeaway there is "cannot find symbol class LocalFilesystemURL".  In Java that happens when you haven't included a class that your code needs to compile.  Of course, I'm not manually including jars building classpaths at all here.  I just checked a box and asked it to build.  Honestly, this stumped me a bit because I wasn't sure where to begin.  A Google search didn't really bring back any hits on that error in combination with the class or file names.  My assumption was that ColdFusion was either missing a PhoneGap plugin or was using the wrong version of one.  Of course, since the compilation happens on the PhoneGap build server, I was fairly sure all my computer sent was the XML file, but it's hard to be sure since I don't have any visibility to that.

I played with several combinations of checking the boxes in CF Builder, from checking only Media, to adding in the File box, and finally just checking all of them.  The  only thing that proved however was that the inclusion of the Media caused a build failure with the error above, and everything else worked.

I also noticed another warning message in the PhoneGap website-- right next to the warning telling me I'm on an old version of PhoneGap.  This one warned me that I had conflicting plugin versions.  It didn't really go into details, but it urged me to check out the Plugins tab on the site.   On that page it showed me that I was using version 0.3.3 of the file-transfer plugin and version 0.2.4 of file plugin, and the latest versions were 0.4.6 and 1.3.1 respectively.  The page went on to state that if you left the version out of your config file, the latest version would be used for you.

Oh Config.xml, Wherefore Art Thou?

Then I remembered a button in CF Builder where you set the PhoneGap settings inside your project properties, called "Export Config".  It will dump out the Config.xml file that's being generated and sent to the PhoneGap build server.  I did this and took a look at how the plugins were being defined.

<gap:plugin  name="org.apache.cordova.inappbrowser" ></gap:plugin>
<gap:plugin  name="org.apache.cordova.device-motion" ></gap:plugin>
<gap:plugin  name="org.apache.cordova.network-information" ></gap:plugin>
<gap:plugin  name="org.apache.cordova.dialogs" ></gap:plugin>
<gap:plugin  name="org.apache.cordova.media-capture" ></gap:plugin>
<gap:plugin  name="org.apache.cordova.battery-status" ></gap:plugin>
<gap:plugin  name="org.apache.cordova.file-transfer" version="0.3.3" ></gap:plugin>
<gap:plugin  name="org.apache.cordova.contacts" ></gap:plugin>
<gap:plugin  name="org.apache.cordova.file" version="0.2.4" ></gap:plugin>
<gap:plugin  name="org.apache.cordova.device" ></gap:plugin>
<gap:plugin  name="org.apache.cordova.vibration" ></gap:plugin>
<gap:plugin  name="org.apache.cordova.media" ></gap:plugin>

Well dang, which of these is not like the other?  I highlighted them in case you couldn't find it.  The file-transfer and file plugins were the only 2 that specified a version.  The rest of the plugins had no version which means the build server was just using the latest.  I can only assume that some of the other plugins are dependant on Java classes only found in later versions of the file-transfer and/or file plugin.

Using the config.xml file that was generated, I edited it and saved it in the root of my project.  I simply removed the version bits like so:

<gap:plugin  name="org.apache.cordova.file-transfer"></gap:plugin>
<gap:plugin  name="org.apache.cordova.file"></gap:plugin>

Now I had to tel Builder to use it.  Luckily on the same screen that allows you to export your config.xml, you can also provide a path to a hand-built config.xml file.  I am committing my config.xml it unfortunately, the setting to use it does not appear to be part of the project.  That means if you check out my code from GitHub, you need to take the same manual configuration steps for it to compile.

Building

I crossed my fingers and ran the build and it passed!  I was overjoyed but my joys were quickly dashed on the rocks as my app still wouldn't play any sounds.  I had downloaded a wav file from the internet of a funny cartoon noise and placed it in a folder, then later in the web root but nothing would happen.  I guess I'll stop and show the actual API code here since we've made it this far.

function mediaPlay( src ) {
	cfclient.audio.play( src );
}

mediaPlay( 'boing_2.wav' );

There's also a "media" object you can create with the src to pass in, but as the play() function also just accepts a path I didn't see the point. 

No sound.  Nothing.  No error.  No warning.  No grumpy cats.  I tried rearranging the code.  I tried wrapping it in extra try/catch statements.  I even tried digging through the JavaScript source code to see how it worked .  Finally I figured out the docs stated I needed to be using a FULL PATH to my file.  That was unexpected since all images, etc have all just been relative to the root.  I also have no idea where these files are stored on the the device's storage.

 I didn't see expandPath() listed as a supported CFClient function, but I tried it anyway to no effect.  I searched the CFCLient docs but there were no mentions on how to determine the root of the site.  Finally I began Googling for generic PhoneGap answers and found a number of blogs and StackOverflow questions on this.   I was surprised and disappointed to find that PhoneGap has no solution for this.  Developers are forced to detect the device they are operating on and hard-code the proper path.  

  • On Android it's /www/android_assets/boing.mp3
  • On iOS it's /var/mobile/Applications/{GUID}/{appName}.app/www/boing.mp3 though supposedly just boing.mp3 will work.
  • I couldn't even find information on other operating systems.

This confuses me greatly since I thought PhoneGap is supposed to sort through all this cruft for us, yet blog posts from actual PhoneGap contributors were suggesting hacky device-detection and hard-coding.  I don't have an iPhone to test on but I wanted to at least write code that has a chance of working so I borrowed this small function as the lesser of several evils found in StackOverflow answers.

// This is to return a device-generic fully qualified path to the root of the PhoneGap app.  
function getPhonegapPath() {
	var path = window.location.pathname;
	return path.slice(0, path.indexOf("/www/") + 5);
}

This takes the path of the current page (index.html) from the window treats it as a slash-delimited list, and carves off the file name and possible hash and the end.  

So after all this, still no sound. Nada.  No errors.  No warnings.  No dancing hamsters.  

Sound Advice

Finally it occurred to me something might be wrong with my wav file (even though it played on my PC fine).  I recorded a short sound bite and saved it as an MP3 and voila- it worked!  I had never been so happy to hear a cartoon sound effect in my life.  Maybe wav files don't work so I tried a different wav.  Nope, that worked as well.  Apparently there was  just something about the bit rate, or something in that one random wav file I downloaded off the Internet that was keeping it from playing.  

I don't think CFClient is to blame for this.  I have a feeling this is an idiosyncrasy of Phonegap itself, but it's a pretty poor show in my opinion to give me zero feedback when it can't play my file.  Sadly, I had literally spent  most of my day getting to this point so the rest of the Media API would have to wait.  

Bonk! Bipp! Blam!

 The first thing I did was add a series of random sound effects to the "roll the ball" game.  Every time the ball contacts the sides of the square, your phone vibrates AND you hear some bouncing/crashing sound.  I've got to say it was pretty entertaining for my kids and me.  I also improved the collision detection such that if the ball hits the wall, the game waits until it rolls away from the wall and back into the wall again to reply the sound effect.  

// Play a random sound effect
function randomBoing() {
	var sounds = [
		'media/audio/boing_2.mp3',
		'media/audio/boing_1.mp3',
		'media/audio/bop.mp3',
		'media/audio/dolphin.mp3',
		'media/audio/drip.mp3',
		'media/audio/explosion.mp3',
		'media/audio/glass_breaking.mp3'
	];
	
	var sound = sounds[ randRange( 1, arrayLen( sounds ) ) ];
	mediaPlay( sound );
}

And since you can't hear code, this is an artist's representation of what the game sounds like:

Food For Thought

I decided it was time to get a proper icon for my app and clean up the name to remove the hyphen.  By default, the PhoneGap build server adds a generic icon for you.  Now that I'm managing my own config.xml file, I just Googled how to add an icon to a PhoneGap and found the answer straight away.

<icon src="images/icon.png" />

This file, thank goodness, is relative to the root!  I crafted a quick icon from a Google image search.  Mmm, I love me a sampler!

The only annoying ting about changing my app name is the PhoneGap build server threw an error that my limit of private repositories had been reached.  You can have as many apps as you want as long as the code is on GitHub.  However,  from what I can tell, the CF Builder process requires you to use the "upload a zip" process which is considered "private" and the build server only allows you to have one of those projects.  When I changed the name on m project, it thought it was different.  I was forced to delete the original project from the builder server for it to go through again.  I think the ability to use GitHub here would be great- even if it meant I needed to commit for every build.  At least I could automate that.

Sound Board

So the last thing I did was I created an Adobe CF Sound Board.  I grabbed some pictures of well-known ColdFusion faces from Adobe off Google images and slapped them into a page.  

Then, a bit of trawling on YouTube  with a sound recorder garnered me a sound byte from each of these people.  I tied it together with an onClick for each image.

<img src="images/ben.jpg" onClick="mediaPlay( 'media/audio/ben.mp3' )"><br>Ben Forta
<img src="images/ray.jpg" onClick="mediaPlay( 'media/audio/ray.mp3' )"><br>Ray Camden
<img src="images/rakshith.jpg" onClick="mediaPlay( 'media/audio/rakshith.mp3' )"><br>Rakshith Naresh
<img src="images/elishia.jpg" onClick="mediaPlay( 'media/audio/elishia.mp3' )"><br>Elisia Dvorak
<img src="images/anit.jpg" onClick="mediaPlay( 'media/audio/anit.mp3' )"><br>Anit Kumar Panda
<img src="images/adrock.jpg" onClick="mediaPlay( 'media/audio/adrock.mp3' )"><br>Adrock

Just load the page and tap each face to here that person say a few words.  You can install the APK right from GitHub to try this out.  If you have an iPhone, well-- isn't it about time you got something better? :)

Troubles

So there have been a couple hiccups I've seen once I got the sound working that have happened enough times to mention them.  The first is that playing a sound over and over enough times will eventually screw up my phone and no more sounds will play from the app until I close and reopen it.  No errors display in GapDebug.  I  think might be some Android nonsense because I've seen the same behavior in other "sound board" type of apps (like FartDroid)

The second thing I've noticed is that sometimes after I install a new version of my app, I'll open it and get a random error in the GapDebug console about not being able to load some JavaScript files or something.  It seems to be different every time.  last time I thin it was related to geolocation, which I'm not even using yet.  This error will halt al JavaScript on the page and nothing in the app will work.  Ouch! it also bypasses my big try/catch I have around all my CFClient code.   Without fail, closing the app and re-opening it will fix.  I'm  not sure what's using this, but it's happened enough times to give me serious concern about rolling out a production app.  I'm unclear on whether it's an Android, PhoneGap, or CFClient issue.

The Codez

 You should know the drill now.  The code for all this is here on my GitHub account:

And I've cut a release with the exact version of the code shown in this entry tagged:


Want to read more in this series?  Pick one of the links below: