Skip to content

Commit

Permalink
feat(*): improve full screen usage for view based player
Browse files Browse the repository at this point in the history
  • Loading branch information
ThibaultBee committed Nov 6, 2023
1 parent 2d6e0c2 commit 5f52f11
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@ import android.os.Bundle
import android.util.Log
import android.util.Size
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.preference.PreferenceManager
import com.android.volley.ClientError
import com.google.android.material.snackbar.Snackbar
import video.api.player.ApiVideoPlayerController
import video.api.player.example.databinding.ActivityMainBinding
import video.api.player.models.ApiVideoPlayerFullScreenController
import video.api.player.models.VideoOptions
import video.api.player.models.VideoType
import video.api.player.views.ApiVideoExoPlayerView
Expand Down Expand Up @@ -51,33 +48,6 @@ class MainActivity : AppCompatActivity() {
}
}

private val fullScreenListener = object : ApiVideoExoPlayerView.FullScreenListener {
override fun onFullScreenModeChanged(isFullScreen: Boolean) {
/**
* For fullscreen video, hides every views and forces orientation in landscape.
*/
if (isFullScreen) {
supportActionBar?.hide()
hideSystemUI()
binding.fab.hide()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
binding.playerView.layoutParams.apply {
width = ViewGroup.LayoutParams.MATCH_PARENT
height = ViewGroup.LayoutParams.MATCH_PARENT
}
} else {
supportActionBar?.show()
showSystemUI()
binding.fab.show()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
binding.playerView.layoutParams.apply {
width = ViewGroup.LayoutParams.WRAP_CONTENT
height = ViewGroup.LayoutParams.WRAP_CONTENT
}
}
}
}

