thebluepandabear
thebluepandabear

Reputation: 571

Wait for result from Coroutine and then use it in Composable function

I am creating a video scraper, and it has the following function which scrapes the video source from a URL that has been given as the parameter:

fun scrapeVideoSrcFromUrl(url: String): String? {
    val document = Jsoup.connect(url).get()

    for (element in document.getElementsByTag("script")) {
        if (element.attr("type") == "application/ld+json") {
            val content = element.data()
            val array = JsonParser.parseString(content).asJsonArray

            val embedUrl = Gson().fromJson(array.get(0).asJsonObject.get("embedUrl"), String::class.java)
            var embedId = ""

            for (char in embedUrl.dropLast(1).reversed()) {
                if (char != '/') {
                    embedId += char
                } else {
                    break
                }
            }

            val doc = Jsoup.connect("$RUMBLE_API_URL${embedId.reversed()}").ignoreContentType(true).get()
            val jsonData = doc.getElementsByTag("body").first()?.text()

            val mp4 = JsonParser.parseString(jsonData).asJsonObject.get("u").asJsonObject.get("mp4").asJsonObject.get("url").toString()

            return mp4.replace("\"", "")
        }
    }

    return null
}

I want to show this in a dialog for a certain link using ExoPlayer, so I did the following:

@Composable
fun VideoPlayer(videoSrc: String) {
    val context = LocalContext.current

    val exoPlayer = remember {
        ExoPlayer.Builder(context).build().apply {
            setMediaItem(
                MediaItem.fromUri(
                    videoSrc
                )
            )
            prepare()
            playWhenReady = true
        }
    }

    Box(modifier = Modifier.fillMaxSize()) {
        DisposableEffect(key1 = Unit) {
            onDispose {
                exoPlayer.release()
            }
        }

        AndroidView(
            factory = {
                StyledPlayerView(context).apply {
                    player = exoPlayer
                    layoutParams =
                        FrameLayout.LayoutParams(
                            ViewGroup.LayoutParams.MATCH_PARENT,
                            ViewGroup.LayoutParams.MATCH_PARENT
                        )
                }
            }
        )
    }
}

Then, in the main Composable:

if (openDialog) {
        AlertDialog(
            onDismissRequest = {
                openDialog = false
            },
            title = {
                Column {
                    Text(
                        text = viewModel.currentRumbleSearchResult?.title ?: ""
                    )
                    Spacer(
                        Modifier.height(8.dp)
                    )
                    Text(
                        text = "By ${viewModel.currentRumbleSearchResult?.channel?.name ?: ""}",
                        style = MaterialTheme.typography.titleSmall
                    )
                }
            },
            text = {
                        VideoPlayer(RumbleScraper.create().scrapeVideoSrcFromUrl("https://rumble.com/v1m9oki-our-first-automatic-afk-farms-locals-minecraft-server-smp-ep3-live-stream.html")!!)
            
            },
            confirmButton = {
                TextButton(
                    onClick = {
                        openDialog = false
                    }
                ) {
                    Text("Exit")
                }
            }
        )
    }

After running that code I keep getting NetworkOnMainThread exceptions, and I tried many things to fix it but nothing worked.

So I am unsure what to do as to how I can go around fixing this. I was wondering how I would go around waiting in the background for a result and then show it in the Compose function when it returns the value?

Upvotes: 0

Views: 2621

Answers (1)

Jan Bína
Jan Bína

Reputation: 7228

You can do something like this:

var videoSrc by remember { mutableStateOf<String?>(null) }

LaunchedEffect(Unit) {
    withContext(Dispatchers.IO) {
        videoSrc = RumbleScraper.create().scrapeVideoSrcFromUrl("")
    }
}

text = { VideoPlayer(videoSrc) }

You can also call the scrapeVideoSrcFromUrl inside your viewModel and update some state that you will use in UI.


If you want to run it in response to some event like item click, you will be better of with something like this:

val scope = rememberCoroutineScope()

Button(
    onClick = {
        scope.launch {
            withContext(Dispatchers.IO) { ... }
        }
    }
)

Upvotes: 3

Related Questions