Last week I blogged about a problem I was facing when recording audio on iOS, using PhoneGap Media APIs. I was seeing following error in the debug console –
ERROR: Method ‘create:withDict:’ not defined in Plugin ‘Media’
and audio was not recorded.
I logged a bug for this in the PhoneGap’s bug tracker, and it turned out that it was not really a bug in PhoneGap. The PhoneGap team responded quickly (thanks Filip and Shazron) and guided me to the solution.
The solution is in fact documented on the PhoneGap API Reference site, but it is easy to miss.This is what I learnt –
- Apparently you can ignore the above error. The first question that the PhoneGap team asked me was, if recording worked even after above error. So I think this is a harmless error. I do hope that they fix it anyway.
- Extension of the file for saving the recorded data must be .wav. I was trying to create a file with .mp3 extension. This was the main reason why recording did not work in my application. Audio is recorded in uncompressed format.
However Android does not complain about
the .mp3 extension. - File must be created (using File APIs) before recording. I was already doing this. Again, you don’t need to do this in Android.
Android quirks are –
- File path to Media is with respect to /sdcard folder. Do not specify complete path of the file.
- You do not need to create the file before recording, but if you want to save it in a sub folder, under ‘sdcard’, then you need to create folder(s). Media API does not create folders for you.
I think implementation of Media class should handle these quirks better, e.g. in the case of iOS, implementation could log console message if the file extension is not .wav and also log a message if file does not exist. In the case of Android, allow complete file path, starting with ‘file://’ and if the file name does not start with it (file://) then assume it to be from ‘sdcard’ folder.
I have put together an example that works on iOS and Android. I am not sure if you can figure out which mobile OS your application is running using PhoneGap APIs, so I have used a boolean variable, isIOS. Set appropriate value to it if you want to run this code. The code expects cordova-1.7.0rc1.js and jquery-1.7.1.js files in the same folder.
<!DOCTYPE html> <html> <head> <title></title> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" /> <meta charset="utf-8"> <script type="text/javascript" charset="utf-8" src="cordova-1.7.0rc1.js"></script> <script type="text/javascript" src="jquery-1.7.1.js"></script> <script type="text/javascript"> var deviceready = false; var mediaVar = null; var recordFileName = "recording.wav"; var status = null; var isIOS = false; function onBodyLoad() { document.addEventListener("deviceready", onDeviceReady, false); deviceready = true; } $(document).ready(function(){ $("#stopBtn").hide(); $("#playBtn").hide(); //validation to check if device is ready is skipped $("#recordBtn").click(function(){ record(); }); $("#playBtn").click(function(){ play(); }); $("#stopBtn").click(function(){ stop(); }); }); function record() { createMedia(function(){ status = "recording"; mediaVar.startRecord(); $("#recordBtn").hide(); $("#stopBtn").show(); $("#playBtn").hide(); },onStatusChange); } function createMedia(onMediaCreated, mediaStatusCallback){ if (mediaVar != null) { onMediaCreated(); return; } if (typeof mediaStatusCallback == 'undefined') mediaStatusCallback = null; if (isIOS) { //first create the file window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem){ fileSystem.root.getFile(recordFileName, { create: true, exclusive: false }, function(fileEntry){ log("File " + recordFileName + " created at " + fileEntry.fullPath); mediaVar = new Media(fileEntry.fullPath, function(){ log("Media created successfully"); }, onError, mediaStatusCallback); //of new Media onMediaCreated(); }, onError); //of getFile }, onError); //of requestFileSystem } else //it's Android { mediaVar = new Media(recordFileName, function(){ log("Media created successfully"); }, onError, mediaStatusCallback); onMediaCreated(); } } function stop() { if (mediaVar == null) return; if (status == 'recording') { mediaVar.stopRecord(); log("Recording stopped"); } else if (status == 'playing') { mediaVar.stop(); log("Play stopped"); } else { log("Nothing stopped"); } $("#recordBtn").show(); $("#stopBtn").hide(); $("#playBtn").show(); status = 'stopped'; } function play() { createMedia(function(){ status = "playing"; mediaVar.play(); $("#recordBtn").hide(); $("#stopBtn").show(); $("#playBtn").hide(); }); } function onStatusChange() { if (arguments[0] == 4) //play stopped { $("#recordBtn").show(); $("#stopBtn").hide(); $("#playBtn").show(); } } function onSuccess() { //do nothing } function onError(err) { if (typeof err.message != 'undefined') err = err.message; alert("Error : " + err); } function log(message) { if (isIOS) console.log(message); else console.info(message); } function onDeviceReady() { } </script> </head> <body onload="onBodyLoad()"> <input type="button" name="recordBtn" id="recordBtn" value="Record"> <input type="button" name="stopBtn" id="stopBtn" value="Stop"> <input type="button" name="playBtn" id="playBtn" value="Play"> </body> </html>
-Ram Kulkarni

Leave a Reply