private val playerControllerListener = object : ApiVideoPlayerController.Listener {
override fun onError(error: Exception) {
val message = when {
Expand Down Expand Up @@ -125,8 +95,23 @@ class MainActivity : AppCompatActivity() {
}
}

private val fullScreenController: ApiVideoPlayerFullScreenController by lazy {
ApiVideoPlayerFullScreenController(
supportFragmentManager,
binding.playerView,
playerController,
object : ApiVideoExoPlayerView.FullScreenListener {
override fun onFullScreenModeChanged(isFullScreen: Boolean) {
requestedOrientation = if (isFullScreen) {
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
} else {
ActivityInfo.SCREEN_ORIENTATION_SENSOR
}
}
}
)
}
private val playerController: ApiVideoPlayerController by lazy {
binding.playerView.fullScreenListener = fullScreenListener
ApiVideoPlayerController(
applicationContext,
null,
Expand All @@ -140,23 +125,6 @@ class MainActivity : AppCompatActivity() {
Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
}

private fun hideSystemUI() {
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}

private fun showSystemUI() {
WindowCompat.setDecorFitsSystemWindows(window, true)
WindowInsetsControllerCompat(
window,
window.decorView
).show(WindowInsetsCompat.Type.systemBars())
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand Down Expand Up @@ -187,8 +155,9 @@ class MainActivity : AppCompatActivity() {
}
binding.unmute.setOnClickListener { playerController.isMuted = false }

binding.playerView.fullScreenListener = fullScreenController
binding.showFullScreenButton.setOnClickListener {
binding.playerView.fullScreenListener = fullScreenListener
binding.playerView.fullScreenListener = fullScreenController
}
binding.hideFullScreenButton.setOnClickListener {
binding.playerView.fullScreenListener = null
Expand Down
2 changes: 2 additions & 0 deletions player/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ dependencies {
implementation "androidx.media3:media3-exoplayer:${exoPlayerVersion}"
implementation "androidx.media3:media3-exoplayer-hls:${exoPlayerVersion}"
implementation "androidx.media:media:1.6.0"
implementation "androidx.fragment:fragment-ktx:1.6.2"
implementation "com.google.android.material:material:1.10.0"

testImplementation 'org.robolectric:robolectric:4.10.3'
testImplementation 'org.robolectric:shadows-httpclient:4.5.1'
Expand Down
16 changes: 13 additions & 3 deletions player/src/main/java/video/api/player/ApiVideoPlayerController.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package video.api.player

import android.annotation.SuppressLint
import android.content.Context
import android.os.Handler
import android.os.Looper
Expand Down Expand Up @@ -597,9 +598,7 @@ constructor(
*
* @param view the player view. An [ApiVideoExoPlayerView] for example.
*/
fun setPlayerView(view: IExoPlayerBasedPlayerView) {
view.playerView.player = exoplayer
}
fun setPlayerView(view: IExoPlayerBasedPlayerView) = setPlayerView(view.playerView)

/**
* Sets the player view
Expand Down Expand Up @@ -637,6 +636,17 @@ constructor(
exoplayer.setVideoSurface(surface)
}

@SuppressLint("UnsafeOptInUsageError")
fun switchTargetView(oldPlayerView: PlayerView, newPlayerView: PlayerView) {
PlayerView.switchTargetView(exoplayer, oldPlayerView, newPlayerView)
}

@SuppressLint("UnsafeOptInUsageError")
fun switchTargetView(
oldPlayerView: IExoPlayerBasedPlayerView,
newPlayerView: IExoPlayerBasedPlayerView
) = switchTargetView(oldPlayerView.playerView, newPlayerView.playerView)

companion object {
private const val TAG = "ApiVideoPlayer"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package video.api.player.extensions

import android.view.Window
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat

/**
* Hides the system UI: status bar, navigation bar and system bars.
*/
fun Window.hideSystemUI() {
WindowCompat.setDecorFitsSystemWindows(this, false)
WindowInsetsControllerCompat(this, this.decorView).let { controller ->
controller.hide(WindowInsetsCompat.Type.systemBars())
controller.hide(WindowInsetsCompat.Type.navigationBars())
controller.hide(WindowInsetsCompat.Type.statusBars())
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package video.api.player.models

import android.util.Log
import android.widget.ImageButton
import androidx.fragment.app.FragmentManager
import video.api.player.ApiVideoPlayerController
import video.api.player.R
import video.api.player.views.ApiVideoExoPlayerView
import video.api.player.views.FullScreenDialogFragment

/**
* A class that handles the player full screen.
*
* Internally, it creates another player view in a full screen dialog fragment.
*
* @param fragmentManager The fragment manager.
* @param originalPlayerView The player view.
* @param playerController The player controller.
* @param fullScreenListener The full screen listener if you want to lock the orientation in full screen.
*/
class ApiVideoPlayerFullScreenController(
private val fragmentManager: FragmentManager,
private val originalPlayerView: ApiVideoExoPlayerView,
private val playerController: ApiVideoPlayerController,
private val fullScreenListener: ApiVideoExoPlayerView.FullScreenListener? = null
) : ApiVideoExoPlayerView.FullScreenListener {
/**
* Full screen listener for the full screen player view.
*/
private val internalFullScreenListener = object : ApiVideoExoPlayerView.FullScreenListener {
override fun onFullScreenModeChanged(isFullScreen: Boolean) {
if (dialogFragment.isVisible) {
fullScreenPlayerView.playerView.findViewById<ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
.setImageResource(R.drawable.exo_styled_controls_fullscreen_exit)
playerController.switchTargetView(fullScreenPlayerView, originalPlayerView)
dialogFragment.dismiss()
fullScreenListener?.onFullScreenModeChanged(false)
} else {
Log.e(TAG, "onFullScreenModeChanged: not expected when dialog is already visible")
}
}
}
private val fullScreenPlayerView: ApiVideoExoPlayerView = originalPlayerView.duplicate().apply {
this.fullScreenListener = this@ApiVideoPlayerFullScreenController.internalFullScreenListener
this.playerView.findViewById<ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
.setImageResource(R.drawable.exo_styled_controls_fullscreen_exit)
}
private val dialogFragment: FullScreenDialogFragment =
FullScreenDialogFragment(fullScreenPlayerView)

/**
* Original view full screen listener.
*/
override fun onFullScreenModeChanged(isFullScreen: Boolean) {
if (!dialogFragment.isVisible) {
originalPlayerView.playerView.findViewById<ImageButton>(androidx.media3.ui.R.id.exo_fullscreen)
.setImageResource(R.drawable.exo_styled_controls_fullscreen_enter)
playerController.switchTargetView(originalPlayerView, fullScreenPlayerView)
dialogFragment.show(fragmentManager, TAG)
fullScreenListener?.onFullScreenModeChanged(true)
} else {
Log.e(TAG, "onFullScreenModeChanged: not expected")
}
}

companion object {
private const val TAG = "FullScreenDialogCtrl"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.media3.ui.PlayerView
import video.api.player.R
import video.api.player.databinding.ExoPlayerLayoutBinding
import video.api.player.interfaces.IExoPlayerBasedPlayerView
import video.api.player.models.ApiVideoPlayerFullScreenController

/**
* The api.video player view class based on an ExoPlayer [PlayerView].
Expand All @@ -23,9 +24,7 @@ import video.api.player.interfaces.IExoPlayerBasedPlayerView
* @param defStyleAttr an attribute in the current theme that contains a reference to a style resource that supplies default values for the view. Can be 0 to not look for defaults.
*/
class ApiVideoExoPlayerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr), IExoPlayerBasedPlayerView {
private val binding = ExoPlayerLayoutBinding.inflate(LayoutInflater.from(context), this)

Expand All @@ -35,10 +34,11 @@ class ApiVideoExoPlayerView @JvmOverloads constructor(
/**
* Sets or gets the full screen listener.
* If set to null, the full screen button is hidden.
*
* To simplify full screen management, you can use [ApiVideoPlayerFullScreenController].
*/
var fullScreenListener: FullScreenListener? = null
@SuppressLint("UnsafeOptInUsageError")
set(value) {
@SuppressLint("UnsafeOptInUsageError") set(value) {
if (value != null) {
playerView.setFullscreenButtonClickListener {
value.onFullScreenModeChanged(it)
Expand All @@ -62,8 +62,7 @@ class ApiVideoExoPlayerView @JvmOverloads constructor(
* Shows or hides the subtitles
*/
var showSubtitles: Boolean = true
@SuppressLint("UnsafeOptInUsageError")
set(value) {
@SuppressLint("UnsafeOptInUsageError") set(value) {
if (value) {
playerView.subtitleView?.visibility = VISIBLE
playerView.setShowSubtitleButton(true)
Expand All @@ -78,10 +77,8 @@ class ApiVideoExoPlayerView @JvmOverloads constructor(
* Sets or gets how the video is fitted in its parent view
*/
var viewFit: ViewFit
@SuppressLint("UnsafeOptInUsageError")
get() = ViewFit.fromValue(playerView.resizeMode)
@SuppressLint("UnsafeOptInUsageError")
set(value) {
@SuppressLint("UnsafeOptInUsageError") get() = ViewFit.fromValue(playerView.resizeMode)
@SuppressLint("UnsafeOptInUsageError") set(value) {
playerView.resizeMode = value.value
}

Expand All @@ -95,6 +92,15 @@ class ApiVideoExoPlayerView @JvmOverloads constructor(
}
}

fun duplicate(): ApiVideoExoPlayerView {
val view = ApiVideoExoPlayerView(context)
view.showControls = showControls
view.showSubtitles = showSubtitles
view.viewFit = viewFit
view.fullScreenListener = fullScreenListener
return view
}

interface FullScreenListener {
/**
* Called when the full screen button has been clicked
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package video.api.player.views

import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.DialogFragment
import video.api.player.R
import video.api.player.databinding.FullscreenLayoutBinding
import video.api.player.extensions.hideSystemUI

/**
* A full screen dialog fragment that contains a [subView].
*
* @param subView The view to be displayed in the dialog.
*/
class FullScreenDialogFragment(private val subView: View) :
DialogFragment() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.AppTheme_FullScreenDialog)
}

override fun onStart() {
super.onStart()

activity?.window?.hideSystemUI()
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
super.onCreateView(inflater, container, savedInstanceState)
val binding = FullscreenLayoutBinding.inflate(layoutInflater, container, false)

if (subView.parent != null) {
(subView.parent as ViewGroup).removeView(subView)
}
subView.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
).apply {
gravity = Gravity.CENTER
}
binding.container.addView(subView)

return binding.root
}
}
4 changes: 2 additions & 2 deletions player/src/main/res/layout/exo_player_layout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<androidx.media3.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:show_subtitle_button="true" />
</merge>
6 changes: 6 additions & 0 deletions player/src/main/res/layout/fullscreen_layout.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
Loading

0 comments on commit 5f52f11

Please sign in to comment.