【问题标题】:Insert Video to gallery [Android Q]将视频插入图库 [Android Q]
【发布时间】:2020-01-15 16:19:49
【问题描述】:

要录制 SurfeceView 我正在使用第 3 方 library ,这个库需要一个路径,在我的案例中保存的输出(录制的视频)是 savedVideoPath :: p>

mRenderPipeline = EZFilter.input(this.effectBmp)
                .addFilter(new Effects().getEffect(VideoMaker.this, i))
                .enableRecord(savedVideoPath, true, false)
                .into(mRenderView);

录制停止后,应该以savedVideoPath为路径保存视频,当我测试代码时,也就是说,当我打开图库应用时,我看到保存的视频在那里,但是当我在 Android Q 上测试时,我什么都看不到。

由于getExternalStoragePublicDirectorygetExternalStorageDirectory 已被弃用,我尝试使用getExternalFilesDir 如下:

private void getPath() {
    String videoFileName = "video_" + System.currentTimeMillis() + ".mp4";
    fileName = videoFileName;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        File imageFile = null;
        File storageDir = new File(
            getExternalFilesDir(Environment.DIRECTORY_MOVIES), 
            "Folder");
        source = storageDir;
        boolean success = true;
        if (!storageDir.exists()) {
            success = storageDir.mkdirs();
        }
        if (success) {
            imageFile = new File(storageDir, videoFileName);
            savedVideoPath = imageFile.getAbsolutePath();
        }
    } else {
        File storageDir = new File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
            + "/Folder");
        boolean success = true;
        if (!storageDir.exists()) {
            success = storageDir.mkdirs();
        }
        if (success) {
            File videoFile = new File(storageDir, videoFileName);
            savedVideoPath = videoFile.getAbsolutePath();
        }
    }
}

录制停止后,我转到 Files Explorer app > Android > data > com.packageName > files > Movies > Folder ,我可以在那里看到所有保存的视频,但我看不到他们在画廊里。

我尝试使用Intent.ACTION_MEDIA_SCANNER_SCAN_FILE 刷新图库,但很遗憾不起作用。

我也试过MediaScannerConnection

MediaScannerConnection.scanFile(
    context, 
    new String[]{savedVideoPath}, 
    new String[]{"video/mp4"}, 
    new MediaScannerConnection.MediaScannerConnectionClient() {

    public void onMediaScannerConnected() {
    }

    public void onScanCompleted(String s, Uri uri) {
    }
});
  • 谁能帮我解决这个问题?我坚持了将近 2 天

【问题讨论】:

  • 试试MediaScannerConnection 和它的scanFile() 方法。请注意,您可能无法使用单个文件同时满足这两个条件(具有文件系统访问权限并且视频出现在 MediaStore 中以供图库应用程序使用)。
  • @CommonsWare,感谢您的评论,我已经尝试过了,但不幸的是,在图库中找不到视频
  • 该库可能适合使用the MediaMuxer constructor that takes a FileDescriptor。然后,您将能够在 ContentResolver 上使用 openFileDescriptor() 以返回您的 MediaStore Uri 问题。否则,在您使用库修改视频后,将其复制到MediaStore(请参阅您之前的问题)并删除您的文件副本。
  • @CommonsWare 感谢您的评论,是复制路径还是文件本身? ,以及如何将其复制到MediaStore,谢谢
  • "复制路径还是文件本身?" - 文件。 “我如何将它复制到 MediaStore”——我们在 an earlier question 中讨论过这个问题。您在那里的代码以及我的答案中的修改应该可以正常工作......只需使用转换后的视频文件作为数据源。 This class 表示正在下载视频;您的代码将是相同的,只是使用一个文件作为您的数据源。

标签: java android android-external-storage android-mediascanner android-10.0


【解决方案1】:

您必须更改库以使其与 Android Q 一起使用。如果您不能这样做,您可以将视频复制到媒体库,然后删除在 getExternalFilesDir() 中创建的旧视频。在此之后,您将获得视频的 URI,并且可以使用该 URI 做您想做的事情。

如果您使用getExternalFilesDir() 保存了视频,您可以在这里使用我的示例:您获得的媒体 URI 是“uriSavedVideo”。这只是一个例子。还应在后台复制大视频。

Uri uriSavedVideo;
File createdvideo = null;
ContentResolver resolver = getContentResolver();
String videoFileName = "video_" + System.currentTimeMillis() + ".mp4";
ContentValues valuesvideos;
valuesvideos = new ContentValues();

if (Build.VERSION.SDK_INT >= 29) {
    valuesvideos.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/" + "Folder");
    valuesvideos.put(MediaStore.Video.Media.TITLE, videoFileName);
    valuesvideos.put(MediaStore.Video.Media.DISPLAY_NAME, videoFileName);
    valuesvideos.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
    valuesvideos.put(
        MediaStore.Video.Media.DATE_ADDED, 
        System.currentTimeMillis() / 1000);

    Uri collection = 
        MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
    uriSavedVideo = resolver.insert(collection, valuesvideos);
} else {
    String directory  = Environment.getExternalStorageDirectory().getAbsolutePath() 
    + File.separator + Environment.DIRECTORY_MOVIES + "/" + "YourFolder";
    createdvideo = new File(directory, videoFileName);

    valuesvideos.put(MediaStore.Video.Media.TITLE, videoFileName);
    valuesvideos.put(MediaStore.Video.Media.DISPLAY_NAME, videoFileName);
    valuesvideos.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
    valuesvideos.put(
        MediaStore.Video.Media.DATE_ADDED, 
        System.currentTimeMillis() / 1000);
    valuesvideos.put(MediaStore.Video.Media.DATA, createdvideo.getAbsolutePath());

    uriSavedVideo = getContentResolver().insert(
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI, 
        valuesvideos);
}

