Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin coroutines return String

I am trying to convert a method from java to kotlin and replace AsyncTask with coroutines, but I do not know how to return value from coroutines

This is my method

override fun getCompressedVideo(context:Context ,video: Uri) {

        GlobalScope.launch(Dispatchers.Main) {

            val inputFile = video.getRealPathFromVideoUri(context)
            val loadJNI: LoadJNI = LoadJNI();
            try {

                val workFolder: String = context.filesDir.absolutePath

                val outputFile: String = getFileFullName(
                    FilesConstants.VIDEO_FOLDER,
                    String.format(FilesConstants.VIDEO_NAME_FILE_FORMAT, System.currentTimeMillis())
                );

                val complexCommand = arrayOf (
                    "ffmpeg", "-y"
                    , "-i", inputFile
                    , "-strict", "experimental"
                    , "-s", "320x240"
                    , "-r", "25"
                    , "-aspect", "4:3"
                    , "-ab", "48000"
                    , "-ac", "2"
                    , "-vcodec", "mpeg4"
                    , "-movflags", "+faststart"
                    , "-ar", "22050"
                    , "-b", "2097k"
                    , outputFile);

                loadJNI.run(complexCommand, workFolder, context);
                return outputFile

            } catch (th: Throwable) {
                return@launch
            }
        }
    }

the line of return outputFile makes compilation error, can anyone please help, it is my first time to use coroutines

EDIT

here is the method after using suspend, but now I do not know how I can return value if any problem happen

override suspend fun getCompressedVideo(context: Context, video: Uri) {

        val outputFile = withContext(Dispatchers.IO) {

            val inputFile = video.getRealPathFromVideoUri(context)
            val loadJNI: LoadJNI = LoadJNI();
            try {

                val workFolder: String = context.filesDir.absolutePath

                val outputFile: String = getFileFullName(
                    FilesConstants.VIDEO_FOLDER,
                    String.format(FilesConstants.VIDEO_NAME_FILE_FORMAT, System.currentTimeMillis())
                );

                val complexCommand = arrayOf(
                    "ffmpeg", "-y"
                    , "-i", inputFile
                    , "-strict", "experimental"
                    , "-s", "320x240"
                    , "-r", "25"
                    , "-aspect", "4:3"
                    , "-ab", "48000"
                    , "-ac", "2"
                    , "-vcodec", "mpeg4"
                    , "-movflags", "+faststart"
                    , "-ar", "22050"
                    , "-b", "2097k"
                    , outputFile
                );

                loadJNI.run(complexCommand, workFolder, context)
            }catch (th: Throwable) {
            }
        }
    }

EDIT 2

you mean like this

override suspend fun getCompressedVideo(context: Context, video: Uri) : String {

        try {
            val retValue = withContext(Dispatchers.IO) {

                val inputFile = video.getRealPathFromVideoUri(context)
                val loadJNI: LoadJNI = LoadJNI()

                val workFolder: String = context.filesDir.absolutePath

                val outputFile: String = getFileFullName(
                    FilesConstants.VIDEO_FOLDER,
                    String.format(FilesConstants.VIDEO_NAME_FILE_FORMAT, System.currentTimeMillis())
                )

                val complexCommand = arrayOf(
                    "ffmpeg", "-y"
                    , "-i", inputFile
                    , "-strict", "experimental"
                    , "-s", "320x240"
                    , "-r", "25"
                    , "-aspect", "4:3"
                    , "-ab", "48000"
                    , "-ac", "2"
                    , "-vcodec", "mpeg4"
                    , "-movflags", "+faststart"
                    , "-ar", "22050"
                    , "-b", "2097k"
                    , outputFile
                )

                loadJNI.run(complexCommand, workFolder, context)
            }

            return retValue.toString()
        } catch (th: Throwable) {
            return ""
        }
    }

and call it like

GlobalScope.launch {
            val retValue = ffmpegFacade.getCompressedVideo(this@TestActivity, Uri.parse(""))
        }
