Managing and moving audio files from a Media recording for Android

2,597 views
Skip to first unread message

Pete Helgren

unread,
Apr 29, 2014, 5:56:11 PM4/29/14
to phon...@googlegroups.com
I am using the Media class to record, save and play an audio recording
using PhoneGap 3.4. Using code from this list and others I can
successfully record the audio and play it back. The issue is that I
would like to control where the file is stored and retrieved. Right now
I call the file "recording2.3gp" and I see this activity in Logcat:

D/AudioPlayer(25394): renaming /storage/emulated/0/tmprecording.3gp to
/storage/emulated/0/recording2.3gp

So the question becomes can I SET where this is copied (renamed) to? If
not, can I copy it from the stored location (presumably
/storage/emulated/0/recording2.3gp) to another location?

A bit more to this request. I noticed that in the phone's storage space
I see this: \Android\data\com.valadd.mobile.common.todo\files. This
appears to be the my application's file folder (the application package
name is "com.valadd.mobile.common.todo") So this *seems* to be the
logical place to put the files. Which leads to the question: How to I
access this folder? How best to copy to direct the file there?

Admittedly, there are thousands of posts about the PhoneGap File API's
but, man! there are a bunch of examples that are non-functional,
confusing, incomplete and just plain junk. Even the documentation is
sparse, particularly with a reference to 3.4. I tried quite a few
examples that showed how to record audio and save it and finally found
one that would work with 3.4 but the file side of the equation has been
a challenge.

Can anyone provide some answers to the above or can point me to working
examples in 3.4? I would appreciate it. I am going to keep digging and
experimenting but I haven't made much progress.

--
Pete Helgren
www.petesworkshop.com
GIAC Secure Software Programmer-Java

Kerri Shotts

unread,
Apr 29, 2014, 6:34:46 PM4/29/14
to phon...@googlegroups.com
You should be able to set the src of your Media object:

var src = "cdvfile://localhost/persistent/file.3gp";
var m = new Media( src, successCallback, errorCallback, statusCallback);

During the recording operation, tmprecording.3gp is still created, but after the operation is complete (don't forget to m.release!), the file will be renamed to the location specified by src. Using the persistent file system is easier than trying to sniff out whether or not to use the SD card vs the internal paths, etc. 

If you have problems, check your adb logcat, it can be very helpful in determining if the media plugin is trying to move the recording (and if it is succeeding).

I hope that helps!

Pete Helgren

unread,
Apr 29, 2014, 7:48:21 PM4/29/14
to phon...@googlegroups.com
Thanks Kerri....

A couple of follow-ups (this is where current examples tend to leave
much left to the imagination)

1. I am running this from an app and would like to store the file
within the apps folder structure. When I connect the phone (in windows)
and navigate the file tree, I find a folder called (as I mentioned
before) \Phone\Android\data\com.valadd.mobile.common.todo\files Is
there a way, besides hard coding it, that I can locate that folder
relative to the app? Often I see this:

window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
onFileSytemSuccess, null);

which, through a series of callbacks walks file and folder information
(additional code not included here). I want to locate that "files"
folder in my app. Is that possible? Then when I have that path I want
the file to go there.

2. If I used var src = "cdvfile://localhost/persistent/file.3gp"; where
would I then find that file? What folder would it end up in?

3. There is also a "card" folder at the root when I view the phone's
storage which appears to be the sdcard inserted in the phone. You
prefaced your example with cdvfile:// does that set the root folder in a
certain location or is that just an example (or a typo)?

I am not picking on you, you have been very helpful, but my primary
complaint with posted examples is that the poster always seems to assume
the example is self explanatory. Yet even a small detail such as a
reference to cdvfile:// can set a noob like me down a long path of
fruitless searching (lots of examples using "cdvfile://" but none
explaining what it is...a Cordova convention but what?). Better to post
and then describe in detail what each component does. IOW: You can
never over-annotate a post.

Again thanks...I am just trying to wrap my head around the file access
API's and they still seem a bit foreign right now.

