I try to show the show video (.mp4) with exoplayer in RecyclerView and ViewPager. I show the video controller with custom layout. so far so good.
Now try to fullscreen the video like other video player how use before but can't find a good way in the exoplayer doc.
can anyone help me?
The ExoPlayer library currently does not provide a built-in way to enable/disable fullscreen mode. You need to implement this yourself or find some third party code for this I'm afraid.
Essentially two steps are required:
a) Set the window and activity properties to fullscreen and/or immersive mode and (if desired) change to landscape mode. That's not difficult. See this page on Android Developers.
b) Transition rendering to a SimpleExoPlayerView
(actually it's about the Surface) which covers the entire viewport in immersive mode. This is more of a challenge to achieve optimal user experience on all API levels.
For an optimal user experience, we want to keep the player playing while doing the transition to full screen and back, to continue playback seamlessly. In a RecyclerView this is kind of tricky to solve for all API levels.
Approach 1
The easiest way is probably having a separate instance of SimpleExoPlayerView which you put on top of your layout as soon as you entered immersive mode (some people open a dialog with the second view for this, some have the second view simply on top in the layout somehow and show/hide it on demand).
Then you detach the player instance from the SimpleExoPlayerView embedded in the RV and attach it to the fullscreen view with a call to a static helper method:
SimpleExoPlayerView.switchTargetView(simpleExoPlayer, oldPlayerView, newPlayerView);
This approach works very well on API >=23. With API 23 the method MediaCodec.setOutputSurface allowing to swap surfaces dynamically has been added. The static method above makes sure this technique is applied. As a result audio and video keep playing and the user experience from entering and exiting fullscreen is super smooth. For API <=22 a new codec instance needs to be create to swap to another surface. This interrupts playback and the user experience for this approach is degraded.
Approach 2
To avoid swapping to another surface on lower API level, you need to use a single surface and transition it to fullscreen somehow. You can either just hide everything else than the SimpleExoPlayerView and set the layout width and height to match it's parent or, you can replace the video view with a placeholder and put it on top and back.
This can work quite well for simple layouts but with complex layouts probably including fragments, viewpagers, recyclerviews this can be a quite intrusive operation causing something to flicker or interrupting playback shortly (on some API levels for instance when removing the player view from the view hierarchy). I've seen this working nicely for various layouts though.
Further approaches/challenges
There might be other and probably better approaches when you dig deeper and/or if you are not using SimpleExoPlayerView at all.
You can easily do this from the XML of the ExoPlayer
. Set the following attribute:
app:resize_mode="fill"
To show the complete full screen video on expo player use this line in you xml file app:resize_mode="fill"
Exoplayer doesn't provide the fullscreen so here is the workaround that worked for me. Here I have restricted the Screen to rotate and manually changing the orientation programmatically change the width and height of the player_view and has set the visibility of the toolbar gone. Used Data Binding and Kotlin.
Follows a code block for XML
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/video_player_container"
android:layout_width="match_parent"
android:layout_height="250dp"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:keepScreenOn="true"
android:padding="0dp"
app:controller_layout_id="@layout/exo_playback_control_view"
app:layout_constraintTop_toTopOf="parent"
app:resize_mode="fill"
app:use_controller="true" />
<LinearLayout
android:id="@+id/nextVideoContainer"
android:layout_width="wrap_content"
android:layout_height="@dimen/spacing_32"
android:background="#90000000"
android:onClick="@{() -> vm.onNextVideo()}"
android:orientation="horizontal"
android:paddingLeft="@dimen/spacing_16"
android:paddingRight="@dimen/spacing_16"
android:visibility="@{vm.shouldShowNextBtn}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.sharedcode.widgets.CustomTextView
android:id="@+id/next_video_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/label_next"
android:textColor="@android:color/white" />
<com.sharedcode.widgets.CustomImageView
android:id="@+id/next_video_image"
android:layout_width="10dp"
android:layout_height="10dp"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:paddingTop="2dp"
android:src="@drawable/ic_play_next" />
</LinearLayout>
<RelativeLayout
android:id="@+id/retry_container"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#90000000"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:onClick="@{() -> vm.onRetry()}">
<com.sharedcode.widgets.CustomTextView
android:id="@+id/txt_no_internet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:layout_centerInParent="true"
android:text="@string/txt_no_internet_connection"
android:textColor="@android:color/white"
android:textSize="@dimen/font_16" />
<com.sharedcode.widgets.CustomTextView
android:layout_width="`wrap_content`"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_no_internet"
android:layout_centerInParent="true"
android:layout_marginTop="@dimen/spacing_16"
android:maxHeight="@dimen/spacing_32"
android:text="@string/txt_tap_to_retry"
android:textColor="@android:color/white"
android:textSize="@dimen/font_16" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Changes in Android Manifest
<activity android:name=".yourPackage.ClassName"
android:screenOrientation="portrait"
android:configChanges="orientation|screenSize|layoutDirection"/>
Check the orientation and rotate it via the following code
mBinding.playerView.exo_fullscreen_btn.setOnClickListener {
if ((activity as TrainingVideoActivity).checkLandscapeOrientation()) {
(activity as TrainingVideoActivity).changeOrientationToLandscape(false)
} else {
(activity as TrainingVideoActivity).changeOrientationToLandscape(true)
}
}
Methods Signature are following
/**
* Changes the Orientation
* @param shouldLandscape
*/
fun changeOrientationToLandscape(shouldLandscape: Boolean) {
requestedOrientation = if (shouldLandscape) {
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
} else {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
}
/**
* Checks the Orientation
* And returns true if Landscape else false
*/
fun checkLandscapeOrientation() : Boolean {
val orientation = resources.configuration.orientation
return orientation == Configuration.ORIENTATION_LANDSCAPE
}
Now Just Override onConfigurationChanged method in your fragment/activity, since here I have used Fragment. So here I have changed the width/height of the parent container in which ExoplayerView is placed.
/**
* Used for Showing Video on Full Screen
* @param newConfig
* Used EXO_PLAYER_VIEW_HEIGHT as 250
*/
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
hideToolbarAndShowFullScreen()
mBinding.playerView.exo_fullscreen_btn.setImageDrawable(ContextCompat.getDrawable(activity!!, R.drawable.ic_shrink))
val params = mBinding.videoPlayerContainer.layoutParams as ConstraintLayout.LayoutParams
params.width = ViewGroup.LayoutParams.MATCH_PARENT
params.height = ViewGroup.LayoutParams.MATCH_PARENT
mBinding.videoPlayerContainer.layoutParams = params
} else {
showToolbarAndClearFullScreen()
mBinding.playerView.exo_fullscreen_btn.setImageDrawable(ContextCompat.getDrawable(activity!!, R.drawable.ic_fullscreen))
val params = mBinding.videoPlayerContainer.layoutParams as ConstraintLayout.LayoutParams
val factor = mBinding.playerView.context.resources.displayMetrics.density
params.width = ViewGroup.LayoutParams.MATCH_PARENT
params.height = (EXO_PLAYER_VIEW_HEIGHT * factor).toInt()
mBinding.videoPlayerContainer.layoutParams = params
}
}
/**
* Show the Toolbar and reset to original in the Portrait Mode
*/
private fun showToolbarAndClearFullScreen() {
(activity as TrainingVideoActivity).supportActionBar!!.show()
(activity as TrainingVideoActivity).window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
Lastly, XML for the player_controller
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#99000000">
<LinearLayout
android:id="@+id/container_play_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="horizontal">
<ImageButton
android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"
android:src="@drawable/ic_play_exoplayer"
/>
<ImageButton
android:id="@id/exo_pause"
style="@style/ExoMediaButton.Pause"
android:src="@drawable/ic_pause_exoplayer"/>
</LinearLayout>
<LinearLayout
android:id="@+id/seekbar_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom"
android:background="#CC000000"
android:clickable="false"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
android:orientation="horizontal"
tools:ignore="UselessParent">
<com.sharedcode.widgets.CustomTextView
android:id="@id/exo_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="#FFBEBEBE"
android:textSize="14sp"
android:textStyle="bold" />
<com.bnb.paynearby.utils.exoplayer.ExtendedTimebar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
app:buffered_color="@color/white"
app:played_color="@color/color_red"
app:scrubber_color="@color/color_red"
app:scrubber_disabled_size="10dp"
app:unplayed_color="#484848" />
<com.sharedcode.widgets.CustomTextView
android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:includeFontPadding="false"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="#FFBEBEBE"
android:textSize="14sp"
android:textStyle="bold" />
<com.sharedcode.widgets.CustomImageView
android:id="@+id/exo_fullscreen_btn"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_margin="8dp"
android:src="@drawable/ic_fullscreen"
/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Let me know if this works.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With