like image 226
Amira Elsayed Ismail Avatar asked Dec 10 '18 15:12

Amira Elsayed Ismail


3 Answers

If you expect this function

override fun getCompressedVideo(context: Context, video: Uri)

to return when the compression is already done, this is not how it can work. Your code launches a concurrent task that will complete at some arbitrary time after your getCompressedVideo has returned.

Instead, I think you should approach it as follows:

override suspend fun getCompressedVideo(
        context: Context, video: Uri
): String? = withContext(Dispatchers.IO) {
    try {
        val inputFile = video.getRealPathFromVideoUri(context)
        val loadJNI = LoadJNI()
        val workFolder: String = context.filesDir.absolutePath
        val outputFile: String = getFileFullName(
                FilesConstants.VIDEO_FOLDER,
                String.format(FilesConstants.VIDEO_NAME_FILE_FORMAT,
                        System.currentTimeMillis())
        )
        val complexCommand = arrayOf("-i", inputFile, "other-params")
        loadJNI.run(complexCommand, workFolder, context);
        outputFile
    } catch (t: Throwable) {
        null
    }
}

As you can see, this means changing the declaration of getCompressedVideo to a suspend fun. You can't call it directly from an Android callback. So, at the call site, write

this.launch {
    val videoFile = ffmpegfacade.getCompressedVideo(context, Uri.parse("example.org/video"))
    // continue processing on the UI thread using videoFile
}

Here take note that we call launch with this as the receiver. The receiver of launch must be a CoroutineScope and you should implement it in your MainActivity or whatever is the context you're calling it from. See structured concurrency for an explanation.

like image 114
Marko Topolnik Avatar answered Sep 24 '22 23:09

Marko Topolnik


One of the possible ways to solve it is to use GlobalScope.async builder:

fun getCompressedVideo() = GlobalScope.async {
    val outputFile: String = "" 

    // ... compress video

    outputFile
}

// Calling getCompressedVideo() from outside
fun compressVideoAsync() {
    GlobalScope.launch(Dispatchers.Main) {
        val compression = getCompressedVideo()
        val outputFile = compression.await() // wait for result of compression operation without blocking the main thread

        // outputFile is ready to use
    }
}
like image 31
Sergey Avatar answered Sep 24 '22 23:09

Sergey


You can specify a Kotlin function return type like so:

override fun getCompressedVideo(context: Context, video: Uri): String {

However you still can't because it's asynchronous, a function is synchronous.

To return from the method it would have to wait until it is complete, which goes against the whole purpose of doing it asynchronously.

You can instead use a high-order function to specify what to do with the data once the async task has been completed.

override fun getCompressedVideo(context:Context ,video: Uri, action: (String?) -> Unit) {

    GlobalScope.launch(Dispatchers.Main) {

        val inputFile = video.getRealPathFromVideoUri(context)
        val loadJNI: LoadJNI = LoadJNI();
        try {

            val workFolder: String = context.filesDir.absolutePath

            val outputFile: String = getFileFullName(
                FilesConstants.VIDEO_FOLDER,
                String.format(FilesConstants.VIDEO_NAME_FILE_FORMAT, System.currentTimeMillis())
            );

            val complexCommand = arrayOf (
                "ffmpeg", "-y"
                , "-i", inputFile
                , "-strict", "experimental"
                , "-s", "320x240"
                , "-r", "25"
                , "-aspect", "4:3"
                , "-ab", "48000"
                , "-ac", "2"
                , "-vcodec", "mpeg4"
                , "-movflags", "+faststart"
                , "-ar", "22050"
                , "-b", "2097k"
                , outputFile);

            loadJNI.run(complexCommand, workFolder, context);
            action(outputFile)

        } catch (th: Throwable) {
            action(null)
        }
    }
}
like image 20
Greg Avatar answered Sep 21 '22 23:09

Greg