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

如何正确使用TextToSpeech实现长文本(如书籍)的连续朗读

解决TextToSpeech朗读超长文本的方案:按句子拆分+UtteranceProgressListener触发续读

我之前做过类似的长文本朗读需求,刚好能给你分享下实际落地的思路——因为TextToSpeech确实有getMaxSpeechInputLength()的长度限制,直接读整本书肯定不行,按句子拆分后用UtteranceProgressListener来衔接每一句的朗读,是非常可行的方案,下面给你拆解具体实现:

一、先把长文本拆分成合适的句子片段

首先得把大段文本拆成一个个能被TextToSpeech处理的小片段,这里要注意两点:

  • 优先按句子标点拆分(比如中文的「。!?;」,英文的「.!?;」),尽量保证语义完整;
  • 还要检查拆分后的单句长度,如果还是超过getMaxSpeechInputLength(),就得进一步拆分(比如按逗号、空格这类次一级分隔符切)。

我常用的拆分方法大概是这样的:

private List<String> splitLongTextIntoSentences(String longText, TextToSpeech tts) {
    List<String> sentences = new ArrayList<>();
    if (TextUtils.isEmpty(longText)) return sentences;

    // 按句子结尾标点拆分,这里以中文为例
    String[] initialSplits = longText.split("[。!?;]");
    for (String rawSentence : initialSplits) {
        String trimmedSentence = rawSentence.trim();
        if (TextUtils.isEmpty(trimmedSentence)) continue;

        // 处理超长单句:如果单句超过最大长度,继续拆分
        while (trimmedSentence.length() > tts.getMaxSpeechInputLength()) {
            // 优先在最大长度内找最后一个逗号,保证语义尽量连贯
            int splitPos = trimmedSentence.lastIndexOf(",", tts.getMaxSpeechInputLength());
            if (splitPos == -1) {
                // 找不到逗号就直接硬切
                splitPos = tts.getMaxSpeechInputLength();
            }
            sentences.add(trimmedSentence.substring(0, splitPos).trim());
            trimmedSentence = trimmedSentence.substring(splitPos).trim();
        }
        sentences.add(trimmedSentence);
    }
    return sentences;
}

二、用UtteranceProgressListener衔接朗读

核心就是利用onDone回调——每读完一句,就自动触发下一句的朗读。这里要维护一个当前朗读的索引,还要给每个朗读任务设置唯一的utteranceId,方便追踪进度:

// 初始化TextToSpeech
TextToSpeech tts = new TextToSpeech(context, status -> {
    if (status == TextToSpeech.SUCCESS) {
        // 设置目标语言,这里以中文为例
        int langResult = tts.setLanguage(Locale.CHINESE);
        if (langResult == TextToSpeech.LANG_MISSING_DATA || langResult == TextToSpeech.LANG_NOT_SUPPORTED) {
            // 处理语言不支持的情况,比如给用户提示
            Toast.makeText(context, "当前语言不支持", Toast.LENGTH_SHORT).show();
        } else {
            // 拆分文本并开始朗读
            List<String> sentenceList = splitLongTextIntoSentences(yourLongText, tts);
            startTTSReading(tts, sentenceList);
        }
    } else {
        Toast.makeText(context, "TTS初始化失败", Toast.LENGTH_SHORT).show();
    }
});

// 开始朗读的核心方法
private void startTTSReading(TextToSpeech tts, List<String> sentenceList) {
    AtomicInteger currentIndex = new AtomicInteger(0);

    tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
        @Override
        public void onStart(String utteranceId) {
            // 可选:更新UI,比如显示当前读到第几句
            Log.d("TTS", "开始朗读第" + utteranceId + "句");
        }

        @Override
        public void onDone(String utteranceId) {
            // 当前句读完,触发下一句
            int nextIndex = currentIndex.incrementAndGet();
            if (nextIndex < sentenceList.size()) {
                speakNextSentence(tts, sentenceList.get(nextIndex), String.valueOf(nextIndex));
            } else {
                // 所有句子读完,释放资源
                tts.shutdown();
                Toast.makeText(context, "朗读完成", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onError(String utteranceId) {
            // 朗读出错,跳过当前句继续下一句
            Log.e("TTS", "第" + utteranceId + "句朗读出错");
            int nextIndex = currentIndex.incrementAndGet();
            if (nextIndex < sentenceList.size()) {
                speakNextSentence(tts, sentenceList.get(nextIndex), String.valueOf(nextIndex));
            }
        }
    });

    // 启动第一句朗读
    if (!sentenceList.isEmpty()) {
        speakNextSentence(tts, sentenceList.get(0), "0");
    }
}

// 封装朗读单句的方法
private void speakNextSentence(TextToSpeech tts, String sentence, String utteranceId) {
    HashMap<String, String> params = new HashMap<>();
    params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
    tts.speak(sentence, TextToSpeech.QUEUE_FLUSH, params);
}

几个需要注意的细节

  • Utterance ID必须唯一:每个朗读任务的ID不能重复,否则回调会混乱,我这里直接用句子索引当ID,简单靠谱;
  • 资源一定要释放:全部朗读完成后记得调用tts.shutdown(),不然会占用系统资源;
  • 错误处理要友好:遇到朗读错误时,直接跳过当前句继续下一句,别让整个流程卡住;
  • 适配不同语言:拆分文本的正则表达式要根据目标语言调整,比如英文要换成[.!?;]。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 04:01:08