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

Android Studio录制视频获取时长远大于实际时长问题

问题根因
  • 当你通过MediaStore.EXTRA_OUTPUT参数指定自定义视频输出路径后,不要使用返回Intent中getData()获取的Uri。不同厂商的系统相机实现存在差异,部分机型返回的Intent携带的Uri要么为空,要么指向无关的历史媒体文件,你当前代码正是读取了无效Uri对应的错误文件,MediaPlayer解析异常才返回了不符合实际的时长数值。
  • 你创建的目标视频文件缺少.mp4后缀,系统相机写入视频时无法正确识别输出格式,会导致视频文件头、时长等元数据写入异常,进一步放大读取错误的概率。
  • 原有代码的权限逻辑存在缺陷:判断到缺少相机/存储权限时仅发起权限申请,没有中断后续执行流程,也没有处理用户授权完成后的回调,会出现未授权就拉起相机、用户授权后不会自动启动录制的问题。
修复步骤
  • 修正文件命名逻辑,给输出的视频文件补上.mp4后缀,保证系统相机能正确识别输出格式
  • 将传给EXTRA_OUTPUT的目标Uri提前存为类成员变量,录制成功后直接使用该Uri读取视频,完全不依赖返回Intent中的data
  • 修正权限判断逻辑:无权限时发起申请后直接return,待用户授权通过后再执行录制流程
  • 替换MediaPlayer读取时长的实现,改用MediaMetadataRetriever读取视频元数据,该方式更轻量,对损坏文件、异常文件的容错性更高,同时做好异常捕获避免崩溃。
核心修改代码

补全权限回调逻辑

// 原有videoUri成员变量直接复用,用来存储自定义输出的视频Uri
private Uri videoUri;

// 补全权限申请回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == CAMERA_PERMISSION_CODE) {
        boolean allGranted = true;
        for (int result : grantResults) {
            if (result != PackageManager.PERMISSION_GRANTED) {
                allGranted = false;
                break;
            }
        }
        if (allGranted) {
            // 授权通过后再启动录制
            recordVideo();
        } else {
            Log.w(TAG, "相机/存储权限被拒绝,无法录制视频");
        }
    }
}

修正后的录制启动方法

private void recordVideo() {
    // 权限校验不通过时,申请权限后直接return,不执行后续逻辑
    boolean needStoragePerm = Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU;
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED
            || (needStoragePerm && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED)) {
        String[] perms;
        if (needStoragePerm) {
            perms = new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE};
        } else {
            perms = new String[]{Manifest.permission.CAMERA};
        }
        ActivityCompat.requestPermissions(this, perms, CAMERA_PERMISSION_CODE);
        return;
    }

    @SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmm");
    // 补上.mp4文件后缀
    String videoFileName = "recordingVideo_" + sdf.format(System.currentTimeMillis()) + ".mp4";
    File storageDir = getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File videoFile = new File(storageDir, videoFileName);
    // 提前将输出Uri赋值给成员变量,回调中直接使用
    videoUri = FileProvider.getUriForFile(UserViewActivity.this,
            getPackageName() + ".provider",
            videoFile);
    Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri);
    videoRecordActivityLauncher.launch(intent);
}

修正后的结果回调逻辑

ActivityResultLauncher<Intent> videoRecordActivityLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),
        result -> {
            if (result.getResultCode() == RESULT_OK) {
                // 直接使用提前存储的成员变量videoUri,不要从intent.getData()取值
                long duration = 0;
                MediaMetadataRetriever retriever = new MediaMetadataRetriever();
                try {
                    retriever.setDataSource(UserViewActivity.this, videoUri);
                    String durationMeta = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
                    if (durationMeta != null) {
                        duration = Long.parseLong(durationMeta);
                    }
                } catch (Exception e) {
                    Log.e(TAG, "读取视频信息失败", e);
                } finally {
                    try {
                        retriever.release();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                Log.i(TAG, "Video is recorded and available at path " + videoUri + " with duration of " + duration + "ms");
            } else if (result.getResultCode() == RESULT_CANCELED) {
                Log.i(TAG, "Recording video is canceled");
            } else {
                Log.w(TAG, "Recording video got some error, resultCode: " + result.getResultCode());
            }
        }
);

补充说明:Android 13(API 33)及以上版本不再需要WRITE_EXTERNAL_STORAGE权限,上述代码已经做了系统版本适配,可直接使用。


内容的提问来源于stack exchange,提问作者Idan Cohen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 08:09:26