Wednesday, May 18, 2011

Using the Media Class in PhoneGap

I've seen a lot of posts recently on the PhoneGap Google Group so I wanted to put something together illustrating the use of the PhoneGap Media class. While putting this example together I noticed a few quirks of the Media class which I'll try in describe in more detail so that other folks don't run into the same problems. Also, this example has been tested on Android but it, for the most part, should work on iOS as well. Part of PhoneGap 0.9.6 will be normalizing how the Media class works between all the major platforms so look for improvements soon.

The example will be a basic Media player with play/pause and stop functionality.  Here's a quick screen shot:


On Android you can create a Media object using one of three ways.  The first way is referencing a file in your "android_asset" directory.  For instance if you have a file called "test.mp3" in your projects "assets/www" directory you would create a new media object by doing:
myMedia = new Media("/android_asset/www/test.mp3");
The second way to create a media object is by referencing a file on your devices file system. Say for instance, you have a file at "/sdcard/test.mp3" of your device you would create the media object by doing:
myMedia = new Media("test.mp3");
You'll notice the "/sdcard/" portion of the file name is omitted. This is because on Android the Media object makes an assumption that the file is stored on your "/sdcard". Note: I may change this behaviour in an upcoming version of PhoneGap but I'll strive to retain backward compatibility.

The third and final way to create a media object is by URL:
myMedia = new Media("http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3");
Now that we've got our media object we want to be able to do the sensible thing and be able to play the audio. For that we'll create a play button:
<a href="#" onclick="playAudio()"><img id="play" src="images/play.png" /></a>
and corresponding function:
function playAudio() {
  myMedia.play();
}
Now the stop button is just as simple:
<a href="#" onclick="stopAudio()"><img id="stop" src="images/stop.png" /></a>
function stopAudio() {
  myMedia.stop();
}
However, that's pretty lame as it would be great to be able to pause the audio playback so let's enhance out play and stop functions:
function playAudio() {
  if (!playing) {
    myMedia.play(); 
    document.getElementById('play').src = "images/pause.png";
    playing = true; 
  } else {
    myMedia.pause();
    document.getElementById('play').src = "images/play.png";    
    playing = false; 
  }
}

function stopAudio() {
  myMedia.stop();
  playing = false;
  document.getElementById('play').src = "images/play.png";    
}
Now when the user is playing some audio the play button image is changed to a pause button and hitting the pause button again will, well, pause the playing of the audio. I'm cheating a little bit by using a global variable called playing as it could easily be changed to check what the currently set image is and doing the correct action.

Of course it is always nice to know where we are at in a given audio clip so we can add some functionality to get the current position of where we are at in the file. So let's create a function that will run every second and update our HTML with the current position of the audio file.
<p id="audio_position">0.000 sec</p>

var mediaTimer = setInterval(function() {
  myMedia.getCurrentPosition(
    function(position) {
      if (position > -1) {
        document.getElementById('audio_position').innerHTML = (position/1000) + " sec";
      }
    },
    function(e) {
      console.log("Error getting pos=" + e);
    }
    );
}, 1000);

That's all well and good but there is one thing that's slipped by our notice so far. When an audio clip finishes playing the pause button should become a play button in case the user wants to play the clip again. That can be accomplished by adding a success call back to our Media object constructor. The success call back is executed when the clip has finished playing. So we'll now instantiate our media like this:
myMedia = new Media("/android_asset/www/test.mp3", stopAudio);
So what we have here are the beginnings of a audio player application for Android. In part two of this article I'll enhance the app with the File System and Directory API showing you how to scan your device for audio files.

The HTML and images can be found here and below is a listing of the HTML code uses in this article. I added a select box so that users could switch between the different ways of creating a Media object. You'll have to supply your own MP3 file as I don't want the RIAA coming after me.

129 comments:

James said...

Hi Simon,

So there seems to be a fourth way to create a Media object and that is with src=null. For iOS, this seems to create something like "documents://1203044830072" where the numbered part is unique with each creation.

What is it creating?

Thanks for your help,
Jo

Simon Mac Donald said...

If I'm not mistaken if you pass src=null to the Media class on iOS it will make it's own file name using the current date. This file name will be used if you call the startAudioRecord() method.

edfilo said...

media.getCurrentPosition and media.getDuration currently do not seem to work in ios , is this support coming in a future release?

Simon Mac Donald said...

Correct, those methods don't work on iOS yet but we are looking into including them for 1.0 due end of July early August.

Rob Archer said...

I've just started developing a media application with PhoneGap, and I noticed that when loading an audio file from a URL, the app becomes unresponsive until it is ready to play. Could this be mitigated by using Web Workers to load them in a different thread?

