Hello guys. I want to make some kind of content pl...
# compose
j
Hello guys. I want to make some kind of content player for tv ( very basic ) and want to control show / hide animation. As I have combination of video and images , image animation works fine while video animation when entering just jump without fade effect. Code in 🧵
Copy code
package com.example.digitalsignage

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.layout.ContentScale
import androidx.media3.common.MediaMetadata
import androidx.media3.common.MimeTypes
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Surface
import coil.compose.AsyncImage
import com.example.digitalsignage.ui.theme.DigitalSignageTheme
import io.sanghun.compose.video.RepeatMode
import io.sanghun.compose.video.VideoPlayer
import io.sanghun.compose.video.controller.VideoPlayerControllerConfig
import io.sanghun.compose.video.uri.VideoPlayerMediaItem
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch


enum class MediaType {
    IMAGE, VIDEO
}

data class Media(
    val url: String,
    val type: MediaType,
    val delay: Long
)

val media = listOf(
    Media("<https://img.freepik.com/free-vector/flat-sale-landing-page-template_23-2149053848.jpg>", MediaType.IMAGE, 5000),
    Media("<https://img.freepik.com/free-vector/flat-sale-landing-page-template_23-2149053848.jpg>", MediaType.IMAGE, 5000 ),
    Media("<http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4>", MediaType.VIDEO, 16000),
    Media("<https://img.freepik.com/free-vector/flat-sale-landing-page-template_23-2149053848.jpg>", MediaType.IMAGE, 5000),
    Media("<http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4>", MediaType.VIDEO, 16000),
    Media("<https://img.freepik.com/free-vector/flat-sale-landing-page-template_23-2149053848.jpg>", MediaType.IMAGE, 5000),
    Media("<http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4>", MediaType.VIDEO, 16000),
    Media("<https://img.freepik.com/free-vector/flat-sale-landing-page-template_23-2149053848.jpg>", MediaType.IMAGE, 5000),
    Media("<http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4>", MediaType.VIDEO, 16000),
    Media("<https://img.freepik.com/free-vector/flat-sale-landing-page-template_23-2149053848.jpg>", MediaType.IMAGE, 5000),
    Media("<http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4>", MediaType.VIDEO, 16000),
)

class MainActivity : ComponentActivity() {
    @OptIn(ExperimentalTvMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            DigitalSignageTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    shape = RectangleShape
                ) {
                    ContentPlayer()
                }
            }
        }
    }
}

@Composable
fun ContentPlayer() {
    var currentIndex by rememberSaveable { mutableIntStateOf(0) }
    val coroutineScope = rememberCoroutineScope()

    fun switchToNextMedia() {
        if (currentIndex == media.size - 1) currentIndex = 0
        else currentIndex += 1
    }

    LaunchedEffect(currentIndex) {
        coroutineScope.launch {
            val currentMedia = media[currentIndex]
            delay(currentMedia.delay)
            switchToNextMedia()
        }
    }

    Box(modifier = Modifier.fillMaxSize()) {
        media.forEachIndexed { index, it ->
            AnimatedVisibility(
                visible = index == currentIndex,
                enter = fadeIn(animationSpec = tween(durationMillis = 1500)),
                exit = fadeOut(animationSpec = tween(durationMillis = 1500))
            ) {
                if (it.type == MediaType.IMAGE) {
                    AsyncImage(
                        contentScale = ContentScale.FillBounds,
                        model = it.url,
                        contentDescription = null,
                        modifier = Modifier.fillMaxSize()
                    )
                } else if (it.type == MediaType.VIDEO) {
                    VideoPlayer(
                        mediaItems = listOf(
                            VideoPlayerMediaItem.NetworkMediaItem(
                                url = it.url,
                                mediaMetadata = MediaMetadata.Builder().setTitle("Video").build(),
                                mimeType = MimeTypes.VIDEO_MP4,
                            )
                        ),
                        handleLifecycle = true,
                        autoPlay = true,
                        usePlayerController = false,
                        enablePip = false,
                        handleAudioFocus = false,
                        controllerConfig = VideoPlayerControllerConfig.Default.copy(
                            showSpeedAndPitchOverlay = false,
                            showSubtitleButton = false,
                            showCurrentTimeAndTotalTime = true,
                            showBufferingProgress = false,
                            showForwardIncrementButton = false,
                            showBackwardIncrementButton = false,
                            showBackTrackButton = false,
                            showNextTrackButton = false,
                            showRepeatModeButton = false,
                            controllerShowTimeMilliSeconds = 5_000,
                            controllerAutoShow = false,
                            showFullScreenButton = false
                        ),
                        volume = 0.5f,
                        repeatMode = RepeatMode.NONE,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            }
        }
    }
}
This is just small POC and for video i use https://github.com/dsa28s/compose-video