https://kotlinlang.org logo
#compose
Title
# compose
l

Luka

03/02/2022, 2:12 PM
Hi. I have 2 screens with videoview inside and i make transition between them using accompanist-navigation-animation lib. Transition between 2 compose “screens” is slow, i think it might be related to how i start videos, but i cant figure it out. Also when transition from first to second screen is made recomposition is run many times. Can somebody with more knowledge in compose please take a look how i handle video playback please. Thank you
🧵 1
f

Filip Wiesner

03/02/2022, 2:14 PM
Could you move the huge code samples to a thread? (as said here)
👍 1
l

Luka

03/02/2022, 2:18 PM
Copy code
@ExperimentalAnimationApi
@Composable
fun Navigation() {
    val navController = rememberAnimatedNavController()
    val activity = (LocalContext.current as? Activity)

    AnimatedNavHost(
        navController = navController,
        startDestination = "promoScreen1"
    ) {
        composable("promoScreen1",
            exitTransition = {
                slideOutOfContainer(
                    AnimatedContentScope.SlideDirection.Left,
                    animationSpec = tween(1000)
                )
            }
        ) {
            val repeatsPromoViewModel: RepeatsPromoViewModel = viewModel()
            RepeatsPromoScreen(
                repeatsPromoViewModel = viewModel(),
                navController = navController,
                videoFileName = "repeat_promo1",
                title = stringResource(id = R.string.repeats_promo_title1),
                subtitle = stringResource(id = R.string.repeats_promo_subtitle),
                description = stringResource(id = R.string.repeats_promo_description),
                bottomButtonText = stringResource(id = R.string.next).uppercase(),
                onBottomButtonClicked = {
                    repeatsPromoViewModel._overlayVisible.value = true
                    repeatsPromoViewModel.isPlaying.value = false
                    navController.navigate("promoScreen2")
                }
            )
        }
        composable("promoScreen2", enterTransition = {
            slideIntoContainer(
                AnimatedContentScope.SlideDirection.Left,
                animationSpec = tween(1000)
            )
        }) {
            val repeatsPromoViewModel: RepeatsPromoViewModel = viewModel()

            BackHandler(true) {} //disable back button
            RepeatsPromoScreen(
                repeatsPromoViewModel = repeatsPromoViewModel,
                navController = navController,
                videoFileName = "repeat_promo2",
                title = stringResource(id = R.string.repeats_promo_title2),
                subtitle = stringResource(id = R.string.repeats_promo_subtitle2),
                description = stringResource(id = R.string.repeats_promo_description2),
                bottomButtonText = stringResource(id = R.string.planning_tutorial_finish).uppercase()
            ) { activity?.finish() }
        }
    }
}

