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

如何在Jetpack Compose中用Slider跟踪TTS朗读进度?

解决Jetpack Compose中TTS朗读进度与Slider关联的问题

核心问题在于Compose依赖状态驱动UI,Slider的进度必须绑定Compose状态变量,同时TTS的进度更新要触发状态变化,从而让Compose自动重组更新UI。以下是具体实现步骤:

1. 定义Compose状态变量

用mutableStateOf存储进度比例和总时长,确保进度变化时UI自动更新:

@Composable
fun TtsPlayer() {
    val currentProgressRatio = remember { mutableStateOf(0f) }
    val totalDurationMs = remember { mutableStateOf(1f) } // 默认值避免除以0
    // 其他代码...
}

2. 实现Slider组件

将Slider的value绑定到状态变量,同时支持拖动跳转朗读位置:

Slider(
    value = currentProgressRatio.value,
    onValueChange = { newRatio ->
        currentProgressRatio.value = newRatio
        // 拖动时跳转TTS到对应位置
        ttsManager.seekTo((newRatio * totalDurationMs.value).toInt())
    },
    valueRange = 0f..1f,
    modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp)
)

3. 修改TTS管理器,添加进度回调

利用TTS的playbackPosition(API 26+)实时获取进度,通过回调通知Compose更新状态:

class TtsManager(private val context: Context) {
    private var tts: TextToSpeech? = null
    private var progressCallback: ((currentMs: Float, totalMs: Float) -> Unit)? = null
    private val progressHandler = Handler(Looper.getMainLooper())
    private var progressRunnable: Runnable? = null

    fun init() {
        tts = TextToSpeech(context) { status ->
            if (status == TextToSpeech.SUCCESS) {
                // 初始化成功,可设置语言等
            }
        }
    }

    fun setProgressCallback(callback: ((Float, Float) -> Unit)?) {
        progressCallback = callback
    }

    fun speak(text: String) {
        val params = hashMapOf(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID to "tts_utterance")
        tts?.speak(text, TextToSpeech.QUEUE_FLUSH, params, "tts_utterance")
        
        // 启动定时进度更新
        progressRunnable = object : Runnable {
            override fun run() {
                val current = tts?.playbackPosition ?: 0
                val total = tts?.totalPlaybackDuration ?: 1
                // 主线程更新Compose状态
                progressCallback?.invoke(current.toFloat(), total.toFloat())
                
                if (current < total && tts?.isSpeaking == true) {
                    progressHandler.postDelayed(this, 100) // 每100ms更新一次
                }
            }
        }
        progressRunnable?.let { progressHandler.post(it) }
    }

    fun seekTo(positionMs: Int) {
        tts?.seekTo(positionMs)
    }

    fun stop() {
        tts?.stop()
        progressRunnable?.let { progressHandler.removeCallbacks(it) }
    }

    fun shutdown() {
        tts?.shutdown()
        progressHandler.removeCallbacksAndMessages(null)
    }
}

4. 在Compose中关联TTS与状态

通过LaunchedEffect设置TTS的进度回调,将实时进度转换为比例更新状态:

@Composable
fun TtsPlayer() {
    val context = LocalContext.current
    val ttsManager = remember { TtsManager(context).apply { init() } }
    val currentProgressRatio = remember { mutableStateOf(0f) }
    val totalDurationMs = remember { mutableStateOf(1f) }

    // 绑定进度回调
    LaunchedEffect(Unit) {
        ttsManager.setProgressCallback { currentMs, totalMs ->
            currentProgressRatio.value = currentMs / totalMs
            totalDurationMs.value = totalMs
        }
    }

    Column(modifier = Modifier.padding(16.dp)) {
        Slider(/* 之前的Slider代码 */)
        Button(onClick = { ttsManager.speak("需要朗读的大段文本内容") }) {
            Text("开始朗读")
        }
        Button(onClick = { ttsManager.stop() }) {
            Text("停止朗读")
        }
    }

    // 页面销毁时释放TTS资源
    DisposableEffect(Unit) {
        onDispose {
            ttsManager.shutdown()
        }
    }
}

关键注意事项

  • API兼容性:playbackPosition和totalPlaybackDuration仅支持Android 8.0(API 26)及以上,低版本需通过文本长度、朗读速度估算进度(精度会降低)。
  • 主线程更新:状态变量必须在主线程更新,上述代码通过Handler(Looper.getMainLooper())确保这一点。
  • 资源释放:页面销毁时务必调用ttsManager.shutdown()并移除Handler回调,避免内存泄漏。

内容的提问来源于stack exchange,提问作者A. Cedano

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 05:41:14