I have a seekbar in my fragment that I use to show the progress of a MediaPlayertrack that is playing. Everything works as expected until I change orientation of the device. I save my fragment instance and load it back, and all of the other views are repopulated as they should be, and the music keeps right on playing as it should, but the seekbar goes right to the max, as if the track has reached it's max duration. The runnable that is updating the seekbar based on the mediaplayer's position keeps running after the orientation change as well, and logging the currentPos value in the runnable shows that it stays in sync with the mMediaPlayer.getCurrentPosition() as it should, even after the orientation change...So basically, it continues to feed the correct position to the seekbar; the seekbar just isn't updating after the orientation change. It makes me wonder if the seekbar instance stays the same after the change. If it doesn't stay the same instance, I can't figure out why or how to fix it.
The onCLick method of my play button calls the following to start the track and hook up the seekbar to it:
private void startPlayer(String url){
try {
mMediaPlayer.setDataSource(url);
} catch (IOException e) {
e.printStackTrace();
}
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
mMediaPlayer.start();
playerState = PlayerState.PLAYING;
setButtonsForPlayerState();
setSeekBarValues();
mHandler = new Handler();
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
if (mMediaPlayer != null ) {
int currentPos = mMediaPlayer.getCurrentPosition();
Log.e(LOG_TAG, "Current pos is " + currentPos);
seekBar.setProgress(currentPos);
if (currentPos<10000){
seekBarStartTime.setText("0:0" + currentPos / 1000);
}else{
seekBarStartTime.setText("0:" + currentPos / 1000);
}
}
if (mMediaPlayer.isPlaying()){
mHandler.postDelayed(this, 50);
}
}
}
);
}
});
}
and here is the setSeekBarValues() method that is called in the above method:
private void setSeekBarValues() {
seekBar.setMax(mMediaPlayer.getDuration());
seekBarFinishTime.setText("0:" + seekBar.getMax()/1000);
seekBar.setProgress(0);
}
seekBarStartTime and seekBarFinishTime are just TextViews that show the current position and end time of the track, respectivily.
Since all of my other views, streaming music, etc. seems to be working as it should on the re-creation of Activity and Fragment, I don't think it's an issue with saving instance state, but if you want me to post that code as well please let me know.
Any ideas are appreciated.
Related
I just stumbled across problem with it's this not-so-obvious solution and thought it might be helpful to some if I share my findings.
There are lots of similar questions but they didn't help me solve mine. (are different)
I have a activity that plays a video with sound, then displays an image for 3 seconds, then displays an animated mapview for 10 seconds and then plays another video.
video1 (~60sec)
image (~3sec)
animated mapview (~10sec)
video2 (~30sec)
the arose when trying to play audio from the moment the first video finished.
the audio played fine for the duration of the mapview, then stopped.
I tried all sorts of scenarios but the music always stopped when something changed, although some combinations worked (e.g. without map and video2 (just blackscreen instead)).
I did take a very close look at all the logs but didnt find anything helpful.
The issue is not that the mediaplayer is being gc'd or finalized somehow before finishing.
private static MediaPlayer mediaplayer;
and in OnCreate:
mediaplayer = new MediaPlayer();
AudioFocusRequest.Builder mp;
mediaplayer.setAudioAttributes(
new AudioAttributes
.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build());
try {
mediaplayer.setDataSource(getApplicationContext(), Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.audio));
} catch (IOException e) {
e.printStackTrace();
Log.e("audio_management","error trying to set data source for audio");
}
try {
mediaplayer.prepare();
} catch (IOException e) {
e.printStackTrace();
Log.e("audio_management","error preparing");
}
Here's an example of how I play the videos
z.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
playPart2();
}
});
}
}, PARTONELENGTH + MAPDISPLAYTIME + IMAGEDISPLAYTIME);
and this is the playPart2() function:
public void playPart2() {
ImageView imgbg = findViewById(R.id.imageView);
ImageView agO = findViewById(R.id.agent1);
ImageView agTw = findViewById(R.id.agent2);
ImageView agTh = findViewById(R.id.agent3);
ImageView agFo = findViewById(R.id.agent4);
ImageView agFi = findViewById(R.id.agent5);
ImageView agSi = findViewById(R.id.agent6);
VideoView player = findViewById(R.id.videoView);
MapView mMap = findViewById(R.id.map);
mMap.setVisibility(View.INVISIBLE);
imgbg.setVisibility(View.INVISIBLE);
agO.setVisibility(View.INVISIBLE);
agTw.setVisibility(View.INVISIBLE);
agTh.setVisibility(View.INVISIBLE);
agFo.setVisibility(View.INVISIBLE);
agFi.setVisibility(View.INVISIBLE);
agSi.setVisibility(View.INVISIBLE);
player.setVisibility(View.VISIBLE);
Uri uri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.part2);
player.setVideoURI(uri);
player.start();
}
I really dont know what is going on - I 've read through all the documentation concerning MediaPlayer and VideoViews. It says that it is possible to output 2 streams of audio at the same time. But it doesnt work.
turns out the videoView starting playback somehow paused the mediaplayer.
what solved the problem was just calling mediaplayer.start() again. it did resume where it had stopped all the times before. the (apparently existing) short break in playback is not noticeable.
why is this behaviour of mediaplayer/videoview not mentioned anywhere?
issue-solving-code:
z.schedule(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
playPart2();
mediaplayer.start();
}
});
}
}, PARTONELENGTH + MAPDISPLAYTIME + IMAGEDISPLAYTIME);
I have a custom view (PieView) that is has a rotating animation. Now I would like to play tick tick tick tick... sound synchronously with the rotation speed (that is, when the rotation speed is fast, the tick tick should be fast, when rotation is slow, the tick tick should be slow).
To do this, first I created an mp3 file named magicbox_tick.mp3 that has only ONE (1) tick. Next I tried to play the sound with Animation.setUpdateListener().
First I tried to play music with MediaPlayer but after some 10 or 15 ticks, it stoped. So now I am trying SoundPool to play the music.
The relevant code segment looks like this:
public PieView extends View {
// ... constructors, other methods etc
private SoundPool soundPool;
private int soundId;
void init(){ // called inside those constructors
SoundPool soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
soundId = soundPool.load(getContext(), R.raw.magicbox_tick, 1);
}
public void rotateTo(){
animate()..setInterpolator(new DecelerateInterpolator())
.setDuration(mDuration)
.setListener(someListener)
.rotation(targetAngle)
.setUpdateListener(animation -> {
myPlaySound(); // <----------------------- This is the sound playing code
})
.start();
}
void myPlaySound(){
soundPool.play(soundId, 1, 1, 0, 0, 1); // this doesnot play the `tick` sound
// previously I used MediaPlayer like this:
/*
MediaPlayer mp = new MediaPlayer.create(getContext(), R.raw.magicbox_tick);
mp.play();
// these 2 line, after some 10 ticks, stopped working.
*/
}
}
I have never done anything like this, and I don't know how to fix this. Can anyone help me?
Please note that I am open to all answers as long as it works. You don't have to use SoundPool. So suppose if you can make it work with android MediaPlayer, I am ok with that.
Special thanks to Mr Mike M for his valuable comment. I was able to fix it using MediaPlayer. MediaPlayer.release() method should be called. And to make the sound synced with the angular motion, I kept an if block that checks if the rotation dTheta is greater than tolerance angle.
So, if anyone needs it, the complete code looks like this:
public PieView extends View{
private float omega0; // holds the previous rotation
/**
* #brief: plays a music using mediaPlayer
* #input:
* #output: void, plays a music
* */
private void myPlayTick() {
float omega1 = Math.abs(getRotation());
float dOmeda = 0;
if(omega1>omega0){
dOmeda = omega1 - omega0;
}else{
dOmeda = omega0-omega1;
}
if(dOmeda > threshold){
omega0 = omega1; // update previous rotation
final MediaPlayer mp = MediaPlayer.create(getContext(), R.raw.magicbox_tick);
mp.start();
mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
releaseMediaPlayer(mp);
}
});
}
}
/**
* #brief: releases mediaPlayer resource so that other mediaPlayers can use sound hardware resources
* #input: MediaPlayer object
* #output: void
* */
private void releaseMediaPlayer(MediaPlayer mediaPlayer) {
try {
if (mediaPlayer != null) {
if (mediaPlayer.isPlaying())
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void rotateTo(){
animate()..setInterpolator(new DecelerateInterpolator())
.setDuration(mDuration)
.setListener(someListener)
.rotation(targetAngle)
.setUpdateListener(animation -> {
myPlayTick();
})
.start();
}
// ... rest of the code, such as constructors, etc
}
The title says it all. How can I tell when onPause() is being called because of an orientation change instead of the back or home buttons being pressed?
I avoided the problem altogether by doing this instead:
You can use onWindowFocusChanged event instead of onPause. This function is not called when orientation changed.
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.d(TAG, "FOCUS = " + hasFocus);
if (!hasFocus) finish();
}
But note: this event is called when activity is still visible (like onPause()), you should use onStop if you want to finish the activity when it is really and fully invisible:
private boolean isInFocus = false;
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.d(TAG, "FOCUS = " + hasFocus);
isInFocus = hasFocus;
}
#Override
public void onStop() {
super.onStop();
if (!isInFocus) finish();
}
You cannot differentiate how onPause is called. What you could do is, You can override onKeyDown and look for back button and home button key events.
Have a flag that marks back/home button presses and check for this flag in onPause(). If this flag is not set, onPause is called cos of orientation change. However, there is a chance that your app is moved to background due to a phone call or alarm! So maynot be a perfect solution.
The other solution is to keep track of orientation changes in onConfigurationChanged
You probably want onSaveInstanceState instead of onPause
they serve similar function but onSaveInstanceState is not called when the user is backing out of the activity.
http://developer.android.com/reference/android/view/OrientationEventListener.html
This should work. Why don't you toast in this.
You can't. Simple as that. You can however measure the time it takes to reach onResume again and if the device was tilted by reading the display configuration.
An interval of 3 seconds is somewhat reasonable, also for slower devices.
Here are the relevant parts that we use:
protected void onCreate() {
...
orientation = getResources().getConfiguration().orientation;
}
protected void onResume() {
...
long time = SystemClock.elapsedRealtime() - pauseTime;
int o = getResources().getConfiguration().orientation;
Log.d(TAG, "pauseTime: " + pauseTime + " System: " + SystemClock.elapsedRealtime() + " elapsed pause time: " + time + " orientation now: " + o + " orientation then: " + orientation);
if (time < 3000 && o != orientation) {
// device was turned
}
orientation = o;
}
protected void onPause() {
...
pauseTime = SystemClock.elapsedRealtime();
}
protected void onSaveInstanceState(final Bundle outState) {
...
outState.putLong(PAUSE_TIME, pauseTime);
outState.putInt(ORIENTATION, orientation);
}
protected void onRestoreInstanceState(final Bundle savedInstanceState) {
...
pauseTime = savedInstanceState.getLong(PAUSE_TIME, -1);
orientation = savedInstanceState.getInt(ORIENTATION, -1);
}
The above code is running on about 40k devices and reliably works.
I'm having a hard time working with this.
I have a Media player, and I can play, pause, stop, play again after pause, stop... whatever.
Now I wanted to have a SeekBar to give a visual component. My problem is:
When I start the player for the first time everything works well. Music plays, and seek bar updates. also works when I pause.
Now, if I stop the player and start it again, the player starts, the run() method executes, but the seekbar doesn't update, and soon the app gives a not responging error.
What am I missing here?
The run method is an implementation from the Runnable interface, and with a simple log, I can see it's being executed, even after the stop/play case. The only thing that seems not to be working is the seek.setProgress(...).
some help, please? :)
Here's my code:
public class MediaPlayerTestingActivity extends Activity
implements OnClickListener, OnPreparedListener, Runnable {
private MediaPlayer mp;
private boolean paused = false;
private SeekBar seek;
private boolean threadStarted = false;;
private Thread thread = new Thread(this);
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
seek = (SeekBar)findViewById(R.id.seek);
mp = new MediaPlayer();
Button start = (Button)findViewById(R.id.start);
start.setOnClickListener(this);
Button pause = (Button)findViewById(R.id.pause);
pause.setOnClickListener(this);
Button stop = (Button)findViewById(R.id.stop);
stop.setOnClickListener(this);
}
//click handlers
public void onClick(View v)
{
int buttonId = v.getId();
switch (buttonId)
{
case R.id.start:
if(mp != null)
{
if(!mp.isPlaying() && !paused)
prepareTostartPlayer();
else
mp.start(); //if it was just paused
}
else
{
mp = new MediaPlayer();
prepareTostartPlayer();
}
break;
case R.id.pause:
if(mp.!= null && mp.isPlaying())
{
mp.pause();
paused = true;
}
break;
case R.id.stop:
if(mp != null && mp.isPlaying())
{
mp.stop();
mp.release();
mp = null;
}
break;
default:
break;
}
}
//When the player is ready to play
public void onPrepared(MediaPlayer arg0)
{
seek.setMax(mp.getDuration());
mp.start();
if(!threadStarted)
thread.start();
else
thread.run();
}
//Method to prepare to start the player
private void prepareTostartPlayer()
{
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
mp.setDataSource("http://feedproxy.google.com/~r/rc-cadernetadecromos/~3/JH1kfZCmP3M/cdc_190112_2.mp3");
mp.prepareAsync();
mp.setOnPreparedListener(this);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void run()
{
threadStarted = true;
int current = 0;
while(mp != null && current < mp.getDuration())
{
try {
current = mp.getCurrentPosition();
seek.setProgress(current);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
I see three issues here:
First, this part:
public void onPrepared(MediaPlayer arg0)
{
seek.setMax(mp.getDuration());
mp.start();
if(!threadStarted)
thread.start();
else
thread.run();
}
The first time your run the thread you set threadStarted to true in the thread code, but you never set it back to false when the thread finishes. So after the first run it will be true forever and thus the else above will always execute, running the player code sequentially and thus blocking your user interface.
Second, you are updating the user interface (the SeekBar) from a different thread than the UI thread. See this related question for how to correctly update the UI in this case: Update UI from Thread
Third, this may be just a recommendation, but since your threadStarted variable is modified between threads, it's better to declare it volatile to prevent some optimizations from breaking your code:
private volatile boolean threadStarted = false;
According to my experience and what is written in docs, threads in Android work only in purely algorythmic parts of a prog. Any try to use them in activities, or anything connected to UI, goes to error. This way it simply won't work. Never can you repair it!
Use AsyncTask (or child Activity/Fragment for greater things) instead.
I know you can seekto() with Mediaplayer, to start at a certain point.
But is there a way to make a track (the audio playing) stop at a certain point? Or would an if statement on a timer loop have to be used?
Doesn't seem possible (correct me if I'm wrong) to do this with media player without resorting to seekto() in a timer loop. However you could try using an AudioTrack in conjunction with setting a notification marker:
AudioTrack.setNotificationMarkerPosition
Sets the position of the notification marker.
and overriding the playback position update listener AudioTrack.OnPlaybackPositionUpdateListener
Interface definition for a callback to be invoked when the playback head position of an AudioTrack has reached a notification marker or has increased by a certain period.
You have to make threat that will trigger getCurrentPosition().
When it will reach stop point, you have to stop MediaPlayer.
public void run() {
while (mp != null && mPosition < mTotal) {
try {
Thread.sleep(500); // you can modify sleep time for better accuracy
if (mp.isPlaying()) {
mPosition = mp.getCurrentPosition();
if (mPosition == mYourStopPoint) { //remember to set mYourStopPoint
mp.stop();
break;
}
}
} catch (InterruptedException e) {
return;
} catch (Exception e) {
return;
}
}
}
Start this Thread in onPreapared callback.
public void onPrepared(MediaPlayer genericPlayer) {
mTotal = mp.getDuration();
new Thread(this).start();
}
Sadly, AudioTrack's position callbacks appear to be fairly seriously broken. http://code.google.com/p/android/issues/detail?id=2563