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

将视频URI存入数据库再读取后,VideoView报‘Cant play video’错误如何解决

解决Android中存储Content URI后无法播放视频的问题

这个问题我做视频相关功能时也踩过坑!核心原因是你通过ACTION_GET_CONTENT获取的content://开头的URI是临时权限——系统只给了当前App会话的一次性访问权限,当你把这个URI字符串存入数据库,下次重启App或过段时间再读取使用时,App已经失去了访问该URI的权限,自然就会弹出“Can't play video”的错误。

下面给你几个可行的解决方案,按推荐程度排序:


方案1:保存视频的绝对文件路径(最直接)

与其存储URI字符串,不如先把URI转换成视频的绝对文件路径,再把路径存入数据库。这样后续读取路径后,直接用VideoView.setVideoPath()或者Uri.fromFile()来播放,就不会有权限问题。

获取绝对路径的代码示例:

private String getRealPathFromUri(Context context, Uri uri) {
    String realPath = null;
    // 针对不同Android版本做兼容处理
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Android 10及以上,通过MediaStore查询路径
        Cursor cursor = context.getContentResolver().query(uri, 
            new String[]{MediaStore.Video.Media.DATA}, 
            null, null, null);
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                realPath = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA));
            }
            cursor.close();
        }
    } else {
        // Android 9及以下,通过DocumentContract解析
        if (DocumentsContract.isDocumentUri(context, uri)) {
            String docId = DocumentsContract.getDocumentId(uri);
            if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
                String id = docId.split(":")[1];
                String selection = MediaStore.Video.Media._ID + "=?";
                String[] selectionArgs = new String[]{id};
                realPath = getDataColumn(context, MediaStore.Video.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs);
            }
        } else if ("content".equalsIgnoreCase(uri.getScheme())) {
            realPath = getDataColumn(context, uri, null, null);
        }
    }
    return realPath;
}

private String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
    Cursor cursor = null;
    final String column = MediaStore.Video.Media.DATA;
    final String[] projection = {column};
    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
        if (cursor != null && cursor.moveToFirst()) {
            final int columnIndex = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(columnIndex);
        }
    } finally {
        if (cursor != null) cursor.close();
    }
    return null;
}

使用方式:

在onActivityResult里替换原代码:

if (resultCode == RESULT_OK && requestCode == SELECT_VIDEO) {
    Uri selectedVideoUri = data.getData();
    if (selectedVideoUri != null) {
        String videoPath = getRealPathFromUri(this, selectedVideoUri);
        if (videoPath != null) {
            videoPathList.add(videoPath);
            // 存入数据库
        }
    }
}

播放时直接用路径:

String videoPath = getIntent().getStringExtra("video_path");
videoView.setVideoPath(videoPath);
// 后续mediaController设置不变
videoView.start();

方案2:申请持久化URI权限

如果你必须存储URI字符串,可以在获取URI后,向系统申请持久化访问权限,这样即使App重启,也能继续访问该URI。

代码示例:

在onActivityResult里添加权限申请逻辑:

if (resultCode == RESULT_OK && requestCode == SELECT_VIDEO) {
    Uri selectedVideoUri = data.getData();
    if (selectedVideoUri != null) {
        // 申请持久化读权限
        int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION);
        getContentResolver().takePersistableUriPermission(selectedVideoUri, takeFlags);
        // 存储URI字符串到数据库
        videoPathList.add(selectedVideoUri.toString());
    }
}

注意事项:

  • 该方法需要用户在文件选择时授权App访问该文件(系统一般会自动弹出授权提示);
  • 如果用户后续删除了该文件或修改了文件权限,URI还是会失效;
  • 仅适用于支持SAF(Storage Access Framework)的文件管理器返回的URI。

方案3:复制视频到App私有目录(最可靠)

把选中的视频复制到App的私有存储目录(比如getFilesDir()或者getCacheDir()),然后存储这个私有路径。因为App对自己的私有目录有完全的访问权限,不会出现权限失效的问题。

代码示例:

private String copyVideoToPrivateDir(Context context, Uri uri) {
    String fileName = "video_" + System.currentTimeMillis() + ".mp4";
    File outputFile = new File(context.getFilesDir(), fileName);
    try (InputStream inputStream = context.getContentResolver().openInputStream(uri);
         OutputStream outputStream = new FileOutputStream(outputFile)) {
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, length);
        }
        return outputFile.getAbsolutePath();
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

使用方式:

在onActivityResult里调用该方法:

if (resultCode == RESULT_OK && requestCode == SELECT_VIDEO) {
    Uri selectedVideoUri = data.getData();
    if (selectedVideoUri != null) {
        String privatePath = copyVideoToPrivateDir(this, selectedVideoUri);
        if (privatePath != null) {
            videoPathList.add(privatePath);
            // 存入数据库
        }
    }
}

播放时直接用私有路径即可,和方案1的播放代码一致。

优缺点:

  • 优点:完全避免权限问题,兼容性最好;
  • 缺点:会占用App的存储空间,如果视频很大,可能会让用户反感,记得添加清理缓存的功能。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:03:22