Reputation: 4337
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
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
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
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
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