ibnu Maksum said...

in simulator it works

but in real device its not work

i want to stream shoutcast

Simon Mac Donald said...

@Rob Archer
I think it has to do with the OpenCore instance being allocated by the Android OS.

@Ibnu Maksum
I'll help you over on the google group.

Franco Alvarez said...

Hi Simon, i have one cuestion... I made all what u said.. but allways, no matter what i do and what function use i allways get a prompt with this:

gap:["Media","startPlayingAudio","Media0",true]

????... U have any idea what is this... i found the code in phonegap-1.0.0.0.js this code:

var r = prompt(PhoneGap.stringify(args), "gap:"+PhoneGap.stringify([service, action, callbackId, true]));

and this is the prompt what i getting... Plis help!

PS: sorry for my inglish, i just starting with it

Franco Alvarez said...

thanks for the code!

Franco Alvarez said...

Hi again Simon.. a few seconds I realized that in the BROWSER allways going to get the prompt, in the divice it s Work Amazin! Both codes, your and mine.
Thanks again.

Best regards

Dr Senior said...

In the example above - with the file page "/android_asset/www/test.mp3" ... will this work "out of the box" on an iPhone is the test.mp3 file is in the same place (www/test.mp3)?

Simon Mac Donald said...

@Dr Senior

No, it won't work out of the box on iOS as it has no concept of the android_asset directory. You'll need to do a conditional for now to determine if you are on iOS or Android and pass the appropriate path.

I'm advocating we spend some time in 1.1 to make Media sane across all platforms.

anh said...

Hello, does media.getDuration work yet on iOS?

Simon Mac Donald said...

@anh

It should but if it doesn't open an issue on github and we'll get it fixed.

https://github.com/phonegap/phonegap-iphone/issues

Lars said...

hi, can you explain, how to use a playlist (play several files sequentially)?

Simon Mac Donald said...

@Lars

I've been meaning to write some code to do exactly that. I would keep and array of file names or urls then in my onSuccess method of the Media constructor, which is stopAudio in the example, I would add one to my array index create the new Media object and start playing.

To make things faster I'd probably keep a buffer of 3 to 5 Media objects so that people could skip or go back in the play list.

Arkan said...

Hi Simon,

i wanna add the ID3 in the player,
i used from this library :
http://web.ist.utl.pt/antonio.afonso/www.aadsm.net//libraries/id3/

http://github.com/aadsm/JavaScript-ID3-Reader

this is my code:

var file="/sdcard/music.mp3";
function callback() {
var pic = ID3.getTag(file, "picture");
var artist = ID3.getTag(file, "artist");
var title = ID3.getTag(file, "title");

document.getElementById('judul').innerHTML = artist+ "\r\n";
document.getElementById('judul').innerHTML += title+ "\r\n";
document.getElementById('urlartis').src = "data:" + pic.format + ";base64," + Base64.encodeBytes(pic.data);
}
myMedia.play();
ID3.loadTags(file, callback, ["picture", "artist", "title"]);
playing = true;

it took 40 second to show the artist & title. but the picture not showed at all.

is there a way to speed up this, and also why the image does'nt show.

thanks before for your help.
Ali

Simon Mac Donald said...

@Arkan

Sorry, I can't help you here as I've never used that library but it looks like a cool one. If I had to guess what it is slow is that they are using JavaScript to read a binary file which it doesn't support out of the box. I number of tricks have to be done for the library to read the binary data.

Jithin said...

Hi simon....

Thanks for the tutorial...
I tried it, it working fine in emulator, but it is not working in my device sony erricsson xperia... what is the problem....

Simon Mac Donald said...

@Jithin

The first thing that jumps out at me is that your Sony may not have to proper codec for playing the file. Run "adb logcat" to see what is going on.

Jithin said...

Hi simon, thanks for the quick response.

Can i add volume controller for this app. ?

Regard
Jithin

Simon Mac Donald said...

@Jithin yes, you can. There is a setVolume() method on the Android PhoneGap Media class. It is coming to other platforms.

Jithin said...

Hi simon,

Can u explay how to run "adb log cat", because a m very new to android.

thanks
jithin

Simon Mac Donald said...

@jithin

adb logcat docs.

Jithin said...

thanks simon

Jithin said...

Hi simon,

your demo url http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3 is working fine in both emulator and device. But i applied another url http://live.ardanradio.com:8228/;audio.mp3. but it is working only in emulator, not working in device. i run adb logcat, it showing D/dalvikvm( 2335): GC freed 7225 objects / 344040 bytes in 100ms
W/MediaPlayer( 2335): info/warning (1, 32)
I/MediaPlayer( 2335): Info (1,32)
D/dalvikvm( 2335): GC freed 20579 objects / 799192 bytes in 118ms
D/dalvikvm( 2335): GC freed 17355 objects / 657824 bytes in 115ms.

