"Hello Android": Part 4
It has been a few weeks since I last touched base on this project, due to the limited time I have away from my day job and my distracted focus on porting a C++ application to Android that I had intended as a component for the product I am constructing. From my perspective, there are really only two viable alternatives to this porting: the Android Native Development Kit (NDK), or CMake, the cross-platform, open source build system.
While it has been several years since I worked on development of a C++ product, that last project involved a number of similar attributes because the code needed to be moved from the laptop sandbox environment to small devices on a regular basis for testing on the plant floor, the devices worked within a wireless network, and the tooling required a lot of the inconveniences that are no longer necessary in the Java world to which I have grown accustomed.
The NDK promises to minimize the inconveniences involved in building native code for Android devices, and while this might be true, at least for my purposes there are at least two problems going this route. The first is that the NDK is limited to Android, so if my product is to be cross-platform, I would still need at a separate set of build scripts for other devices. And in reading the introductory notes that are recommended following the downloading of the NDK, it is quickly apparent that the restrictions inherent to the NDK are quite extensive.
Some of these restrictions are intended to protect the developer, because Google indicates in its documentation that much is subject to change in future releases. As of right now, I am considering development with the NDK too much of an architectural risk for my needs, and will plan to offer a subset of out-of-the-box functionality that I had originally planned on incorporating into this product.
One aspect of the architecture I spent considerable time working on this weekend was audio record and playback. As the PhoneGap framework documentation notes, the Media object provides the ability to record and play back audio files on a device. This functionality is a big deal, since every device platform handles audio differently, although PhoneGap is sure to communicate a related caveat as follows:
Note: The current implementation does not adhere to a W3C specification for media capture, and is provided for convenience only. A future implementation will adhere to the latest W3C specification and may deprecate the current APIs.
A reminder that I am currently using PhoneGap 0.9.3, even though PhoneGap 0.9.4 was recently released. Getting audio record and playback to work was tedious. Media object examples provided at the link above are really not complete from the perspective of a developer using the API for the first time, and I poured through dozens of examples provided by developers using this functionality, only to finally go directly to the PhoneGap source code to figure it out.
Playing a prerecorded MP3 file using PhoneGap was fairly trivial. The following are some code snippets (along with PhoneGap 0.9.3, these snippets use jQuery 1.4.4 and jQuery Mobile 1.0a2), from which I have removed all debugging code:
<div data-role="page" id="page1">
<div data-role="content">
<div id="buttonPlayPrerecorded" data-role="button">Play Prerecorded</div>
</div>
</div>
function playAudio(src) {
var mediaObject = null;
mediaObject = new Media(src);
mediaObject.play();
}
$("#buttonPlayPrerecorded").button().click(
function(){
playAudio("/android_asset/www/prerecorded.wav");
}
);
A rather simple exercise. Clicking on the button plays the WAV stored in the “/assets/www” folder within the Eclipse project directory that is packaged as an APK. Recording and then playing back an audio file that is not packaged within an APK, however, is not as straightforward. First of all, following a Run As…Android Application in Eclipse while connected via USB to the external device, you will need to disconnect the USB before running from the device, because file storage of the new file needs to be made on the device, on the SD card.
The following are some more code snippets, this time for recording and playback:
<div data-role="page" id="page1">
<div data-role="content">
<div id="buttonRecord" data-role="button">Record</div>
</div>
</div>
function recordAudio(src) {
var mediaObject = null;
var recordInterval = null;
var recordTime = 0;
mediaObject = new Media(src, onSuccess, onError);
mediaObject.startRecord();
recInterval = setInterval(function() {
recTime = recTime + 1;
if (recTime >= 10) {
clearInterval(recInterval);
mediaObject.stopRecord();
}
}, 1000);
}
$("#buttonPlayRecorded").button().click(
function(){
playAudio("recorded.mp3");
}
);
$("#buttonRecord").button().click(
function(){
recordAudio("recorded.mp3");
}
);
Because file storage is made on the device, APK folder “/android_asset/www” should not be referenced. The last piece of the puzzle is what had me looking at PhoneGap source code, something you would not be able to do with commercial code that is not published, but then again this code is probably being made available earlier than would otherwise have been made because it is open source.
So where is “recorded.mp3” referenced in the above snippets being stored? Check out the PhoneGap API source code for AudioPlayer.java here (interesting licensing and copyright information, by the way; standard MIT license followed by Nitobi and IBM copyrights). The first snippet to call out in this file is the following:
public AudioPlayer(AudioHandler handler, String id) {
this.handler = handler;
this.id = id;
// YES, I know this is bad, but I can't do it the right way because Google didn't have the
// foresight to add android.os.environment.getExternalDataDirectory until Android 2.2
this.tempFile = "/sdcard/tmprecording.mp3";
}
Ah. Some hard-coding. (And with all due respect to the code author, but I am actually using Android 2.2 on this project.) The recording is saved to the default SD card directory. I ended up stumbling upon this “feature” when I was initially referencing the APK folder, and noticed “tmprecording.mp3” on the SD card.
public void startRecording(String file) {
...
this.recorder.setOutputFile(this.tempFile);
...
}
The following explains why only the file name is actually needed in the JavaScript:
public void moveFile(String file) {
/* this is a hack to save the file as the specified name */
File f = new File(this.tempFile);
f.renameTo(new File("/sdcard/" + file));
}
Agreement with the code author that this is another hack. Hopefully this explanation helps someone looking at this portion of the PhoneGap API for the first time. Rest assured that I came across a lot of incorrect advice this weekend on how this is all supposed to work.