Using PhoneGap Media APIs

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

40 Replies to “Using PhoneGap Media APIs”

  1. hi Ram i have the same problem:
    2012-05-11 17:03:37.269 app[2351:707] ERROR: Method ‘create:withDict:’ not defined in Plugin ‘Media’
    2012-05-11 17:03:37.270 app[2351:707] FAILED pluginJSON = {“className”:”Media”,”methodName”:”create”,”arguments”:[“Media5″,”d77496df-a643-f395-9a05-0e898d80543f”,”/var/mobile/Applications/861ACAFE-B073-4B3E-8A48-0EEBFE25D36D/Documents/blank.wav”]}

    but the onSuccess Function is called.

      1. Hi Ram
        Before i want to thank u because your post it very useful and explains in a perfect way the phonegap implementation. I want to ask you if you know how to format de wav file, because i need to use it to convert it into a byte array in java, but i found some troubles, because it seems that java doesn’t recognize the file recorded with phonegap like a wav file. I would like to know i you have passed some trouble like this.
        Thank u very much.

        1. My use case was to record audio and play it in the phone itself, so using PhoneGap APIs for recording and playback worked fine. I had no need to convert it to any other format.

          If you wanted to convert the file to byte array, did you try doing it using FileInputStream?

  2. Great weblog here! Additionally your web site quite a bit up very fast! What host are you using? Can I am getting your associate link in your host? I wish my web site loaded up as quickly as yours lol

  3. This is really a fantastic site, could you be interested in doing an interview regarding just how you designed it? If so e-mail me personally!

  4. Magnificent goods from you, man. I’ve remember your stuff previous to and you’re just extremely magnificent. I really like what you have obtained here, really like what you’re stating and the way in which in which you assert it. You’re making it enjoyable and you still take care of to stay it sensible. I can’t wait to learn much more from you. That is really a great web site.

  5. Excellent post at Using PhoneGap Media APIs | Ram's Blog. I was checking continuously this blog and I am impressed! Extremely useful information particularly the last part 🙂 I care for such info a lot. I was seeking this certain information for a long time. Thank you and best of luck.

  6. Hi ram, i am using your code on i phone. But there is an error while try to record. That is “Failed to initialize AVAudioRecorder:(null)”.

    In some blog they are saying that use the code in real device. But I am facing the same problem while running the code in real device

    1. Hi Shyantanu,
      I am not very sure about this error. However I checked the source code of CDVSound class and the error is thrown in startRecordingAudio function. The exact code is –
      ———
      // create a new recorder for each start record
      audioFile.recorder = [[[CDVAudioRecorder alloc] initWithURL:audioFile.resourceURL settings:nil error:&error] autorelease];

      if (error != nil) {
      errorMsg = [NSString stringWithFormat: @”Failed to initialize AVAudioRecorder: %@n”, [error localizedFailureReason]];
      —–
      My guess is that audioFile could be null. Can you check if the file is actually created? I am not an expert in iOS development, so I could be wrong about my analysis.

  7. Hi Ram,
    Using your code, I have successfully recorded on my android device. (it doesn’t playback with the playback button however). I can exit my app, and go through the device file manager and find the recording and play it that way, ok so I know it’s there, I do know how to make a file folder. Now I want to know how to record INTO the file folder.on my SD card on my Android. Can you explain that to me? Thanks for all of your help! Stuart DeUsoz

    1. To save recorded file in a folder, in Android, the folder must exist. Then you can set path of the file from /sdcard folder. For example, in the code in this post, if you want to save file in ‘recordings’ folder on sdcard, then set value of recordFileName variable as follows –

      var recordFileName = “/recordings/recording.wav”;

      Make sure the path starts with ‘/’

    1. For iOS you need to create the file before recording, as I have mentioned in the post. Regarding saving file in the folder, I think you need not specify the path starting with ‘/’ for iOS, because each application on iOS has its private folder where all files created by the application are saved. But I could be wrong, because I haven’t programmed much for iOS.

  8. Hi Ram,

    I am using phonegap recording and the file is saving as mp3 format(as per phonegaps demo code). The file is playing on mobile but it is not playing in browsers. I downloaded the recorded file it is playing only in quick time player other players showing that the file is error. Also I can’t convert it in to other formats.

      1. Am able to create a wav but not able to further convert to mp3 or some uncompressed format …. Need some expert help n how to go abt… Thanks in advance!

        1. I am not aware of any JavaScript library that would convert wav to mp3. My guess is that you will have to use native (Android, iOS) code. You may want to check if any PhoneGap plugins already do this.

  9. The information on this page seems to be wrong regarding Android devices. Even if you write a wav-extension in your code, it doesn’t actually save a WAV-encoded file, but an AMR-encoded file with a wrong file ending. Go ahead – check the encoding yourself.. It’s actually not possible to record an MP3 or WAV file on Android through Phonegap. But if you know of a method that actually works, then I would really like to hear it 🙂

    1. I haven’t really verified encoding of the audio file generated on Android. You could be right. However I was able to convert the encoded file using WAV->MP3 converter and the MP3 file played fine. I know that does not prove that generated file was WAV, but just wanted to mention that.

    2. Yes it is correct. The information in this page is wrong when it goes to Android. You can even save a file like “myrecording.xcsdsd” and it will work.

  10. But how to change the phoengap audioCapture saving directory? by default its saving straight inside the SD card.but i want to save the captured audio inside a app specific destination folder in SD card

  11. Thank you for the awesome tutorial.
    I have an issue while using directories. I tried storing the recordFile in a different directory and I get below error

    AudioPlayer : FAILED renaming /storage/emulated/0/tmprecording.3gp to /myApp/test.wav

    Any help would be very much appreciated.

      1. Below is my code wherein I got above mentioned error:

        function createMedia(recordFileName,onMediaCreated){
        if (mediaVar != null) {
        onMediaCreated();
        return;
        }

        window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem){
        fileSystem.root.getDirectory(“myApp”,{create:true},function(directory){
        directory.getFile(recordFileName, {
        create: true,
        exclusive: false
        }, function(fileEntry){
        console.log(“File ” + recordFileName + ” created at ” + fileEntry.fullPath);
        mediaVar = new Media(fileEntry.fullPath, onSuccess, onError);
        onMediaCreated();
        }, onError); //of getFile

        },onError);
        }, onError); //of requestFileSystem

        }

        After your suggesstion I updated the code as below

        > mediaVar = new Media(“myApp/” + recordFileName, onSuccess, onError); //of new Media

        And this time it worked meaning I do see the file in myApp folder and I was able to play it.

        But in the logs I still see this error

        AudioPlayer : FAILED renaming /storage/emulated/0/tmprecording.3gp to /storage/emulated/0/myApp/test.wav

        I guess I can ignore this error.

        Thanks for your help.Appreciate it.

    1. I don’t think there is any API in PhoneGap for this. If you are looking for solution on Android, then you might want to take a look at this PhoneGap Plugin. It gets phone number from device. If it fails, I think you can assume that there is no SIM. I can’t think of any other PhoneGap solution.

  12. Hi Ram,
    Your demo code worked great for me. Thank you for posting this!
    However, as per my requirements I need to access the file I created and get it’s binary data. In the success callback function where you get the fileEntry object, I try to get it’s File instance like this.
    fileEntry.file(fileSuccess, fileFailure);
    I do get a File object in the fileSuccess function but it’s size is 0. Would you know why? Or any other way to get a File instance? From the Media instance?

    Thanks in advance 🙂

  13. I want to save mutliple recording from my phonegap app but it cant save what is the solution for that…?????

  14. thanks! this sample code do great in Android. But not working in IOS with my iPhone…it seems that there’s problem with window.requestFileSystem. I have added to config.xml. still not working….what extra should i do?? @@

Leave a Reply to thashi Cancel reply

Social