等待声音结束后再继续

Wait for sound to finish before continuing

我正在创建一个遍历字符串的循环,并根据出现的字符播放不同的声音。不幸的是,它没有等到声音结束才继续播放,这会导致媒体播放器出错。我的循环看起来像这样:

for (char ch : morsechars) {
            if (Character.toString(ch).equals(".") ) {
                Log.d("play: ", "dot");
                try {
                    startPlayback();
                } catch (Exception e){
                    e.printStackTrace();
                }
            } else if (Character.toString(ch).equals("-")){
                Log.d("play: ", "dash");
                try {
                    startPlayback();
                } catch (Exception e){
                    e.printStackTrace();
                }
            } 
        }

我的播放器看起来像这样:

public void startPlayback() throws Exception{
        play = MediaPlayer.create(this, dotFILE);
        play.start();
        isplaying = true;
        while (isplaying == true){

        }


        play.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            public void onCompletion(MediaPlayer play) {
                isplaying = false;
                play.stop();
                play.reset();
                play.release();
            }
        });
    }

我不确定如何在继续循环中的下一个字符之前等待完成侦听器。

1) MediaPlayer 不是这项工作的正确工具。看看SoundPool。它适用于重复播放的短音。

2) 对于你的情况,你想要的并不是真正的循环。声音将异步播放,因此您 运行 会遇到播放调用堆积在一起的情况。这将导致 MediaPlayer 的本机代码耗尽可用资源。

你要的是一系列的回调。 MediaPlayer 具有您已经在使用的完成回调,但是,虽然这似乎正是所需要的,但它不足以满足您的用例。 MediaPlayer 的本机代码将无法足够快地释放其资源以供每次后续调用播放,并且会发生相同的资源耗尽。对于 MediaPlayer 的工作方式来说,这只是一个不幸的现实。

SoundPool 没有方便的完成回调,但在这种情况下,您可能只有两个声音样本,而且您知道它们有多长。一个简单的定时器延迟可能就足够了。您可以使用 Handler.postDelayed 来实现此效果。在每个回调事件中,只需播放下一个声音,推进您用于跟踪当前声音的任何变量,然后再次 post 相同的回调以播放任何剩余的声音。这是一种异步循环。

更新

为了帮助理解发生了什么,这里有一些来自 Android 关于 Handler 的文档:

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

因此,如果我对您的代码结构的猜测接近正确,您将创建一个 Handler 对象作为 Activity class 的成员。这会将它绑定到 GUI 线程,这应该没问题,因为您不会在那里做太多工作。您还需要一个 Runnable 作为获得 posted 的任务。也让它成为您的 class 的成员。您需要 String 包含字符才能成为会员。您将需要一个 int 来跟踪您当前所在的角色。

private Handler playMorseHandler = new Handler();
private String morseChars = ".--...---"; // I don't know where you set this
private int charIndex = 0;
private Runnable playMorseTask = new Runnable() {
    if (morseChars == null ||
        charIndex < 0 ||
        charIndex >= morseChars.length()) {
        Log.w("whateverTag", "Basic assumptions failed");
        return;
    }
    char ch = morseChars.charAt(charIndex);
    long lengthOfClip = 0L;
    boolean postAgain = true;
    if (ch == '.') {
        Log.d("play: ", "dot");
        lengthOfClip = 300L; // I don't know how long your clips are
        try {
            startPlayback();
        } catch (Exception e){
            e.printStackTrace();
        }
    } else if (ch == '-') {
        Log.d("play: ", "dash");
        lengthOfClip = 500L; // I don't know how long your clips are
        try {
            startPlayback();
        } catch (Exception e){
            e.printStackTrace();
        }
    } else {
        Log.d("whateverTag", "unexpected input");
        postAgain = false;
    }
    ++charIndex;
    if (postAgain && charIndex < morseChars.length()) {
        playMorseHandler.postDelayed(this, lengthOfClip);
    }
};

无论你想从哪里开始播放声音,你只需调用:

charIndex = 0;
playMorseHandler.post(playMorseTask);

唯一剩下的就是让您的 startPlayback() 方法做正确的事情,即使用 SoundPool 而不是 MediaPlayer。那是因为,正如我之前提到的,MediaPlayer 将不可避免地导致系统 运行 耗尽资源来如此快速地播放这些短音。