5

I have a MediaPlayer which is contained in a custom ViewHolder and created by a RecyclerViewAdapter which is run by a Fragment. I am trying to update the seekbar every second to show the progress of the audio that the MediaPlayer is playing, using this question's answer:

private Handler mHandler = new Handler();
//Make sure you update Seekbar on UI thread
MainActivity.this.runOnUiThread(new Runnable() {

    @Override
    public void run() {
        if(mMediaPlayer != null){
            int mCurrentPosition = mMediaPlayer.getCurrentPosition() / 1000;
            mSeekBar.setProgress(mCurrentPosition);
        }
        mHandler.postDelayed(this, 1000);
    }
});

However, since I am running it in an adapter from a fragment and not directly within an activity, I cannot use this line:

MainActivity.this.runOnUiThread(new Runnable() {

So instead, I have passed getActivity() from the fragment into the RecyclerViewAdapter with the adapter's constructor and set it as a global variable parentActivity.
I then created the code to update the seekbar within the RecyclerViewAdapter's onBindViewHolder() like so:

final Handler mHandler = new Handler();
this.parentActivity.runOnUiThread(new Runnable(){

     @Override
     public void run(){
          if (mMediaPlayer != null){
               int mCurrentPosition = mMediaPlayer.getCurrentPosition();
               myViewHolder.seekBar.setProgress(mCurrentPosition);
          }
          mHandler.postDelayed(this, 1000);
     }
});

My problem is that now when I play the audio, although the seekbar updates properly, the audio briefly pauses, or "skips" every second. What could be causing this and how could I fix it?

EDIT: each ViewHolder has its own SeekBar and the mMediaPlayer is defined as a global variable in the Adapter.

Paradox
  • 4,602
  • 12
  • 44
  • 88

2 Answers2

1

I'd guess that the skipping and pausing thing is caused by using main thred too much and what is more important having MULTIPLE instances of your activity. That's exactly what happens if you copy it to every ViewHolder of your Recycler.

Acctually it is very bad pattern and should be avoided by any chance.

It's hard to give you solution because you didn't clarify how exactly your app looks like (eg are those seekbars present in each ViewHolder etc). But assuming that it's the 'worst' case I suggest you to pass another listener to the adapter, which will listen for your seekBar or ViewHolder (depends on you, acctually this interface could be bigger for extended controll). And the Fragment will do the refresh thing inside of itself.

class Adapter(... startPlayingListener: (SeekBar) -> Unit) {

   fun onBindViewHolder() {
      whateverButton.setOnClickListener {startPlayingListener.invoke(seekBar)}
   }
}


class Fragment(...) {

   fun bindAdapter() {
       adapter = YourAdapter(..., this::onPlayerStart)
   }

   fun onPlayerStart(seekBar: SeekBar) {
        this.parentActivity.runOnUiThread(
        {
            if (mMediaPlayer != null){
               int mCurrentPosition = mMediaPlayer.getCurrentPosition();
               seekBar.setProgress(mCurrentPosition);
            }
            mHandler.postDelayed(this, 1000);
        }
   }
}

EDIT:

As you've said - your solution has just a list of songs to play, and each item has it's own SeekBar. In such case I'd suggest you to create a delegate class that will handle logic of playing music, and use ViewHolder as a view only (in other worlds - create class for Presenter layer of your MVP). Below I posted the code that is showing this idea more or less.

public interface YourPlayerView() {
    void updateSeekBar(int currentPosition);
}

public interface YourPlayerPresenter {
    void startPlaying(YourPlayerView view, trackID or something)
    void stopPlaying(YourPlayerView view, trackID or something)
}

class YourPlayer(...) implements YourPlayerPresenter {
    private MediaPlayer;

   @Override
    public void startPlaying(YourPlayerView view, trackId or something) {
        //start the track - possibly pass it as an argument of this method
        //start your handler calling view.updateSeekBar(...)
    }
}

public class ViewHolder(YourPlayerPresenter player) implements YourPlayerView {

    void bind(...) {
        buttonStart.setOnClickListener(player.startPlaying(this, trackId);
    }

    @Override
    public void updateSeekBar(int currentPosition) {
        mSeekBar.setProgress(currentPositition)
    }
}
muminers
  • 1,190
  • 10
  • 19
  • Thank you for your solution. Yes the seekbars are present in each viewholder and each viewholder may need to play a different audio file so each view holder does an `mMediaPlayer = MediaPlayer.create(mContext, audioFile);` I'll try to see if I can get this solution working. Also I'm guessing that code is in kotlin and not java as I'm having trouble reading it? – Paradox Mar 17 '18 at 01:52
  • So are you passing in the seekbar to the fragment? If so, each ViewHolder has it's own viewholder. – Paradox Mar 17 '18 at 02:01
  • each viewholder has its own seekbar * – Paradox Mar 17 '18 at 02:12
  • Also, are you suggesting that I create the mMediaPlayer in the fragment and pass it into the adapter too instead of declaring it in the adapter? – Paradox Mar 17 '18 at 02:26
  • Yes my code is in Kotlin, sorry I didn't check. Yes - -I strongly suggest you to remove MediaPlayer and Activity references from ViewHolder because it's just to much to handle. – muminers Mar 17 '18 at 09:58
1

You may need to create the mMediaPlayer in the fragment and then pass a listener to the adapter as a constructor param. Once your listener is invoked, you may pass the mediaplayer params to the listener and do the rest of the mMediaPlayer operations inside your fragment. In this way you have access to the getActivity() context as well

You can pass this listener to your adapter from the fragment

public interface OnMediaPlayerInvoked {
void onSeekBarPosition(your params);
}

Then invoke this listener from your adapter and pass your params to the listener OnMediaPlayerInvoked, so that the rest of the operations can be performed in fragment

Navneet Krishna
  • 5,009
  • 5
  • 25
  • 44