AbedElazizShe / LightCompressor

A powerful and easy-to-use video compression library for android uses MediaCodec API.
Apache License 2.0
535 stars 116 forks source link

Issue when I try to compress a video in Android API 33 #136

Closed EbelloImbox closed 1 year ago

EbelloImbox commented 2 years ago

Hello, I am having an issue when I try to compress a video in Android API 33, this is the logcat:

Process: com.spotbros.defense, PID: 6652
    java.io.FileNotFoundException: : open failed: ENOENT (No such file or directory)
        at libcore.io.IoBridge.open(IoBridge.java:574)
        at java.io.FileInputStream.<init>(FileInputStream.java:160)
        at com.abedelazizshe.lightcompressorlibrary.VideoCompressor.saveVideoInExternal(VideoCompressor.kt:255)
        at com.abedelazizshe.lightcompressorlibrary.VideoCompressor.saveVideoFile(VideoCompressor.kt:182)
        at com.abedelazizshe.lightcompressorlibrary.VideoCompressor.access$saveVideoFile(VideoCompressor.kt:26)
        at com.abedelazizshe.lightcompressorlibrary.VideoCompressor$doVideoCompression$1.invokeSuspend(VideoCompressor.kt:104)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at android.os.Handler.handleCallback(Handler.java:942)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7898)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
     Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory)
        at libcore.io.Linux.open(Native Method)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:563)
        at libcore.io.BlockGuardOs.open(BlockGuardOs.java:274)
        at libcore.io.ForwardingOs.open(ForwardingOs.java:563)
        at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7784)
        at libcore.io.IoBridge.open(IoBridge.java:560)
        at java.io.FileInputStream.<init>(FileInputStream.java:160) 
        at com.abedelazizshe.lightcompressorlibrary.VideoCompressor.saveVideoInExternal(VideoCompressor.kt:255) 
        at com.abedelazizshe.lightcompressorlibrary.VideoCompressor.saveVideoFile(VideoCompressor.kt:182) 
        at com.abedelazizshe.lightcompressorlibrary.VideoCompressor.access$saveVideoFile(VideoCompressor.kt:26) 
        at com.abedelazizshe.lightcompressorlibrary.VideoCompressor$doVideoCompression$1.invokeSuspend(VideoCompressor.kt:104) 
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) 
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) 
        at android.os.Handler.handleCallback(Handler.java:942) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loopOnce(Looper.java:201) 
        at android.os.Looper.loop(Looper.java:288) 
        at android.app.ActivityThread.main(ActivityThread.java:7898) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) 

I am using a video saved in standar video directory from Android Emulator, uri: /storage/emulated/0/Movies/VID_20220830_180410.mp4, and in this part of the library code:

private fun getMediaPath(context: Context, uri: Uri): String {

        val resolver = context.contentResolver
        val projection = arrayOf(MediaStore.Video.Media.DATA)
        var cursor: Cursor? = null
        try {
            cursor = resolver.query(uri, projection, null, null, null)
            return if (cursor != null) {
                val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
                cursor.moveToFirst()
                cursor.getString(columnIndex)

            } else ""

        } catch (e: Exception) {
            resolver.let {
                val filePath = (context.applicationInfo.dataDir + File.separator
                        + System.currentTimeMillis())
                val file = File(filePath)

                resolver.openInputStream(uri)?.use { inputStream ->
                    FileOutputStream(file).use { outputStream ->
                        val buf = ByteArray(4096)
                        var len: Int
                        while (inputStream.read(buf).also { len = it } > 0) outputStream.write(
                            buf,
                            0,
                            len
                        )
                    }
                }
                return file.absolutePath
            }
        } finally {
            cursor?.close()
        }
    }

When this part of the code is executing:

cursor = resolver.query(uri, projection, null, null, null)
            return if (cursor != null) {
                val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
                cursor.moveToFirst()
                cursor.getString(columnIndex)

            } else ""