and also i checked with this shoutcast url:http://62.75.216.8:8008/;audio.mp3. but same problem

Simon Mac Donald said...

@Jithin

Can't test right now as I am at a conference but try removing the /;audio.mp3 from the end of the URL.

Jithin said...

hi simon

thanks for the quick response,
i tried with http://62.75.216.8:8008 this url. But working only in emulator but not in emulator.

Unknown said...

hello sir me new to phonegap n android development i have audio in binary i.e base 64 format however it does not play on android audio tag.....please help me...

Simon Mac Donald said...

@Unknown the "audio" tag does not work in the WebView for some strange reason. Use the Media class instead.

Simon Mac Donald said...

@Jithin that URL plays fine in the emulator and my Samsung Galaxy S phone. What version of Android is your phone using?

Jithin said...

Hai simon,

i am using samsung galaxy s5570(android 2.3)...

Simon Mac Donald said...

Sorry @Jithin I can't reproduce your problem. I wonder if it is a missing codec problem on your phone as, as you say, it works in the emulator.

Jithin said...

Hi simon,

I don't know what wrong thing i did. i checked with these two url 1. http://62.75.216.8:8008
2. http://stream.lexandterry.com:8000

But first one working only in emulator and second one working in both device(samsung galaxy s) and emulator. After that i checked with sony Ericsson experia. But same problem happening.

Simon Mac Donald said...

Sorry @Jithin, I don't know what to tell you. Some devices may not support streaming audio. I know you need Android 2.2 or better for this to work though.

Unknown said...

hello Simon Sir,

my application requires me to play mp3 BASE64 audio..

phonegap my media doesn't play bas64 audio source...
kindly help Thanks in advance...

Simon Mac Donald said...

@Unknown you'd have to write a plugin to convert your base64 encoded mp3 to a binary file and then pass that the the Media class to be played.

Andrea Levinge said...

Dear Simon,

Thanks for writing this awesome tutorial. I'm having trouble getting the full filepath of the new recorded Media object and uploading it to the server. I wonder if you could suggest how to do it? I've already tried just saving it to android_asset but file upload doesn't work with /android_asset/www/myrecording.mp3 - I know the problem isn't with my file upload methods because I can already upload other files. I just can't get the fullpath of this recording I've made using your recorder/player code.

Simon Mac Donald said...

@Andrea Levinge

That's because the Media class isn't following any know spec. The file name you pass in is assumed to be under /sdcard. So if you pass in temp/data/record.mp3 the file will end up at /sdcard/temp/data/record.mp3.

You cannot save files to the android_assets directory as that is a "virtual" directory that is part of your .apk.

Andrea Levinge said...

Hi Simon,

Thank you so much for replying!
I created a new Media file:

mediaRec = new Media('myrecording.amr', onSuccess, onError);
mediaRec.startRecord();

I want to get that mediaRec's fullPath.

I check where the file is actually located on my sdcard and it's located at /mnt/sdcard/external_sd/

I've tried the following filepaths for uploading:
/mnt/sdcard/external_sd/myrecording.amr
/sdcard/myrecording.amr
myrecording.amr

but the upload always fails as if the file does not exist.

I've successfully uploaded photos and videos already but those were captured using the example code from the API. This recording uses a new Media object. I suspect it is saving it at content://media/external but I don't know how to get the URI of the new Media object I've created. All I know is that photos are getting temporarily saved to content://media/external/images/media/ but I'm sure that's different for audio recordings. I've tried grabbing it from the onSuccess method, but it seems to pass nothing to the onSuccess method when creating a new Media file.

Let us know when your book comes out, I will be ordering it for sure.

kukulako said...

Kukulako from Puerto Rico...

Can I put seek bar or progres bar?

Simon Mac Donald said...

@kukulako

Sure, but you'd have to implement it yourself using getDuration and getCurrentPosition.

John Wargo said...

Simon,

How does this work with PhoneGap Build and PhoneGap 1.2? Where are the media files that are packaged with the application placed and how would I access them cleanly from any platform?

Simon Mac Donald said...

@John

I'm not sure how it would work on the build service as I don't use it. For regular PhoneGap you'd have to do an OS detection and pass the correct prefix along with your file name to the Media constructor.

John Arrowwood said...

This has the potential to be great, but can I have a looping background track with an independent volume, while playing a foreground track, or playlist?

Simon Mac Donald said...

@John A

These are some great ideas and we should make the Media class better for people making games. You should raise a ticket at:

