如何正确使用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
相关产品推荐
相关产品推荐

