You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android中Google Cloud TTS无法同步执行问题的解决方法

问题原因

你遇到的问题是因为onSpeechComplete回调触发时机错误:当前AsyncTask的onPostExecute方法在音频文件准备好、开始播放后就立即执行了,而非等到音频播放完成后才触发,所以日志会提前打印。同时当前的busy标记逻辑无法支持顺序执行多个语音任务,导致并发执行。

解决方案

我们需要调整回调触发时机,并添加任务队列来实现顺序播放:

1. 定义任务队列与内部任务类

在SpeechService中添加队列结构,用于存储待播放的语音任务(包含文本和回调):

// 新增:存储待播放的语音任务
private static class SpeechTask {
    String text;
    SpeechCallback callback;

    SpeechTask(String text, SpeechCallback callback) {
        this.text = text;
        this.callback = callback;
    }
}

private Queue<SpeechTask> speechQueue;

2. 初始化队列

在SpeechService的构造函数中初始化队列:

private SpeechService(Context context) {
    this.context = context;
    // 初始化任务队列
    speechQueue = new LinkedList<>();
}

3. 修改speak方法

如果当前正处于播放状态,就把任务加入队列;否则直接执行任务:

public void speak(String text, SpeechCallback callback) {
    SpeechTask task = new SpeechTask(text, callback);
    if (busy) {
        Log.d(TAG, "Speech Service busy! Adding task to queue.");
        speechQueue.add(task);
        return;
    }

    busy = true;
    executeSpeechTask(task);
}

// 新增:执行单个语音任务
private void executeSpeechTask(SpeechTask task) {
    speechCallback = task.callback;
    new TextToSpeechTask().execute(task.text);
}

4. 调整回调触发时机

将onSpeechComplete的触发从AsyncTask的onPostExecute移到MediaPlayer的onCompletionListener中,同时处理队列的下一个任务:

private void playAudio(byte[] audioData) {
    try {
        releaseMediaPlayer();

        File tempFile = File.createTempFile("tempAudio", ".pcm", context.getCacheDir());
        FileOutputStream fos = new FileOutputStream(tempFile);
        fos.write(audioData);
        fos.close();

        mediaPlayer = new MediaPlayer();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                    .build());
        } else {
            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        }

        mediaPlayer.setDataSource(tempFile.getAbsolutePath());
        mediaPlayer.prepare();
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                releaseMediaPlayer();
                tempFile.delete();

                // 播放完成后触发回调
                if (speechCallback != null) {
                    speechCallback.onSpeechComplete();
                }

                // 处理队列中的下一个任务
                busy = false;
                if (!speechQueue.isEmpty()) {
                    SpeechTask nextTask = speechQueue.poll();
                    executeSpeechTask(nextTask);
                }
            }
        });

        mediaPlayer.start();

    } catch (IOException e) {
        Log.e(TAG, "Error playing audio: " + e.getMessage());
        // 播放出错时也要重置状态,处理队列任务
        busy = false;
        if (!speechQueue.isEmpty()) {
            SpeechTask nextTask = speechQueue.poll();
            executeSpeechTask(nextTask);
        }
    }
}

5. 修改AsyncTask的onPostExecute

移除原来的回调触发和busy状态修改,因为现在这些逻辑都在MediaPlayer的完成监听中:

@Override
protected void onPostExecute(String result) {
    // 这里不再处理回调和busy状态,交给MediaPlayer的onCompletion处理
}
效果验证

修改后,当你调用多次speak方法时,语音会按顺序播放,每个语音播放完成后才会触发对应的onSpeechComplete回调,同时日志Done with TTS会在音频播放完成后才打印。

内容的提问来源于stack exchange,提问作者JSNerd01

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 03:44:55