1

In my android application i am downloading Facebook video and audio separately and merging it in an output file. The reason is Facebook videos URLs have no sound and audio URL is separate. I have tried using this mp4Parser for merging audio and video and it works pretty well in most of devices but having issues in Redmi/MI devices when playing from device files.

Steps i followed

  • Download NO SOUND Video in mp4 format.
  • Download audio in m4a format.
  • Merge both files and create an output file with mp4 format.
  • After merging is done deleted no sound video and temporary audio.

Below is my code

Dependencies

 implementation 'com.iceteck.silicompressorr:silicompressor:2.2.4'
    implementation('com.googlecode.mp4parser:isoparser:1.0.6') {
        exclude group: 'org.aspectj', module: 'aspectjrt'
    }

Merging code

  private fun mergeAudioAndVideo(
        id: Int,
        model: DatabaseDownloadItem?,
        file: File?,
        activityInstance: LocaleAwareCompatActivity?
    ) {
        val mergingDetail = sharedPreferencesManager.getItemInMergingList(id)
        if (mergingDetail != null) {
            val timeInMillis = System.currentTimeMillis()
            var list: ArrayList<DatabaseDownloadItem>? = sharedViewmodel.pendingMerge.value
            if (list == null) {
                list = arrayListOf()
            }
            list.add(model!!)
            sharedViewmodel.pendingMerge.postValue(list)
            sharedViewmodel.pendingMerge.value?.add(model!!)
            CoroutineScope(Dispatchers.Main).launch {
                var mergePendingItems: ArrayList<Int>? =
                    tinyDB?.getMergeList(AppConstants.MERGE_PENDING_DOWNLOADS)
                if (mergePendingItems == null) {
                    mergePendingItems = arrayListOf()
                }
                if (!mergePendingItems.contains(model.id)) {
                    mergePendingItems.add(model.id)
                }
                tinyDB?.putMergeList(AppConstants.MERGE_PENDING_DOWNLOADS, mergePendingItems)
                adapter.notifyDataSetChanged()
                CoroutineScope(Dispatchers.IO + exceptionHandler).async {
                    val audiopath = mergingDetail.audioPath
                    val videopath = mergingDetail.videoPath
                    /*
                    following comment is kept for reference
                    val audiopath = "/storage/emulated/0/VideoDownloader/tempAudio_1643715180488.m4a"
                    val videopath = "/storage/emulated/0/VideoDownloader/1643715180480.mp4"*/
                    val output: String
                    val outputName = "output_${mergingDetail.originalFileName}"
                    mergingDetail.outputFileName = outputName
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                        val file = File(
                            getDownloaderFolderFromDownloads(),
                            "$outputName.mp4"
                        )
                        output = file.absolutePath
                        mergingDetail.outputFile = file

                    } else {
                        val file = File(
                            Environment.getExternalStorageDirectory()
                                .toString() + "/VideoDownloader/",
                            "$outputName.mp4"
                        )
                        output = file.absolutePath
                        mergingDetail.outputFile = file

                    }
                    var videoOutput: Movie? = null
                    var audioOutput: Movie? = null
                    kotlin.runCatching {
                        videoOutput = MovieCreator.build(videopath)
                        audioOutput = MovieCreator.build(audiopath)
                    }
                    val finalTrack: MutableList<Track> = ArrayList()
                    for (track in videoOutput?.tracks!!) {
                        if (track.handler.equals("vide")) finalTrack.add(track)
                    }
                    for (track in audioOutput?.tracks!!) {
                        if (track.handler.equals("soun")) finalTrack.add(track)
                    }
                    videoOutput?.tracks = finalTrack
                    val mp4file: Container = DefaultMp4Builder().build(videoOutput)
                    kotlin.runCatching {
                        val fc: FileChannel = FileOutputStream(File(output)).getChannel()
                        mp4file.writeContainer(fc)
                        fc.close()
                    }.onFailure { error ->
                        val abc = error
                    }.onSuccess { someFuncReturnValue ->
                        val abc = "dss"
                    }

                }.await()
                Toast.makeText(activityInstance, "Audio Video Merging Done", Toast.LENGTH_SHORT)
                    .show()
                mergingDetail.isMergingDone = true
                sharedPreferencesManager.updateItemInMergingList(id, mergingDetail)
                executeDownloadCompletion(model, file, activityInstance)
                deleteTempAudioFile(File(mergingDetail.audioPath!!), activityInstance)
                deleteOldMutedVideo(File(mergingDetail.videoPath!!), activityInstance)
                renameOutputFileWithDBName(mergingDetail, activityInstance)
                var list: ArrayList<DatabaseDownloadItem>? = sharedViewmodel.pendingMerge.value
                if (list != null && list!!.size > 0) {
                    list!!.remove(model!!)
                    sharedViewmodel.pendingMerge.postValue(list)
                    sharedViewmodel.pendingMerge.value?.add(model!!)
                }
                notificationWork(model, file, activityInstance)
                sharedPreferencesManager.deleteItemInMergingList(id)

            }
        }
    }

This works fine in most of devices but in MI Devices (i.e Redmi S2, MUI Version MUI Global 12.0.2) i am having an issue while playing the output file its corrupted and cannot be played. The error which shows up is just with title unknown.

Please note that i have also tried the following combination of dependencies, but this was generating lags in output file so removed these two.

implementation 'org.mp4parser:isoparser:1.9.41'
     implementation 'org.mp4parser:muxer:1.9.41

Can somebody please suggest the proper usgae and dependency version of mp4parser for merging audio file and video file.

Any help will be appreciated.

Thank you

Nayab
  • 112
  • 14

1 Answers1

0

Use FFmpeg library and it will do all the job. You just have to provide a Facebook Audio and Video link and the storage path of your directory which you will get through File API. It will download the audio and video and then merge them.

This simple command will do all the magic.

val cmd = "-i $videoUrl -i  $audioUrl -c:v copy -c:a aac $storagePath"
FFmpeg.executeAsync(cmd, object: ExecuteCallback())
Nabeel Ahmed
  • 223
  • 5
  • 15
  • Did you faced increased apk size issue using this? As using FFmpeg added around 20mb to my original apk size. – Nayab Mar 02 '22 at 07:15
  • 1
    Yes, it increased apk size but it's worth using this library, as it does the job with few lines of code. And if you have any other solution for this, you can share as well. – Nabeel Ahmed Mar 02 '22 at 07:23
  • Its returning error code 1. tried adding file keyword before storage path but it keeps returning error code 1 – Nayab Mar 02 '22 at 07:54
  • It's because you are getting storagePath with MediaStore API. You have to get storagePath with File API. Set requestLegacyExternalStorage="true" and then create a folder and its file(mp3 or mp4) and then get it's absolutePath which you have to pass on that command. It's working on my android 11 and 10 devices as well. So don't worry about any scope storage restrictions. – Nabeel Ahmed Mar 02 '22 at 09:54