cursor is always null, so it will return always "" and for this reason I am experimenting this error.

Any help?

EbelloImbox commented 2 years ago

I am watching that the problem is only happening in APi > 30 for this code:

if (Build.VERSION.SDK_INT >= 30) {
                if (!storageConfiguration.isExternal) {
                    return saveVideoInInternal(context, videoFileName, videoFile)
                } else {
                    return saveVideoInExternal(context, videoFileName, folderName, videoFile)
                }
            }

videoFile is always empty because of the cursor in the code above is null.

james04gr commented 2 years ago

I dealt with the same issue today using API 31!

EbelloImbox commented 2 years ago

the problem is that you are using an uri like mine and you have to use an uri like a fileprovider: content://com.google.android.apps.photos.contentprovider/-1/2/content%3A%2F%2Fmedia%2Fexternal%2Fvideo%2Fmedia%2F1000000095/ORIGINAL/NONE/video%2Fmp4/1108575362

I am trying to parse to this kind of uri, if you know how to do it tell me it please.

james04gr commented 2 years ago

the problem is that you are using an uri like mine and you have to use an uri like a fileprovider: content://com.google.android.apps.photos.contentprovider/-1/2/content%3A%2F%2Fmedia%2Fexternal%2Fvideo%2Fmedia%2F1000000095/ORIGINAL/NONE/video%2Fmp4/1108575362

I am trying to parse to this kind of uri, if you know how to do it tell me it please.

You are right. Now that i save my video to Enviroment.DIRECTORY_MOVIES i get a content:// uri. Temporarilly i will use this. But as soon as i figure it out (because this is not where i want to save my videos before compress) i will inform you!

EbelloImbox commented 2 years ago

The way to get this kind of URI is using this: FileProvider.getUriForFile(context, authority, file), but I am seeing to come back to version 1.0.1 because there are a lot of bugs in this new version in kotlin.

philblais commented 2 years ago

I'm also getting the same issue in API 33

Amberon-voldi commented 2 years ago

hey @EbelloImbox i've faced the same issue with this package and found the cause, with newer version of android their is scoped storage to access file outside of it's own directory. before API 30 we needed to add android:requestLegacyExternalStorage="true" flag to access file in external storage but After Android 11, Android just ignores android:requestLegacyExternalStorage="true" and android:requestLegacyExternalStorage="true"" flags and i assume this package is not yet compatible with scoped storage for flutter a workaround can be compressing the video in the internal directory then storing it in the external dir with other packages which does support scoped storage

escorcia21 commented 1 year ago

I am watching that the problem is only happening in APi > 30 for this code:

if (Build.VERSION.SDK_INT >= 30) {
                if (!storageConfiguration.isExternal) {
                    return saveVideoInInternal(context, videoFileName, videoFile)
                } else {
                    return saveVideoInExternal(context, videoFileName, folderName, videoFile)
                }
            }

videoFile is always empty because of the cursor in the code above is null.

Any solution?

Amberon-voldi commented 1 year ago

I am watching that the problem is only happening in APi > 30 for this code:

if (Build.VERSION.SDK_INT >= 30) {
                if (!storageConfiguration.isExternal) {
                    return saveVideoInInternal(context, videoFileName, videoFile)
                } else {
                    return saveVideoInExternal(context, videoFileName, folderName, videoFile)
                }
            }

videoFile is always empty because of the cursor in the code above is null.

Any solution?

a workaround can be compressing the file into the app's internal directory then use something like gallery_saver package to save the file in the external storage

escorcia21 commented 1 year ago

gallery_saver package is available in kotlin/java?

AbedElazizShe commented 1 year ago

Hi @EbelloImbox thank you for openning the issue and apolgies for the late reply. Was very busy the past 10 months.

This issue is happening due to permission issues as Google has been changing the behaviours a lot when it comes to privacy and media access.

I will release a new version later today with updated README. A new permission should be requested now for API >= 33;