Insert Video to gallery [Android Q]

To record a SurfeceView I'm using a 3rd-party library , this library requires a path where the output (the recorded video) saved in my case is savedVideoPath :

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

After the recording stopped, the video should be saved with savedVideoPath as a path, when I test the code, that is to say , when I open the gallery app, I see the saved video there, but when I tested on Android Q, I can't see anything.

Since getExternalStoragePublicDirectory and getExternalStorageDirectory are deprecated ,I tried to use getExternalFilesDir as following :

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();
        }
    }
}

After the recording stopped, I go to Files Explorer app > Android > data > com.packageName > files > Movies > Folder ,I can see all saved videos there,but I can't see them on the gallery.

I tried to use Intent.ACTION_MEDIA_SCANNER_SCAN_FILE to refresh the gallery, but unfortunately doesn't work.

I also tried MediaScannerConnection:

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

    public void onMediaScannerConnected() {
    }

    public void onScanCompleted(String s, Uri uri) {
    }
});

Upvotes: 13

Views: 11865

Answers (4)

Khaled Mahmoud
Khaled Mahmoud

Reputation: 343

Thanks to other solutions I managed to get done with this code:

fun saveVideo(filePath: String?, isMOV: Boolean, fileName: String) {
     filePath?.let {
        val context = requireContext()
        val values = ContentValues().apply {
            val folderName = Environment.DIRECTORY_MOVIES

            put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
            put(MediaStore.Video.Media.TITLE, fileName)
            put(
                MediaStore.Video.Media.MIME_TYPE, if (isMOV) {
                    "video/quicktime"
                } else {
                    "video/mp4"
                }
            )
            if (Build.VERSION.SDK_INT >= 29) {
                put(
                    MediaStore.Video.Media.RELATIVE_PATH,
                    folderName + "/${context.getString(R.string.app_name)}"
                )
                put(
                    MediaStore.Video.Media.DATE_ADDED,
                    System.currentTimeMillis() / 1000
                )
                put(MediaStore.Video.Media.IS_PENDING, 1)

            } else {
                put(
                    MediaStore.Video.Media.DATE_ADDED,
                    System.currentTimeMillis() / 1000
                )

            }
        }
        val fileUri = if (Build.VERSION.SDK_INT >= 29) {
            val collection =
                MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
            context.contentResolver.insert(collection, values)
        } else {
            requireContext().contentResolver.insert(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                values
            )!!
        }
        fileUri?.let {
            context.contentResolver.openFileDescriptor(fileUri, "w").use { descriptor ->
                descriptor?.let {
                    try {
                        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)
                                }
                            }
                        }
                    } catch (e: Exception) {
                        Toast.makeText(context, "couldn't save the video", Toast.LENGTH_SHORT)
                            .show()
                        return
                    }
                }
            }

            values.clear()
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                values.put(MediaStore.Video.Media.IS_PENDING, 0)
            }
            try {
                requireContext().contentResolver.update(fileUri, values, null, null)
            }catch (e:Exception){}
        }
    }
    Toast.makeText(context, "Video saved to gallery", Toast.LENGTH_SHORT).show()
}

Upvotes: 2

Hemant patel
Hemant patel

Reputation: 21

The reply is late but may benefit someone in the future. here is my version of code perfectly working in android 26 to 31. Lower than 26 I did not check. Need to implement Apache commons Io.

suspend fun moveVideo1(context: Context,src: String,videoFileName: String,extension: String): Boolean
{
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
    {
        val valueVideo: ContentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.RELATIVE_PATH,getRelativeDownloadFolder())
            put(MediaStore.MediaColumns.TITLE,videoFileName)
            put(MediaStore.MediaColumns.DISPLAY_NAME,videoFileName)
            put(MediaStore.MediaColumns.MIME_TYPE, MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension))
            put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis() / 1000)
            put(MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis());
            put(MediaStore.MediaColumns.IS_PENDING, 1);
        }

        val uriSavedVideo = context.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI,valueVideo)?: Uri.EMPTY

        val pfd: ParcelFileDescriptor?

        try {
            pfd = context.contentResolver.openFileDescriptor(uriSavedVideo, "w")
            val out = FileOutputStream(pfd?.fileDescriptor)
            val inStream = FileInputStream(File(src))
            val buf = ByteArray(8192)

            var len: Int
            while (inStream.read(buf).also { len = it } > 0) {
                out.write(buf, 0, len)
            }
            out.close()
            inStream.close()
            pfd?.close()
            File(src).delete()
        }catch (e: Exception) {
            e.printStackTrace()
            return false
        }

        valueVideo.clear();
        valueVideo.put(MediaStore.MediaColumns.IS_PENDING, 0);
        context.contentResolver.update(uriSavedVideo, valueVideo, null, null);
        return true
    }
    else
    {
        try {
            FileUtils.moveFile(File(src),File(getDefaultDownloadFolder(context), "$videoFileName.$extension"))
        }catch (e: Exception) {
            e.printStackTrace()
            return false
        }
        return true
    }
}

Upvotes: 1

Frank
Frank

Reputation: 2788

You have to change the library to make it work with Android Q. If you cannot do this you could copy the video to the media gallery and then delete the old video created in getExternalFilesDir(). After this you have the URI of the video and can do what you want with the URI.

If you have saved the video with getExternalFilesDir() you could use my example here: The media URI you get is "uriSavedVideo". This is only an example. A large video should also be copied in the background.

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);
}

Upvotes: 19

nAkhmedov
nAkhmedov

Reputation: 3592

Here it is my solution - save photo/video to Gallery.

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)
    }
}
}

Upvotes: 12

Related Questions