Jetpack compose - Exoplayer full screen

Viewed 1228

i am trying to steam a video in my android app made with jetpack compose. To stream i using ExoPlayer but i can't really understand how to implement a full screen button, some advice?

@Composable
private fun VideoPlayer() {
    val videoURI = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
    val httpDataSourceFactory: HttpDataSource.Factory =
        DefaultHttpDataSource.Factory().setAllowCrossProtocolRedirects(false)
    val dataSourceFactory: DataSource.Factory = DataSource.Factory {
        val dataSource = httpDataSourceFactory.createDataSource()
        dataSource.setRequestProperty(
            "cookie", "cookieValue"
        )
        dataSource.setRequestProperty("Range", "1-10000")
        dataSource
    }

    val mContext = LocalContext.current
    // Initializing ExoPLayer
    val mExoPlayer = remember(mContext) {
        ExoPlayer.Builder(mContext)
            .setMediaSourceFactory(DefaultMediaSourceFactory(dataSourceFactory)).build().apply {

                val mediaItem = MediaItem.Builder()
                    .setUri(Uri.parse(videoURI))
                    .build()
                setMediaItem(mediaItem)
                playWhenReady = true
                prepare()

            }

    }

    DisposableEffect(

        // Implementing ExoPlayer
        AndroidView(factory = { context ->
            StyledPlayerView(context).apply {
                player = mExoPlayer
            }
        })
    ) {
        onDispose {
            mExoPlayer.release()
        }
    }
}

Edit Adding the setControllerOnFullScreenModeChangedListener props exo will show a build in button for the fullscreen, i solved my problem calling the full screen function inside this listner

            AndroidView(
                factory = { context ->
                    StyledPlayerView(context).apply {
                        player = mExoPlayer
                        setControllerOnFullScreenModeChangedListener {
                        if(it)
                            //fullscreen
                        else
                            //minimize
                    }                    }
                })
3 Answers

To make an app go full-screen, there's

with(WindowCompat.getInsetsController(window, window.decorView)) {
    systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
    hide(WindowInsetsCompat.Type.systemBars())
}

Which I've tailored to Kotlin, so all you need to do is just hook it up in a Button's onClick and you're good to go.

Button(
  onclick = { /*Paste above Code here*/ }
){
 Text("Go full-screen") // Whatever here, per your use-case
}

If this does not work for some reason, or something is not accessible through the onClick, just create a LaunchedEffect with a MutableState<Boolean> as the key and change the key to trigger the reaction. Won't be necessary, most probably since the onClick should work just fine.

use this code

AndroidView(factory = {
        StyledPlayerView(context).apply {
            player = exoPlayer
            setFullscreenButtonClickListener { isFullScreen ->
                with(context) {
                    if (isFullScreen) {
                         setScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
                    } else {
                         setScreenOrientation(orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
                    }
                }
            }
      }
})

use this extension function for find activity instance:

fun Context.findActivity(): Activity? = when (this) {
    is Activity       -> this
    is ContextWrapper -> baseContext.findActivity()
    else              -> null
}

to set screen orientation use below extension function:

fun Context.setScreenOrientation(orientation: Int) {
    val activity = this.findActivity() ?: return
    activity.requestedOrientation = orientation
    if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
       hideSystemUi()
    } else {
       showSystemUi()
    }
}

and finally use this functions for hide/show system ui (status bar):

fun Context.hideSystemUi() {
    val activity = this.findActivity() ?: return
    val window = activity.window ?: return
    WindowCompat.setDecorFitsSystemWindows(window, false)
    WindowInsetsControllerCompat(window, window.decorView).let { controller ->
    controller.hide(WindowInsetsCompat.Type.systemBars())
    controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
   }
}

fun Context.showSystemUi() {
    val activity = this.findActivity() ?: return
    val window = activity.window ?: return
    WindowCompat.setDecorFitsSystemWindows(window, true)
   WindowInsetsControllerCompat(
    window,
    window.decorView
   ).show(WindowInsetsCompat.Type.systemBars())
}