Pete Helgren
www.petesworkshop.com
GIAC Secure Software Programmer-Java

Kerri Shotts

unread,
Apr 29, 2014, 11:54:08 PM4/29/14
to phon...@googlegroups.com
Pete,

1.

"cdvfile://" is a url scheme used by Cordova to represent persistent/temporary file storage as specified by the HTML5 File API. Persistent storage is represented by "cdvfile://localhost/persistent", and temporary by "cdvfile://localhost/temporary". (To be honest, I'm not sure "cdvfile" is the best scheme; the HTML5 File API uses "filesystem", IIRC. But there's always the risk of odd collisions with what the system browser implements vs what cordova implements, so "cdvfile" is probably the best compromise there.) 

Where these point depend on a setting in your config.xml:

<preference name="AndroidPersistentFileLocation" value="Internal" />

will store the persistent file system on internal memory (as defined here: this page), whereas leaving it out or specifying "Compatibility" will store the persistent file system on the root of the SD card, if present.

The Media API supports the use of "cdvfile://" URLs, but if you need to work with/manage/write to/read from the file system in addition to recording, you're going to want to delve into the HTML5 File API. Your example in #1 looks like the start of that, but it's not complete. If you need the native path of the file system, I would expect something like this to work:

function onFileSystemSuccess ( fs )
{
  var nativePath = fs.root.toURL();
}
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onFileSytemSuccess, null); 

Never, ever, hard-code file system paths, as you can't be certain of the native file system. Doing so is only asking for failure on some device out there that isn't structured the way you expect. Never store references to the full native paths either -- store a cdvfile reference instead (cdvfile://localhost/persistent/path/to/file) or a path relative to the file system (/path/to/file). 

2.

It depends. Without altering your config.xml, it's almost certain the file would end up on the root of your SD card. If you need to use external storage, I would suggest creating a separate directory so you don't end up cluttering the root directory. If you use the "Internal" setting as indicated above, it will be stored in internal storage and not on the SD card.

3. 

I assume "card" points to your SD card. As mentioned in #1 and #2, "cdvfile://" uses a setting in config.xml to determine where it stores the persistent file system. 

4.

If you're having pain points with the HTML5 File API, there are some libraries that can be useful. I've got one that uses Promises (a way to avoid the callback hell that the File API engenders) at https://gist.github.com/kerrishotts/7147214 which simplifies things to a degree (though you do need to know what's going on underneath). Here's another (though I'm not sure if it still works): https://github.com/tonyhursh/gapfile

5.

It's been a long day, and we all have our day- (and night-) jobs, and I can't always be as verbose as I'd like. Sometimes I just have to assume the one asking the question will pursue the rabbit hole where it leads. Nor is it always clear at what level the poster is at -- so sometimes I make the wrong assumptions about the poster's knowledge set. 

And, sometimes all this requires some experimentation where the PG docs aren't as obvious as they perhaps should be -- I've certainly spent my fair share of time tracing through Cordova's code trying to figure out why a feature doesn't work they way I understood it to work. Sometimes it's a bug, and sometimes it's my misunderstanding. Would that I could write a book on every post, but oft-times life just doesn't work that way.

Hopefully this helps somewhat. Take a look at the File API docs (http://plugins.cordova.io/#/package/org.apache.cordova.file) and the Media API docs(http://plugins.cordova.io/#/package/org.apache.cordova.media). Don't be afraid to peek at the code, too: sometimes that's the best way to figure out how it all works. If something breaks, check adb logcat (using your command prompt; it should display the emulator/connected device's log) as it often has indicators as to why something failed. (And sometimes it doesn't, but it's always one of the first things we'll ask for.)

Pete Helgren

unread,
Apr 30, 2014, 5:46:00 PM4/30/14
to phon...@googlegroups.com
Kerri,

Again, many thanks...so much to learn and so little time! (and yeah, the day AND night job thing can wear you out...)....

I started two emails detailing the problems I was having and in both cases while I was investigating and gathering the details, I found the solution, so at this point I have a functional app and I actually understand what is going on.....

Your patience was much appreciated.


Pete Helgren
www.petesworkshop.com
GIAC Secure Software Programmer-Java
--
-- You received this message because you are subscribed to the Google
Groups "phonegap" group.
To post to this group, send email to phon...@googlegroups.com
To unsubscribe from this group, send email to
phonegap+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/phonegap?hl=en?hl=en
 
For more info on PhoneGap or to download the code go to www.phonegap.com
 
To compile in the cloud, check out build.phonegap.com
---
You received this message because you are subscribed to the Google Groups "phonegap" group.
To unsubscribe from this group and stop receiving emails from it, send an email to phonegap+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Kerri Shotts

unread,
Apr 30, 2014, 6:47:48 PM4/30/14
to phon...@googlegroups.com
Woohooo!!!! Get up and do a little happy dance!

Glad you got everything working! Having a functional app is such a great feeling! ;-)

Pete Helgren

unread,
May 1, 2014, 4:00:06 PM5/1/14
to phon...@googlegroups.com
I interrupt this happy dance to bring a special message....

ONE more thing I would like to do .  I had hoped to add a button to the app that would show the play button only if the file actually existed...so I added code that uses the getFile method to determine whether the file exists or not.  It works, but since all the callbacks are asynchronous, I can't quite figure out how to determine make it synchronous. Yeah, I know, sounds strange.

But, here is the deal:  I want to determine the files existence before the editing panel will display either a play or record button, so I wrote the code similar to this:

if(checkIfFileExists(fileName))
    $("#btnPlay").show();
else
    $("#btnRecord").show();

and of course, checkIfFileExists calls:

window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, getFSSuccess, getFSFail);

