This is a somewhat broad question that covers a few topics that are all related to the same goal: Understanding how to properly load multimedia content e.g. videos and images with the standard Android "picker"/"gallery", not using any third party library.
Before I upgraded to targetSdkVersion 30, this code worked properly on every known device:
val intent = Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
intent.type = "video/*"
intent.action = Intent.ACTION_GET_CONTENT
intent.putExtra(
MediaStore.EXTRA_OUTPUT,
Uri.fromFile(File(filesDir, "chosenVideo"))
)
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), PICK_VIDEO_PERMISSION)
} else {
startActivityForResult(Intent.createChooser(intent, "Select Video"), PICK_VIDEO)
}
However, not only does this not work on some devices, startActivityForResult
is also deprecated.
The result is processed like this:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
//...
val dataSource = PathUtils.getPath(this, data!!.data) /* Code for getPath further blow */
dataSource
is the file path in form of a string and passed to the next activity, where the error occurs:
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
Log.d("VIDEOPLAYER", "SURFACE")
mediaPlayer.setSurface(Surface(surface))
mediaPlayer.isLooping = true
mediaPlayer.setDataSource(fullFilePath) // line 428
Sidenote about the error: The error occurred on e.g. a Samsung Galaxy S9, but sadly at a somewhat unrelated part of the code, where I basically try to load the chosen video and it crashes because I am not allowed to. Can't reproduce the error on my device, so I have yet to debug it properly, but from what it looks like, it's a permission issue. But as I said, it works on my Pixel 4a, but not on a Samsung Galaxy S9, which is odd.
Screenshot from Firebase:
Full error:
libcore.io.Linux.open (Linux.java)
libcore.io.ForwardingOs.open (ForwardingOs.java:167)
libcore.io.BlockGuardOs.open (BlockGuardOs.java:252)
libcore.io.ForwardingOs.open (ForwardingOs.java:167)
android.app.ActivityThread$AndroidOs.open (ActivityThread.java:8044)
libcore.io.IoBridge.open (IoBridge.java:482)
java.io.FileInputStream.<init> (FileInputStream.java:159)
android.media.MediaPlayer.setDataSource (MediaPlayer.java:1268)
android.media.MediaPlayer.setDataSource (MediaPlayer.java:1239)
android.media.MediaPlayer.setDataSource (MediaPlayer.java:1204)
com.mycompany.app.camera.VideoEditActivity.onSurfaceTextureAvailable (VideoEditActivity.kt:428)
android.view.TextureView.getTextureLayer (TextureView.java:400)
android.view.TextureView.draw (TextureView.java:349)
android.view.View.updateDisplayListIfDirty (View.java:22062)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw (ConstraintLayout.java:1994)
android.view.View.draw (View.java:23190)
android.view.View.updateDisplayListIfDirty (View.java:22062)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
androidx.constraintlayout.widget.ConstraintLayout.dispatchDraw (ConstraintLayout.java:1994)
android.view.View.draw (View.java:23190)
android.view.View.updateDisplayListIfDirty (View.java:22062)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
android.view.View.updateDisplayListIfDirty (View.java:22048)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
android.view.View.updateDisplayListIfDirty (View.java:22048)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
android.view.View.updateDisplayListIfDirty (View.java:22048)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
android.view.View.updateDisplayListIfDirty (View.java:22048)
android.view.View.draw (View.java:22917)
android.view.ViewGroup.drawChild (ViewGroup.java:5230)
android.view.ViewGroup.dispatchDraw (ViewGroup.java:4987)
android.view.View.draw (View.java:23190)
com.android.internal.policy.DecorView.draw (DecorView.java:1154)
android.view.View.updateDisplayListIfDirty (View.java:22062)
android.view.ThreadedRenderer.updateViewTreeDisplayList (ThreadedRenderer.java:588)
android.view.ThreadedRenderer.updateRootDisplayList (ThreadedRenderer.java:594)
android.view.ThreadedRenderer.draw (ThreadedRenderer.java:667)
android.view.ViewRootImpl.draw (ViewRootImpl.java:4293)
android.view.ViewRootImpl.performDraw (ViewRootImpl.java:4077)
android.view.ViewRootImpl.performTraversals (ViewRootImpl.java:3345)
android.view.ViewRootImpl.doTraversal (ViewRootImpl.java:2222)
android.view.ViewRootImpl$TraversalRunnable.run (ViewRootImpl.java:9123)
android.view.Choreographer$CallbackRecord.run (Choreographer.java:999)
android.view.Choreographer.doCallbacks (Choreographer.java:797)
android.view.Choreographer.doFrame (Choreographer.java:732)
android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:984)
android.os.Handler.handleCallback (Handler.java:883)
android.os.Handler.dispatchMessage (Handler.java:100)
android.os.Looper.loop (Looper.java:237)
android.app.ActivityThread.main (ActivityThread.java:8167)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:496)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1100)
So, my question is: is there a "right" way to let a person pick a video or image and get the file on all devices, without any permission issues.
I looked at some documentations:
https://developer.android.com/reference/android/database/Cursor
https://developer.android.com/reference/androidx/loader/content/CursorLoader
https://developer.android.com/reference/android/provider/MediaStore.Images.Media.html
https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts
And some more.
In addition to that, I am using code that I took from other SO questions, which I can't find at the moment, like the one above to let the user pick an image/video and this one I generically use and which I just copy-pasted to get the file in the format I need it:
public class PathUtils {
public static String getPath(final Context context, final Uri uri)
{
// DocumentProvider
if (DocumentsContract.isDocumentUri(context, uri)) {
if (isDownloadsDocument(uri)) {// DownloadsProvider
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.parseLong(id)
);
return getDataColumn(context, contentUri, null, null);
} else if (isMediaDocument(uri)) {// MediaProvider
final String docId = DocumentsContract.getDocumentId(uri);
final String [] split = docId . split (":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String [] selectionArgs = new String[]{
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {// MediaStore (and general)
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
} else if ("file".equalsIgnoreCase(uri.getScheme())) {// File
return uri.getPath();
}
return null;
}
public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs)
{
Cursor cursor = null;
final String column = "_data";
final String [] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
public static boolean isDownloadsDocument(Uri uri)
{
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
public static boolean isMediaDocument(Uri uri)
{
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
public static boolean isGooglePhotosUri(Uri uri)
{
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
}
Which I quite honestly don't fully understand.
Please note: I am not looking for "opinions" about what is better or worse. I am looking for specific flaws in my approach and a reliable and correct way of doing this, that is supported with good arguments, references to documentations and official statements.