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.

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

I am using FFMPEG to generate video slide show from images but the problem is I can only show preview of the video after executing FFMPEG command.

Is googled so many blogs and all and I know there is a possible way available to display preview of the output.

Check below image from reference app. I am developing something similar to this app. There is an option to replace the image in video. This app displays instant preview once I change the image.

Here is the link of the reference app if anyone wants to check:

Clicking an EXPORT button this app lets you generate video output. I can do that as I know FFMPEGand how to generate OUTPUT FILE using FFMPEG but what I don't know is how to display quick preview of OUTPUT VIDEO before generating actual VIDEO FILE.

On my UBUNTU device I can view output of FFMPEG command using FFPLAY tool but how to do the same on android device.

Some useful link: http://androidwarzone.blogspot.com/2011/12/ffmpeg4android.html

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.


My Android app not supporting Android version 9

I have made an android app which uses webView. Everything was fine until I tried my app on Android P. On Android "P" the app is directly showing the error page that the app can't use Internet connection even though it is ON.

I have made an android app which uses webView. Everything was fine until I tried my app on Android P. On Android "P" the app is directly showing the error page that the app can't use Internet connection even though it is ON.

Android Manifest File:

    <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xxx.xxxxx.xxxxx">
&lt;!-- android:roundIcon="@mipmap/ic_launcher_round" --&gt;

&lt;uses-permission android:name="android.permission.INTERNET" /&gt;
&lt;uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/&gt;
&lt;uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /&gt;
&lt;uses-permission android:name="android.permission.CAMERA" /&gt;

&lt;application
    android:allowBackup="true"
    android:icon="@drawable/icon"
    android:label="@string/app_name"
    android:hardwareAccelerated="true"
    android:supportsRtl="true"
    android:theme="@style/AppTheme"&gt;
    &lt;activity android:name="com.xxxxx.xxxxx.xxxxxx.MainActivity"&gt;
        &lt;intent-filter&gt;
            &lt;action android:name="android.intent.action.MAIN" /&gt;

            &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
        &lt;/intent-filter&gt;
    &lt;/activity&gt;
&lt;/application&gt;

</manifest>

Gradle File:

    apply plugin: 'com.android.application'

android {
compileSdkVersion 28
defaultConfig {
applicationId "com.xxx.xxx.xxx"
minSdkVersion 19
targetSdkVersion 28
versionCode 3
versionName "3.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:design:28.0.0'
}

On Getting error in Android P, directly this section is being accessed:

public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
// You can redirect to your own page instead getting the default error page

            super.onReceivedError(view, errorCode, description, failingUrl);
            String path = Uri.parse("file:///android_asset/error.html").toString();
            webView.loadUrl("about:blank");
            view.loadUrl(path);
        }


Hire Dedicated Android Developers India | Offshore Android Programmer USA

Hire Android app developers with multi-business-domain expertise from award-winning Android mobile, tablet and wearable app Development Company of India and USA to develop an Android app from scratch or work in existing Android application.

Hire Android app developers with multi-business-domain expertise from award-winning Android mobile, tablet and wearable app Development Company of India and USA to develop an Android app from scratch or work in existing Android application.