2

I have a ListView with custom List Items and in each List Item a Button and a SeekBar. Now when a button is clicked a audio starts playing, and I want to set the progress of that SeekBar for where the button was clicked. With the code that I have, the correct SeekBar starts moving but when I scroll up and down a few times, a different SeekBar starts moving. I have tried lots of things, and this here is my last one:

import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Handler;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.TextView;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;

public class MyListAdapter extends ArrayAdapter<Audio> {

ArrayList<Audio> audios;
Context context;
int resource;

private static MediaPlayer mediaPlayer;

public Handler mHandler = new Handler();
TextView totalTime;
TextView playingTime;

private MyListAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull ArrayList<Audio> audios) {
    super(context, resource, audios);
    Collections.reverse(audios);
    this.audios = audios;
    this. context = context;
    this.resource = resource;
}

@NonNull
 @Override
 public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
    final ViewHolder viewHolder;
    View row;
    if (convertView == null) {
        LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        row = layoutInflater.inflate(R.layout.audio_cell, parent, false);

        viewHolder = new ViewHolder();
        viewHolder.playButton = (ImageButton) row.findViewById(R.id.playButton);
        viewHolder.seekBar = (SeekBar) row.findViewById(R.id.seekBar);
        viewHolder.playButton.setTag(position);
        viewHolder.seekBar.setTag(position);
        row.setTag(viewHolder);
    } else {
        row = convertView;
        viewHolder = (ViewHolder) row.getTag();
    }

    viewHolder.seekBar.setProgress(0);
    //I get the audio url here
    final Audio audio = getItem(position);
    assert audio != null;
    String theURL = audio.getURL();

    //Button Click
    viewHolder.playButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //here I call the method to play the audio and after that I want 
            //to update the SeekBar
            playAudio(theURL);

            updateSeekBar(viewHolder.seekBar);
        }
    });
    return row;
}

private void updateSeekBar(final SeekBar sb) {

    Runnable mUpdateTimeTask = new Runnable() {
        @Override
        public void run() {
            if (mediaPlayer != null && mediaPlayer.isPlaying()) {
                int mCurrentPosition = mediaPlayer.getCurrentPosition() / 1000;
                mHandler.postDelayed(this, 100);
                sb.setProgress(mCurrentPosition);
            }
        }
    };
    mHandler.postDelayed(mUpdateTimeTask, 100);
}
}

And this is my ViewHolder class:

public class ViewHolder {
    public ImageButton playButton;
    public SeekBar seekBar;
}

Now as I said I have tried lots of things but nothing seems to work, I think of the things that I've tried it's all the same because I get the same results every time. As soon as I scroll up and down a few times it sets the progress on a different SeekBar. Any help appreciated!

Pancho D
  • 21
  • 3

3 Answers3

1

The list view recycles the view in getView() since you are using getTag() and setTAg().So when items are clicked it gets reflected in some other positions and also the song starts playing in other positions since it is reusing view.

To solve this. create a global int value that stores the position of the clicked item.

private int mSelectedItem = -1;

Whenever play button is clicked add that position to mSelectedItem

viewHolder.playButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        //Store the position here
         mSelectedItem=position;
            //here I call the method to play the audio and after that I want 
            //to update the SeekBar
            playAudio(theURL);

            updateSeekBar(viewHolder.seekBar);
        }
    });

Then update the seekBar like this

pass the position while calling method

updateSeekBar(viewHolder.seekBar,position);

check is the position is same as the one you clicked and update seekbar

private void updateSeekBar(final SeekBar sb,int position) {
    final Runnable mUpdateTimeTask=null;
    if(mSelectedItem==position){
         mUpdateTimeTask = new Runnable() {
           @Override
           public void run() {
               if (mediaPlayer != null && mediaPlayer.isPlaying()) {
                    int mCurrentPosition = mediaPlayer.getCurrentPosition() / 1000;
                    mHandler.postDelayed(this, 100);
                    sb.setProgress(mCurrentPosition);
                }
            }
        };
        mHandler.postDelayed(mUpdateTimeTask, 100);
    }else{
         mHandler.removeCallbacks(mUpdateTimeTask);
         sb.setProgress(0);
    }
}    
Sharath kumar
  • 4,064
  • 1
  • 14
  • 20
  • This also works at first, but when I scroll down it also updates a different seekBar. – Pancho D Nov 02 '17 at 05:35
  • I can't removeCallbacks from mUpdateTimeTask because its declared in an the if statement. But don't I need to call updateSeekBar() outside of the onClick() method?? So that it gets called everytime I scroll? – Pancho D Nov 02 '17 at 05:56
  • you need to add Runnable reference outside if statement.Yes you should call it outside onclick such that it is called every time when you scroll..see the updated code – Sharath kumar Nov 02 '17 at 06:49
  • mUpdateTimeTask may not have been initialized in the else statement. But I tried, in getView meyhod, if (mSelectedItem==position) { updateSeekbar(viewHolder.seekBar, postiotion;} and I think that does the same. But still doesn't work. – Pancho D Nov 02 '17 at 20:26
0

You can write like bellow..

  1. Declare variable

        private MediaPlayer mediaPlayer;
        private double startTime = 0;
        private double finalTime = 0;
        public  int oneTimeOnly = 0;
    
  2. change viewHolder.playButton

      viewHolder.playButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    //Being...
    
                    try {
                        mediaPlayer = new MediaPlayer();
                        mediaPlayer.setDataSource(theURL);
                        try {
    
    
                            // Log.e("ddd***",tv_directory.getText().toString());
    
                            if(mediaPlayer.isPlaying()){
                                Log.e("isplaying**",mediaPlayer.isPlaying()+"");
    
                                mediaPlayer.pause();
                                finalTime = mediaPlayer.getDuration();
                                startTime = mediaPlayer.getCurrentPosition();
    
                                if (oneTimeOnly == 0) {
                                    seekBar.setMax((int) finalTime);
                                    oneTimeOnly = 1;
                                }
    
    
                            }else{
                                // Log.e("isplaying**",mediaPlayer.isPlaying()+"");
    
    
    
                                mediaPlayer.prepare();
                                //mediaPlayer.start();
    
    
                                mediaPlayer.start();
    
    
                                finalTime = mediaPlayer.getDuration();
                                startTime = mediaPlayer.getCurrentPosition();
    
                                if (oneTimeOnly == 0) {
                                    seekBar.setMax((int) finalTime);
                                    oneTimeOnly = 1;
                                }
    
    
    
    
    
                            }
                        } catch (Exception e) {
                            Log.e("err***",e.toString());
                        }
    
    
    
                        myHandler.postDelayed(new Runnable(){
                            public void run() {
                                startTime = mediaPlayer.getCurrentPosition();
                                tv_start_time.setText(String.format("%d min, %d sec",
                                        TimeUnit.MILLISECONDS.toMinutes((long) startTime),
                                        TimeUnit.MILLISECONDS.toSeconds((long) startTime) -
                                                TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.
                                                        toMinutes((long) startTime)))
    
                                );
    
    
    
                                seekBar.setProgress((int) startTime);
    
                                myHandler.postDelayed(this, 100);
                            }
                        }, 100);
    
    
                    } catch (Exception e) {
                        Log.e("err***",e.toString());
    
                    }
                    //End....
                }
            });
    
Enamul Haque
  • 4,789
  • 1
  • 37
  • 50
0

Ok I found the problem, it happened because

if(convertView == null)

is true only for only for the first items that appear on the screen. Here it's explained why that happens. And in my case, this answer worked. It's not the best way of doing it but for ListView with few items it works.

Pancho D
  • 21
  • 3