2

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:

enter image description here

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.

halfer
  • 19,824
  • 17
  • 99
  • 186
Benjamin Basmaci
  • 2,247
  • 2
  • 25
  • 46
  • 1
    Start by [getting rid of `PathUtils`](https://stackoverflow.com/questions/49221312/android-get-real-path-of-a-txt-file-selected-from-the-file-explorer). Beyond that, `ACTION_GET_CONTENT` does not take `EXTRA_OUTPUT` as an extra. Your `ACTION_GET_CONTENT` code loads an image, but your error is about a video. Your question does not show how you are using the `Uri` that you get back, including whether you are using it in the same activity instance that you receive it. And, your stack trace is shown as an image, rather than text, and it is missing most of the lines. – CommonsWare Sep 17 '21 at 15:30
  • @CommonsWare Sorry and thank you for pointing that out. I corrected the code that I took from the wrong part of my project and added the missing code. – Benjamin Basmaci Sep 17 '21 at 16:21
  • Yeah, definitely get rid of `PathUtils`. That code has never been appropriate. And, it is unnecessary, as `MediaPlayer`, ExoPlayer, and image-loading libraries (e.g., Glide, Picasso) work fine with a `Uri`. – CommonsWare Sep 17 '21 at 16:28
  • `how to properly load multimedia content e.g. videos and images with the standard andriod "picker"/"galery", not using any third party library.` You can load nothing with the standard picker. Only let the user select something. – blackapps Sep 17 '21 at 19:24

0 Answers0