https://issues.apache.org/jira/browse/CB

Uday Kumar said...

Hi Simon,
Please help me out in displaying the All Songs and playlists of android device and how to access android device media table to display the list using phone gap.

Thanks in advance.

Simon Mac Donald said...

Hi Uday, I haven't written the code yet but what you'd need to do is query the content provider for the available music.

http://developer.android.com/guide/topics/providers/content-providers.html

Uday Kumar said...

Thanks Simon for reply,
Is there any way in phonegap to get the data using content provider and display using phonegap javascript and html5. Do i need to write any phone gag plug in to read data using content provider.

Simon Mac Donald said...

@Uday

Yes, you'd need to write a plugin for that type of functionality.

Simon Mac Donald said...

@Andrea

Sorry I missed your comment. After you call stopRecord() the file should be moved to /sdcard. So you should be able to use:

/sdcard/myrecording.amr
of
file:///sdcard/myrecording.amr

Michael J Smith said...

In my app, setVolume() possible values are 0.0 - 1.0. It's looking for a float value.

MrReview said...

Hi Devgeeks,

I'm testing your demo with a shoutcast streaming url and it does'nt work :(

I tested with the streaming url somebody said in this google group.

The debug window in Xcode stucks on this line:
2012-02-08 15:02:21.274 altafullaradio[3219:13403] Will use resource 'http://live.ardanradio.com:8228/;audio.mp3' from the Internet.

I'm using the latest version of phonegap, and iOS 5.0.1

With a normal mp3 url it works good. But with a streaming url it does'nt

What I should try?

Thank you.

Kind regards,

Simon Mac Donald said...

@MrReview

Sounds like a bug in iOS. I'll let Becky or Shaz address this on the google group where you also asked your question.

pcasa.net said...

Very useful tutorial on Media Player.
Maybe you have an hint for a apparently blocking problem: is there a way to pass the mediaplayer between different html pages (not dynamically injected)? If I create the mediaplayer in page one.html, how could I use it in page two.html (loaded as "external")? I know the object is still alive, I can still hear audio (if I don't release it), however, I've no reference to the mediaplayer to control it.
Thank you for any hint!

Kind regards
Paolo

Simon MacDonald said...

@pcasa.net

Once you go to two.html all the JS from one.html is unloaded. You'll want to register a "pause" listener to release your Media object. Then create a new Media object in page two.html.

ArulMurugan Thiagarajan said...

Hi Simon,

How is it decide where the media file is when I am developing an android phonegap application?
assets/www or sdcard

Simon MacDonald said...

@ArulMurugan

It's up to you. Files that are deployed with the application end up in assets. Files downloaded generally end up on the sd card.

juxiang peng said...

Hi Simon ,
When the phone is sleeping or active other app ,how can i keep the audio play in the background? would u give me some solution suggestion? and does this be feasible ?

Simon MacDonald said...

@juxiang peng

As long as you use the home button to put your app in the background it should continue to play.

juxiang peng said...

Thank u for the reply!
In android it play good ; but ios is not ok, i maked the audio play as
//code start
new Media(src ,onSuccess, onError,onStatus).play() ;
//code end

Any suggestion for the ios ?

Simon MacDonald said...

@juxiang peng

I would split out the play onto the next line. iOS might need some time to prepare the Media object.

juxiang peng said...

Thank u for your relay ;
I solve the background play problem from this link :http://mobile.tutsplus.com/tutorials/iphone/ios-sdk_background-audio/

and *-info.plist a configure :

Once the project has been created go to NoiseMaker-Info.plist and add UIBackgroundModes as a new row. It should then create the array. "

profilcaleg said...

Thanks Simon, i was buid Android apps with this plugin and tested with Android Ice Cream Sandwich.
Its works, but the buffering is to much wasting time.

profilcaleg said...

I was succesfully tested it on Android Ice Cream Sandwich, but how about the Blackberry? It also works too?

ikkimassu said...

Hi..

I'm beginner on phonegap.
I make some page that play some music on each page.
Page1.html, play page1.mp3
Page2.html, play page2.mp3

but, when I'm on page2.html and go back to page1.html, the page1.mp3 becomes echo..
when I go to the page2.html and go back again to the page1.html, the page1.mp3 becomes double echo -_-

what should I do ???

very need help.
thank you

reezo ahmad said...

I have download music file in phone memory. Location is : file://data/data/com.luontoportti/files/2/audio/Accnis.mp3

How can I play these files. It works if i download file in sd card. But not for this. Please help me.

Regards
Reezo

wilson said...

Thanks for the great post....
I include my app resources in the html application manifest cache.
What I want to do is cache the mp3 file in my manifest as well so it can be play when my app is offline. it doesn't seems to work (it works fine if I am online). can this be done using phonegap media type while offline?

thanks,

Simon MacDonald said...

@profilcaleg

Yeah, if you want to get around the buffering you will probably need to download the file to the sdcard to make playback faster. Yes, I believe Drew just added support for Media for BB,

Simon MacDonald said...

@ikkimassu

Register an event listener for the "pause" event and stop playing your file when you leave the page and then wait for the "resume" event to start playing the file again.

Simon MacDonald said...

@reezo ahmad

Only the application that is "com.luontoportti" will be able to play those files. No other application will have access to your internal app storage directory. That's why it works when they are on the sd card as that has not file protection.

Simon MacDonald said...

@wilson

The Media class will not look at the app cache. You'll need to download the audio to the sd card in order to cache it manually.

Patrick Rouillier said...

HI Simon and thanks for your great works
I use you plugin for playing audio in an app on my Galaxy tab (android 3.2, Cordova-1.8.1) and its works perfectly
If i try this app on an LG (Android 2.3.3) or a samsung (Android 2.3.3) its does'nt works
What could you suggest ?
Thanks

Simon MacDonald said...

@Patrick

Are you trying to play a local file, a file in assets or a remote file? Run "adb logcat" to see what is going on.

Patrick Rouillier said...

Thanks for your answer.
I am sorry but i make a mistake, i was calling phone gap 1.8.1 as i was running on phone gap 1.9.
All is ok now

Doubts said...

Hi Simon,
I am trying to create a audio player using phonegap and jquery,
I want to use the duration of the Media object to set the slide 's max value. And the position of the Media to move the slide the slider accordingly.
But the duration of the media using the getDuration is always returning -1, because of which the slider stop at the end of 100 sec.
How can I fix this?
Also I getting these errors..


(1) D/CordovaLog(7511): file:///android_asset/www/PhopeGapLibraries/cordova-1.6.0.js: Line 3491 : JSCallback Error: TypeError: Cannot set property '_position' of undefined


(2)JSCallback Error: TypeError: Cannot set property '_position' of undefined at file:///android_asset/www/PhopeGapLibraries/cordova-1.6.0.js:3491


(3) E/MediaPlayer(7511): Attempt to perform seekTo in wrong state: mPlayer=0x0, mCurrentState=1


(4) SCallback Error: TypeError: Cannot set property '_duration' of undefined


(5) I/Web Console(7511): JSCallback Error: TypeError: Cannot set property '_duration' of undefined at file:///android_asset/www/PhopeGapLibraries/cordova-1.6.0.js:3491

Simon MacDonald said...

@Doubts

That is odd the getDuration should return the proper duration of any local media file. As for building the slider you won't be able to seek in a file that is not playing as the file is not prepared yet. We've made some changes in 2.0.0 and we are going to get it right in 2.1.0 to make Media work much better. Get ready for another blog post later this month.

Doubts said...

Hi Simon,
I wanted to have a toolbar kind of interface for my media player while the phone is in lock mode. Do you have any suggestions as to how to implement it?
I am using phonegap, jquery on android.

Simon MacDonald said...

@Doubts

It isn't very easy to add stuff to the lock screen. You would need to write a plugin for sure. Check out this SO answer for some possible solutions:

http://stackoverflow.com/questions/4116001/android-lock-screen-widget

-igor said...

Hi Simon,

Thank you for your great article.

It seems that something is not working correctly in my setup. If I call play() only once, the player does not start. Only if I call it twice the playback starts. What am I doing wrong?

Here is the environment; Samsung Galaxy with android 2.3.3, cordova 2.0.0.

var m = null;
function myTest1(){
if(m != null){
m.stop();
m.release();
m = null;
}
m = new Media(media_src, onSuccess, onError);
m.play();
m.play();
}

Thank you,
-igor

Simon MacDonald said...

@igor

You are running into a bug in 2.0.0. 2.1.0 with a bunch of fixes to Media will take care of it.

umairqureshi_6 said...

hi simon
i have added your given code and images to asset/www
but it doesn't work
any other changes to made?
i am new to phonegap
waiting for your response

Simon MacDonald said...

@umairqureshi_6 et al

I'm going to stop answering comments that are along the lines of "it just doesn't work". Obviously it does work or I wouldn't have posted this information up in the first place. I'm sorry but you probably didn't follow the instructions correctly or at least that's what I'll have to assume if you don't give me enough information like:

1) Android version
2) PhoneGap version
3) error logs (learn "adb logcat" folks)

