- Fullscreen functionality with Android ExoPlayer
- How to Add a Fullscreen Toggle Button to ExoPlayer in Android
- The XML
- Add The Controller Id
- Add The Player View To MainActivity’s Layout
- The Java
- Entering Fullscreen
- Exiting Fullscreen
- Initializing The Fullscreen Button
- That’s It!
- 33 Comments
- Buttons
- Responding to Click Events
- Kotlin
- Using an OnClickListener
- Kotlin
- Styling Your Button
- Borderless button
- Custom background
- artemisia-absynthium / AndroidManifest.xml
Fullscreen functionality with Android ExoPlayer
ExoPlayer is a great Android library that deals with a lot of the pains you might encounter when trying to stream video. However, unless the video you’re playing is being displayed in a fullscreen Activity by default this can cause some issues when it comes to smooth playback.
You may find that the audio will keep playing while the video hangs waiting to resync with the audio, or that when you go back to the calling activity the video has now gone blank but still responds to input.
As much as I tried to find a good solution on StackOverflow and other sources on the internet, I just couldn’t. After approaching this problem from various angles. Here is a nice, reliable solution that I came up with:
This class uses the Singleton Design Pattern to ensure there always only one instance in the app as we’re only interested in the playback of one video. If you want to do multiple videos at the same time look at keeping references to each instance of this class instead.
After we have got the refernce to the current instance of the ExoPlayerVideoHandler we then prepare the SimpleExoPlayerView of the parent activity to play the video from the URI.
If the player variable is null or the URI does not match the previously stored URI than we want to play a new video and re-run the code required to setup ExoPlayer. Otherwise, we want to reset the video surface (this prevents the screen from being blank when going back to an activity) and reset the position of the player to it’s current position. (The +1 also forces a re-draw by buffering the video).
goToBackground and goToForeground are 2 helper methods that ensure that the video state is kept when switching between activities (namely the parent Activity and the video’s fullscreen Activity) and will continue playing or stay paused.
Used in practice the code looks something like this:
The view is found and set to the ExoPlayer instance created inside the ExoPlayerVideoHandler. When the activity is paused the player goes to the background and when the activity is destroyed the player is released.
An OnClickListener is also added to the SimpleExoPlayerView’s added fullscreen button. This fires off an intent to the FullscreenVideoActivity. The code of which looks like this:
This Activity gets the existing instance of the player from the ExoPlayerVideoHandler and brings it back to the foreground which will continue playing the video from where the last activity was.
The destroyVideo flag determines if the entire app is being forcibly killed by the OS or if the user simply wants to stop viewing the video in fullscreen. By default the flag is true and will only be set to false if the user makes a deliberate action. Otherwise, the player will be released.
Hopefully this will help anyone out that’s also having the same issues when trying to show the same ExoPlayer video stream in different activities. Good luck!
Источник
How to Add a Fullscreen Toggle Button to ExoPlayer in Android
Download the complete source code for this project:
— as a ZIP file
— view on Github
ExoPlayer is a great alternative to Android’s MediaPlayer API and adds support for HLS, DASH, and SmoothStreaming adaptive playback. While it is far superior to Android’s default media player, it lacks the ability to easily toggle a video in and out of a full screen mode. Today I’ll show you a solution to this problem.
Rather than creating a new activity, this solution uses the magic of a fullscreen dialog. When the video is toggled into fullscreen mode, the SimpleExoPlayerView will be removed from the activity layout, and added to a new dialog. The transition between views is very fast, and there is no audio stuttering or buffering when streaming video.
The XML
First we’ll add the fullscreen toggle button to the Exoplayer controls. This can be done by overriding the default PlaybackControlView layout. To do this, copy the exo_playback_control_view.xml file from the exoplayer-ui package into your project’s layout directory. You’ll also want some image assets to use for the expand and shrink icons. You can download the icons used in this example here:
– ic_fullscreen_expand.png | |
– ic_fullscreen_shrink.png |
Ideally, you’d want different sized images for different screen densities, but for this example you can just copy these assets into your project’s drawable directory.
Next, we’ll edit the exo_playback_control_view.xml file. Add the following to the second horizontal LinearLayout . This code should go towards the bottom of the file, after the exo_duration TextView and before the second-to-last closing tag:
Add The Controller Id
Now copy the exo_simple_player_view.xml file into your project’s layout directory. Add the following towards the bottom of the file, between the exo_controller_placeholder View and the exo_overlay FrameLayout:
This step is necessary in order to get a reference to the PlayBackControlView from our activity by calling findViewById on the SimpleExoPlayerView object (more on this later.)
Add The Player View To MainActivity’s Layout
To finish up the XML, we’ll add SimpleExoPlayerView to our activity. Inside of activity_main.xml, add the following:
Adjust the FrameLayout’s width and height to your liking. The sample project assigns the layout a height of 0dp with a weight of 0.5 so it will fill half of the screen.
We wrap the SimpleExoPlayerView in a FrameLayout so that the view can be removed and re-added when the video is toggled in and out of full screen.
The Java
Ok, onto the Java code. Create a Dialog member variable called mFullScreenDialog. When initializing it, override the dialog’s onBackPressed() method:
This allows the user to exit fullscreen mode by either pressing the shrink button in the lower right of their screen, or by using their device’s back button. Also notice that we passed the android.R.style.Theme_Black_NoTitleBar_Fullscreen style into the dialog’s constructor. This will make the dialog fill the entire screen when it’s shown.
Entering Fullscreen
Next, create a method called openFullscreenDialog() . This method programmatically removes the SimpleExoPlayerView from the activity, adds a new instance of the view to the fullscreen dialog, and shows the dialog:
Exiting Fullscreen
Now create a method called closeFullscreenDialog() . This method adds a new SimpleExoPlayerView to the activity, removes the view from the fullscreen dialog, and dismisses the dialog:
Initializing The Fullscreen Button
Finally, create a method to initialize the fullscreen button. In order to get a reference to the button, we first need a reference to the player’s PlaybackControlView. To do this, we call mExoPlayerView.findViewById(R.id.exo_controller) . We added a view with this id earlier to exo_simple_player_view.xml .
To understand why a view with this specific id is necessary, we can take a look at the source code of SimpleExoPlayerView . Inside the view’s constructor we see the following:
If this id isn’t found, the PlaybackControlView is created elsewhere programmatically.
That’s It!
That should be everything you need to add a fullscreen mode to an existing Exoplayer implementation. If you need further examples of how to initialize Exoplayer and tie everything together, you can view the complete source code for the activity here, or download the complete project on GitHub.
33 Comments
Hi,
I have problems with playing mp4 video when switch to fullscreen/normal, start buffering and play video from start. For mp4 video i use ExtractorMediaSource.
Источник
Buttons
A button consists of text or an icon (or both text and an icon) that communicates what action occurs when the user touches it.
Depending on whether you want a button with text, an icon, or both, you can create the button in your layout in three ways:
- With text, using the Button class:
- With an icon, using the ImageButton class:
- With text and an icon, using the Button class with the android:drawableLeft attribute:
Key classes are the following:
Responding to Click Events
When the user clicks a button, the Button object receives an on-click event.
To define the click event handler for a button, add the android:onClick attribute to the element in your XML layout. The value for this attribute must be the name of the method you want to call in response to a click event. The Activity hosting the layout must then implement the corresponding method.
For example, here’s a layout with a button using android:onClick :
Within the Activity that hosts this layout, the following method handles the click event:
Kotlin
The method you declare in the android:onClick attribute must have a signature exactly as shown above. Specifically, the method must:
- Be public
- Return void
- Define a View as its only parameter (this will be the View that was clicked)
Using an OnClickListener
You can also declare the click event handler programmatically rather than in an XML layout. This might be necessary if you instantiate the Button at runtime or you need to declare the click behavior in a Fragment subclass.
To declare the event handler programmatically, create an View.OnClickListener object and assign it to the button by calling setOnClickListener(View.OnClickListener) . For example:
Kotlin
Styling Your Button
The appearance of your button (background image and font) may vary from one device to another, because devices by different manufacturers often have different default styles for input controls.
You can control exactly how your controls are styled using a theme that you apply to your entire application. For instance, to ensure that all devices running Android 4.0 and higher use the Holo theme in your app, declare android:theme=»@android:style/Theme.Holo» in your manifest’s element. Also read the blog post, Holo Everywhere for information about using the Holo theme while supporting older devices.
To customize individual buttons with a different background, specify the android:background attribute with a drawable or color resource. Alternatively, you can apply a style for the button, which works in a manner similar to HTML styles to define multiple style properties such as the background, font, size, and others. For more information about applying styles, see Styles and Themes.
Borderless button
One design that can be useful is a «borderless» button. Borderless buttons resemble basic buttons except that they have no borders or background but still change appearance during different states, such as when clicked.
To create a borderless button, apply the borderlessButtonStyle style to the button. For example:
Custom background
If you want to truly redefine the appearance of your button, you can specify a custom background. Instead of supplying a simple bitmap or color, however, your background should be a state list resource that changes appearance depending on the button’s current state.
You can define the state list in an XML file that defines three different images or colors to use for the different button states.
To create a state list drawable for your button background:
- Create three bitmaps for the button background that represent the default, pressed, and focused button states.
To ensure that your images fit buttons of various sizes, create the bitmaps as Nine-patch bitmaps.
Источник
artemisia-absynthium / AndroidManifest.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
xml version = » 1.0 » encoding = » utf-8 » ?> |
FrameLayout xmlns : android = » http://schemas.android.com/apk/res/android « |
xmlns : tools = » http://schemas.android.com/tools « |
android : id = » @+id/enclosing_layout « |
android : layout_width = » match_parent « |
android : layout_height = » match_parent « |
android : background = » @android:color/black « |
tools : context = » .FullscreenVideoActivity » > |
com .google.android.exoplayer2.ui.PlayerView |
android : id = » @+id/player_view « |
android : layout_width = » match_parent « |
android : layout_height = » match_parent »/> |
FrameLayout > |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
. |
activity |
android : name = » .FullscreenVideoActivity « |
android : configChanges = » orientation|keyboardHidden|screenSize « |
android : label = » @string/app_name « |
android : theme = » @style/FullscreenTheme »/> |
. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
. |
color name = » black_overlay » >#66000000 color > |
. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
xml version = » 1.0 » encoding = » utf-8 » ?> |
LinearLayout xmlns : android = » http://schemas.android.com/apk/res/android « |
xmlns : app = » http://schemas.android.com/apk/res-auto « |
android : layout_width = » match_parent « |
android : layout_height = » wrap_content « |
android : layout_gravity = » bottom « |
android : background = » #CC000000 « |
android : layoutDirection = » ltr « |
android : orientation = » vertical » > |
LinearLayout |
android : layout_width = » match_parent « |
android : layout_height = » wrap_content « |
android : gravity = » center « |
android : orientation = » horizontal « |
android : paddingTop = » 4dp » > |
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_shuffle « |
style = » @style/ExoMediaButton.Shuffle »/> |
ImageButton |
android : id = » @id/exo_repeat_toggle « |
style = » @style/ExoMediaButton »/> |
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 »/> |
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 : includeFontPadding = » false « |
android : paddingLeft = » 4dp « |
android : paddingRight = » 4dp « |
android : textColor = » #FFBEBEBE « |
android : textSize = » 14sp « |
android : textStyle = » bold »/> |
com .google.android.exoplayer2.ui.DefaultTimeBar |
android : id = » @id/exo_progress « |
android : layout_width = » 0dp « |
android : layout_height = » 26dp « |
android : layout_weight = » 1 »/> |
TextView |
android : id = » @id/exo_duration « |
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 »/> |
FrameLayout |
android : id = » @+id/exo_fullscreen_button « |
android : layout_width = » 32dp « |
android : layout_height = » wrap_content « |
android : layout_gravity = » right » > |
ImageView |
android : id = » @+id/exo_fullscreen_icon « |
android : layout_width = » 40dp « |
android : layout_height = » 40dp « |
android : layout_gravity = » center « |
android : adjustViewBounds = » true « |
android : scaleType = » fitCenter « |
app : srcCompat = » @drawable/ic_fullscreen »/> |
FrameLayout > |
LinearLayout > |
LinearLayout > |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
xml version = » 1.0 » encoding = » utf-8 » ?> |
merge xmlns : android = » http://schemas.android.com/apk/res/android » > |
com .google.android.exoplayer2.ui.AspectRatioFrameLayout |
android : id = » @id/exo_content_frame « |
android : layout_width = » match_parent « |
android : layout_height = » match_parent « |
android : layout_gravity = » center » > |
Video surface will be inserted as the first child of the content frame. —> |
View |
android : id = » @id/exo_shutter « |
android : layout_width = » match_parent « |
android : layout_height = » match_parent « |
android : background = » @android:color/black »/> |
ImageView |
android : id = » @id/exo_artwork « |
android : layout_width = » match_parent « |
android : layout_height = » match_parent « |
android : scaleType = » fitXY »/> |
com .google.android.exoplayer2.ui.SubtitleView |
android : id = » @id/exo_subtitles « |
android : layout_width = » match_parent « |
android : layout_height = » match_parent »/> |
com .google.android.exoplayer2.ui.AspectRatioFrameLayout> |
FrameLayout |
android : id = » @id/exo_overlay « |
android : layout_width = » match_parent « |
android : layout_height = » match_parent »/> |
com .google.android.exoplayer2.ui.PlayerControlView |
android : id = » @id/exo_controller « |
android : layout_width = » match_parent « |
android : layout_height = » match_parent »/> |
merge > |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
public class ExoPlayerViewManager < |
private static final String TAG = » ExoPlayerViewManager » ; |
public static final String EXTRA_VIDEO_URI = » video_uri » ; |
private static Map String , ExoPlayerViewManager > instances = new HashMap<> (); |
private Uri videoUri; |
public static ExoPlayerViewManager getInstance ( String videoUri ) < |
ExoPlayerViewManager instance = instances . get(videoUri); |
if (instance == null ) < |
instance = new ExoPlayerViewManager (videoUri); |
instances . put(videoUri, instance); |
> |
return instance; |
> |
private SimpleExoPlayer player; |
private boolean isPlayerPlaying; |
private ExoPlayerViewManager ( String videoUri ) < |
this . videoUri = Uri . parse(videoUri); |
> |
public void prepareExoPlayer ( Context context , PlayerView exoPlayerView ) < |
if (context == null || exoPlayerView == null ) < |
return ; |
> |
if (player == null ) < |
// Create a new player if the player is null or |
// we want to play a new video |
// Do all the standard ExoPlayer code here. |
// Prepare the player with the source. |
player . prepare(videoSource); |
> |
player . clearVideoSurface(); |
player . setVideoSurfaceView(( SurfaceView ) exoPlayerView . getVideoSurfaceView()); |
player . seekTo(player . getCurrentPosition() + 1 ); |
exoPlayerView . setPlayer(player); |
> |
public void releaseVideoPlayer () < |
if (player != null ) < |
player . release(); |
> |
player = null ; |
> |
public void goToBackground () < |
if (player != null ) < |
isPlayerPlaying = player . getPlayWhenReady(); |
player . setPlayWhenReady( false ); |
> |
> |
public void goToForeground () < |
if (player != null ) < |
player . setPlayWhenReady(isPlayerPlaying); |
> |
> |
> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
// Fullscreen related code taken from Android Studio blueprint |
public class FullscreenVideoActivity extends AppCompatActivity < |
/** |
* Some older devices needs a small delay between UI widget updates |
* and a change of the status and navigation bar. |
*/ |
private static final int UI_ANIMATION_DELAY = 300 ; |
private final Handler mHideHandler = new Handler (); |
private View mContentView; |
private final Runnable mHidePart2Runnable = new Runnable () < |
@SuppressLint ( » InlinedApi » ) |
@Override |
public void run () < |
// Delayed removal of status and navigation bar |
// Note that some of these constants are new as of |
// API 19 (KitKat). It is safe to use them, as they are inlined |
// at compile-time and do nothing on earlier devices. |
mContentView . setSystemUiVisibility( View . SYSTEM_UI_FLAG_LOW_PROFILE |
| View . SYSTEM_UI_FLAG_FULLSCREEN |
| View . SYSTEM_UI_FLAG_LAYOUT_STABLE |
| View . SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
| View . SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
| View . SYSTEM_UI_FLAG_HIDE_NAVIGATION ); |
> |
>; |
private final Runnable mHideRunnable = new Runnable () < |
@Override |
public void run () < |
hide(); |
> |
>; |
private String mVideoUri; |
@Override |
public void onCreate ( Bundle savedInstanceState ) < |
super . onCreate(savedInstanceState); |
setContentView( R . layout . activity_fullscreen_video); |
mContentView = findViewById( R . id . enclosing_layout); |
PlayerView playerView = findViewById( R . id . player_view); |
mVideoUri = getIntent() . getStringExtra( ExoPlayerViewManager . EXTRA_VIDEO_URI ); |
ExoPlayerViewManager . getInstance(mVideoUri) |
.prepareExoPlayer( this , playerView); |
// Set the fullscreen button to «close fullscreen» icon |
View controlView = playerView . findViewById( R . id . exo_controller); |
ImageView fullscreenIcon = controlView . findViewById( R . id . exo_fullscreen_icon); |
fullscreenIcon . setImageResource( R . drawable . exo_controls_fullscreen_exit); |
controlView . findViewById( R . id . exo_fullscreen_button) |
.setOnClickListener( new View . OnClickListener () < |
@Override |
public void onClick ( View v ) < |
finish(); |
> |
>); |
> |
@Override |
public void onResume () < |
super . onResume(); |
ExoPlayerViewManager . getInstance(mVideoUri) . goToForeground(); |
> |
@Override |
public void onPause () < |
super . onPause(); |
ExoPlayerViewManager . getInstance(mVideoUri) . goToBackground(); |
> |
@Override |
public void onPostCreate ( Bundle savedInstanceState ) < |
super . onPostCreate(savedInstanceState); |
// Trigger the initial hide() shortly after the activity has been |
// created, to briefly hint to the user that UI controls |
// are available. |
delayedHide(); |
> |
private void hide () < |
// Schedule a runnable to remove the status and navigation bar after a delay |
mHideHandler . postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY ); |
> |
/** |
* Schedules a call to hide() in delay milliseconds, canceling any |
* previously scheduled calls. |
*/ |
private void delayedHide () < |
mHideHandler . removeCallbacks(mHideRunnable); |
mHideHandler . postDelayed(mHideRunnable, 100 ); |
> |
> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
public class MyActivity extends AppCompatActivity < |
private List String > mVideoUrls; |
@Override |
public void onCreate ( Bundle savedInstanceState ) < |
super . onCreate(savedInstanceState); |
// Your activity setup code. |
for ( String videoUrl : mVideoUrls) < |
setupPlayerView(videoView, videoUrl); |
> |
> |
@Override |
public void onResume () < |
super . onResume(); |
for ( String videoUrl : mVideoUrls) < |
ExoPlayerViewManager . getInstance(videoUrl) . goToForeground(); |
> |
> |
@Override |
public void onPause () < |
super . onPause(); |
for ( String videoUrl : mVideoUrls) < |
ExoPlayerViewManager . getInstance(videoUrl) . goToBackground(); |
> |
> |
@Override |
public void onDestroyView () < |
super . onDestroyView(); |
for ( String videoUrl : mVideoUrls) < |
ExoPlayerViewManager . getInstance(videoUrl) . releaseVideoPlayer(); |
> |
> |
private void setupPlayerView ( final PlayerView videoView , final String videoUrl ) < |
ExoPlayerViewManager . getInstance(videoUrl) . prepareExoPlayer(getContext(), videoView); |
ExoPlayerViewManager . getInstance(videoUrl) . goToForeground(); |
View controlView = videoView . findViewById( R . id . exo_controller); |
controlView . findViewById( R . id . exo_fullscreen_button) |
.setOnClickListener( new View . OnClickListener () < |
@Override |
public void onClick ( View v ) < |
Intent intent = new Intent (getContext(), FullscreenVideoActivity . class); |
intent . putExtra( ExoPlayerViewManager . EXTRA_VIDEO_URI , videoUrl); |
getActivity() . startActivity(intent); |
> |
>); |
> |
> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Источник