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

Android Java实现TTS暂停与续播功能的技术方案问询

解决Android TTS书籍朗读的暂停续播问题(无需生成大量音频文件)

我之前也碰到过类似的需求,不想生成一堆零散的音频文件,直接用TTS原生API就能搞定核心逻辑。关键是跟踪朗读的实时位置,结合OnUtteranceProgressListener记录当前读到哪一段、哪一个字符,暂停后从这个位置重新启动朗读就行。

核心思路

  1. 维护几个关键状态变量:当前段落索引、段落内已朗读的字符位置、暂停标志位。
  2. 利用TTS的OnUtteranceProgressListener,通过onRangeStart回调实时更新朗读的字符位置;通过onDone回调自动推进到下一段。
  3. 暂停时调用tts.stop()清空队列,保存当前状态;续播时从保存的位置截取文本,重新加入队列朗读。

具体实现代码

1. 定义状态变量

private TextToSpeech tts;
private List<String> bookParagraphs; // 你已经拆分好的书籍段落列表(每段≤4000字符)
private int currentParagraphIndex = 0;
private int currentCharPosition = 0;
private boolean isPaused = false;
private static final String UTTERANCE_ID_PREFIX = "book_paragraph_"; // 唯一标识每个段落的朗读任务

2. 初始化TTS并设置监听

// 初始化TTS
tts = new TextToSpeech(getApplicationContext(), status -> {
    if (status == TextToSpeech.SUCCESS) {
        // 设置语言(根据你的需求调整)
        int result = tts.setLanguage(Locale.CHINESE);
        if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
            Log.e("TTS", "语言不支持");
        } else {
            // 设置朗读进度监听
            tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
                @Override
                public void onStart(String utteranceId) {
                    // 当开始朗读某一段时,更新当前段落索引
                    currentParagraphIndex = Integer.parseInt(utteranceId.replace(UTTERANCE_ID_PREFIX, ""));
                }

                @Override
                public void onDone(String utteranceId) {
                    // 当前段落朗读完成,自动启动下一段(除非处于暂停状态)
                    if (!isPaused) {
                        int nextIndex = Integer.parseInt(utteranceId.replace(UTTERANCE_ID_PREFIX, "")) + 1;
                        if (nextIndex < bookParagraphs.size()) {
                            speakTargetParagraph(nextIndex);
                        }
                    }
                }

                @Override
                public void onRangeStart(String utteranceId, int start, int length, int frame) {
                    // 实时更新当前段落内已朗读到的字符位置
                    currentCharPosition = start;
                }

                @Override
                public void onError(String utteranceId) {
                    Log.e("TTS", "段落朗读出错: " + utteranceId);
                }
            });
        }
    } else {
        Log.e("TTS", "初始化失败");
    }
});

3. 封装段落朗读方法

/**
 * 朗读指定索引的段落,支持从指定位置续播
 */
private void speakTargetParagraph(int index) {
    if (index >= bookParagraphs.size()) {
        Log.d("TTS", "已读完所有段落");
        return;
    }

    String targetParagraph = bookParagraphs.get(index);
    HashMap<String, String> params = new HashMap<>();
    params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, UTTERANCE_ID_PREFIX + index);

    // 如果是续播当前段落,从记录的字符位置开始截取文本
    String textToSpeak = isPaused && index == currentParagraphIndex 
        ? targetParagraph.substring(currentCharPosition) 
        : targetParagraph;

    // 加入队列朗读(stop后队列已清空,直接添加即可)
    tts.speak(textToSpeak, TextToSpeech.QUEUE_ADD, params, null);
    isPaused = false; // 续播后重置暂停标志
}

4. 暂停/续播按钮逻辑

// 暂停按钮点击事件
pauseBtn.setOnClickListener(v -> {
    if (tts != null && tts.isSpeaking()) {
        tts.stop(); // 停止当前朗读并清空队列
        isPaused = true;
        // currentCharPosition已经被onRangeStart实时更新,无需额外保存
    }
});

// 续播按钮点击事件(也可以和暂停按钮合并成一个切换按钮)
resumeBtn.setOnClickListener(v -> {
    if (isPaused && tts != null) {
        speakTargetParagraph(currentParagraphIndex);
    }
});

关键注意事项

  • UtteranceID唯一性:每个段落的UtteranceID必须唯一,这样才能准确对应到当前朗读的段落,避免索引混乱。
  • onRangeStart回调兼容性:部分第三方TTS引擎可能不支持onRangeStart,如果遇到这种情况,可以考虑按句子拆分段落(更细粒度),牺牲一点精度换兼容性,但主流引擎(如Google TTS、系统自带TTS)都支持该回调。
  • 资源释放:在Activity/Fragment销毁时,记得调用tts.shutdown()释放TTS资源,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:43:39