Sorry to be an ass but I don't have time to debug everyones problems or chase them for information they aren't providing.

吳志忠 said...

Hi simon :
It's ok for playing files under /android_asset/sample.mp3. but it will failed when using url as remote http (ex :http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3). Log as below . Can media object support playing mp3 via http ?
emulator : android 2.2
cordova : 1.8
Error : E/MediaPlayer(685): Error (1,-2147483648)

Simon MacDonald said...

@吳志忠

Yes, it supports playing files over http.

Alexis Gaitan said...

Hi Simon, thanks for this great example, im trying to stream this url: http://streaming507.com:9996 is shoutcast aac+ but when i click play i just hear strange noices not the music itself can u help me please.

thanks very much.

Simon MacDonald said...

@Alexis Gaitan

Right now it says that server is down so I can't tell you for sure if that url would even work. I can tell you that streaming audio only works on Android 2.3 or later, if I remember correctly. Maybe it is Android 2.2.

Ozzy Shahzaib said...

Simon, thank you for the wonderful article, but could you please help me figure out the correct directory structure to play my audio files in PhoneGap on Android. Currently my audio is stored in the directory: assets/www in a folder audio.

Thatis the index.html file in assets/www has path names referred as: audio/publisher104_book311_view3.mp3

Simon MacDonald said...

