How can I trim a video from Uri, including files that `mp4parser` library can handle, but using Android's framework instead?

How can I trim a video from Uri, including files that `mp4parser` library can handle, but using Android's framework instead?

Over the past few days, I've worked on making a customizable, more updated version of a library for video trimming,&nbsp;<a href="https://github.com/AndroidDeveloperLB/VideoTrimmer/" target="_blank"><strong>here</strong></a>&nbsp;(based on&nbsp;<a href="https://github.com/titansgroup/k4l-video-trimmer" target="_blank"><strong>this library</strong></a>)

Background

Over the past few days, I've worked on making a customizable, more updated version of a library for video trimming, here (based on this library)

The problem

While for the most part, I've succeeded making it customizable and even converted all files into Kotlin, it had a major issue with the trimming itself.

It assumes the input is always a File, so if the user chooses an item from the apps chooser that returns a Uri, it crashes. The reason for this is not just the UI itself, but also because a library that it uses for trimming (mp4parser) assumes an input of only File (or filepath) and not a Uri (wrote about it here). I tried multiple ways to let it get a Uri instead, but failed. Also wrote about it here.

That's why I used a solution that I've found on StackOverflow (here)for the trimming itself. The good thing about it is that it's quiet short and uses just Android's framework itself. However, it seems that for some video files, it always fails to trim them. As an example of such files, there is one on the original library repository, here (issue reported here).

Looking at the exception, this is what I got:

E: Unsupported mime 'audio/ac3'
E: FATAL EXCEPTION: pool-1-thread-1
    Process: life.knowledge4.videocroppersample, PID: 26274
    java.lang.IllegalStateException: Failed to add the track to the muxer
        at android.media.MediaMuxer.nativeAddTrack(Native Method)
        at android.media.MediaMuxer.addTrack(MediaMuxer.java:626)
        at life.knowledge4.videotrimmer.utils.TrimVideoUtils.genVideoUsingMuxer(TrimVideoUtils.kt:77)
        at life.knowledge4.videotrimmer.utils.TrimVideoUtils.genVideoUsingMp4Parser(TrimVideoUtils.kt:144)
        at life.knowledge4.videotrimmer.utils.TrimVideoUtils.startTrim(TrimVideoUtils.kt:47)
        at life.knowledge4.videotrimmer.BaseVideoTrimmerView$initiateTrimming$1.execute(BaseVideoTrimmerView.kt:220)
        at life.knowledge4.videotrimmer.utils.BackgroundExecutor$Task.run(BackgroundExecutor.java:210)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:458)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)

What I've found

  1. Reported about the issue here. I don't think it will get an answer, as the library hasn't updated in years...
  2. Looking at the exception, I tried to also trim without sound. This works, but it's not a good thing, because we want to trim normally.
  3. Thinking that this code might be based on someone else's code, I tried to find the original one. I've found that it is based on some old Google code on its gallery app, here, in a class called "VideoUtils.java" in package of "Gallery3d". Sadly, I don't see any new version for it. Latest one that I see is of Gingerbread, here.

The code that I've made out of it looks as such:

