I'm having a hard time with media player in android. Basically I try to play a song from my sd card which is downloading (some sort of streaming). So after the song was downloaded (20%) the song starts to play and if I leave it like this it works fine until the end. But if I try to seek at the end (over 20%) obviously it won't work because my seek position is over EOF file so I made this code inside a method
MediaPlayer mp = new MediaPlayer();
mp.setDataSource(sFilePath);
mp.prepare();
int offset = (progress * mp.getDuration()) / 100;
if (sCompleted) return;
sLastSeek = offset;
if (offset > sMediaPlayer.getDuration()) {
sMediaPlayer.reset();
sMediaPlayer.setDataSource(sFilePath);
sMediaPlayer.prepare();
} else {
sMediaPlayer.seekTo(offset);
}
Where sMediaPlayer is my mediaPlayer, and sFilePath is the file path. In theory the case I presented should be covered but when I seek over the file length the player is reseted (as the code says) but is not playing and this is very awkward and is over my powers.
Maybe the mediaPlayer stopt because it reaches the end of the song. Either start it again, refresh the file in steady intervals (say every 5 secs), or just use onCompletionListener. In the last case, when the file ends, you can reload the file and continue playing... mp.getCurrentPosition() to get where it left off playing.
I also noticed you refer to sMediaPlayer, but your MediaPlayer is defined as mp.
Related
I have a number of mp3 files that I use with Android MediaPlayer to play from certain offsets.
Using seekTo() seems to stop at correct location. player.getCurrrentPosition() returns the correct offset, but in some cases the real position is off for as much as 200 ms. The files are about 3 minutes worth of recording and the incorrect offsets seem to appear at the end. Of some of the files.
I have the same effect either trying with Android 4.0.3 device or 4.3 emulator.
Anybody has experience with "finetuning" MediaPlayer offsets? Any experience why MediaPlayer might not be working correctly with some files? They are all CBR, stereo, some have sampling frequency 22050, some 44100, different bitrates.
I'm setting the offsets from another program and saving to mp3 tags, then in case of doubt verifying manually using Audacity. Audacity agrees with my estimate of what the correct offset is, MediaPlayer seems to disagree.
I'm aware that I could use AudioTrack with raw sound files and have a better control, however it might be impractical as there are many mp3 files, so using raw sound data will make pretty large application or many large data files.
The code is nothing fancy:
player.seekTo(start);
player.start();
CountDownTimer timer = new CountDownTimer(length, 100) {
#Override
public void onTick(long millisUntilFinished) {
if (player!=null) setInt(R.id.nLocation, player.getCurrentPosition());
}
#Override
public void onFinish() {
if (player!=null) {
if (player.isPlaying()) {
player.pause();
}
setInt(R.id.nLocation, player.getCurrentPosition());
player.stop();
player.release();
player = null;
}
}
};
timer.start();
I did not manage to find the rule why the MediaPlayer interprets offset (seekTo) differently for a group of MP3 files. For example when creating a new MP3 file with the same parameters from Audacity+Lame (MPEG1, Layer III, 44100 Hz, 192 Kb/s) it worked perfectly.
However:
this can be reproduced - rip MP3 file using Windows Media Player, settings: MP3, 192 kb/s [added when edited]
I found the workaround that seems to work for any recording.
The background - in order to tell MediaPlayer to play from certain offset, I store certain data in MP3 tags. I use a separate program to set up the playback (in frames): Label A, start frame=1000, length=100 frames, Label B, start #1500 etc. Now when I need to play it back, I read the MP3 headers, determine the frame length, for example 26.12245 ms/frame and calculate the offset (1000 frames will be 26122 ms).
The workaround is to store in MP3 tag also the frame count and length in ms (or pass through again and count the frames). Then when start MediaPlayer, compare MediaPlayer.getDuration() (MediaPlayer estimate) with the duration stored in MP3 tag. Then adjust the frame size:
adjustedFrameSizeMs = realFrameSizeMs + (player.getDuration()-storedDurationMs)/storedframeCount;
In my case (for the files with incorrect offset) the adjusted frame length always was between 26.08 and 26.09 ms (instead of 26.12245).
I attempted to try see if this is because Android plays the recording quicker (so it estimates the "real time", not the time according to frame size and frame count). It seems that it really does plays quicker. But even quicker than its own estimate. For example a recording of about 1 hour:
my estimate: 2448 s
MediaPlayer: 2444 s (4 sec difference)
Audacity: 2442 s (here we are in disagreement)
Foobar: 2448 s (another witness that agrees with my estimate :-)
MediaPlayer, real play time: 2438 s
The real playtime was 6 s (0.25%) less than MediaPlayer own estimate. Another attempt on a different sample gave the same percentage difference. However the fact that Audacity and Foobar did not always agree with my estimates, does not let me put all the blame on MediaPlayer.
I've got an application where I will be playing 2 audio files simultaneously and the user will be attempting to hear the one (spoken words) over the other (background noise). With successful feedback input that they heard the file correctly, I want to decrease the volume of 1 (words) of the 2 files and play it again and they will attempt to hear it over the noise. I only want to decrease the volume of one file... the other one needs to remain constant (otherwise it defeats the purpose of trying to hear the one over the other). All I've found so far with the MediaManager is the ability to change the global volume of the application and not the specific audio clips within the application.
Any help is greatly appreciated.
My impression that the media player was global was incorrect. You can set the volume for this file and it will keep that volume even when you play a louder or quieter file (even when using the same mp). The oncompletion listener is important to releasing the file when over other wise you'll get exceptions and lose all sound. The logarithmic math function is important to getting the volume to scale appropriately between 0 & 100.
Assuming MAX_VOLUME is 100:
mp = MediaPlayer.create(getApplicationContext(), R.raw.noise);
final float noisevolume = (float) (1 - (Math.log(MAX_VOLUME - 50) / Math.log(MAX_VOLUME)));
mp.setVolume(noisevolume, noisevolume);
mp.start();
mp.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
mp.release();
}
});
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
I am making a game and I want to have a list of songs play in the background and loop once it's gone though them all. To do this I loop through creating MediaPlayers and calling setNextMediaPlayer with next player in the list unless it's the last item; then I set it back to the first item in the list. This works but when it loops and tries to play the first song again it tells me that I called play() in an invalid state. So I added an OnCompleteListener so when a song ends I call prepare() on it again but that didn't help. What do I need to do to a MediaPlayer that has reached the end in order to get it to play again?
-= UPDATE =-
Here's my code for initializing the MediaPlayers (minus the try's and catches since that just clutters it up):
String[] assets = manager.list("songs");
Collections.shuffle(Arrays.asList(assets));
for(String name : assets)
{
MediaPlayer player = new MediaPlayer();
AssetFileDescriptor descriptor = manager.openFd("songs/"+name);
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
player.setOnCompletionListener(_reset);
player.prepare();
if(_songs.size() > 0)
{
_songs.get(_songs.size() - 1).setNextMediaPlayer(player);
}
_songs.add(player);
}
_songs.get(_songs.size() - 1).setNextMediaPlayer(_songs.get(0));
If you are getting the same error after calling prepare inside onCompleteListener it might be because the next MediaPlayer, which is now not prepared, is invoked before onCompleteListener.
I would suggest a different approach that might solve your problem and be more efficient as well:
Maintain the order of songs in some data structure such as Circular Linked List so you give it the current song name and it give you the next one
Prepare and start playing the first song
Override onPrepared and get the next song name from your data structure then call setNextMediaPlayer with a newly created and prepared MediaPlayer object
I believe this approach is more efficient because you do not create many instances of MediaPlayer in advance. Also, you provide a prepared nextMediaPlayer every time when you start the current song.
When using MediaPlayer, I noticed that whenever my phone stucks, the MediaPlayer glitches and then continues playing from the position in the audio it glitched.
This is bad for my implementation since I want the audio to be played at a specific time.
If I have a song of 1000 millisecond length, I want is the ability to set MediaPlayer to start playing at some specific time t, and then exactly stop at at time t+1000.
This means that I actually need two things:
1) Start MediaPlayer at a specific time with a very small delay.
2) Making MediaPlayer glitches ignore the audio they glitched on and continue playing in order to finish the song on time.
The delay of the functions is very important to me and I need the audio to be played exactly(~) at the time it was supposed to be played.
Thanks!
You will need to use possibly mp.getDuration(); and/or mp.getCurrentPosition(); although it's impossible to know exactly what you mean by "I need the audio to be played exactly(~) at the time it was supposed to be played."
Something like this should get you started:
int a = (mp.getCurrentPosition() + b);
Thanks for the answer Mike. but unfortunately this won't help me. Let's say that I asked MediaPlayer to start playing a song of length 3:45 at 00:00. At 01:00 I started using the phone's resources, due to the heavy usage my phone glitched making MediaPlayer pause for 2 seconds.
Time:
00:00-01:00 - I heard the audio at 00:00-01:00
01:00-01:02 - I heard silence because the phone glitched
01:02-03:47 - I heard the audio at 01:00-03:45 with 2 second time skew
Now from what I understood MediaPlayer is a bad choice of usage on this problem domain, since MediaPlayer provides a high level API.I am currently experimenting with the
AudioTrack class which should provide me with what I need:
//Creating a new audio track
AudioTrack audioTrack = new AudioTrack(...)
//Get start time
long start = System.currentTimeMillis();
// loop until finished
for (...) {
// Get time in song
long now = System.currentTimeMillis();
long nowInSong = now - start;
// get a buffer from the song at time nowInSong with a length of 1 second
byte[] b = getAudioBuffer(nowInSong);
// play 1 second of music
audioTrack.write(b, 0, b.length);
// remove any unplayed data
audioTrack.flush();
}
Now if I glitch I only glitch for 1 second and then I correct myself by playing the right audio at the right time!
NOTE
I haven't tested this code but it seems like the right way to do it. If it will actually work I will update this post again.
P.S. seeking in MediaPlayer is:
1. A heavy operation that will surely delay my music (every millisecond counts here)
2. Is not thread safe and cannot be used from multiple threads (seeks, starts etc...)