@Ozzy Shahzaib

You should use:

myMedia = new Media("/android_asset/www/audio/publisher104_book311_view3.mp3");
myMedia.play();

Mike Alireza said...

Hi Simon,

I have been following your posts all over the web (here, stackoverflow, ect), and I have been trying to resolve this issue that I have with Cordova 2.1 for Android...

Basically I want to create an array of Media objects in javascript upon recording the file. At the top of the js file an array - mediaFiles = []; - is initialized, and, upon each "onRecord" event that is triggered, I do this:
-
mediaFiles[counter]= new Media (path, success, error, status) ;
-
and then i do:

mediaFiles[counter].startRecord and mediaFiles[counter].stopRecord respectively.

--------

I know that the media files are created successfully, because when I open them with a file manager I can play them back. However here is where my confusion occurs.

On the "playFile()" event that is triggered, I attempt to simply determine which file the user wants to play, and then call:
-
mediaFiles[item].play();
-
Now, this is the ONLY place that it does not work! I know I am referencing the correct array index, i've checked it over and over again, and I cannot seem to find out what is wrong... Furthermore, there is nothing in the LogCat either. I have tried to do an "alert(mediaFile[item].play)" and all I get is the function definition of the play function for the media File.

Do you think you would be able to reproduce the same issue?

Thank you very much for your help!
-Mike

Simon MacDonald said...

@Mike Alireza

There may be a bug in the Media API. You should try creating a new Media object to play the file you've recorded.

Mike Alireza said...

@Simon

Thank you for your reply.

Interestingly enough there was a phonegap update on Nov 1st (which you posted as well I believe). I reverted back to my initial version with 2 objects only, each being created when needed and released when not needed (the correct way).

Surprisingly, it works perfectly fine now...Guess there was a bug!

Thanks!
-Mike

Simon MacDonald said...

@Mike Alireza

Great, glad we fixed it.

Dave Lowerre said...

getCurrentPosition can be terribly inaccurate at times. Why is it implemented as an asynchronous callback?

I have detailed description of the issue posted here:

http://stackoverflow.com/questions/12633726/phonegap-media-getcurrentposition-is-inaccurate-and-slow

Simon MacDonald said...

@Dave Lowerre

I feel your pain. No one is more disappointed in the Media API than I am. I've been after the guys to do a review of this API for ages. I really want to replace it with and implementation of the W3C Audio tag API in JavaScript instead.

Have you looked at the Low Latency Audio Plugin? It may not do 100% what you want but it is probably more performant and could be modified to return a current position.

https://github.com/phonegap/phonegap-plugins/tree/master/Android/LowLatencyAudio

Mishbah Razzaque said...

Hi Simon,

Thank you so much for this awesome tutorial. I'm new to PhoneGap and jquery but I would be grateful if you can help me.

I'm using the code you have posted and I was wondering if its possible to stop the media playback if the user decides to leave the page.

Excuse my poor english. Many Thanks for your help.

Simon MacDonald said...

@Mishbah Razzaque

Register an event listener for the "pause" event. When you get a pause event call media.stop().

rahulmendonca said...

Hi Simon

Can we analyse audio intensity of the recorded sound using phonegap.

Simon MacDonald said...

@rahulmendonca

No, you'd need to write a plugin to do this.

scottii said...

any thought on how to continue playing audio in the background?

thx

Simon MacDonald said...

@scottii

What behaviour are you getting as I can do that just fine on Android? Make sure keep running is set to true in onCreate:

