The MediaPlayer's getDuration() method is giving me an incorrect value for some audio files. I think the common trait for all these files is that they were manipulated using Audacity or some other audio editing tool. This is a problem when trying to tie MediaPlayer progress to a Progress Bar.
I went ahead and logged it:
while(mPlayer.isPlaying())
Log.i("progress/total",
mPlayer.getCurrentPosition() +
"/" + mPlayer.getDuration());
and found this:
I/progress/total(643): 14615/14620
I/progress/total(643): 14647/14620
This is only two log line of thousands, but the point is after the progress passes what getDuration() believes to be the total duration of the song, it just keeps going. Because the MediaPlayer can in fact give the correct total for duration, is there a way to use this to get a proper maximum for my ProgressBar?
I had similar problem when MediaPlayer.getDuration() returned 542434 ms for mp3 file (HTC Desire C with ICS 4.0.3).
File itself was around 89 seconds, difference is too big.
I checked mp3 file content and saw some strange xml like:
<?xpacket begin="п»ї" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.2.2-c063 53.351735, 2008/07/22-18:04:26 ">
After saving this file as new one that xml was dropped and getDuration() returned correct value.
I know that it will not help those who need playing files you can't modify, but for those who can - it should help.
I was trying to play demo player a few while ago ,when i tested in Android emulator ,its behavior was the same as like you mentioned in your question but when i tried in some real device it gave me accurate value of media duration.
If your intention is only play media syncing with seekbar then you can do something like below ,
if (!mediaPlayer.isPlaying())
mediaPlayer.start();
handler.post(new SeekbarRefresh(seekbar));
//Class to update progress of seekbar according to music player
private class SeekbarRefresh implements Runnable {
SeekBar seekBar;
public SeekbarRefresh(SeekBar seekBar, ImageView imageView) {
this.seekBar = seekBar;
}
#Override
public void run() {
if (mediaPlayer != null) {
if (mediaPlayer.getDuration() > 0) {
int currentDuration = mediaPlayer.getCurrentPosition();
seekBar.setProgress(currentDuration);
if (mediaPlayer.isPlaying()){
handler.post(this);
isAudioPlaying = true;
}
else {
handler.removeCallbacks(this);
isAudioPlaying = false;
}
}
}
}
}
seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
mediaPlayer.seekTo(progress);
}
});
I have encountered a similar situation. In my case the time difference between mPlayer.getDuration() to mPlayer.getCurrentPosition() was around 80 seconds.
After reading few posts on the subject, I used third party software to convert the mp3's sample rate from 22,000 kHz to 44,100 kHz. Once converted, the result of getDuration() and getCurrentPosition() are the almost the same (0.0012s constant error).
Here is the test used:
dur = mp.getDuration();
Log.d("dur", dur + " <- getDuration");
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer mp) {
// finish current activity
Log.d("dur", mp.getCurrentPosition() + " <- getCurrentPostion");
}
});
Related
I'm trying to build a game which plays some sounds effects on click & at the same time music in the background.
I tried implementing this with two MediaPlayer objects.
The first one, which served for the effects on click works great.
The second one however sometimes logs error 100, sometimes error 38. No sound at all.
Variables
private MediaPlayer mEffects;
private MediaPlayer mpSoundBackground;
Implementation of the sound media player:
mpSoundBackground = MediaPlayer.create(MainActivity.this, R.raw.soundbackground1small);
mpSoundBackground.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
Logger.d("prepared");
musicPrepared = true;
}
});
mpSoundBackground.setOnErrorListener(new MediaPlayer.OnErrorListener() {
#Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Logger.d("error "+what);
return false;
}
});
if (musicPrepared) {
mpSoundBackground.start();
Logger.d("music is prepared");
} else {
Logger.d("music is not prepared");
}
Implementation of the effects Media Player:
stopPlaying();
mEffects= MediaPlayer.create(MainActivity.this, R.raw.soundhit);
mEffects.start();
private void stopPlaying() {
if (mEffects!= null) {
mEffects.stop();
mEffects.release();
mEffects= null;
}
}
Update
To add to the confusion: It does seem to work in emulator
(Genymotion), but does not work on my OnePlus One, running Lollipop
You need to use the setOnPreparedListener method for both players. also if you want to play a sound on clicks consider using SoundPool.
Also in the public void onPrepared(MediaPlayer mp) method, you can use mp.start there is no need for that flag, since you can not know for sure that it is prepared once you reach that prepared flag
I couldn't make the errors go away, until I reconverted my soundfile to MP3.
Now it plays both on device & simulator without any problems.
Moral of this story: if you are running into errors, try a few encodings of the same file (possibly a few file sizes too!), it might be the solution.
I have set:
mSeekBar.setMax(mp.getDuration()); // 8480
After completion of Audiofile, What I am getting is:
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer mediaPlayer)
{
Log.e("onComplete>>", ""+mediaPlayer.getCurrentPosition());
// mediaPlayer.getCurrentPosition() = 8192
Log.e("getDuration", ""+mediaPlayer.getDuration());
// mediaPlayer.getDuration() = 8480
if(mediaPlayer.getCurrentPosition()>=mediaPlayer.getDuration())
{
// Why never get called???
}
}
});
So, Why is MediaPlayer's Current position never reaches the total duration of Audio file ?
Or technically we can say as:
Why Not?
mediaPlayer.getCurrentPosition()==mediaPlayer.getDuration()
Why Always
mediaPlayer.getCurrentPosition() < mediaPlayer.getDuration()
in OnCompletion listener?
For Example:
I have a Play Symbol for starting the Player. Now when I press play symbol it will convert to Pause symbol.
I have a Maxduration of audiodfile.
Now I want to convert Pause symbol to Play Symbol when Audio file is played completely.
SO what I am doing is Checking:
if(mediaPlayer.getCurrentPosition()>=mediaPlayer.getDuration())
{
// Convert Imagview from Pause to Play
// But never get called
}
If you need to do some task on completion by checking currentPoision to duration, you can do below trick:
if(mediaPlayer.getDuration()-mediaPlayer.getCurrentPosition()<1000){//milliseconds
new Handler().postDelayed(
new Runnable() {
#Override
public void run() {
// Convert Imageview from Pause to Play
}
},1000);
}
I have many short audio fragments which should be played sequentially with minimum latency.
How can I queue these sounds efficiently?
There are two ways I can see this working: using a MediaPlayer or a SoundPool
MediaPlayer
You can use MediaPlayer.OnCompletionListener combined with a list of files to simply listen for completion and play the next one. By and large this works quite well and I have used it to play sequences of very short clips (< 1s) without problems.
SoundPool
Another way is to use the SoundPool class combined with a handler to simply queue up play events in advance. This assumes you know the length of each clip but depending on the format you should be able to find this out.
Which solution you choose depends on a number of factors: how many files, how short they are, what format they are in, where you are getting them from, how fixed or variable the list is, etc.
Personally I would go with MediaPlayer as this is easier to implement and would probably fit your needs.
One way is to concatenate them into a single audio file, then create a MediaPlayer for it and set an OnSeekCompleteListener. Seek to each segment in tern in whichever order you like and then play them in onSeekComplete(). It's timing not exact and MediaPlayer is touchy to use so it may not be the best choice for you but it's good enough for my purposes.
Here's my code:
private MediaPlayer MP = new MediaPlayer();
#Override
public void onCreate(Bundle savedInstanceState) {
//...
FileDescriptor fd = getResources().openRawResourceFd(R.raw.pronounciations).getFileDescriptor();
try {
setVolumeControlStream(AudioManager.STREAM_MUSIC); // Lets the user control the volume.
MP.setDataSource(fd);
MP.setLooping(false);
MP.prepare();
MP.start(); // HACK! Some playing seems required before seeking will work.
Thread.sleep(60); // Really need something like MediaPlayer.bufferFully().
MP.pause();
MP.setOnErrorListener(new OnErrorListener() {
public boolean onError(MediaPlayer mp, int what, int extra) {
return false;
}
});
MP.setOnSeekCompleteListener(new OnSeekCompleteListener() {
public void onSeekComplete(MediaPlayer mp) {
// The clip is queued up and read to play.
// I needed to do this in a background thread for UI purposes.
// You may not need to.
new Thread() {
#Override
public void run() {
MP.start();
try {
Thread.sleep(soundRanges[curWordID][1]);
} catch(InterruptedException e) {}
MP.pause();
}
}.start();
}
});
} catch(Throwable t) {}
}
private void playCurrentWord() {
if(soundRanges != null && !MP.isPlaying() && !silentMode) {
try {
MP.seekTo(soundRanges[curWordID][0]);
}
catch(Throwable t) {}
}
}
You would likely need to concatenate your clips using an external sound editing tool. I used Audacity to find and edit the clip beginnings and lengths that I saved in a file.
soundRanges[curWordID][0] is the offset in the sound file of the beginning of a clip and soundRanges[curWordID][1] is its length.
this following source code snippet is given:
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
#Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END){
activity.dismissDialog(DialogID.DIALOG_LOADING);
return true;
}
return false;
}
});
}
});
I am streaming HLS streams with Android 3.x+ devices and trying to hide a loading dialog once the buffering is completed.
The video streaming works, but the info events are never fired.
Any ideas?
I know its too late, But posting it for the users still seeking for the solution (This worked for me):
progressDialog.show();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
#Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END){
progressDialog.dismiss();
return true;
} else if(what == MediaPlayer.MEDIA_INFO_BUFFERING_START){
progressDialog.show();
}
return false;
}
});
progressDialog.dismiss();
videoView.start();
}
});
You're right, the events are never fired. This is a known HLS bug that I don't think Google will fix.
This applies to the onInfo and the buffering events.
See https://code.google.com/p/android/issues/detail?id=42767 and https://code.google.com/p/googletv-issues/issues/detail?id=2
Sorry!
Not fully sure as to what the OP is asking, but here are some very untimely bits of information.
I wouldn't rely on onPrepared. I find it to be unreliable.
I have found the two most useful pieces of information for HLS streaming through the MediaPlayer are the duration of the video and the progress position of the video. You get both of these by listening to progress updates.
When the duration is greater than zero, you know the video is truly prepared and can be manipulate (scrub). When progress position changes, you know the video is done buffering and has commenced playback. This last item only works when the video is playing of course. The MediaPlayer tends to relay inaccurate information.
These pieces of information are mostly accurate and can usually be relied upon to be "fairly" timely. This timeliness varies from device to device.
onPrepared is called when the MediaPlayer is prepared to start buffering, not when the video is completely buffered. However, it is completely natural to dismiss the loading dialog from within the onPrepared method.
Also MEDIA_INFO_BUFFERING_END is used when MediaPlayer is resuming playback after filling buffers, so I do not think it should be something to use to dismiss the dialog. So this should work:
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mediaPlayer) {
activity.dismissDialog(DialogID.DIALOG_LOADING);
}
});
You can able to set OnPreparedListener on videoView because its your object but if you checkout source of VideoView you will find that mMediaPlayer is its private member so any change that you do from external will not be applied to it.
As per your requirement you need buffering status so you can have thread or handler or some thing so you can update your UI to get buffer status there is one method
int percent = videoView.getBufferPercentage();
if(percent == 100){
// buffering done
}
You no need to go through setOnInfoListener
by overriding setOnPreparedListener method is enough. as in the api show
public void setOnPreparedListener (MediaPlayer.OnPreparedListener l)
Register a callback to be invoked when the media file is loaded and
ready to go.
so, you can dismiss your dialog inside setOnPreparedListener method is enough
like this
vv.setOnPreparedListener(new OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
handler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(MainActivity.this, "finish11", Toast.LENGTH_LONG).show();
}
});
}
});
If you want to show loading each time it's buffering (initial time or subsequent buffer underruns) just ensure to show it again:
// at the beginning
show
boolean onInfo(int what, int extra) {
switch (what) {
case MEDIA_INFO_BUFFERING_END:
"hide";
break;
case MEDIA_INFO_BUFFERING_START
"show":
}
}
So this event sequence will do as desired:
- whenever you start (setVideoURI or start): show
- onPrepared: just plug the info listener
- onInfo BUFFERING_END hide (it's playing)
- onInfo BUFFERING_START show (it's buffering again)
- onInfo BUFFERING_END hide (it's playing)
Update:
This is assuming the info events work. Of course.
I need to catch touch events to the MediaController progress slider, so that updates to the MediaPlayer don't occur until the finger lifts off the slider.
[ Perhaps my situation is unique: I am playing streaming video that comes in multiple "stacks" for each "program". The next stack is not loaded until the previous one is finished. The slider needs to represent the duration of all stacks, and the progress of the "thumb" needs to represent total duration. This is easily done by overriding the getBufferPercentage(), getCurrentPosition(), and getDuration() methods of MediaPlayerControl ]
More problematic is "scrubbing" (moving the thumb) back and forth along the timeline. If it causes the data source to be set multiple times along with a seekTo for every movement, things very quickly bog down and crash. It would be better if the MediaPlayer did no action until the user has finished the scrub.
As others have written, Yes it would be best to write my own implementation of the MediaController. But why redo all that work? I tried extending MediaController, but that got complicated quickly. I just wanted to catch the touch events to the slider!
One is able to get a handle to the elements of MediaController:
final int topContainerId1 = getResources().getIdentifier("mediacontroller_progress", "id", "android");
final SeekBar seekbar = (SeekBar) mController.findViewById(topContainerId1);
Then set a listener on the seekbar, which will require you to implement its three public methods:
seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// Log.i("TAG", "#### onProgressChanged: " + progress);
// update current position here
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
// Log.i("TAG", "#### onStartTrackingTouch");
// this tells the controller to stay visible while user scrubs
mController.show(3600000);
// if it is possible, pause the video
if (playerState == MPState.Started || playerState == MPState.Paused) {
mediaPlayer.pause();
playerState = MPState.Paused;
}
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
int seekValue = seekBar.getProgress();
int newMinutes = Math.round((float)getTotalDuration() * (float)seekValue / (float)seekBar.getMax());
// Log.i("TAG", "#### onStopTrackingTouch: " + newMinutes);
mController.show(3000); // = sDefaultTimeout, hide in 3 seconds
}
});
Caveats:
This is not updating the current position as you scrub, the left-hand-side TextView time value. (I had a truly remarkable way to do this which this margin is too small to contain).