and getFSSuccess  calls   .getFile(fileToLookFor, { create: false }, fileExists, fileDoesNotExist);

and fileExists sets the value to true or false.  But, of course you see the problem:  checkIfFileExists returns as soon as requestFileSystem is called and the true/false value hasn't been set yet.  So, this is more of a javaScript issue (and a lack of a full understanding of callbacks) than a specific PhoneGap issue...but it is a File issue...

I settled on window.resolveLocalFileSystemURL but that also seems to be async as well so I am not sure the best way to go....

Ideas?


Pete Helgren
www.petesworkshop.com
GIAC Secure Software Programmer-Java

Kerri Shotts

unread,
May 2, 2014, 3:27:28 PM5/2/14
to phon...@googlegroups.com
Pete,

Welcome to callback hell.

Almost everything you do with the File API is asynchronous, and there's no good way around it. You can wrap the methods to return promises instead, which helps reduce the headache quite a bit (my library on gist does that), but even then, it isn't like synchronous I/O (which we're most likely used to from other languages).

Without promises, your code should look something like this:

function checkIfFileExists ( filename, callback )
{
  function fileExists(file)
  {
    callback(true);
  }
  function fileDoesNotExist(e)
  {
    callback(false);
  }
  function getFSSuccess(fs)
  {
    fs.root.getFile(fileName, { create: false }, fileExists, fileDoesNotExist);
  }
  function getFSFail(e)
  {
    // error
  }
  window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, getFSSuccess, getFSFail);
}
checkIfFileExists(fileName, function(doesFileExist)
{
  if (doesFileExist) { ... }
  else ( ... }
});

Yes, this looks absolutely horrible, doesn't it! Promises makes it a bit easier -- for example, using my gist, you'd have something like this:

var fm = new FileManager();
fm.init ( fm.PERSISTENT, 0 )
    .then ( function() { return fm.getFileEntry( fileName, {create: false} ); } )
    .then ( function() { // file exists, do what you want } )
    .catch ( function() { // error occurred, or file does NOT exist } )
    .done();

That said, it's doing all the same stuff underneath the hood, and you do need to understand everything that is going on underneath the hood in order to use my library effectively. But it does show how promises can create much more readable code without getting into callback hell. 

Does that help any?
Reply all
Reply to author
Forward
0 new messages