super.setBooleanProperty("keepRunning", true);

joseph said...

Hi Simon,

How can we add a loading image while the audio file is streaming. My files are bit larger so the loading of file taking some time, how can I solve it.

Simon MacDonald said...

@joseph

The Media API is actually pretty lame. There is no event fired when the file is loaded. Without making some additions to the native code I don't know of a way to do this.

gammler said...

@Simon

Thanks for this great tutorial, it really opend my eyes!! But since 3 days im really disfrustated, i only can play the mp3 from the Url. I tried the whole tutorial with Phonegap Build, so that i add the test.mp3 simply in the same package with the index.html. But somehow it wont play! And how do i save the mp3 file on the sdcard, to load it how you described with ("test.mp3")? Greetings from Germany! Hope you understand my englisch! Sorry!

Simon MacDonald said...

@gammler

You should have any trouble playing the file if you use the path I specified in the tutorial if the mp3 is in the same folder as your index.html.

If you want to put a file on the sdcard use adb push.

randhawaz.com said...

Hi Simon, I am using your exact code and when i run the App;icationin Browser Mozilla just to test if this triggers the File or not, I am getting the following error:

TypeError: myMedia is null
[Break On This Error]

myMedia.play();

ReferenceError: Media is not defined
[Break On This Error]

...dia = new Media(yourSelect.options[yourSelect.selectedIndex].value, stopAudio, n...

Simon MacDonald said...

@randhawaz.com

The Media object doesn't exist in Firefox.

gavyrandhawa said...

Hi Simon,

Here i tested on Chrome:

Uncaught TypeError: Cannot call method 'play' of null
playAudio
onclick

Uncaught ReferenceError: Media is not defined
updateMedia
onchange

Simon MacDonald said...

@gavyrandhawa

It's not in Chrome, Firefox, Opera, etc. you have to run it as a PhoneGap application in the emulator or on the device.

gmayer said...

Hi Simon
thanks for the excellent article.

I'm experiencing a strange problem that I can't find any leads on:

In a simple PhoneGap Android project, I can play sound just fine through the Media object.
However, within a larger framework I'm using on a current project at work, the sound does not play at all. No errors thrown - just no sound at all.

The framework links in many other javascript libraries for various purposes, but none related to audio.

Are there any know issues/conflicts between PhoneGap and other libraries?

(note - iOS works fine)

Thanks
Greg

Simon MacDonald said...

@gmayer

None that I know of. You may want to try grepping the other libs for instances of "Media" to see if there is a conflict.

Nidhi Dagar said...

Hi Simon I am trying to play an audio using different url. The audio is playing using http://audio.ibeat.org/content/p1rj1s/p1rj1s_-_rockGuitar.mp3 url but when I try a different url, there's no response. Please help.

Simon MacDonald said...

@Nidhi Dagar

What URL are you using? Did you make sure it was whitelisted? Do you know if the codec is compatible on your OS?

Gautam Kishore said...

Hi Simon, I am trying record and play on the same file. Record is working but play from /sdcard/ is not working on the device. I even tried creating a new Media object.
Code: http://pastebin.com/GUG0jTUh

What might be the problem?

Simon MacDonald said...

@Gautam Kishore

Looking at your code in pastebin you use "/voiceit/myrecording.mp3" as the location of your file. That means it is trying to store it in the root file system in a directory called "voiceit". First thing I would do is to try your code with just "myrecording.mp3" which should just drop it in the sdcard. Then if that works and you have a directory called "/sdcard/voiceit" then try using the path "voiceit/myrecording.mp3" instead.

Gautam Kishore said...

Thanks Simon. But, I've tried only myrecording.mp3 too. Also, the extra slash I noticed and removed after I pasted it to you in pastebin and its creating the file in /sdcard/voiceit/ but my problem is, its not playing from sd card.

Simon MacDonald said...

@Gautam Kishore

Try using the paths "voiceit/myrecording.mp3" and "/sdcard/voiceit/myrecording.mp3" either one of those should work. If they don't what are you seeing in "adb logcat" when you try to play the file?

Sunil said...

Hi Simon,

I wanted to play an Url streamed thru shoutcast server in windows phone 7 in cordova. I tried it many times without any luck. Can u pls suggest a wau to do it. Thanks in advance.

Simon MacDonald said...

@Sunil

Sorry, I don't know if Windows Phone can play a shoutcast stream. Try asked Jesse MacFadyen @purplecabbage

Kingsley said...

Great post Simon. My media player works great on android but on ios it seems to freeze for a bit before finally playing. Any idea why?
My code is thus

var path = "http:// ....";
var player = new media (path, onsucess, onfailure)
player.play()

The log file just says "will use resource 'http://....' from the internet."

Thanks

Simon MacDonald said...

@Kingsley

It probably takes a bit to buffer the file before it can start playing. Typically http delay.

My computer science learning log. said...

Hi, Simon. The file format on Android is not 'mp3'. The codec is 'AMR' and the container is '.3gp'. What's more, on iOS it changes to '.wav'. Why does no one change the document till now? It mislead lots of people.

Simon MacDonald said...

@My computer science learning log

Sorry, we're not perfect. Sometimes we make mistakes.

Khushwant Singh said...

Hi Simon,

I have been facing an issue with Media object while playing live streaming (shoutcast). It looks like Media object's error function does not get called if the URL fails to return valid stream (bytes) but does return status code 200 (OK). The LOG CAT clearly shows that there is an error and Media cannot be played but this error message does not get forwarded to the media error function and Media object dies silently. The error function only gets called when I try to call Media.Stop() function.

I tested the same URL on Chrome, that implicitly uses HTML5 Audio object, and it seems like in case they do not get valid stream but 200 (OK), the Audio object keeps on trying and stops until it gets audio stream back. I used fiddler and can see Audio object calls the same URL again and again until it gets response with stream.

So my question here are,

1) Can we make Media object works the same way HTML5 Audio control works?

