Skip to content

Commit

Permalink
feat(compose): add support for full screen in a dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
ThibaultBee committed Nov 8, 2023
1 parent 129b7c1 commit 1f1baac
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 25 deletions.
1 change: 1 addition & 0 deletions compose-player/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ android {
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
def composeBom = platform('androidx.compose:compose-bom:2023.10.00')
implementation(composeBom)
androidTestImplementation(composeBom)
Expand Down
167 changes: 147 additions & 20 deletions compose-player/src/main/java/video/api/compose/player/ApiVideoPlayer.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
package video.api.compose.player

import android.annotation.SuppressLint
import android.content.pm.ActivityInfo
import android.widget.ImageButton
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.SecureFlagPolicy
import video.api.compose.player.extensions.findActivity
import video.api.player.ApiVideoPlayerController
import video.api.player.extensions.showSystemUI
import video.api.player.models.VideoOptions
import video.api.player.models.VideoType
import video.api.player.notifications.ApiVideoPlayerNotificationController
import video.api.player.views.ApiVideoExoPlayerView

/**
* [ApiVideoPlayer] is a composable that displays an api.video video.
* [ApiVideoPlayer] is a composable that displays an api.video video from the video options.
*
* @param videoOptions The video options
* @param modifier The modifier to be applied to the view
* @param viewFit Sets how the video is fitted in its parent view
* @param showControls Shows or hides the control buttons
* @param showSubtitles Shows or hides the subtitles and the subtitle button
* @param showFullScreenButton Shows or hides the full screen button
* @param showSubtitleButton Shows or hides the subtitles and the subtitle button
* @param autoplay True to play the video immediately, false otherwise
* @param notificationController The notification controller. Set to null to disable the notification.
*/
Expand All @@ -32,7 +46,8 @@ fun ApiVideoPlayer(
modifier: Modifier = Modifier,
viewFit: ApiVideoExoPlayerView.ViewFit = ApiVideoExoPlayerView.ViewFit.Contains,
showControls: Boolean = true,
showSubtitles: Boolean = true,
showFullScreenButton: Boolean = true,
showSubtitleButton: Boolean = true,
autoplay: Boolean = true,
notificationController: ApiVideoPlayerNotificationController? = ApiVideoPlayerNotificationController(
LocalContext.current
Expand All @@ -53,7 +68,8 @@ fun ApiVideoPlayer(
modifier = modifier,
viewFit = viewFit,
showControls = showControls,
showSubtitles = showSubtitles
showFullScreenButton = showFullScreenButton,
showSubtitleButton = showSubtitleButton
)
}

Expand All @@ -65,44 +81,155 @@ fun ApiVideoPlayer(
* @param modifier The modifier to be applied to the view
* @param viewFit Sets how the video is fitted in its parent view
* @param showControls Shows or hides the control buttons
* @param showSubtitles Shows or hides the subtitles and the subtitle button
* @param showFullScreenButton Shows or hides the full screen button
* @param showSubtitleButton Shows or hides the subtitles and the subtitle button
*/
@SuppressLint("OpaqueUnitKey")
@Composable
fun ApiVideoPlayer(
controller: ApiVideoPlayerController,
modifier: Modifier = Modifier,
viewFit: ApiVideoExoPlayerView.ViewFit = ApiVideoExoPlayerView.ViewFit.Contains,
showControls: Boolean = true,
showSubtitles: Boolean = true
showFullScreenButton: Boolean = true,
showSubtitleButton: Boolean = true
) {
val context = LocalContext.current

var isFullScreenModeEntered by remember { mutableStateOf(false) }

// player view
DisposableEffect(
AndroidView(
modifier = modifier,
factory = {
ApiVideoExoPlayerView(context).apply {
this.fullScreenListener = null
this.viewFit = viewFit
this.showControls = showControls
this.showSubtitles = showSubtitles

controller.setPlayerView(this)
val playerView = remember {
ApiVideoExoPlayerView(context).apply {
this.viewFit = viewFit
this.showControls = showControls
this.showSubtitles = showSubtitleButton

if (showFullScreenButton) {
this.fullScreenListener = object : ApiVideoExoPlayerView.FullScreenListener {
override fun onFullScreenModeChanged(isFullScreen: Boolean) {
isFullScreenModeEntered = isFullScreen
}
}
}
)
) {

controller.setPlayerView(this)
}
}

DisposableEffect(
if (isFullScreenModeEntered) {
FullScreenPlayer(
originalView = playerView,
playerController = controller,
securePolicy = SecureFlagPolicy.Inherit,
onDismissRequest = {
isFullScreenModeEntered = false
},
)
} else {
ApiVideoPlayer(
view = playerView,
modifier = modifier
)
},

) {
onDispose {
controller.release()
}
}
}

/**
* [ApiVideoPlayer] is a composable that displays the api.video player view.
*
* @param view The Android player view
* @param modifier The modifier to be applied to the view
*/
@SuppressLint("OpaqueUnitKey")
@Composable
private fun ApiVideoPlayer(
view: ApiVideoExoPlayerView,
modifier: Modifier = Modifier,
) {
AndroidView(
modifier = modifier,
factory = {
view.apply {
setBackgroundColor(android.graphics.Color.BLACK)
}
}
)
}

@Preview
@Composable
private fun ApiVideoPlayerPreview() {
MaterialTheme {
ApiVideoPlayer(VideoOptions("vi77Dgk0F8eLwaFOtC5870yn", VideoType.VOD))
}
}

@Composable
private fun FullScreenPlayer(
originalView: ApiVideoExoPlayerView,
playerController: ApiVideoPlayerController,
securePolicy: SecureFlagPolicy,
onDismissRequest: () -> Unit,
) {
val context = LocalContext.current
val currentActivity = context.findActivity()

val originalOrientation = remember {
currentActivity!!.requestedOrientation
}
val fullScreenPlayerView = remember {
originalView.duplicate()
}

val internalOnDismissRequest = {
// Going back to normal screen
playerController.switchTargetView(fullScreenPlayerView, originalView)
originalView.findViewById<ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
.performClick()

currentActivity?.requestedOrientation = originalOrientation
currentActivity?.window?.showSystemUI()

onDismissRequest()
}

SideEffect {
fullScreenPlayerView.fullScreenListener =
object : ApiVideoExoPlayerView.FullScreenListener {
override fun onFullScreenModeChanged(isFullScreen: Boolean) {
if (!isFullScreen) {
internalOnDismissRequest()
}
}
}
}
SideEffect {
// Going to full screen
currentActivity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
playerController.switchTargetView(originalView, fullScreenPlayerView)
fullScreenPlayerView.findViewById<ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
.performClick()
}

FullScreenDialog({
internalOnDismissRequest()
}, securePolicy) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black),
) {
ApiVideoPlayer(
view = fullScreenPlayerView,
modifier = Modifier.fillMaxSize()
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package video.api.compose.player

import android.annotation.SuppressLint
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.SecureFlagPolicy
import video.api.compose.player.extensions.findActivity
import video.api.player.extensions.hideSystemUI
import video.api.player.models.VideoOptions
import video.api.player.models.VideoType

@SuppressLint("UnsafeOptInUsageError")
@Composable
fun FullScreenDialog(
onDismissRequest: () -> Unit = {},
securePolicy: SecureFlagPolicy = SecureFlagPolicy.Inherit,
content: @Composable () -> Unit,
) {
val context = LocalContext.current

Dialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(
dismissOnClickOutside = false,
usePlatformDefaultWidth = false,
securePolicy = securePolicy,
decorFitsSystemWindows = false,
),
) {
SideEffect {
val currentActivity = context.findActivity()
currentActivity!!.window.hideSystemUI()
}

content()
}
}

@Preview
@Composable
private fun FullScreenDialogPreview() {
MaterialTheme {
FullScreenDialog {
ApiVideoPlayer(
VideoOptions("vi77Dgk0F8eLwaFOtC5870yn", VideoType.VOD),
modifier = Modifier
.fillMaxSize(),
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package video.api.compose.player.extensions

import android.app.Activity
import android.content.Context
import android.content.ContextWrapper

fun Context.findActivity(): Activity? = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package video.api.compose.player.example
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import video.api.compose.player.ApiVideoPlayer
import video.api.player.models.VideoOptions
import video.api.player.models.VideoType
Expand All @@ -12,11 +17,19 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ApiVideoPlayer(
videoOptions = VideoOptions("vi77Dgk0F8eLwaFOtC5870yn", VideoType.VOD),
viewFit = ApiVideoExoPlayerView.ViewFit.FitHeight,
autoplay = true
)
/**
* Use a [Surface] to set the background color of the player in fullscreen mode.
*/
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.Black,
) {
ApiVideoPlayer(
videoOptions = VideoOptions("vi77Dgk0F8eLwaFOtC5870yn", VideoType.VOD),
viewFit = ApiVideoExoPlayerView.ViewFit.FitHeight,
autoplay = true
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,18 @@ fun Window.hideSystemUI() {
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}

/**
* Hides the system UI: status bar, navigation bar and system bars.
*/
fun Window.showSystemUI() {
WindowCompat.setDecorFitsSystemWindows(this, true)
WindowInsetsControllerCompat(this, this.decorView).let { controller ->
controller.show(WindowInsetsCompat.Type.systemBars())
controller.show(WindowInsetsCompat.Type.navigationBars())
controller.show(WindowInsetsCompat.Type.statusBars())
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
}
}

0 comments on commit 1f1baac

Please sign in to comment.