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

Jetpack Compose中Text-to-Speech功能集成问题求助

问题分析与解决方案

核心问题拆解

  1. 首次点击延迟/无响应:TTS初始化是异步操作,首次点击时TTS可能还未完成初始化,导致点击无效;原代码中TTS语言设置存在空指针风险,初始化逻辑不严谨。
  2. 状态管理混乱:AudioButton中直接写isSpeaking = false会在每次重组时重置状态,无法正确跟踪播放状态;未监听TTS播放完成事件,无法自动更新状态。
  3. 多实例冗余:每个AudioButton单独创建TTS实例,既浪费资源,也可能导致初始化冲突。

修复后的代码实现

1. 重构TTS记忆函数

@Composable
fun rememberTextToSpeech(ttsLocale: Locale): MutableState<TextToSpeech?> {
    val context = LocalContext.current
    val tts = remember { mutableStateOf<TextToSpeech?>(null) }

    // 以locale为依赖项,语言变化时自动重建TTS
    DisposableEffect(context, ttsLocale) {
        val textToSpeech = TextToSpeech(context) { status ->
            if (status == TextToSpeech.SUCCESS) {
                // 直接用局部变量设置语言,避免空指针
                val langResult = textToSpeech.setLanguage(ttsLocale)
                if (langResult == TextToSpeech.LANG_MISSING_DATA || langResult == TextToSpeech.LANG_NOT_SUPPORTED) {
                    Log.e("TTS", "不支持目标语言:$ttsLocale")
                }
            } else {
                Log.e("TTS", "TTS初始化失败,状态码:$status")
            }
        }
        tts.value = textToSpeech

        onDispose {
            textToSpeech.stop()
            textToSpeech.shutdown()
            tts.value = null
        }
    }
    return tts
}

2. 修复音频按钮组件

@Composable
fun AudioButton(
    language: Language,
    text: String,
    // 可选:从父组件传入共享的TTS实例,避免多实例冗余
    sharedTts: MutableState<TextToSpeech?>? = null
) {
    val ttsLocale = when (language) {
        Language.EN -> Locale("en", "GB")
        // 补充其他语言分支
        else -> Locale.getDefault()
    }

    var isSpeaking by remember { mutableStateOf(false) }
    val tts = sharedTts ?: rememberTextToSpeech(ttsLocale)

    // 监听TTS播放状态,自动更新UI状态
    LaunchedEffect(tts.value) {
        tts.value?.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
            override fun onStart(utteranceId: String?) {
                MainScope().launch { isSpeaking = true }
            }

            override fun onDone(utteranceId: String?) {
                MainScope().launch { isSpeaking = false }
            }

            override fun onError(utteranceId: String?) {
                MainScope().launch { isSpeaking = false }
            }
        })
    }

    Button(
        onClick = {
            val currentTts = tts.value ?: return@Button
            if (currentTts.isSpeaking) {
                currentTts.stop()
                isSpeaking = false
            } else {
                // 用唯一ID标记播放请求,确保状态跟踪准确
                val utteranceId = UUID.randomUUID().toString()
                val params = Bundle().apply {
                    putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId)
                }
                currentTts.speak(text, TextToSpeech.QUEUE_FLUSH, params, utteranceId)
            }
        },
        modifier = Modifier.size(48.dp),
        // TTS未初始化或正在播放时禁用按钮,避免无效点击
        enabled = tts.value != null && !isSpeaking
    ) {
        Icon(
            modifier = Modifier.size(24.dp),
            imageVector = if (isSpeaking) Icons.Default.Pause else Icons.Default.PlayArrow,
            contentDescription = if (isSpeaking) "暂停播放" else "播放音频"
        )
    }
}

关键优化点说明

  • 异步初始化处理:通过enabled状态控制按钮可用性,避免TTS未就绪时的无效点击;
  • 状态自动同步:借助UtteranceProgressListener监听播放生命周期,自动更新isSpeaking状态,实现播放结束后自动重置;
  • 资源复用:支持从父组件传入共享TTS实例,减少多按钮场景下的初始化开销;
  • 严谨性提升:修复语言设置的空指针问题,增加语言不支持的错误日志。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 00:55:02