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();
});
Related
I'm developing a game in Android and I came across a very annoying, hard-to-find bug. The issue is that when you are using SoundPool to play your sounds, you can actually loop whatever sound you are playing. In this case, the issue is the "running steps" sound; this sound gets executed quite fast and continually (around every 400ms) when the main character is running.
Now when playing the sound on a regular (not so powerful) device e.g. Samsung SII, the sound is played every 500ms - however, if I run the very same code on another device (let's say, Samsung SIV, Samsung SIII), the sound plays twice or even three times faster.
It seems like the more powerful the device hardware specs are, the faster it plays. On some devices, it plays so fast that you almost hear one solid continuous sound. I've been looking for techniques to set a specific ratio on the time period between sound plays, but it doesn't work properly and the issue remains. Does anyone know how to fix it, either using SoundPool, MediaPlayer, or any other sound-controlling API on Android?
You could use an AudioTrack to play a continuous stream of PCM data, since you would pass a stream you could be sure about the interval between sounds. the downside could be a little delay when first starting the sound but it depends on the minimum buffer size, and it depends, I think, on android version and device. On my galaxy s2 android 4.1 it was about 20ms.if you think this could be an option I can post some code
The problem with just looping or using a regular interval for something like footsteps is that you have a possible decoupling of sound and visuals. If your sound gets delays or sped up, or your visuals get delayed or sped up, you would have to adjust for that delay dynamically and automatically. You already have that issue right here
A better solution would be to place a trigger on the exact event which should trigger the sound (in this case, the foot being placed down), which then plays the sound. This also means that if you have multiple sources of the sound (like multiple footsteps), you don't have to manually start the sound with the right interval.
I can't seem to replicate the issue on Galaxy Nexus and Nexus S, does that mean I fixed it? Or maybe you could show what you're doing differently from this:
SoundPool soundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 100);
Integer sound1 = soundPool.load(this, R.raw.file1, 1);
Integer sound2 = soundPool.load(this, R.raw.file2, 1);
playSound(sound1);
public void playSound(int sound) {
AudioManager mgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
float volume = mgr.getStreamVolume(AudioManager.STREAM_MUSIC)
/ mgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
soundPool.play(sound, volume, volume, 1, -1, 1.0f);
}
If the problem is that you want to control the interval between the discrete sounds, The easiest way to do this is with a handler.
Basically you start a sound playing which is an asynchronous process. Then you use a handler to schedule a message to play the next sound sometime in the future. It will take some trial and error to get it right, but you will be guaranteed that the sound will start at the same interval after the previous sound on every device.
Here is some code to illustrate what I am talking about.
Here is a handler implementation you could use:
handler = new Handler() {
/* (non-Javadoc)
* #see android.os.Handler#handleMessage(android.os.Message)
*/
#Override
public void handleMessage(Message msg) {
if (msg.what == NEXT_ITEM_MSG) {
playNextSound();
}
else if (msg.what == SEQUENCE_COMPLETE_MSG) {
// notify a listener
listener.onSoundComplete()
}
}
};
Then you could write playNextSound like this:
private void playNextSound() {
if (mRunning) {
// Get the first item
SoundSequenceItem item = currentSequence.getNextSequenceItem();
if (item == null) {
Message msg = handler.obtainMessage(SEQUENCE_COMPLETE_MSG);
handler.sendMessage(msg);
return;
}
// Play the sound
int iSoundResId = item.getSoundResId();
if (iSoundResId != -1) {
player.playSoundNow(soundResId);
}
// schedule a message to advance to next item after duration
Message msg = handler.obtainMessage(NEXT_ITEM_MSG);
handler.sendMessageDelayed(msg, item.getDuration());
}
}
and your SoundSequenceItem could just be a simple class that has a sound file resource id and a duration. If you want to keep playing the sound while the character is moving you could do something like this:
public void onSoundComplete() {
if (character.isRunning()) {
currentSequence.addSequenceItem(new SoundSequenceItem(R.id.footsteps,500);
playNextSound();
}
}
Or you could modify playNextSound to continually play the same sound. Mine is written this way to be able to play different sounds in sequence.
I have had a lot of problems developing apps which used sounds and stuff like that. I would not suggest you to use SoundPool since it is bug-affected, and also be aware that looping sounds with SoundPool won't work on devices which are 4.3 and higher, see this open issue, at AOSP - Issue tracker.
I think that the solution is to go native and use OpenSL ES o similar libraries.
I am using SoundPool to play sfx sounds in a game on Android. In most cases it works perfectly, except sometimes I need to stop all sounds at once not pause (doesn't matter if they are set to loop or not).
I can't figure out how to stop a sound from playing without knowing the StreamID of that sound. What I know:
soundpool.load(...some sound...) returns a soundID
soundpool.play(soundID) plays the sound and returns a streamID
soundpool.stop(streamID) stops the sound
My question is, how can I stop a sound without knowing the streamID ? I tried tracking all streamIDs in a list, but sometimes there are so many short streams playing at once, that it won't work. And I can't find any method in SoundPoolto get the active streamIDs. Does anyone know how to stop all sounds?
Any hint is appreciated! thanks
I'd recommend using autoPause() and autoResume() to pause and restart your sounds. This function is part of the soundpool:
http://developer.android.com/reference/android/media/SoundPool.html#autoPause()
What about using .release() ? Accourding to the documentation:
Release the SoundPool resources. Release all memory and native resources used by the SoundPool object. The SoundPool can no longer be used and the reference should be set to null.
I think it also stops everything. (Actually it was causing a bug at one of my apps, that's why I say it)
However, I believe that if you want to play any sounds later, you may have to load them again.
SoundPool doesn't have a method to retrieve the streamID from the index of the stream, like it should. So even when you know the maximum number of streams, you can't just iterate through like the following:
for(int index=0;index<MAXSTREAMS;index++){
int streamid = SoundPool.getStreamID(index);//getStreamID action doesn't exist :(
soundpool.stop(streamid);
}
As you mentioned, the coder has to manage this himself with a list. I implement the declaration as follows:
List<Integer> streams = new ArrayList<Integer>();
You can add and remove soundIDs to this list as you play and stop sounds as follows:
streams.add(batattack_soundID);
streams.remove(Integer.valueOf(batattack_soundID));//NOT streams.remove(batattack_soundID);
Be sure to use Integer.valueOf() or .remove() will interpret your parameter as an index position, likely giving you an IndexOutOfBoundsException.
To clean everything up you can code as follows:
for (Integer stream : streams) {
soundPool.stop(stream);
}
streams.clear();
I try to stream (progressive e.g: http://server.com/video.mp4)
when i use the standard google mediaplayer (VideoView from android package) and register an onBufferingUpdateListener then i get the bufferpercentage that refers to the download state of the hole video. This player has also a loading view where i can see the buffer state.
This bufferpercentage and view shows me how much of the video has been downloaded.
Now when i use the Vitamio player, the onBufferingUpdateListener shows me after a few seconds 99 percent of buffering and there is no loading view too. And when i pause the playback it stops buffering immediately instead of continue buffering like the google videoview does. This is very usefull if you have a slow http stream.
Is there a way to make the vitamio-videoplayer buffer the videofiles in the same way as the google videoplayer does?
thank you
daniel
Sorry i posted that question as wrong user. Here the Answer of what i tried:
VideoView (android default - just plays few video formats) from inside the android.widget and from io.vov.vitamio.widget (vitamio - plays most video formats) package has the same structure. In both you can register an OnBufferingUdateListener that returns the bufferstate in percent:
videoview.setOnBufferingUpdateListener(new io.vov.vitamio.MediaPlayer.OnBufferingUpdateListener() {
public void onBufferingUpdate(io.vov.vitamio.MediaPlayer mp, int i) {
Log.v(TAG, "Buffer percentage done: "+i);
}
});
or with the android default VideoView:
videoview.setOnBufferingUpdateListener(new android.media.MediaPlayer.OnBufferingUpdateListener() {
public void onBufferingUpdate(android.media.MediaPlayer mp, int i) {
Log.v(TAG, "Buffer percentage done: "+i);
}
});
If i use android.widget.VideoView the buffer percentage slowly increases until it reaches 100% - The video file has been downloaded completely. And it continues updating BufferingUpdate when i press the pause button.
When i use io.vov.vitamio.widget.VideoView the percentage reaches 100% within seconds. Then the video starts and the OnBufferingUpdateListener never gets called again (when i call getBufferPercentage it is always at 99 percent. That seems to be the reason). And as i sayed: It seems to stop buffering when i press the pause button.
I think the buffering works different in vitamio. But that's crap. Especially when i stream videos from the web and the video datarate is higher than the download speed i need to prebuffer the video by pressing pause and wait until it has downloaded enough data to watch it smoothly. Hope you got what i mean. thank you
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 am trying to Seek to a particular location in a video in Android and I am completely stuck because of the inconsistencies it is showing in its behaviour. Here's a list of things I ve done
VideoView.Seekto goes to 5:19 for one video, 5:17 for one video and 5:32 for another for the same milliseconds(326000 ms)!
Since VideoView does not support onSeekListener, I've modified the
source VideoView and added support for it. Yet it does not pause and
start seeking from where I want it to - there is always a lag! The
onSeekListener is called immediately some 6 s before where I want it
to stop. In many cases the seek bar shows the right time and suddenly jumps back a few seconds even though I am calling video.start
from onSeekCompleteListener
Why is this so inconsistent ? Is there a definite way of seeking to where you want to go in milliseconds in videoview? I know I should use MediaPLayer + Surface but since VideoView is just a wrapper of the two I am modifying it at source but to no avail.
I am aware of this : http://code.google.com/p/android/issues/detail?id=9135
But is there any way to get around this and have a definite way of
1.) Seeking to the exact time in milliseconds
2.) Pausing and resuming at the exact time?
You have to wait for the seeking to complete.
VideoView does not have a OnSeekCompleteListener() but you can access the MediaPlayer from the onPrepared method of the VideoView and then set the OnSeekCompleteListener, like this :
videoView.setOnPreparedListener(new OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
mp.setOnSeekCompleteListener(new OnSeekCompleteListener() {
#Override
public void onSeekComplete(MediaPlayer mp) {
//Seek completed. Move seekbar
}
});
}
});
10 years later and I came across the same question exactly.
Any way, for me, the solution was to use the mediaPlayer from inside VideoView (Android Oreo 8.0+):
Explanation:
videoView default seekTo function use mediaPlayer default seekTo function (source)
mediaPlayer default seekTo overload is the same as seekTo(long, int) with SEEK_PREVIOUS_SYNC mode, you should use SEEK_PREVIOUS_SYNC only if you want to seek to a sync frame that has a timestamp earlier than or the same as milliseconds given,
However, SEEK_CLOSEST will seek to a frame that may or may not be a sync frame but is closest to or the same as milliseconds.
I know this is not the proper solution to your question but take a look into this library that is being made over the Google's default video view
https://github.com/brianwernick/ExoMedia
It has all the functionality and more that is being supported by default video view.
You can use this video view and emVideoView.seekTo(1000); to jump to 1000 millisecond in the video. You can also have setOnSeekCompletionListener to do process when seek complete.
I solved this problem like this
the seekTo() function doesn't work in VideoView