Есть ли какой-нибудь правильный способ заставить один поток сказать другому "ждать", пока он выполняется? (MediaPlayer и SeekBar комбинированные) - PullRequest
2 голосов
/ 01 апреля 2020

Хорошо, я новичок ie Android и java. Делая это только для хобби и изучая его в свободное время, которое я могу найти.

Я наткнулся на SeekBar и решил создать простой медиаплеер, чтобы поэкспериментировать с ним. После начала работы я открыл Spotify и попытался имитировать c его поведение SeekBar.

Итак, вот концепция: У меня один Runnable работает каждые 200 мс, когда MediaPlayer находится в статус воспроизведения, чтобы обновить левую метку времени и позицию SeekBar.

enter image description here

Теперь, когда вы держите Spotify SeekBar и перетаскиваете его, происходит следующее: что крайняя левая метка времени обновляется при перемещении SeekBar, отображая временную метку того, где находится SeekBar, без влияния на musi c. Это только эффективно изменит «позицию» musi c, когда мы выпустим SeekBar.

Итак, мой подход к этому подходу был следующим: создать второй Runnable, который будет вызываться через Handler, когда пользователь удерживает SeekBar и освободить обработчик, когда пользователь выпустит SeekBar. Кроме того, этот исполняемый файл будет обновляться каждые 50 мс, потому что перетаскивание панели является довольно быстрым и визуально изменяет метку таймера, когда пользователь перемещает панель.

Теперь вот код моей MainActivity, выполняющий все это:

package com.heymilkshake.simpleaudioplayer;

import androidx.appcompat.app.AppCompatActivity;

import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button playBtn;
    private Button stopBtn;
    private Button skipForward10Btn;
    private Button skipBackward10Btn;
    private TextView displayStatus;
    private TextView songCurrentTime;
    private TextView songTimeEnd;
    private SeekBar seekBar;

    private MediaPlayer mediaPlayer;
    private Handler seekBarHandler;
    private Runnable updateSeekBar;
    private Runnable updateTimer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViews();
        tagButtons();

        createPlayer();
        initialize();
    }

    private void initialize() {
        songCurrentTime.setText(formatTime(mediaPlayer.getCurrentPosition()));
        songTimeEnd.setText(formatTime(mediaPlayer.getDuration()));
        seekBar.setMax(mediaPlayer.getDuration());
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                if (mediaPlayer.isPlaying()) {
                    seekBarHandler.removeCallbacks(updateSeekBar);
                }
                seekBarHandler.postDelayed(updateTimer, 0);
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                seekBarHandler.removeCallbacks(updateTimer);
                mediaPlayer.seekTo(seekBar.getProgress());
                if (mediaPlayer.isPlaying()) {
                    seekBarHandler.postDelayed(updateSeekBar, 0);
                }
            }
        });
        seekBarHandler = new Handler();
        updateSeekBar = new Runnable() {

            @Override
            public void run() {
                updateSeekAndTime();
                seekBarHandler.postDelayed(this, 200);
            }
        };
        updateTimer = new Runnable() {

            @Override
            public void run() {
                songCurrentTime.setText(formatTime(seekBar.getProgress()));
                seekBarHandler.postDelayed(this, 50);
            }
        };
    }

    private String formatTime(int ms) {
        return String.format("%d:%02d",
                TimeUnit.MILLISECONDS.toMinutes((long) ms),
                TimeUnit.MILLISECONDS.toSeconds((long) ms) -
                TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes((long) ms)));
    }

    private void createPlayer() {
        mediaPlayer = MediaPlayer.create(this, R.raw.come_alive);
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

            @Override
            public void onCompletion(MediaPlayer mp) {
                handleStopButton();
            }
        });
    }

    private void findViews() {
        playBtn = findViewById(R.id.button_play);
        stopBtn = findViewById(R.id.button_stop);
        skipForward10Btn = findViewById(R.id.button_skip_forward_10);
        skipBackward10Btn = findViewById(R.id.button_skip_backward_10);
        displayStatus = findViewById(R.id.display_status);
        songCurrentTime = findViewById(R.id.song_time_start);
        songTimeEnd = findViewById(R.id.song_time_end);
        seekBar = findViewById(R.id.seek_bar);
    }

    private void tagButtons() {
        int i = 0;
        for (Button button : Arrays.asList(
                playBtn,
                stopBtn,
                skipForward10Btn,
                skipBackward10Btn)) {
            button.setTag(i++);
            button.setOnClickListener(this);
        }
    }

    @Override
    public void onClick(View v) {
        int clickedTag = (int) v.getTag();
        switch (clickedTag) {
            case 0:
                handlePlayButton();
                break;
            case 1:
                handleStopButton();
                break;
            case 2:
                handleForward10Btn();
                break;
            case 3:
                handleBackwards10Btn();
                break;
            default:
        }
    }

    private void handleBackwards10Btn() {
        if (mediaPlayer.getCurrentPosition() - 10000 < 0) {
            mediaPlayer.seekTo(0);
        } else {
            mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() - 10000);
        }
        updateSeekAndTime();
    }

    private void handleForward10Btn() {
        if (mediaPlayer.getCurrentPosition() + 10000 > mediaPlayer.getDuration()) {
            mediaPlayer.seekTo(mediaPlayer.getDuration());
        } else {
            mediaPlayer.seekTo(mediaPlayer.getCurrentPosition() + 10000);
        }
        updateSeekAndTime();
    }

    private void updateSeekAndTime() {
        seekBar.setProgress(mediaPlayer.getCurrentPosition());
        songCurrentTime.setText(formatTime(seekBar.getProgress()));
    }

    private void handleStopButton() {
        mediaPlayer.pause();
        mediaPlayer.seekTo(0);
        seekBarHandler.removeCallbacks(updateSeekBar);
        updateSeekAndTime();
        displayStatus.setText("Stopped");
        playBtn.setText("Play ►");
    }

    private void handlePlayButton() {
        if (mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
            seekBarHandler.removeCallbacks(updateSeekBar);
            displayStatus.setText("Paused");
            playBtn.setText("Play ►");
        } else {
            mediaPlayer.start();
            seekBarHandler.postDelayed(updateSeekBar, 0);
            displayStatus.setText("Playing");
            playBtn.setText("Pause ||");
        }
    }
}

