I recently observed a very weird problem with MediaPlayer playing an mp3 file. I'm running this code (mPlayer is a MediaPlayer):
Log.d(TAG, "Pausing");
try {
mPlayer.pause();
Log.d(TAG, "Paused");
} catch (IllegalStateException e) {
Log.w(TAG, "exception pausing player");
}
The weird thing is that if I'm close to the end of the audio file, the player sends a completion notification to my OnCompletionListener a short time after the above code completes. (I haven't pinned down exactly how close I have to be, but it's on the order of 1/4 second.) For instance, here's a typical logcat output when this occurs:
05-27 17:23:43.439: DEBUG/Player(266): Pausing
05-27 17:23:43.487: DEBUG/Player(266): Paused
05-27 17:23:43.838: WARN/Player(266): Audio completed (state=PAUSED)
Note that the warning line (logged from my OnCompletionListener), comes over 300 ms after the call to pause() returned!
The result is that the media player enters the PlaybackCompleted state when I don't expect it. This screws up the behavior of my code (as well as start(), which restarts from the beginning instead of playing the last little bit of the file).
This has happened on emulators from 1.6 through 2.3 and on at least one device running 2.2. Does anyone know about this problem and what to do about it?
The mediaplayer runs in a separate thread and you have to wait until the ui thread and the mediaplayer's thread have synced. So the delay is normal. It may be possible that the player really completes the file because of your pause-command reaching the player too late. Try what happens with a longer audio-file.
Another problem which might come up is other code seeking beyond the end of the track after the pause-command. What's the code of your listener?
Related
When I play a sound in my app it comes off as clippy and distorted. Here is a recording: recording.
Here is the sound file as it was uploaded to android studio: success sound cue
Here is the function that calls the sound from the .raw
public void playCorrectAnswerSound() {
final MediaPlayer mp = MediaPlayer.create(this, R.raw.correct);
mp.start();
}
Heres how I call it:
Thread t = new Thread(){
public void run(){
playCorrectAnswerSound();
}
};
t.start()
This is my first time debugging a sound related issue. I don't know what else to include in this post, so if you need more info please say so.
EDIT: I was asked to record more of the distortion. Here it is. Also I should say that after more testing, my physical device lacks the sound distortion while the sound distortion is present on 3 different emulators.
I'm going to say this is stuttering due to underrun (starvation) of the MediaPlayer's internal playback buffer. That is, the MediaPlayer can't supply data fast enough to keep up with the sound hardware (which suggests a severe lack of processing power). If the buffer starves, it'll start to play back old data (because it's a circular buffer). This causes a sharp phase transition, which sounds like a "click". Presumably the MediaPlayer recovers quickly enough that the "correct" sound resumes playing shortly thereafter.
Here is a picture of the spectrum from Audacity. 0-4KHz. The first row is the clean .mp3; the next four rows are the distorted recordings (in no particular order). All rows have been aligned in time, and are roughly the same amplitude. The large vertical stripes in the last four rows represent the distortion/clicks that you hear.
I'm having a problem with OpenSL ES on Android. I'm using OpenSL to play sound effects. Currently I'm creating a new player each time I play a sound. (I know this isn't terribly efficient, but it's "good enough" for the time being.)
After a while of playback, I start to get these errors:
E/libOpenSLES(25131): Too many objects
W/libOpenSLES(25131): Leaving Engine::CreateAudioPlayer (SL_RESULT_MEMORY_FAILURE)
I'm tracking my create/destroy pattern and I never go above 4 outstanding objects at any given time, well below the system limit of 32. Of course, this is assuming that the Destroy is properly working.
My only guess right now is that I'm doing something incorrectly when I clean up the player objects. One possible issue is that the Destroy is often called in the context of the player callback (basically destroying the player after it's finished playing), although I can't find any reference suggesting this is a problem. Are there any other cleanup steps I should be taking besides "Destroy"-ing the player object? Do the Interfaces need to be cleaned up somehow as well?
-- Added --
After more testing, it happens consistently after the 30th player is created (there is an engine and a mix too, so that brings the total to 32 objects). So I must not be destroying the object properly. Here's the code--I'd love to know what's going wrong:
SLuint32 playerState = 0;
SLresult result = (*pPlayerObject)->GetState(pPlayerObject, &playerState);
return_if_fail(result);
if (playerState == SL_OBJECT_STATE_REALIZED)
{
(*pPlayerObject)->AbortAsyncOperation(pPlayerObject);
(*pPlayerObject)->Destroy(pPlayerObject);
}
else
{
__android_log_print(1, LOG_TAG, "Player object in unexpected state (%d)", playerState);
return 1002;
}
if (playerState == SL_OBJECT_STATE_REALIZED)
is not needed. Try to do it always.
AbortAsyncOperation is called in Destroy => not needed.
So try just (*pPlayerObject)->Destroy(pPlayerObject); it should be enough.
Edit:
I tested, and found solution.
You cannot call Destroy() from player callback. Should make "destroy" list and destroy it somewhere else, for example, in main thread.
Apparently no exception is thrown so that I can recognize an error while buffering streaming audio content. For example I've disconnected my router and the app will continue to try to buffer the whole time. When I reconnect then it completes buffering and continues even after being disconnected for over a minute!
So the problem is I can't let my user sit there for that long without considering that a problem. What is the proper method to detect a buffering problem with the Android media player?
I'm thinking about using a Timer for a timeout. I'll start probably with 15 seconds (using a proxy I tested a 5kbps connection, which would be a worst case, was able to start playing in 6-10 seconds, so I think 15 seconds would be a reasonable timeout period). Does this sound like a good plan? If so should I create a new Timer with each buffer attempt or should I keep the same Timer throughout the lifetime of the playback service?
So basically I'm asking two questions:
1) What's the proper way to detect if a buffer is having a problem? Is there a listener I'm overlooking? I've tried MediaPlayer.OnErrorListener of course that doesn't fire in my tests. My conclusion is I have to have a timeout to detect a buffering error.
2) If I'm correct on number one, what is the proper way to use a Timer? Create one with each buffer attempt or reuse the same one? EDIT Also should I restart the (or cancel and create a new) Timer onBufferUpdate? With the onBufferUpdate listener I should know that some data is coming back so should maybe reset the timer with that.
From your question, I understand that the primary objective is to detect a situation if your player is stalled due to buffering and take some actions thereof. To handle this situation, I feel that the following 2 listeners may be helpful to identify the same.
MediaPlayer.onBufferingUpdate would provide the timely progress of the buffering. So, if there are 2 callbacks with same percent value, this could be an indication of potential buffering.
There is another listener MediaPlayer.onInfoListener which has some specific events which could be of interest to you. On this listener, if the what is MEDIA_INFO_BUFFERING_START, this would indicate that the player is pausing the playback for buffering i.e. trigger for your logic. Similarly MEDIA_INFO_BUFFERING_END indicates the restart of the playback after filling the buffers.
You Should see this article. The mediaplayer has a ErrorListener to get any error.
http://developer.android.com/reference/android/media/MediaPlayer.OnErrorListener.html
int count=40;//for 40 seconds to wait for buffering after it will finish the activity
//boolean timeoutflag=false;
timeout = new Handler(Looper.getMainLooper()) {
#Override
public void handleMessage(Message msg) {
System.out.println("value of count="+msg.getData().getLong("count"));
if (msg.getData().getBoolean("valid")) {
if (msg.getData().getLong("count") == 0 && !timeoutflag)
{
if (pDialog != null && pDialog.isShowing())
{
try
{
pDialog.dismiss();
}catch(Exception e)
{
e.printStackTrace();
}
}
Toast.makeText(getApplicationContext(),
"Unable To Load This Video", Toast.LENGTH_LONG).show();
finish();
} else {
}
}
}
};
timeout.postDelayed(null, 0);
new Thread(new Runnable() {
#Override
public void run() {
while (count > 0) {
try {
Thread.sleep(1020);
} catch (Exception e) {
}
Message msg = new Message();
Bundle b = new Bundle();
b.putBoolean("valid", true);
b.putLong("count", --count);
msg.setData(b);
timeout.sendMessage(msg);
}
}
}).start();
// set timeoutflag=true; in setOnPreparedListener of video view
For buffering during preparation, you have to set your own timer which calls player.reset() after some interval. This puts the player back into init state.
For buffering after preparation (during play) you have to monitor getPosition(). If it falls behind some maximum, call reset(). This allows you to set an experience threshold for your playback. Handles not only failed connection, but also choppy connection.
Best solution is to not use MediaPlayer. Use a public VLC derivative instead. MP has too many internalized private design limitations requiring horrible workarounds (eg. CANT add codecs). RTFM gives you false hope in this case.
Unless you are doing a very straight laced android app, don't depend on any android api. Some opensource substitutes are better supported, and for good reason.
(really bandeely olly jolly satisfying editorial rant deleted)
I'm making an Android game-app in which you start as a little fish, and you need to eat smaller fish.
Each time you eat a smaller fish (by swimming against them), a sound plays. This is the code for playing the sound:
mp = MediaPlayer.create(GobbleFishGame.this, R.raw.bite);
if (!mPlaySound) {
return;
}
mp.start();
MPRelease();
This works well for the first few fishes. But after having eaten some fishes (the amount varies, but it's around 8-11 fishes), the game suddenly stops and I get the "Activity GobbleFish (in application GobbleFish) is not responding." error. I think it has something to do with too much resources being allocated to the MediaPlayer, therefore I've created a sub which releases mp's resources, and then creating a new MediaPlayer mp (this sub is called every time a fish is eaten). This is the code for the sub:
private void MPRelease(){
MPReleaseCount += 1;
if(MPReleaseCount==5){
mp.release();
mp = new MediaPlayer();
//mp = MediaPlayer.create(GobbleFishGame.this, R.raw.bite);
//mp.reset();
MPReleaseCount=0;
}
}
When MPReleaseCount is 5, mp gets released and recreated (so after 5 fishes are eaten).
I hoped that this would work, but it doesn't. I still have the problem that after some fishes are eaten (last time was 17 fishes, so MPRelease should have been called 3 times already), I get the "is not responding" error and I have to Force Close or Wait.
Can anyone tell me how I could do this to release the resources? Because I think that's the issue. I can't really tell if that's the issue because I don't get any error message in my LogCat in Eclipse. I did get this error report after Force Closing the app because of a "is not responding" error:
ANR in (my activity)
Reason: keyDispatchingTimedOut
Thanks in advance.
Don't release and create the MediaPlayer everytime you used it. Create it once with your audiosource and call a seekTo(0) in an OnCompletionListener(). Then play it again.
Also have a look at SoundPool http://developer.android.com/reference/android/media/SoundPool.html which is made for usecases like yours where you have to play the same audio multiple times. It stores the audio and plays it on demand. You also have a lower audio latency than with MediaPLayer.
I've got to be missing something obvious here, but I can't seem to find anything to allow me to determine when MediaPlayer is buffering audio. I'm streaming internet audio and I want to display a buffering indicator, but nothing I've tried allows me to know when MediaPlayer interrupts the audio to buffer, so I can't properly display a buffering indicator. Any clues?
Like below (API level ≥ 9):
mp.setOnInfoListener(new OnInfoListener() {
#Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START:
loadingDialog.setVisibility(View.VISIBLE);
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END:
loadingDialog.setVisibility(View.GONE);
break;
}
return false;
}
});
NOTE : There is a known bug in Android. When playing HLS stream it's just never calls OnInfoListener or OnBuffering. check this link OnInfoListener bug
Ok, I feel a little vindicated now. I checked out the Pandora app and it doesn't display a buffering indicator. When music is interrupted for buffering, it just sits there as if nothing happened and the UI looks like it's still playing. So I've come to the conclusion that if you're using MediaPlayer, it's just not possible to determine if the track is temporarily paused for buffering.
However, I did notice that there are a couple MediaPlayer constants that could be of use:
MEDIA_INFO_BUFFERING_START and MEDIA_INFO_BUFFERING_END. But they're only available in API level 9+, and the docs don't say anything about them. I'm assuming they can be used with an OnInfoListener.
I'm disappointed, but at least I can stop spinning my wheels now and move on to something else.
#Daniel, per your comment on #JRL's answer, you could probably get this working by spinning up a thread and waiting for a timeout.
Something like DetectBufferTimeout.java (untested) would do nicely.
I do, however, agree that spinning up this separate thread is a bit of a hack. Perhaps OnBufferingUpdateListener could make a guarantee as to how often it calls onBufferingUpdate() regardless of whether a change in the buffering progress has occurred so we can detect if we're getting the same value over and over.
Register an OnBufferingUpdate listener.
You can use a thread that checks the current position of the MediaPlayer. If the position doesnt change, you can conclude that the media is in buffering state. Here is the complete tutorial that i wrote: http://www.ottodroid.net/?p=260
Just like #JRL said, register a OnBufferUpdateListener but register it on the MediaPlayer object in OnPreparedListener, that way anytime the music is buffering it'll always indicate. as this listener is always called when mediaplayer is buffering. like so:
player.setOnPreparedListener(mediaPlayer -> {
mediaPlayer.setOnBufferingUpdateListener((mediaPlayer1, percent) -> {
if (percent<=99)view.showMusicBuffer();
else view.hideMusicBuffer();
});
view.setTrackDuration(mediaPlayer.getDuration());
mediaPlayer.start();
changeTrackBarProgress();
});