Search code examples
androidandroid-jetpack-composeexoplayer2.x

Jetpack compose, ExoPlayer is destroyed late when changing the screen


To create a video player in Jetpack compose, I use ExoPlayer2. The problem is this - there is screen 1 on which the video player is located and screen 2. When switching from screen 1 to screen 2, the video player screen is displayed on screen 2 for about a second and only then disappears.

Tell me what could be the reason?

@Composable
internal fun ShowAboutCorpScreen() {
    val state = localStateEventEffectModel.current.state

    Scaffold(
        topBar = {},
    ) { contentPadding ->
        Column(
            modifier = Modifier
                .padding(contentPadding)
                .fillMaxSize()
                .background(SynergyTheme.colors.bgPrimary)
                .verticalScroll(rememberScrollState()),
        ) {
            state.aboutCorpData?.sections?.forEach {
                SectionType.entries.find { a -> a.name == it.code.uppercase() }?.let { type ->
                    when (type) {
                        SectionType.DESCRIPTION -> BlockDescription(model = it)
                        else -> {}
                    }
                }
            }
        }
    }
}

@Composable
internal fun BlockDescription(model: SectionAboutModel) {
    Column(
        modifier = Modifier
            .padding(horizontal = SynergyTheme.dimensions.x4)
            .padding(top = SynergyTheme.dimensions.x6),
    ) {
        Text(
            text = model.title,
            style = SynergyTheme.typography.textSemibold.copy(
                fontSize = Constants.fontsize.f22,
                lineHeight = Constants.fontsize.f28,
            ),
            color = colorResource(id = UIStyleR.color.textPrimary),
        )

        VideoPlayer(model.video)

        Text(
            modifier = Modifier.padding(top = SynergyTheme.dimensions.x2_5),
            text = model.text,
            style = SynergyTheme.typography.text1Medium,
            lineHeight = Constants.fontsize.f22,
            color = colorResource(id = UIStyleR.color.textPrimary),
        )
    }
}

@Composable
private fun VideoPlayer(videoUrl: String) {
    val context = LocalContext.current
    var isPlaying by remember { mutableStateOf(false) }
    val videoUri = Uri.parse(videoUrl)

    val cacheDataSource = CacheDataSourceFactory().createCacheDataSource()

    val exoPlayer = remember {
        ExoPlayer.Builder(context).build().apply {
            val mediaSource = ProgressiveMediaSource.Factory(cacheDataSource)
                .createMediaSource(MediaItem.fromUri(videoUri))
            setMediaSource(mediaSource)
            playWhenReady = false
            prepare()
        }
    }

    LaunchedEffect(isPlaying) {
        if (isPlaying) {
            exoPlayer.seekToDefaultPosition()
            exoPlayer.play()
        } else {
            exoPlayer.pause()
        }
    }

    if (isPlaying) {
        Box(
            modifier = Modifier
                .height(Constants.dimensions.d220)
                .padding(vertical = SynergyTheme.dimensions.x2_5)
                .clip(SynergyTheme.shapes.medium),
        ) {
            ExoPlayerLifecycleOwner(exoPlayer)
        }
    } else {
        Box {
            ImageFromSource(
                iconSource = IconSource.FromResource(
                    resourceId = R.drawable.about_synergy,
                    contentDescription = null,
                ),
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(vertical = SynergyTheme.dimensions.x2_5)
                    .clip(SynergyTheme.shapes.medium),
                contentScale = ContentScale.FillWidth,
            )
            Image(
                modifier = Modifier
                    .size(Constants.dimensions.d56)
                    .align(Alignment.Center)
                    .clickable { isPlaying = true },
                painter = painterResource(id = R.drawable.about_play_video),
                contentDescription = null,
                contentScale = ContentScale.Crop,
            )
        }
    }
}

@Composable
internal fun ExoPlayerLifecycleOwner(exoPlayer: ExoPlayer) {
    val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current)
    val context = LocalContext.current

    DisposableEffect(
        key1 = AndroidView(
            modifier = Modifier.fillMaxSize(),
            factory = {
                StyledPlayerView(context).apply {
                    resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
                    player = exoPlayer
                }
            },
        ),
        effect = {
            val observer = LifecycleEventObserver { _, event ->
                when (event) {
                    Lifecycle.Event.ON_RESUME -> {
                        exoPlayer.play()
                    }
                    Lifecycle.Event.ON_PAUSE -> {
                        exoPlayer.stop()
                    }
                    else -> {}
                }
            }

            val lifecycle = lifecycleOwner.value.lifecycle
            lifecycle.addObserver(observer)

            onDispose {
                exoPlayer.release()
                lifecycle.removeObserver(observer)
            }
        },
    )
}

Solution

  • Use TextureView for the player surface.

    You can find the relevant code on how to do that in the sample:

    https://github.com/androidx/media/blob/release/demos/compose/src/main/java/androidx/media3/demo/compose/PlayerSurface.kt