object TrimVideoUtils {
    private const val DEFAULT_BUFFER_SIZE = 1024 * 1024

@JvmStatic
@WorkerThread
fun startTrim(context: Context, src: Uri, dst: File, startMs: Long, endMs: Long, callback: VideoTrimmingListener) {
    dst.parentFile.mkdirs()
    //Log.d(TAG, "Generated file path " + filePath);
    val succeeded = genVideoUsingMuxer(context, src, dst.absolutePath, startMs, endMs, true, true)
    Handler(Looper.getMainLooper()).post { callback.onFinishedTrimming(if (succeeded) Uri.parse(dst.toString()) else null) }
}

//https://stackoverflow.com/a/44653626/878126 https://android.googlesource.com/platform/packages/apps/Gallery2/+/634248d/src/com/android/gallery3d/app/VideoUtils.java
@JvmStatic
@WorkerThread
private fun genVideoUsingMuxer(context: Context, uri: Uri, dstPath: String, startMs: Long, endMs: Long, useAudio: Boolean, useVideo: Boolean): Boolean {
    // Set up MediaExtractor to read from the source.
    val extractor = MediaExtractor()
    //       val isRawResId=uri.scheme == "android.resource" &amp;&amp; uri.host == context.packageName &amp;&amp; !uri.pathSegments.isNullOrEmpty())
    val fileDescriptor = context.contentResolver.openFileDescriptor(uri, "r")!!.fileDescriptor
    extractor.setDataSource(fileDescriptor)
    val trackCount = extractor.trackCount
    // Set up MediaMuxer for the destination.
    val muxer = MediaMuxer(dstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
    // Set up the tracks and retrieve the max buffer size for selected tracks.
    val indexMap = SparseIntArray(trackCount)
    var bufferSize = -1
    try {
        for (i in 0 until trackCount) {
            val format = extractor.getTrackFormat(i)
            val mime = format.getString(MediaFormat.KEY_MIME)
            var selectCurrentTrack = false
            if (mime.startsWith("audio/") &amp;&amp; useAudio) {
                selectCurrentTrack = true
            } else if (mime.startsWith("video/") &amp;&amp; useVideo) {
                selectCurrentTrack = true
            }
            if (selectCurrentTrack) {
                extractor.selectTrack(i)
                val dstIndex = muxer.addTrack(format)
                indexMap.put(i, dstIndex)
                if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
                    val newSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
                    bufferSize = if (newSize &gt; bufferSize) newSize else bufferSize
                }
            }
        }
        if (bufferSize &lt; 0)
            bufferSize = DEFAULT_BUFFER_SIZE
        // Set up the orientation and starting time for extractor.
        val retrieverSrc = MediaMetadataRetriever()
        retrieverSrc.setDataSource(fileDescriptor)
        val degreesString = retrieverSrc.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)
        if (degreesString != null) {
            val degrees = Integer.parseInt(degreesString)
            if (degrees &gt;= 0)
                muxer.setOrientationHint(degrees)
        }
        if (startMs &gt; 0)
            extractor.seekTo(startMs * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC)
        // Copy the samples from MediaExtractor to MediaMuxer. We will loop
        // for copying each sample and stop when we get to the end of the source
        // file or exceed the end time of the trimming.
        val offset = 0
        var trackIndex: Int
        val dstBuf = ByteBuffer.allocate(bufferSize)
        val bufferInfo = MediaCodec.BufferInfo()

// try { muxer.start() while (true) { bufferInfo.offset = offset bufferInfo.size = extractor.readSampleData(dstBuf, offset) if (bufferInfo.size < 0) { //InstabugSDKLogger.d(TAG, "Saw input EOS."); bufferInfo.size = 0 break } else { bufferInfo.presentationTimeUs = extractor.sampleTime if (endMs > 0 && bufferInfo.presentationTimeUs > endMs * 1000) { //InstabugSDKLogger.d(TAG, "The current sample is over the trim end time."); break } else { bufferInfo.flags = extractor.sampleFlags trackIndex = extractor.sampleTrackIndex muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo) extractor.advance() } } } muxer.stop() return true // } catch (e: IllegalStateException) { // Swallow the exception due to malformed source. //InstabugSDKLogger.w(TAG, "The source video file is malformed"); } catch (e: Exception) { e.printStackTrace() } finally { muxer.release() } return false }

}

The exception is thrown on val dstIndex = muxer.addTrack(format) . For now, I've wrapped it in try-catch, to avoid a real crash.

I tried to search for newer versions of this code (assuming that it got fixed later), but failed.

  1. Searching on the Internet and here, I've found only one similar question, here, but it's not the same at all.

The questions

  1. Is it possible to use Android's framework to trim such problematic files? Maybe there is a newer version of the trimming of the videos code? I'm interested of course only for the pure implementation of video trimming, like the function I wrote above, of "genVideoUsingMuxer" .
  2. As a temporary solution, is it possible to detect problematic input videos, so that I won't let the user start to trim them, as I know they will fail?
  3. Is there maybe another alternative to both of those, that have a permissive license and doesn't bloat the app? For mp4parser, I wrote a separate question, here.


Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

Brave, Chrome, Firefox, Opera or Edge: Which is Better and Faster?

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Android : Show quick preview (live) before exporting FFmpeg Video

Basically I am developing a video editing app that lets user choose some of their images and create video slide show with music.

Android App Development Company in UAE

Have a plan to build an robust Android App Development with multiple features? We at Data EximIT is one of the best **[Android App Development Company in UAE](https://www.dataeximit.com/android-application-development/ "Android App Development...

Kotlin Coroutines on Android - How to use Coroutines on Android

Coroutines are a Kotlin feature that convert async callbacks for long-running tasks, such as database or network access, into sequential code. This Kotlin Coroutines tutorial will show you how to use coroutines on Android, and how the new androidx-concurrent library makes it easy to use them to get things off the main thread. You'll also learn how the new library helps coroutines work with Architecture Components. This session also covers coroutine patterns, best practices, and even how to test coroutines!