I have an example which implements fullscreen toggling with compose-media. You don't have to use this library. But the solution is similar.

  1. Implement a composable MediaContent for media playback, and you can encapsulate fullscreen toggling relative logic inside it. Below code will work similar to YouTube's fullscreen toggling behavior (not UI).
@Composable
private fun MediaContent(
    mediaState: MediaState,
    isLandscape: Boolean,
    modifier: Modifier = Modifier
) {
    val activity = LocalContext.current.findActivity()!!
    val enterFullscreen = { activity.requestedOrientation = SCREEN_ORIENTATION_USER_LANDSCAPE }
    val exitFullscreen = {
        @SuppressLint("SourceLockedOrientationActivity")
        // Will reset to SCREEN_ORIENTATION_USER later
        activity.requestedOrientation = SCREEN_ORIENTATION_USER_PORTRAIT
    }
    Box(modifier) {
        Media(
            mediaState,
            modifier = Modifier.fillMaxSize(),
            showBuffering = ShowBuffering.Always,
            buffering = {
                Box(Modifier.fillMaxSize(), Alignment.Center) {
                    CircularProgressIndicator()
                }
            },
        )
        Button(
            modifier = Modifier.align(Alignment.BottomEnd),
            onClick = if (isLandscape) exitFullscreen else enterFullscreen
        ) {
            Text(text = if (isLandscape) "Exit Fullscreen" else "Enter Fullscreen")
        }
    }
    val onBackPressedCallback = remember {
        object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                exitFullscreen()
            }
        }
    }
    val onBackPressedDispatcher = activity.onBackPressedDispatcher
    DisposableEffect(onBackPressedDispatcher) {
        onBackPressedDispatcher.addCallback(onBackPressedCallback)
        onDispose { onBackPressedCallback.remove() }
    }
    SideEffect {
        onBackPressedCallback.isEnabled = isLandscape
        if (isLandscape) {
            if (activity.requestedOrientation == SCREEN_ORIENTATION_USER) {
                activity.requestedOrientation = SCREEN_ORIENTATION_USER_LANDSCAPE
            }
        } else {
            activity.requestedOrientation = SCREEN_ORIENTATION_USER
        }
    }
}

  1. Then you can use it:
@Composable
fun FullscreenToggle() {
    val configuration = LocalConfiguration.current
    val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE

    // hide system ui in fullscreen mode, if you need
    val systemUiController = rememberSystemUiController()
    SideEffect {
        systemUiController.isStatusBarVisible = !isLandscape
        systemUiController.isNavigationBarVisible = !isLandscape
    }

    // create and remember a MediaState instance
    val player by rememberManagedExoPlayer()
    val mediaState = rememberMediaState(player)

    // with movableContentOf, MediaContent's node can be reused between toggling
    val mediaContent = remember {
        movableContentOf { isLandscape: Boolean, modifier: Modifier ->
            MediaContent(mediaState, isLandscape, modifier)
        }
    }
    // this is the UI for normal mode, display the MediaContent with other UI elements
    Scaffold(
        topBar = {
          ...
        },
        modifier = Modifier
            .fillMaxSize()
            .systemBarsPadding(),
    ) { padding ->
        if (!isLandscape) {
            mediaContent(
                false,
                Modifier
                    .padding(padding)
                    .fillMaxWidth()
                    .aspectRatio(16f / 9f)
            )
        }
    }
    // this is the UI for fullscreen mode, only display the MediaContent
    if (isLandscape) {
        mediaContent(
            true,
            Modifier
                .fillMaxSize()
                .background(Color.Black)
        )
    }
}

You can find full code here: https://github.com/fengdai/compose-media/blob/master/sample/src/main/java/com/github/fengdai/compose/media/sample/FullscreenToggle.kt

Related