2) Can we force Media object to call error function in case there is any error (just after play).

I am hoping that you can help me on this.

URL
*******
http://radio.sgpc.net:8020/live16?1.mp3

LogCat logs
********************
08-12 11:09:32.348: E/HTTPStream(21328): HTTPStream::receive(): recv failed, server is gone, total received: 0 bytes
08-12 11:09:32.348: E/NuHTTPDataSource(21328): NuHTTPDataSource::readAt(offset 0, size 65536) err(-1005) = internalRead()
08-12 11:09:32.348: E/NuCachedSource2(21328): source returned error -1005
08-12 11:09:32.548: E/MediaPlayer(31929): error (1, -2147483648)
08-12 11:09:32.548: E/MediaPlayer(31929): Error (1,-2147483648)
08-12 11:09:42.988: E/CordovaWebView(31929): CordovaWebView: TIMEOUT ERROR!


Many Thanks,
Khushwant

Simon MacDonald said...

@Khushwant Singh

Yeah, I'd have liked to have fixed the Media object a long time ago but we never seemed to have had the time. You have a good case and description here, you should open a bug on JIRA:

https://issues.apache.org/jira/browse/CB

PradeepIT said...

Hi Simon
I have query, when i'm try to convert website to android apps using phonegap its work fine but audio backgroud not functioning. can u please guide me step by step.

Thanks
Pradeep
itspradeepit@gmail.com

Simon MacDonald said...

@PradeepIT

Are you using an audio tag or the Media class from PhoneGap? The audio tag doesn't work too well in a WebView.

phatreaction said...

Hey Simon,

really hoping you can shed some light on this, as I've spent a bunch of time trying to get things working with the Media class.

Here's the deal, I'm first using fileTransfer to download the MP3 and putting it into a sub directory called "cornish-mining" on the sdcard. But when I use the exact same URI in the media statement it does not play the file when on an Android device (iOS is just fine)

The file ends up in file://mnt/sdcard/cornish-mining/track_1.mp3

So after reading all the other comments and your instructions I tried moving the MP3 to the root of the SD card, and changed the path accordingly, no joy.

I've had no joy with

/mnt/sdcard/track_1.mp3
/mnt/sdcard/cornish-mining/track_1.mp3
/sdcard/track_1.mp3
/sdcard/cornish-mining/track_1.mp3
track_1.mp3
cornish-mining/track_1.mp3

(not forgetting that I have the same MP3 in the root and in the cornish-mining directory.

I'm really at a loss as to where to go next, so strange that it works on iOS and not on Android.

To add to the plot, I use window.appRootDir.fullPath to get the fully qualified location of the app directory (file://........../cornish-mining/) on the SD card as I know on my old galaxy-y the file path is vastly different to the S-4, neither of which are able to play the audio.

Please tell me I am just being silly and making a noob mistake.

Many thanks for your help in advance, if you want to see the code do let me know

Simon MacDonald said...

@phatreaction

Okay that should work but one thing I notice is you are using the path "file://mnt/sdcard/cornish-mining/track_1.mp3" with 2 / but you should double check to make sure it has 3 / as in "file:///" as the first to are for the protocol and the last indicates the root of the file system.

The Media class is a bit of PITA and I wish we'd refactor it. What are you seeing in "adb logcat" when you try to play the file? If you can post a snipped of the code up somewhere like gist.github.com it would probably help.