如何实现Android TTS停止后从断点继续朗读图像识别文本?
Android TTS断点续读实现:追踪朗读位置解决停止后无法继续的问题
问题分析
你的代码无法实现断点续读的核心问题有两个:
- 未监听TextToSpeech的实时朗读进度,无法记录中断时的准确文本位置
continueSpeech方法中的位置计算逻辑完全错误(lastPosition = spokenText.length() - lastPosition逻辑颠倒,且无正确初始值)
实现步骤
1. 添加进度监听,实时记录朗读位置
初始化TextToSpeech时,设置OnUtteranceProgressListener,通过监听朗读的字符范围,实时更新当前读到的文本索引。需要:
- 给每个朗读任务设置唯一的
utteranceId,用于区分任务类型 - 维护全局变量
currentSpeechOffset,保存当前朗读到的文本位置
2. 修正停止与续读逻辑
- 停止朗读时保留
currentSpeechOffset,不重置位置 - 续读时从保存的偏移位置截取剩余文本进行朗读
3. 调整状态变量逻辑
修正isSpeaking、isPaused的状态切换逻辑,确保按钮文本与状态匹配
修改后的完整代码
TTS初始化部分(补充原代码缺失的初始化逻辑)
private TextToSpeech textToSpeech; private int currentSpeechOffset = 0; // 当前朗读到的文本位置 private String spokenText = ""; // 要朗读的完整文本 private boolean isSpeaking = false; private boolean isPaused = false; private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化TextToSpeech textToSpeech = new TextToSpeech(this, status -> { if (status == TextToSpeech.SUCCESS) { // 设置为中文朗读(可根据需求修改语言) int langResult = textToSpeech.setLanguage(Locale.CHINESE); if (langResult == TextToSpeech.LANG_MISSING_DATA || langResult == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(this, "不支持当前语言", Toast.LENGTH_SHORT).show(); } // 设置朗读进度监听器 textToSpeech.setOnUtteranceProgressListener(new TextToSpeech.OnUtteranceProgressListener() { @Override public void onStart(String utteranceId) { // 从头开始朗读时重置偏移量 if (utteranceId.equals("START_FROM_BEGINNING")) { currentSpeechOffset = 0; } } @Override public void onDone(String utteranceId) { runOnUiThread(() -> { isSpeaking = false; isPaused = false; startStopContinueSpeechBtn.setText("Start"); currentSpeechOffset = 0; // 朗读完成后重置位置 }); } @Override public void onError(String utteranceId) {} // Android 11+ 支持实时监听朗读字符范围 @Override public void onRangeStart(String utteranceId, int start, int end, int frame) { super.onRangeStart(utteranceId, start, end, frame); currentSpeechOffset = start; } }); } else { Toast.makeText(MainActivity.this, "TTS初始化失败", Toast.LENGTH_SHORT).show(); } }); }
修正后的核心功能方法
private void recognizedTextFromImage() { Log.d(TAG, "recognizedTextFromImage: "); try { InputImage inputImage = InputImage.fromFilePath(this, imageUri); textRecognizer.process(inputImage) .addOnSuccessListener(text -> { String recognizedText = text.getText(); recognizedTextEt.setText(recognizedText); // 保存完整文本,从头开始朗读 spokenText = recognizedText; currentSpeechOffset = 0; speakRecognizedText(recognizedText); }) .addOnFailureListener(e -> { Log.e(TAG, "onFailure: ", e); Toast.makeText(MainActivity.this, "识别失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); }); } catch (Exception e) { Log.e(TAG, "recognizedTextFromImage: ", e); Toast.makeText(MainActivity.this, "图片处理失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } } private void speakRecognizedText(String text) { if (textToSpeech != null && textToSpeech.getEngines().size() > 0) { final Handler handler = new Handler(Looper.getMainLooper()); handler.postDelayed(() -> { HashMap<String, String> params = new HashMap<>(); params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "START_FROM_BEGINNING"); textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, params); isSpeaking = true; isPaused = false; startStopContinueSpeechBtn.setText("Stop"); }, 100); } } private void stopSpeech() { if (textToSpeech != null && isSpeaking) { textToSpeech.stop(); isSpeaking = false; isPaused = true; startStopContinueSpeechBtn.setText("Continue"); } } private void continueSpeech() { if (textToSpeech != null && isPaused && !spokenText.isEmpty()) { String remainingText = spokenText.substring(currentSpeechOffset); if (!remainingText.isEmpty()) { HashMap<String, String> params = new HashMap<>(); params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "CONTINUE_FROM_OFFSET"); textToSpeech.speak(remainingText, TextToSpeech.QUEUE_FLUSH, params); isSpeaking = true; isPaused = false; startStopContinueSpeechBtn.setText("Stop"); } } } private void startSpeech() { if (textToSpeech != null) { String text = recognizedTextEt.getText().toString(); if (!text.isEmpty()) { spokenText = text; currentSpeechOffset = 0; HashMap<String, String> params = new HashMap<>(); params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "START_FROM_BEGINNING"); textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, params); isSpeaking = true; isPaused = false; startStopContinueSpeechBtn.setText("Stop"); } } } @Override protected void onDestroy() { if (textToSpeech != null) { textToSpeech.stop(); textToSpeech.shutdown(); } super.onDestroy(); }
补充说明
onRangeStart方法是Android 11(API 30)新增的API,低版本可通过分段朗读+OnUtteranceCompletedListener实现兼容,但实时性和准确性稍差- 每个朗读任务必须设置
utteranceId,用于在监听器中区分任务类型(从头开始/续读) - 停止朗读时不要重置
currentSpeechOffset,保留位置用于后续续读
内容的提问来源于stack exchange,提问作者AKD2022
相关产品推荐
相关产品推荐

