Jetpack Compose中添加onPlayerStateChanged监听器后ExoPlayer重播按钮失效
问题:ExoPlayer重播按钮在视频结束后失效,显示下一页按钮触发重组导致异常
问题概述
创建了ExoPlayer实例和重播按钮,按钮在视频播放过程中正常工作,但视频结束后完全失效。推测是视频结束时调用viewModel.showNextButton()触发UI重组,破坏了原有的ExoPlayer引用。
核心问题分析
- ExoPlayer实例未跨重组保留:原代码中ExoPlayer没有用
remember包裹,每次UI重组都会重新创建实例,重播按钮引用的是旧的已失效实例。 - ViewModel状态更新逻辑错误:直接修改StateFlow内部的
MutableState值,不符合StateFlow的不可变数据更新规范,同时状态收集方式错误,无法正确响应变化。 - UI状态引用错误:显示下一页按钮时引用了未定义的
viewModel.showButton.value,属于代码笔误。
修复方案
1. 用remember保留ExoPlayer实例
在Compose中,需要跨重组保留的对象必须用remember包裹,确保重播按钮始终引用同一个有效实例:
val context = LocalContext.current // 用remember保存ExoPlayer,避免重组时重新创建 val exoPlayer = remember { ExoPlayer.Builder(context) .build() .also { player -> val mediaItem = MediaItem.Builder() .setUri(Uri.parse("android.resource://$packageName/${R.raw.xxxxxx}")) .build() player.setMediaItem(mediaItem) player.playWhenReady = true player.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING player.addListener(playerEventListener) player.prepare() } } DisposableEffect(Unit) { onDispose { exoPlayer.release() } } AndroidView(factory = { StyledPlayerView(context).apply { hideController() useController = false player = exoPlayer } })
2. 修正ViewModel状态更新逻辑
将UiState改为不可变数据类,通过复制整个对象更新状态,符合StateFlow的使用规范:
// 不可变UiState,用普通Boolean替代MutableState data class TutorialUiState( val showNextButton: Boolean = false ) class TutorialViewModel(application: HDiagsApplication) : AndroidViewModel(application) { private val _uiState = MutableStateFlow(TutorialUiState()) val uiState = _uiState.asStateFlow() fun showNextButton() { // 通过复制更新整个UiState _uiState.value = _uiState.value.copy(showNextButton = true) } // 可选:添加隐藏按钮的方法,用于重播时重置状态 fun hideNextButton() { _uiState.value = _uiState.value.copy(showNextButton = false) } }
3. 正确收集ViewModel状态
用collectAsState收集StateFlow的状态更新,确保UI能正确响应变化:
// 正确收集StateFlow状态 val uiState by viewModel.uiState.collectAsState() // 显示下一页按钮 if (uiState.showNextButton) { Button(onClick = { startActivity(Intent(this@TutorialJetpack, ParadigmControllerJetpack::class.java)) }) { Text(text = "Begin test") } }
4. 优化重播按钮逻辑
确保重播时重置播放状态,可选隐藏下一页按钮:
Button(onClick = { exoPlayer.seekTo(0) exoPlayer.playWhenReady = true // 重播时隐藏下一页按钮 viewModel.hideNextButton() }){ Text( text = "replay", fontSize = 18.sp, modifier = Modifier.padding(5.dp), textAlign = TextAlign.Center ) }
内容的提问来源于stack exchange,提问作者Luke Dawe
相关产品推荐
相关产品推荐

