[Android] Make the video screenshots available in the Android photo picker
Nicolas Pomepuy
git at videolan.org
Tue Mar 25 15:17:41 UTC 2025
vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Mon Mar 24 11:39:14 2025 +0100| [fa95814856710611d767223f44a990fc00bb3e93] | committer: Duncan McNamara
Make the video screenshots available in the Android photo picker
Fixes #3176
> https://code.videolan.org/videolan/vlc-android/commit/fa95814856710611d767223f44a990fc00bb3e93
---
.../src/org/videolan/vlc/gui/helpers/BitmapUtil.kt | 56 +++++++++++++++++++++-
.../videolan/vlc/gui/video/VideoPlayerActivity.kt | 2 +-
2 files changed, 55 insertions(+), 3 deletions(-)
diff --git a/application/vlc-android/src/org/videolan/vlc/gui/helpers/BitmapUtil.kt b/application/vlc-android/src/org/videolan/vlc/gui/helpers/BitmapUtil.kt
index 281daa7c59..5ac42ac36c 100644
--- a/application/vlc-android/src/org/videolan/vlc/gui/helpers/BitmapUtil.kt
+++ b/application/vlc-android/src/org/videolan/vlc/gui/helpers/BitmapUtil.kt
@@ -20,19 +20,33 @@
package org.videolan.vlc.gui.helpers
+import android.content.ContentValues
import android.content.Context
import android.content.res.Resources
-import android.graphics.*
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.graphics.RectF
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.StateListDrawable
import android.graphics.drawable.VectorDrawable
import android.net.Uri
import android.os.Build
+import android.os.Environment
+import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.view.View.MeasureSpec
import androidx.annotation.DrawableRes
+import androidx.annotation.RequiresApi
+import androidx.annotation.WorkerThread
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
@@ -50,6 +64,7 @@ import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
+import java.io.OutputStream
import java.text.DecimalFormat
@@ -310,7 +325,9 @@ object BitmapUtil {
else -> Rect(0, 0, width, height)
}
- fun saveOnDisk(bitmap: Bitmap, destPath: String):Boolean {
+ fun saveOnDisk(bitmap: Bitmap, destPath: String, publish: Boolean = false, context: Context? = null):Boolean {
+ if (publish && context == null) throw IllegalStateException("Cannot publish image without context")
+ if (publish && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) return saveImageInQ(context!!, bitmap, File(destPath).name)
val destFile = File(destPath)
return when {
destFile.parentFile?.canWrite() == true -> {
@@ -319,6 +336,8 @@ object BitmapUtil {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)
FileOutputStream(destFile).use { it.write(stream.toByteArray()) }
}
+ if (publish)
+ MediaStore.Images.Media.insertImage(context!!.contentResolver, destPath, File(destPath).name, File(destPath).name);
true
} catch (e: IOException) {
Log.e(TAG, "Could not save image to disk", e)
@@ -332,6 +351,39 @@ object BitmapUtil {
}
}
+ /**
+ * Save image for Android version >= Q
+ *
+ * @param context the context to use to get the [android.content.ContentResolver]
+ * @param bitmap the bitmap to save
+ * @param filename the filename
+ * @return true if the bitmap has been saved successfully
+ */
+ @WorkerThread
+ @RequiresApi(Build.VERSION_CODES.Q)
+ private fun saveImageInQ(context: Context, bitmap: Bitmap, filename: String): Boolean {
+ var fos: OutputStream? = null
+ var imageUri: Uri? = null
+ val contentValues = ContentValues().apply {
+ put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
+ put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
+ put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_PICTURES}/${Environment.DIRECTORY_SCREENSHOTS}")
+ put(MediaStore.Images.Media.IS_PENDING, 1)
+ }
+ val contentResolver = context.applicationContext.contentResolver
+ contentResolver.also { resolver ->
+ imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
+ fos = imageUri?.let { resolver.openOutputStream(it) }
+ }
+ fos?.use { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it) }
+ contentValues.clear()
+ contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
+ imageUri?.let {
+ contentResolver.update(it, contentValues, null, null)
+ }
+ return imageUri != null
+ }
+
}
/**
diff --git a/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt b/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt
index 1dd2774cf3..660976a497 100644
--- a/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt
+++ b/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt
@@ -1152,7 +1152,7 @@ open class VideoPlayerActivity : AppCompatActivity(), PlaybackService.Callback,
}
val coords = IntArray(2)
surfaceView.getLocationOnScreen(coords)
- if (BitmapUtil.saveOnDisk(bitmap, dst.absolutePath))
+ if (BitmapUtil.saveOnDisk(bitmap, dst.absolutePath, publish = true, context = this at VideoPlayerActivity))
screenshotDelegate.takeScreenshot(dst, bitmap, coords, surface.width, surface.height)
else
UiTools.snacker(this at VideoPlayerActivity, R.string.screenshot_error)
More information about the Android
mailing list