if (Build.VERSION.SDK_INT >= 29) {
    valuesvideos.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
    valuesvideos.put(MediaStore.Video.Media.IS_PENDING, 1);
}

ParcelFileDescriptor pfd;
try {
    pfd = getContentResolver().openFileDescriptor(uriSavedVideo, "w");

    FileOutputStream out = new FileOutputStream(pfd.getFileDescriptor());
    // get the already saved video as fileinputstream
    // The Directory where your file is saved
    File storageDir = new File(
        getExternalFilesDir(Environment.DIRECTORY_MOVIES), 
        "Folder");
    //Directory and the name of your video file to copy
    File videoFile = new File(storageDir, "Myvideo"); 
    FileInputStream in = new FileInputStream(videoFile);

    byte[] buf = new byte[8192];
    int len;
    while ((len = in.read(buf)) > 0) {
        out.write(buf, 0, len);
    }

    out.close();
    in.close();
    pfd.close();
} catch (Exception e) {
    e.printStackTrace();
}

if (Build.VERSION.SDK_INT >= 29) {
    valuesvideos.clear();
    valuesvideos.put(MediaStore.Video.Media.IS_PENDING, 0);
    getContentResolver().update(uriSavedVideo, valuesvideos, null, null);
}

【讨论】:

  • Android Q 及以上版本的最佳方式是这样。非常感谢!
  • 我的一些用户报告了问题。崩溃报告在resolver.insert() 上显示java.lang.UnsupportedOperationException - 有什么想法吗?他们可能没有外部存储吗?
  • 您好,请考虑 MediaStore.Video.Media.DATE_TAKEN 和 MediaStore.Video.Media.IS_PENDING 仅适用于 APi >= 29 !!!但是您也可以继续使用文件的旧方法,因为 Android 11 再次支持“媒体文件”的文件方法(查看 docs for scoped storage)。要使其在 Android 10 上也能正常工作,只需将 android:requestLegacyExternalStorage="true" 添加到清单中的应用程序即可。
  • 不 - 在旧设备上获得 java.lang.UnsupportedOperationException: Unknown URI: content://media/external_primary/video/media 所以它似乎与 DATE_TAKENIS_PENDING 无关
  • 嗨,JCutting8。我更新了完整的示例,向您展示 Api >=29 和 lower 29 之间的区别。所以我想现在你可以理解这些差异了。如何插入视频和获取 uri 存在一些差异。这应该没有任何问题。只调整文件名和目录等内容
【解决方案2】:

这是我的解决方案 - 将照片/视频保存到图库。

private fun saveMediaFile2(filePath: String?, isVideo: Boolean, fileName: String) {
filePath?.let {
    val context = MyApp.applicationContext
    val values = ContentValues().apply {
        val folderName = if (isVideo) {
            Environment.DIRECTORY_MOVIES
        } else {
            Environment.DIRECTORY_PICTURES
        }
        put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
        put(MediaStore.Images.Media.MIME_TYPE, MimeUtils.guessMimeTypeFromExtension(getExtension(fileName)))
        put(MediaStore.Images.Media.RELATIVE_PATH, folderName + "/${context.getString(R.string.app_name)}/")
        put(MediaStore.Images.Media.IS_PENDING, 1)
    }

    val collection = if (isVideo) {
        MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    } else {
        MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    }
    val fileUri = context.contentResolver.insert(collection, values)

    fileUri?.let {
        if (isVideo) {
            context.contentResolver.openFileDescriptor(fileUri, "w").use { descriptor ->
                descriptor?.let {
                    FileOutputStream(descriptor.fileDescriptor).use { out ->
                        val videoFile = File(filePath)
                        FileInputStream(videoFile).use { inputStream ->
                            val buf = ByteArray(8192)
                            while (true) {
                                val sz = inputStream.read(buf)
                                if (sz <= 0) break
                                out.write(buf, 0, sz)
                            }
                        }
                    }
                }
            }
        } else {
            context.contentResolver.openOutputStream(fileUri).use { out ->
                val bmOptions = BitmapFactory.Options()
                val bmp = BitmapFactory.decodeFile(filePath, bmOptions)
                bmp.compress(Bitmap.CompressFormat.JPEG, 90, out)
                bmp.recycle()
            }
        }
        values.clear()
        values.put(if (isVideo) MediaStore.Video.Media.IS_PENDING else MediaStore.Images.Media.IS_PENDING, 0)
        context.contentResolver.update(fileUri, values, null, null)
    }
}
}

【讨论】:

  • 嗨,这个解决方案很棒!但对于视频,我不断收到FileNotFoundException,因为视频与图像位于同一应用程序目录下。图像工作完美。对此有什么想法吗?
猜你喜欢
  • 2020-01-15
  • 1970-01-01
  • 1970-01-01
  • 2011-11-20
  • 1970-01-01
  • 2016-08-24
  • 1970-01-01
  • 1970-01-01
  • 2016-07-21
相关资源
最近更新 更多