@ExperimentalAnimationApi
@Composable
fun RepeatsPromoScreen(
    repeatsPromoViewModel: RepeatsPromoViewModel,
    navController: NavController,
    videoFileName: String,
    title: String,
    subtitle: String,
    description: String,
    bottomButtonText: String,
    onBottomButtonClicked: () -> Unit
) {
    val activity = (LocalContext.current as? Activity)
    val overlayVisible by repeatsPromoViewModel.overlayVisible

    val configuration = LocalConfiguration.current
    val isSmallScreen = configuration.screenHeightDp < 720 || configuration.screenWidthDp <= 360

    var modifier = Modifier
        .fillMaxSize()
        .background(color = Color(0xFFEBF4F9))

    if (isSmallScreen) {
        modifier =
            modifier.then(Modifier.verticalScroll(rememberScrollState())) //enable scrolling on small screens
    }

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = modifier
    ) {

        Box(
            modifier = Modifier
                .fillMaxWidth()
                .align(Alignment.Start)
        ) {
            IconButton(
                onClick = { activity?.finish() }
            ) {
                Icon(
                    Icons.Filled.Close,
                    contentDescription = "Close screen"
                )
            }
        }

        Text(
            text = title,
            fontSize = 28.sp,
            lineHeight = 22.sp,
            textAlign = TextAlign.Center,
            fontFamily = FontFamily(Font(R.font.source_serif_pro_black)),
            modifier = Modifier.padding(top = 24.dp),
            color = colorResource(id = R.color.toshl_profile_name)
        )
        Text(
            text = subtitle,
            fontSize = 14.sp,
            lineHeight = 17.sp,
            textAlign = TextAlign.Center,
            modifier = Modifier.padding(top = 10.dp, start = 50.dp, end = 50.dp),
            color = colorResource(id = R.color.toshl_sync_subtitle)
        )

        val rawId =
            activity?.resources?.getIdentifier(videoFileName, "raw", activity.packageName)
        val path = "android.resource://" + activity?.packageName + "/" + rawId

        val retriever = MediaMetadataRetriever()
        retriever.setDataSource(activity, Uri.parse(path))
        val frame = retriever.frameAtTime

        val videoWidth = frame?.width?.toFloat() ?: 0.toFloat()
        val videoHeight = frame?.height?.toFloat() ?: 0.toFloat()

        val ratio = configuration.screenWidthDp / UiHelper.convertPxToDp(
            activity!!.applicationContext,
            videoWidth
        )

        val videoView = remember(activity) {
            VideoView(activity).apply {
                setVideoPath(path)
                setOnPreparedListener { mp: MediaPlayer ->
                    mp.setOnInfoListener(MediaPlayer.OnInfoListener { mp, what, _ ->
                        if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
                            repeatsPromoViewModel._overlayVisible.value = false
                            return@OnInfoListener true
                        }
                        false
                    })

                    mp.start()
                    mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING)
                    mp.isLooping = true
                }
            }
        }

        val videoContainerHeight =
            UiHelper.convertPxToDp(activity.applicationContext, videoHeight) * ratio
        Box() {

            AndroidView(
                modifier = Modifier
                    .padding(top = 25.dp, start = 0.dp, end = 0.dp)
                    .height(videoContainerHeight.dp),
                factory = { context ->
                    videoView
                }
            )
            //overlay
            this@Column.AnimatedVisibility(visible = overlayVisible) {
                Box(
                    modifier = Modifier
                        .padding(top = 25.dp, start = 0.dp, end = 0.dp)
                        .height(videoContainerHeight.dp)
                        .background(Color(0xFFEBF4F9))
                        .clip(RectangleShape)
                        .fillMaxSize()
                )
            }
        }

        Text(
            text = description,
            fontSize = 14.sp,
            lineHeight = 17.sp,
            textAlign = TextAlign.Center,
            modifier = Modifier.padding(top = 30.dp, start = 50.dp, end = 50.dp),
            color = colorResource(id = R.color.toshl_sync_subtitle)
        )

        if (isSmallScreen) { //just add button below other elements
            PromoBottomButton(
                paddingTop = 32.dp,
                paddingBottom = 16.dp,
                onFinishClicked = { },
                text = stringResource(id = R.string.next).uppercase()
            )
        } else { //pin button to bottom of screen (that's why its wrapped inside another column)
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .align(Alignment.CenterHorizontally),
                verticalArrangement = Arrangement.Bottom
            ) {
                PromoBottomButton(
                    paddingTop = 16.dp,
                    paddingBottom = 32.dp,
                    onFinishClicked = { onBottomButtonClicked.invoke() },
                    text = bottomButtonText
                )
            }
        }
    }

}
🙏 1
f

Filip Wiesner

03/02/2022, 2:22 PM
I haven't looked through your code but I saw you mentioned that it is "slow". Have you tested it with production build (
debug
false and
minify
true)? This may speed things up significantly
Basically testing responsiveness of Compose UI on debug build does not say much because Compose relies heavily on a lot of optimizations (compile & run time) that can't be leveraged in debug build. But I don't know much about this subject to give you proper explanation
l

Luka

03/02/2022, 4:19 PM
Will try that, but i think i am missing something else, because i get really low framerate on e.g. pixel 6 when going from one compose screen to another.
3 Views