Jetpack Compose中ExoPlayer Media3视频播放器视图重叠与显示异常
HorizontalPager中VideoPlayerView问题修复
问题1:切换页面后播放器显示错误视频
原因
当前代码里remember{ createExoPlayer(context, uri) }未将uri作为记忆依赖键,导致不同页面的VideoPlayerView复用同一个ExoPlayer实例,媒体源没有随uri更新;同时AndroidView也未根据uri变化调整播放器内容。
修复方案
- 给
remember添加uri作为依赖键,确保不同uri对应独立的ExoPlayer实例:
val exoPlayer = remember(uri) { createExoPlayer(context, uri) }
- 给
AndroidView设置key = uri,保证uri变化时重建播放器视图;同时在update块中新增媒体源校验逻辑,避免复用错误内容:
AndroidView( factory = { context -> // 原有PlayerView创建代码 }, update = { playerView -> // 新增:校验当前媒体源,不匹配则更新 val currentMediaItem = playerView.player?.currentMediaItem if (currentMediaItem?.mediaId != uri) { val source = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context)) .createMediaSource(MediaItem.fromUri(uri)) exoPlayer.setMediaSource(source) exoPlayer.prepare() } // 原有生命周期处理代码 }, key = uri, // 新增:uri变化时重建视图 modifier = modifier.fillMaxSize() )
- 移除
setKeepContentOnPlayerReset(true),避免播放器重置后残留旧视频画面。
问题2:视频缩放裁剪后溢出Pager页面
原因
视频启用裁剪缩放模式后,超出了Pager页面的边界,且播放器容器未设置裁剪限制,导致内容溢出。
修复方案
- 给播放器容器
Box添加clip(RectangleShape)修饰符,强制裁剪超出容器范围的内容:
Box( modifier = Modifier .clip(RectangleShape) // 新增:裁剪溢出内容 .onGloballyPositioned { /* 原有定位逻辑 */ }, contentAlignment = Alignment.Center )
- 调整
PlayerView的resizeMode为RESIZE_MODE_FILL,配合ExoPlayer的裁剪缩放模式,确保视频填充容器同时不溢出:
val playerView = PlayerView(context).apply { // 原有设置 resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL // 原有设置 }
- 确保
VideoPlayerView的宽度被限制为Pager页面宽度,调用时直接使用fillMaxWidth()即可(HorizontalPager默认每个页面占满屏幕宽度)。
修改后的完整VideoPlayerView代码
@OptIn(UnstableApi::class) @Composable fun VideoPlayerView( uri: String, modifier: Modifier = Modifier ) { val context = LocalContext.current var isReady by remember { mutableStateOf(false) } val model = ImageRequest.Builder(context) .data(uri) .videoFrameMillis(10000) .decoderFactory { result, options, _ -> VideoFrameDecoder( result.source, options ) } .build() // 修复问题1:添加uri作为remember依赖键 val exoPlayer = remember(uri) { createExoPlayer(context, uri) } exoPlayer.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING exoPlayer.repeatMode = Player.REPEAT_MODE_ONE exoPlayer.addListener(object : Player.Listener { override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { isReady = playWhenReady super.onPlayWhenReadyChanged(playWhenReady, reason) } }) var lifecycle by remember { mutableStateOf(Lifecycle.Event.ON_CREATE) } val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> lifecycle = event } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) exoPlayer.release() } } Box( modifier = modifier // 修复问题2:添加裁剪修饰符 .clip(RectangleShape) .onGloballyPositioned { coordinates -> val parentRect = coordinates.boundsInParent() val isFullyVisible = parentRect.contains(coordinates.positionInWindow()) if (isFullyVisible) { exoPlayer.play() } else { exoPlayer.pause() } }, contentAlignment = Alignment.Center ) { if (exoPlayer.isLoading) { AsyncImage( modifier = Modifier.fillMaxSize(), model = model, contentDescription = "video thumbnail", contentScale = ContentScale.Crop ) } else { AndroidView( factory = { context -> val playerView = PlayerView(context).apply { hideController() useController = false player = exoPlayer // 修复问题2:修改resizeMode为FILL resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) // 移除旧画面保留设置 } playerView }, update = { playerView -> // 修复问题1:校验并更新媒体源 val currentMediaItem = playerView.player?.currentMediaItem if (currentMediaItem?.mediaId != uri) { val source = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context)) .createMediaSource(MediaItem.fromUri(uri)) exoPlayer.setMediaSource(source) exoPlayer.prepare() } when (lifecycle) { Lifecycle.Event.ON_PAUSE -> { playerView.onPause() playerView.player?.pause() } Lifecycle.Event.ON_RESUME -> { playerView.onResume() } else -> Unit } }, // 修复问题1:添加uri作为AndroidView的键 key = uri, modifier = Modifier.fillMaxSize() ) } } } @OptIn(UnstableApi::class) private fun createExoPlayer(context: Context, uri: String): ExoPlayer { return ExoPlayer.Builder(context) .build() .apply { val dataSourceFactory: DataSource.Factory = DefaultDataSource.Factory(context) val source = ProgressiveMediaSource.Factory(dataSourceFactory) .createMediaSource(MediaItem.fromUri(uri)) setMediaSource(source) prepare() } }
内容的提问来源于stack exchange,提问作者Jeevan Rupacha
相关产品推荐
相关产品推荐

