I'm using exoPlayer to stream video to my app and so far works fine. What I would like to do now is to add some extra functionality such as a button on the bottom right edge to act as a "full screen button".
But there are two problems. The first is that ExoPlayer
doesn't seems to provide any Control class so you can simple add a button and override its functionality. What I guess is that I have to display that button on top of the video so I might have to wrap both in a FrameLayout
and add gravity = bottom for the button or is there another way?
The second problem is: if user clicks the full screen button what should I do next? Add another Fragment
with the video view in full screen? But how can I start the video from the point it was when user click the button and not start it from the beginning? I cant find in exoPlayer anything relative to start from a specific time.
If you are using SimpleExoPlayerView
, there is a way to customize the Player's view, especially the Control's view. Check the documentation of SimpleExoPlayerView
:
Attributes
The following attributes can be set on a
SimpleExoPlayerView
when used in a layout XML file: ...controller_layout_id - Specifies the id of the layout resource to be inflated by the child
PlaybackControlView
. See below for more details.
Corresponding method: None
Default: R.id.exo_playback_control_view
...
So basically you can provide your own layout file for the controller (you can copy the exo_playback_control_view layout mentioned in the docs, which is the default one, and customize that as you want. Note that you'll need to provide the same view ids for the existing controls (so it's best to actually copy that), as mentioned in the docs of PlaybackControlView
:
Overriding the layout file
To customize the layout of
PlaybackControlView
throughout your app, or just for certain configurations, you can define exo_playback_control_view.xml layout files in your application res/layout* directories. These layouts will override the one provided by the ExoPlayer library, and will be inflated for use byPlaybackControlView
. The view identifies and binds its children by looking for the following ids:
exo_play - The play button.
exo_pause - The pause button.
exo_ffwd - The fast forward button.
exo_rew - The rewind button.
exo_prev - The previous track button.
exo_next - The next track button.
exo_position - Text view displaying the current playback position.
exo_duration - Text view displaying the current media duration.
exo_progress - Seek bar that's updated during playback and allows seeking.
All child views are optional and so can be omitted if not required, however where defined they must be of the expected type.
Below is the customized layout with fullscreen button. You get the reference to the button by view.findViewById(R.id.exo_fullscreen_button)
and attach the OnClickListener
to the button. Inside onClick()
you can start your full screen activity (you can define it's full screen either in the AndroidManifest.xml or programatically) or show another fragment, that has the SimpleExoPlayerView
occupying the whole screen. As of your second point you can get the playback position like this: playbackPosition = player.getCurrentPosition()
and pass it to the new full screen Activity/Fragment as an Intent extra. Then, in that full screen Activity/Fragment you load the video, extract that playbackPosition
value and call:
player.seekTo(playbackPosition);
player.setPlayWhenReady(true);
Here is the control layout file:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="4dp"
android:orientation="horizontal">
<ImageButton android:id="@id/exo_prev"
style="@style/ExoMediaButton.Previous"/>
<ImageButton android:id="@id/exo_rew"
style="@style/ExoMediaButton.Rewind"/>
<ImageButton android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"/>
<ImageButton android:id="@id/exo_pause"
style="@style/ExoMediaButton.Pause"/>
<ImageButton android:id="@id/exo_ffwd"
style="@style/ExoMediaButton.FastForward"/>
<ImageButton android:id="@id/exo_next"
style="@style/ExoMediaButton.Next"/>
// This is the custom button
<ImageButton
android:id="@+id/exo_fullscreen_button"
style="@style/ExoMediaButton"
android:src="@drawable/ic_fullscreen"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView android:id="@id/exo_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"/>
<SeekBar android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="32dp"
android:focusable="false"
style="?android:attr/progressBarStyleHorizontal"/>
<TextView android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"/>
</LinearLayout>
</LinearLayout>
The other answer is great and points you in the right direction, but it's rather theoretical, and I still had to fill some gaps and solve a few things when writing the code. I'll try to complement it.
Start by copying the layout exo_playback_control_view.xml
from the ExoPlayer library to res/layout
. File link: https://github.com/google/ExoPlayer/blob/release-v2/library/ui/src/main/res/layout/exo_playback_control_view.xml
Modify the layout to add the full screen button, which can be something like this:
<FrameLayout
android:id="@+id/exo_fullscreen_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="end">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@drawable/ic_fullscreen_expand"
android:focusable="true" />
</FrameLayout>
Note that you can have different layouts for different PlayerView
s by using the attribute app:controller_layout_id
. You can also remove player buttons if you don't want them. This is covered in the docs: https://exoplayer.dev/ui-components.html#overriding-layout-files
Once you have the full screen button, set an OnClickListener
on it:
findViewById<View>(R.id.exo_fullscreen_button).setOnClickListener {
player.playWhenReady = false // pause current video if it's playing
startActivity(
FullScreenVideoActivity.newIntent(
context,
videoUrl,
player.currentPosition
)
)
}
Add the FullScreenVideoActivity
:
private const val EXTRA_VIDEO_URL = "EXTRA_VIDEO_URL"
private const val EXTRA_PLAYBACK_POSITION_MS = "EXTRA_PLAYBACK_POSITION_MS"
private const val STATE_PLAYBACK_POSITION_MS = "STATE_PLAYBACK_POSITION_MS"
class FullScreenVideoActivity : AppCompatActivity() {
companion object {
fun newIntent(packageContext: Context, videoUrl: String, playbackPositionMs: Long): Intent {
val intent =
Intent(packageContext, FullScreenVideoActivity::class.java)
intent.putExtra(EXTRA_VIDEO_URL, videoUrl)
intent.putExtra(EXTRA_PLAYBACK_POSITION_MS, playbackPositionMs)
return intent
}
}
private lateinit var player: SimpleExoPlayer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_full_screen_video)
val videoUrl = intent.getStringExtra(EXTRA_VIDEO_URL)
var playbackPositionMs = intent.getLongExtra(EXTRA_PLAYBACK_POSITION_MS, 0)
if (savedInstanceState != null) {
// The user rotated the screen
playbackPositionMs = savedInstanceState.getLong(STATE_PLAYBACK_POSITION_MS)
}
findViewById<View>(R.id.exo_fullscreen_button).setOnClickListener {
finish()
}
val playerView: PlayerView = findViewById(R.id.player_view)
player = ExoPlayerFactory.newSimpleInstance(this)
val userAgent = Util.getUserAgent(this, getString(R.string.app_name))
val dataSourceFactory = DefaultDataSourceFactory(this, userAgent)
val mediaSource: MediaSource =
ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(videoUrl))
player.prepare(mediaSource)
player.seekTo(playbackPositionMs)
player.playWhenReady = true
playerView.player = player
}
override fun onPause() {
super.onPause()
player.playWhenReady = false
}
override fun onDestroy() {
super.onDestroy()
player.release()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(STATE_PLAYBACK_POSITION_MS, player.currentPosition)
}
}
Add the activity to the Manifest:
<activity
android:name=".ui.FullScreenVideoActivity"
android:screenOrientation="landscape" <-- this is optional
android:theme="@style/AppTheme.NoActionBar.FullScreen" />
Finally add the theme to styles.xml
:
<style name="AppTheme.NoActionBar.FullScreen">
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
That's all! Hope it helps.
The above solution works fine and it's trivial. However it requires re-downloading bits of the video you've already downloaded, which interrupts the playback :/
I've been trying to avoid this by following this directions . This is what I have so far:
activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
val fullScreenPlayerView = PlayerView(context)
val dialog = object : Dialog(context!!, android.R.style.Theme_Black_NoTitleBar_Fullscreen) {
override fun onBackPressed() {
activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
PlayerView.switchTargetView(player, fullScreenPlayerView, playerView)
super.onBackPressed()
}
}
dialog.addContentView(
fullScreenPlayerView,
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
)
dialog.show()
PlayerView.switchTargetView(player, playerView, fullScreenPlayerView)
Note that for this to work you have to set android:configChanges="orientation|screenSize|layoutDirection"
to your Activity in the Manifest.
You can now display a full screen button by setting a click listener on the StyledPlayerView
:
videoPlayer.setFullscreenButtonClickListener {}
Where the view is:
<com.google.android.exoplayer2.ui.StyledPlayerView/>
I made a kotlin extension function for that. You need two Exoplayer and the extension will switch between the two players (fullscreen and normal).
@SuppressLint("SourceLockedOrientationActivity")
fun SimpleExoPlayer.preparePlayer(playerView: PlayerView, playerViewFullscreen: PlayerView) {
(playerView.context as AppCompatActivity).apply {
val fullScreenButton: ImageView = playerView.findViewById(R.id.exo_fullscreen_icon)
val normalScreenButton: ImageView = playerViewFullscreen.findViewById(R.id.exo_fullscreen_icon)
fullScreenButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_fullscreen_open))
normalScreenButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_fullscreen_close))
fullScreenButton.setOnClickListener {
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
supportActionBar?.hide()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
playerView.visibility = View.GONE
playerViewFullscreen.visibility = View.VISIBLE
PlayerView.switchTargetView(this@preparePlayer, playerView, playerViewFullscreen)
}
normalScreenButton.setOnClickListener {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
supportActionBar?.show()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
normalScreenButton.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_fullscreen_close))
playerView.visibility = View.VISIBLE
playerViewFullscreen.visibility = View.GONE
PlayerView.switchTargetView(this@preparePlayer, playerViewFullscreen, playerView)
}
playerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT
playerView.player = this@preparePlayer
}
}
fun SimpleExoPlayer.setSource(context: Context, url: String){
val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(context, Util.getUserAgent(context, "app"))
val videoSource: MediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(url))
this.prepare(videoSource)
}
For example with this layout :
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
tools:context=".MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="300dp">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerViewFullscreen"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
You can use it like this :
class MainActivity : AppCompatActivity() {
private val player: SimpleExoPlayer by lazy { SimpleExoPlayer.Builder(applicationContext).build() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
player.preparePlayer(playerView, playerViewFullscreen)
player.setSource(applicationContext, "http://html5videoformatconverter.com/data/images/happyfit2.mp4")
player.playWhenReady = true
}
public override fun onPause() {
super.onPause()
player.playWhenReady = false
}
public override fun onDestroy() {
player.release()
super.onDestroy()
}
}
If you prefer not copy this code, you can add this lib. It do the same : https://github.com/Norulab/android-exoplayer-fullscreen
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.