Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make audio player with play/pause button and seekbar in recycleview

I need some help, I want to make a recycle view containing audio in each item, how to play the audio when press play button and set progress to seekbar and at the same time pause other sound and convert the play button to play mode in all row except the playing one be in pause mode such as whatsapp or chats in facebook messenger

This is my code: MyAdapter.class

    private List<String> positions = new ArrayList<>();

  public myAdapter(Activity activity, ArrayList<Tweet> list) {
    this.activity = activity;
    this.list = list;
    mPlayer = new MediaPlayer();
    }
    public void onBindViewHolder(final viewHolder holder, final int position) {
        if (!positions.contains(String.valueOf(position)))
            positions.add(String.valueOf(position));
        }
        }

viewHolder.class

  public class viewHolder extends RecyclerView.ViewHolder implements
        SeekBar.OnSeekBarChangeListener, View.OnClickListener {
    LinearLayout parentPanel;
    int viewType;
    private RelativeLayout pauseLayout, playLayout;
    private ImageView pauseIcon, playIcon;
    private SeekBar seekBar;
    private TextView periodTime;
    AudioCallbacks mAudioCallbacks;
    private int last_position;

    viewHolder(final View itemView, int viewType) {
        super(itemView);
        this.viewType = viewType;

            playLayout = (RelativeLayout) itemView.findViewById(R.id.play_layout);
            pauseLayout = (RelativeLayout) itemView.findViewById(R.id.pause_layout);
            playIcon = (ImageView) itemView.findViewById(R.id.play_icon);
            pauseIcon = (ImageView) itemView.findViewById(R.id.pause_icon);
            seekBar = (SeekBar) itemView.findViewById(R.id.seekBar);
            periodTime = (TextView) itemView.findViewById(R.id.period_time);


            seekBar.setOnSeekBarChangeListener(this);
            playLayout.setOnClickListener(this);
            pauseLayout.setOnClickListener(this);
            mAudioCallbacks = new AudioCallbacks() {
                @Override
                public void onUpdate(int percentage) {
                    seekBar.setProgress(percentage);
                    if (percentage == 100)
                        mAudioCallbacks.onStop();
                }
                @Override
                public void onPause() {
                    Log.i("on pause audio", " pause");
                }
                @Override
                public void onStop() {
                    Log.i("on stop audio", " stop");
                    stopPlayingAudio();
                }
            };
    }

    void stopPlayingAudio() {
        if (mPlayer != null) {
            if (mPlayer.isPlaying()) {
                updateAudioProgressBar();
                mPlayer.stop();
                mPlayer.reset();
                seekBar.setProgress(0);
                playLayout.setVisibility(View.VISIBLE);
                pauseLayout.setVisibility(View.GONE);
            }
        }
    }

    void pausePlayingAudio() {
        if (mPlayer != null) {
            if (mPlayer.isPlaying()) {
                mPlayer.pause();
                updateAudioProgressBar();
                mAudioCallbacks.onPause();
            }
        }
    }
    void playingAudio(Tweet message) {
        updateAudioProgressBar();
        if (mPlayer != null) {
            try {
                mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
                mPlayer.setDataSource(message.getAudioUrl());
                mPlayer.prepare();
                mPlayer.start();
                periodTime.setVisibility(View.VISIBLE);
                periodTime.setText(String.valueOf(message.getAudioDuration()));
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    void updateAudioProgressBar() {
        durationHandler.postDelayed(mUpdateTimeTask, 100);
    }
    private Runnable mUpdateTimeTask = new Runnable() {
        public void run() {
            try {
                if (mPlayer.isPlaying()) {
                    long totalDuration = mPlayer.getDuration();
                    long currentDuration = mPlayer.getCurrentPosition();
                    int progress = Utils.getProgressPercentage(currentDuration, totalDuration);
                    mAudioCallbacks.onUpdate(progress);
                    periodTime.setText(Utils.getFileTime(currentDuration));
                    durationHandler.postDelayed(this, 100);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    };

    @Override
    public void onProgressChanged(SeekBar seekBar, int i, boolean b) {

    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        durationHandler.removeCallbacks(mUpdateTimeTask);
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        int totalDuration = mPlayer.getDuration();
        int currentPosition = Utils.progressToTimer(seekBar.getProgress(), totalDuration);
        mPlayer.seekTo(currentPosition);
        updateAudioProgressBar();
    }

    @Override
    public void onClick(View view) {
        Log.e("getAdapterPosition()L", last_position + " /");
        for (String pos : positions) {
            if (!pos.equals(String.valueOf(getAdapterPosition())))
                notifyItemChanged(Integer.parseInt(pos));
        }
        last_position = getAdapterPosition();
        Log.e("getAdapterPosition()F", last_position + " /");
        Tweet message = list.get(getAdapterPosition());
        switch (view.getId()) {
            case R.id.pause_layout:
                playLayout.setVisibility(View.VISIBLE);
                pauseLayout.setVisibility(View.GONE);
                pausePlayingAudio();
                break;
            case R.id.play_layout:
                playLayout.setVisibility(View.GONE);
                pauseLayout.setVisibility(View.VISIBLE);
                mAudioCallbacks.onStop();
                playingAudio(message);
                break;
        }
    }
    }
    public interface AudioCallbacks {
    void onUpdate(int percentage);
    void onPause();
    void onStop();
    }

the problem of this code is :

  1. some time the image not reversed of other object when play specific item .
  2. when scrolled the pause button appear in multiple row.
  3. when play one then play another, stop the first and run the new one but when return to the previous not playing.
  4. when leave the activity or press back press button the sound not stopped.

Can someone helped me, please?

like image 495
Amal Kronz Avatar asked Sep 11 '17 12:09

Amal Kronz


1 Answers

Controlling and updating progress of MediaPlayer from RecyclerView cells is tricky. Your solution does not correctly update the view states in onBindViewHolder call, which is the root cause of all the issues you are facing. Few other notes one should keep in mind are as follows:

  • update cell view state correctly, which also include add/remove progress updater
  • try to avoid anonymous allocations of xxxListeners, Runnables in onBindViewHolder, i.e. ViewHolder can be used to implement xxxListener interfaces etc.
  • remove the seek bar updater, when media player completes the playback of audio
  • respect activity life-cycle and release/stop media player if it is not required to play the audio

Below source shows possible implementation of Adapter

private class AudioItemAdapter extends RecyclerView.Adapter<AudioItemAdapter.AudioItemsViewHolder> {

    private MediaPlayer mediaPlayer;

    private List<AudioItem> audioItems;
    private int currentPlayingPosition;
    private SeekBarUpdater seekBarUpdater;
    private AudioItemsViewHolder playingHolder;

    AudioItemAdapter(List<AudioItem> audioItems) {
        this.audioItems = audioItems;
        this.currentPlayingPosition = -1;
        seekBarUpdater = new SeekBarUpdater();
    }

    @Override
    public AudioItemsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new AudioItemsViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false));
    }

    @Override
    public void onBindViewHolder(AudioItemsViewHolder holder, int position) {
        if (position == currentPlayingPosition) {
            playingHolder = holder;
            updatePlayingView();
        } else {
            updateNonPlayingView(holder);
        }
    }

    @Override
    public void onViewRecycled(AudioItemsViewHolder holder) {
        super.onViewRecycled(holder);
        if (currentPlayingPosition == holder.getAdapterPosition()) {
            updateNonPlayingView(playingHolder);
            playingHolder = null;
        }
    }

    private void updateNonPlayingView(AudioItemsViewHolder holder) {
        holder.sbProgress.removeCallbacks(seekBarUpdater);
        holder.sbProgress.setEnabled(false);
        holder.sbProgress.setProgress(0);
        holder.ivPlayPause.setImageResource(R.drawable.ic_play_arrow);
    }

    private void updatePlayingView() {
        playingHolder.sbProgress.setMax(mediaPlayer.getDuration());
        playingHolder.sbProgress.setProgress(mediaPlayer.getCurrentPosition());
        playingHolder.sbProgress.setEnabled(true);
        if (mediaPlayer.isPlaying()) {
            playingHolder.sbProgress.postDelayed(seekBarUpdater, 100);
            playingHolder.ivPlayPause.setImageResource(R.drawable.ic_pause);
        } else {
            playingHolder.sbProgress.removeCallbacks(seekBarUpdater);
            playingHolder.ivPlayPause.setImageResource(R.drawable.ic_play_arrow);
        }
    }

    void stopPlayer() {
        if (null != mediaPlayer) {
            releaseMediaPlayer();
        }
    }

    private class SeekBarUpdater implements Runnable {
        @Override
        public void run() {
            if (null != playingHolder) {
                playingHolder.sbProgress.setProgress(mediaPlayer.getCurrentPosition());
                playingHolder.sbProgress.postDelayed(this, 100);
            }
        }
    }

    @Override
    public int getItemCount() {
        return audioItems.size();
    }

    class AudioItemsViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, SeekBar.OnSeekBarChangeListener {
        SeekBar sbProgress;
        ImageView ivPlayPause;

        AudioItemsViewHolder(View itemView) {
            super(itemView);
            ivPlayPause = (ImageView) itemView.findViewById(R.id.ivPlayPause);
            ivPlayPause.setOnClickListener(this);
            sbProgress = (SeekBar) itemView.findViewById(R.id.sbProgress);
            sbProgress.setOnSeekBarChangeListener(this);
        }

        @Override
        public void onClick(View v) {
            if (getAdapterPosition() == currentPlayingPosition) {
                if (mediaPlayer.isPlaying()) {
                    mediaPlayer.pause();
                } else {
                    mediaPlayer.start();
                }
            } else {
                currentPlayingPosition = getAdapterPosition();
                if (mediaPlayer != null) {
                    if (null != playingHolder) {
                        updateNonPlayingView(playingHolder);
                    }
                    mediaPlayer.release();
                }
                playingHolder = this;
                startMediaPlayer(audioItems.get(currentPlayingPosition).audioResId);
            }
            updatePlayingView();
        }

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            if (fromUser) {
                mediaPlayer.seekTo(progress);
            }
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }
    }

    private void startMediaPlayer(int audioResId) {
        mediaPlayer = MediaPlayer.create(getApplicationContext(), audioResId);
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                releaseMediaPlayer();
            }
        });
        mediaPlayer.start();
    }

    private void releaseMediaPlayer() {
        if (null != playingHolder) {
            updateNonPlayingView(playingHolder);
        }
        mediaPlayer.release();
        mediaPlayer = null;
        currentPlayingPosition = -1;
    }
}

You can find complete working solution here - GitHub

like image 137
user3161880 Avatar answered Nov 12 '22 23:11

user3161880