'How to place the controls of an Exoplayer outside of the PlayerView

I'm using Exoplayer2 to show videos in my application. I need the controls to be visible at all time. I can archive this by setting app:show_timeout="0". But when the controls are always visible they take up space in the PlayerView.

I would like to show the controls beneath the PlayerView, so that I can always show to whole video.

This is my layout file:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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"
    tools:context=".MediaPlayerActivity"
    android:theme="@android:style/Theme.NoTitleBar"
    android:contentDescription="hf_hide_help">

    <com.google.android.exoplayer2.ui.PlayerView
        android:id="@+id/playerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:use_controller="true"
        app:rewind_increment="0"
        app:fastforward_increment="0"
        app:repeat_toggle_modes="none"
        app:show_timeout="0"
        />
</RelativeLayout>

And this is my exo_player_control_view.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:layoutDirection="ltr"
    android:background="#CC000000"
    android:orientation="vertical">

    <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_play"
            style="@style/ExoMediaButton.Play"/>

        <ImageButton android:id="@id/exo_pause"
            style="@style/ExoMediaButton.Pause"/>

        <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"/>

        <com.google.android.exoplayer2.ui.DefaultTimeBar
            android:id="@id/exo_progress"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="26dp"/>

        <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>

This is what it looks like: Player

And this is what it looks like, when I click on the player - the controls hide. Player without controls



Solution 1:[1]

Your use-case is not something general and ExoPlayer doesn't support it. In your case, you will need to disable the default controls and add your own custom controls beneath the player view. It would be very easy to build the layout and add the actions to them.

Update: (To answer Gyan's comment about time-bar)

Two approaches: (I explain the strategies since thousands of code examples are available already on SO and other sites)

1 - My suggestion which has worked perfectly for my needs: override default controls view of the exoplayer (having a view with the same name in your layouts folder would do the job). Then remove all unnecessary buttons which you brought down in your custom view but just keep the time bar in overridden view. By doing that, all the appearing / disappearing behaviors are automatically kept for the time bar and you will have the rest in your own view. You wouldn't need to worry about handling the time bar if you liked that approach.

2- Just in case you insist on bringing the time bar out under the player window, what you need is a horizontal progressbar (Notice that you can customize the look of progress view to match your design). Then write a listener on exoplayer to take the timings (passed / remaining / current play-time) and calculate and update the progress of the bar based upon them. Remember that the progress bar needs to be touchable (and focusable) so that the user could drag it back and forth. Then you would need to re-calculate playtime when user drags the progress. (lots of coding and calculations obviously but almost easy stuff). Hope it works for you. If you needed code samples please let me know.

Solution 2:[2]

Here I have play/pause button on the video and other controls under the video, here is how to do it.

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
        android:orientation="vertical">
    
        <com.google.android.exoplayer2.ui.StyledPlayerView
            android:id="@+id/video_view"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_gravity="center"
            app:auto_show="true"
            app:controller_layout_id="@layout/custom_exo_overlay_controller_view"
            app:layout_constraintBottom_toTopOf="@id/exoBottomControls"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="1.0"
            app:repeat_toggle_modes="none"
            app:resize_mode="fixed_width"
            app:surface_type="surface_view"
            app:use_controller="true" />
    
        <com.google.android.exoplayer2.ui.StyledPlayerControlView
            android:id="@+id/exoBottomControls"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            app:controller_layout_id="@layout/custom_exo_bottom_controller_view"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/video_view"
            app:show_timeout="0" />
    
    </androidx.constraintlayout.widget.ConstraintLayout> 

here i am using both use_controller=true and StyledPlayerControlView.

controls which should appear on the video should go in your custom_exo_overlay_controller_view other views which should appear under the video should go in custom_exo_bottom_controller_view.

here is custom_exo_overlay_controller_view.xml

    <androidx.constraintlayout.widget.ConstraintLayout 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="match_parent">
    
        <ImageButton
            android:id="@id/exo_play_pause"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>

and custom_exo_bottom_controller_view.xml

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <View
            android:id="@id/exo_controls_background"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@color/color_secondary_black"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <TextView
            android:id="@id/exo_position"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginBottom="8dp"
            android:textColor="@color/colorWhite"
            android:textSize="14sp"
            app:layout_constraintBottom_toTopOf="@+id/exo_progress"
            app:layout_constraintStart_toStartOf="parent"
            tools:text="00:50" />
    
        <TextView
            android:id="@id/exo_duration"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="8dp"
            android:textColor="@color/colorWhite"
            android:textSize="14sp"
            app:layout_constraintBottom_toTopOf="@+id/exo_progress"
            app:layout_constraintEnd_toEndOf="parent"
            tools:text="02:50" />
    
        <ImageButton
            android:id="@+id/exo_sound_on"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="32dp"
            android:background="@android:color/transparent"
            android:src="@drawable/ic_sound_on"
            app:layout_constraintBottom_toTopOf="@+id/exo_duration"
            app:layout_constraintEnd_toEndOf="parent" />
    
        <ImageButton
            android:id="@+id/exo_sound_off"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="32dp"
            android:background="@android:color/transparent"
            android:src="@drawable/ic_sound_off"
            android:visibility="gone"
            app:layout_constraintBottom_toTopOf="@+id/exo_duration"
            app:layout_constraintEnd_toEndOf="parent" />
    
        <com.google.android.exoplayer2.ui.DefaultTimeBar
            android:id="@id/exo_progress"
            android:layout_width="0dp"
            android:layout_height="52dp"
            app:buffered_color="@android:color/transparent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:played_color="@android:color/transparent"
            app:scrubber_drawable="@drawable/ic_scrubber"
            app:touch_target_height="52dp"
            app:unplayed_color="@android:color/transparent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>

also you should assign your controller like this

        player = ExoPlayer.Builder(binding.root.context).build().also {
            binding.videoView.player = it
            binding.exoBottomControls.player = it
            val mediaItem = MediaItem.fromUri(video.url)
            it.setMediaItem(mediaItem)
            it.seekTo(video.currentWindow, video.playbackPosition)
            it.prepare()
        }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Amr