I have a ListView
which displays voice messages (just like WhatsApp's), I'm using BaseAdapter
, and my layout contains a play button
and a SeekBar
. What I want is to play the audio on the click of the play button
, and update the SeekBar
.
So I implemented this custom SeekBar
which updates itself.
SelfUpdatingSeekBar.java
:
public class SelfUpdatingSeekBar extends SeekBar {
MediaPlayer mp;
boolean mActive;
public void setMediaPlayer(MediaPlayer mp){
this.mp = mp;
}
Runnable mUpdate = new Runnable() {
@Override
public void run() {
long totalDuration = mp.getDuration();
long currentDuration = mp.getCurrentPosition();
// Updating progress bar
int progress = (int)(getProgressPercentage(currentDuration, totalDuration));
setProgress(progress);
postDelayed(this, 100);
}
} ;
public void setActive(int progress) {
if (!mActive) {
mActive = true;
removeCallbacks(mUpdate);
setProgress(progress);
post(mUpdate);
}
}
public void setInactive(int progress) {
if (mActive) {
mActive = false;
removeCallbacks(mUpdate);
}
setProgress(progress);
}
public SelfUpdatingSeekBar(Context context) {
super(context);
}
public SelfUpdatingSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SelfUpdatingSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SelfUpdatingSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public int getProgressPercentage(long currentDuration, long totalDuration){
Double percentage = (double) 0;
long currentSeconds = (int) (currentDuration / 1000);
long totalSeconds = (int) (totalDuration / 1000);
// calculating percentage
percentage =(((double)currentSeconds)/totalSeconds)*100;
// return percentage
return percentage.intValue();
}
}
This is how my getView()
looks like:
public View getView(final int i, View convertView, ViewGroup viewGroup){
final ViewHolder viewHolder;
if(convertView == null){
LayoutInflater inflater = ((MainActivity) context).getLayoutInflater();
viewHolder = new ViewHolder();
convertView = inflater.inflate(R.layout.sent_voice_bubble, viewGroup, false);
viewHolder.sent_voice_seekbar = (SelfUpdatingSeekBar) convertView.findViewById(R.id.sent_seekbar);
viewHolder.sent_voice_seekbar.setMediaPlayer(mMediaPlayer);
viewHolder.sent_voice_play = (ImageView) convertView.findViewById(R.id.sent_playAudio);
viewHolder.sent_voice_container = convertView.findViewById(R.id.sent_vmessagesection);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(((int)(x * 0.7f)), RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
viewHolder.sent_voice_container.setLayoutParams(params);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.sent_voice_play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Check if the Item is currently playing
if(list.get(i).isPlaying()){
// Stop audio and save progress
stopItemAudio(list.get(i), viewHolder.sent_voice_seekbar);
// Change button's image to Pause ||
((ImageView) v).setImageDrawable(((MainActivity) context).pauseDrawable);
}else{
// Start audio and update the SeekBar
playItemAudio(list.get(i), viewHolder.sent_voice_seekbar);
// Change button's image to Play >
((ImageView) v).setImageDrawable(((MainActivity) context).playDrawable);
}
}
});
if(list.get(i).isPlaying()){
viewHolder.sent_voice_seekbar.setActive(list.get(i).seekbar_resume_position);
viewHolder.sent_voice_play.setImageDrawable(((MainActivity) context).pauseDrawable);
}else{
viewHolder.sent_voice_seekbar.setInactive(list.get(i).seekbar_resume_position);
viewHolder.sent_voice_play.setImageDrawable(((MainActivity) context).playDrawable);
}
return convertView;
}
public void stopItemAudio(AudioRow item, SelfUpdatingSeekBar seekBar){
if(mMediaPlayer == null){
mMediaPlayer = new MediaPlayer();
}
mMediaPlayer.stop();
item.setPlaying(false);
int percentage = getProgressPercentage(mMediaPlayer.getCurrentPosition(), mMediaPlayer.getDuration());
item.setSeekBarResumePosition(percentage);
item.setMediaPlayerResumePosition(mMediaPlayer.getCurrentPosition());
seekBar.setInactive(percentage);
notifyDataSetChanged();
}
public void playItemAudio(final AudioRow item, SelfUpdatingSeekBar seekBar){
if(mMediaPlayer == null){
mMediaPlayer = new MediaPlayer();
}
try {
mMediaPlayer.reset();
mMediaPlayer.setDataSource(item.audioPath);
mMediaPlayer.prepare();
mMediaPlayer.seekTo(item.mediaPlayer_resume_position);
seekBar.setActive(item.seekbar_resume_position); // Starts from where it stopped
mMediaPlayer.start();
// declaring Item started to play
item.setPlaying(true);
notifyDataSetChanged();
mMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
item.setSeekBarResumePosition(0);
item.setPlaying(false);
}
});
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
public int getProgressPercentage(long currentDuration, long totalDuration){
Double percentage = (double) 0;
long currentSeconds = (int) (currentDuration / 1000);
long totalSeconds = (int) (totalDuration / 1000);
// calculating percentage
percentage =(((double)currentSeconds)/totalSeconds)*100;
// return percentage
return percentage.intValue();
}
}
The problem is when I click on Item1
, and it starts playing, and click on Item2
both SeekBars animate at the same time, and that is expected since I did not stop Item1
on the click of Item2
My question is:
Item1
on the click of Item2
?Your SeekBar
implementation should not have its own MediaPlayer
. Have only one MediaPlayer
instance in your activity. Every time a "play" button is clicked in your list view you do something like this:
MediaPlayer mMediaPlayer = ((MainActivity) context).getSingleMediaPlayer();
((MainActivity) context).setCurrentlyPlayingItem(i);
mMediaPlayer.reset();
mMediaPlayer.setDataSource(item.audioPath);
mMediaPlayer.prepare();
notifyDatasetHasChanged()
Now on your getView()
implementation, if the first argument: i
is not equal to the currently playing item of MainActivity
its SeekBar
should be at zero.
MainActivity
should also be the one implementing the update runnable. At this point you might want to use just a normal SeekBar
. To update the correct View, you need to figure out first if it is visible, because the user could have scrolled to a different position, so you need this function in your MainActivity
:
public View getVisibleItemView(int position){
if(listview.getCount() > 0) {
int start = listview.getFirstVisiblePosition();
for (int i = start, j = listview.getLastVisiblePosition(); i <= j; i++) {
if (i == position) {
return listview.getChildAt(i - start);
}
}
}
return null;
}
This function returns the view with the SeekBar
that you want to update, but only if it is visible, otherwise it returns null. in your update runnable you should call it, and if the result is not null, you update the SeekBar
.
Also, you might want to consider using an interface instead of casting context references to MainActivity
in your adapter.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.