Когда я тестировал свой код, у меня было такое поведение: полоса, перепрыгивающая назад и вперед к месту, где играет музыка c, пока пользователь перетаскивает ее (gif для визуализации нежелательного поведения)

unwanted behavior

Ну, я прекрасно понимаю, почему это происходит: updateSeekBar выполняется каждые 200 мс, вызывая метод updateSeekAndTime(), который меняет метку и полосу на текущую позицию, где играет музыка c. Итак, когда onStartTrackingTouch обнаруживает, что пользователь удерживает SeekBar, мы запускаем updateTimer с возможностью запуска каждые 50 мс. Этот Runnable изменяет текст метки времени одновременно с updateSeekBar Runnable, вызывая нежелательное поведение, описанное выше.

Чтобы решить эту проблему, я изменил код анонимной реализации интерфейса SeekBar.OnSeekBarChangeListener(), добавив некоторые проверки условий : Если песня воспроизводится, и пользователь берет SeekBar, удалите обратный вызов на updateSeekBar, чтобы он прекратил работу, и когда пользователь отпустит панель, запустите ее снова. Итак, вот изменения кода:

seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        if (mediaPlayer.isPlaying()) {
            seekBarHandler.removeCallbacks(updateSeekBar);
        }
        seekBarHandler.postDelayed(updateTimer, 0);
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        seekBarHandler.removeCallbacks(updateTimer);
        mediaPlayer.seekTo(seekBar.getProgress());
        if (mediaPlayer.isPlaying()) {
            seekBarHandler.postDelayed(updateSeekBar, 0);
        }
    }
});

Хорошо, это сработало просто замечательно! Посмотрите на другой GIF:

desired behavior

Но это заставило меня задуматься, и именно здесь я спрашиваю вас, ребята:

  • Есть ли лучший способ сделать это?
  • Есть ли какой-либо способ для потока, который зацикливается с помощью обработчика, чтобы дать команду другому потоку "ждать", пока он выполняется?
  • Как бы вы решили эту проблему ??

ВАЖНОЕ ПРИМЕЧАНИЕ: Попробуйте упростить или поговорить о вещах простым способом, поскольку я всего лишь Java и Android новичок.

Спасибо всем !!

...