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

Jetpack Compose Android中从URI播放视频遇权限拒绝问题求助

解决ExoPlayer播放文件管理器选择视频时的权限拒绝问题

从你贴出的错误日志来看,核心问题是权限未正确获取:通过文件管理器拿到的content://com.android.providers.media.documents/...这类Uri属于系统媒体提供者,直接访问会触发SecurityException,必须通过ACTION_OPEN_DOCUMENT系列API获取持久访问权限;另外你提到的Navigation传递Uri的问题,也需要通过编码处理来规避特殊字符冲突。下面一步步帮你解决:

1. 用正确方式获取带持久权限的视频Uri

不要直接获取文件Uri,改用ACTION_OPEN_DOCUMENT启动文件选择器,同时申请持久访问权限,这样App重启后也能正常访问该文件:

// 在Compose中注册文件选择器回调
val pickVideoLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
    uri?.let { selectedUri ->
        // 申请持久读权限,这是关键!
        requireContext().contentResolver.takePersistableUriPermission(
            selectedUri,
            Intent.FLAG_GRANT_READ_URI_PERMISSION
        )
        // 把Uri传递给VideoView(比如通过ViewModel)
        viewModel.updateSelectedVideoUri(selectedUri)
    }
}

// 触发视频选择的按钮逻辑
Button(onClick = { pickVideoLauncher.launch(arrayOf("video/*")) }) {
    Text("选择视频")
}

2. 修复ExoPlayer的初始化与生命周期管理

你的原代码中DisposableEffect的用法有点问题,调整为更规范的Player生命周期管理,同时用MediaItem来构建媒体源(ExoPlayer推荐写法):

@Composable
fun VideoView(context: Context, mediaUri: Uri) {
    val exoPlayer = remember(context, mediaUri) {
        SimpleExoPlayer.Builder(context).build().apply {
            val dataSourceFactory = DefaultDataSourceFactory(
                context,
                Util.getUserAgent(context, context.packageName)
            )
            val mediaItem = MediaItem.fromUri(mediaUri)
            val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
                .createMediaSource(mediaItem)
            setMediaSource(mediaSource)
            playWhenReady = true
            prepare()
        }
    }

    // 渲染PlayerView
    AndroidView(
        modifier = Modifier.testTag("VideoPlayer"),
        factory = {
            PlayerView(context).apply {
                player = exoPlayer
                layoutParams = FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT
                )
            }
        }
    )

    // 页面销毁时释放Player资源
    DisposableEffect(exoPlayer) {
        onDispose {
            exoPlayer.release()
        }
    }
}

3. 解决Navigation间传递Uri的编码问题

直接传递Uri字符串会因为特殊字符(比如:、/)导致解析失败,需要先编码再传递,接收方再解码:

跳转时传递编码后的Uri:

val encodedUri = Uri.encode(mediaUri.toString())
navController.navigate("video_play_screen?videoUri=$encodedUri")

目标页面接收并解析:

val navBackStackEntry = rememberNavController().currentBackStackEntry!!
val encodedUri = navBackStackEntry.arguments?.getString("videoUri")
val mediaUri = encodedUri?.let { Uri.parse(Uri.decode(it)) }

4. 补充Manifest权限配置

如果你的App需要兼容Android 10(API 29)及以下设备,添加只读外部存储权限(Android 11+无需此权限,ACTION_OPEN_DOCUMENT已足够):

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="29" />

按照以上步骤调整后,权限拒绝的问题应该就能解决,同时Uri传递的冲突也会被规避。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 09:14:08