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
相关产品推荐
相关产品推荐

