Добавить кнопку для полноэкранного видео с Exo Player

Я использую exoPlayer для потоковой передачи видео в мое приложение и пока работает нормально. Теперь я хотел бы добавить некоторые дополнительные функции, такие как кнопка в правом нижнем углу, чтобы она работала как "кнопка полного экрана".

Но есть две проблемы. Во-первых, это ExoPlayer Похоже, он не предоставляет никакого класса Control, поэтому вы можете просто добавить кнопку и переопределить ее функциональность. Я предполагаю, что мне нужно отобразить эту кнопку в верхней части видео, чтобы мне пришлось обернуть оба в FrameLayout и добавить гравитацию = основание для кнопки или есть другой способ?

Вторая проблема: если пользователь нажимает кнопку полноэкранного режима, что мне делать дальше? Добавить еще Fragment с просмотром видео на весь экран? Но как я могу начать видео с того места, где он был, когда пользователь нажал на кнопку, а не запустить его с самого начала? Я не могу найти в exoPlayer ничего относительно начала с определенного времени.

4 ответа

Если вы используете SimpleExoPlayerView Есть способ настроить вид проигрывателя, особенно вид элемента управления. Проверьте документацию SimpleExoPlayerView:

Атрибуты

Следующие атрибуты могут быть установлены на SimpleExoPlayerView при использовании в файле макета XML:...

controller_layout_id - Определяет идентификатор ресурса макета, который будет раздуваться дочерним элементом PlaybackControlView, Подробности смотрите ниже.

  • Соответствующий метод: нет

  • По умолчанию: R.id.exo_playback_control_view

...

Таким образом, в основном вы можете предоставить свой собственный файл макета для контроллера (вы можете скопировать макет exo_playback_control_view, упомянутый в документации, который является стандартным, и настроить его по своему усмотрению. Обратите внимание, что вам нужно будет предоставить те же идентификаторы вида для существующие элементы управления (так что лучше всего скопировать это), как указано в документах PlaybackControlView:

Переопределение файла макета

Чтобы настроить макет PlaybackControlView во всем приложении или только для определенных конфигураций вы можете определять файлы макетов exo_playback_control_view.xml в каталогах res/layout* вашего приложения. Эти макеты будут иметь приоритет над макетом, предоставляемым библиотекой ExoPlayer, и будут раздуты для использования PlaybackControlView, Представление идентифицирует и связывает своих потомков, ища следующие идентификаторы:

  • exo_play - кнопка воспроизведения.

  • exo_pause - кнопка паузы.

  • exo_ffwd - кнопка быстрой перемотки вперед.

  • exo_rew - кнопка перемотки.

  • exo_prev - кнопка предыдущего трека.

  • exo_next - кнопка следующего трека.

  • exo_position - текстовое представление, отображающее текущую позицию воспроизведения.

  • exo_duration - текстовое представление, отображающее текущую продолжительность мультимедиа.

  • exo_progress - строка поиска, которая обновляется во время воспроизведения и позволяет искать.

Все дочерние представления являются необязательными и поэтому могут быть опущены, если не требуются, однако, если они определены, они должны иметь ожидаемый тип.

Ниже приведен индивидуальный макет с полноэкранной кнопкой. Вы получаете ссылку на кнопку view.findViewById(R.id.exo_fullscreen_button) и приложить OnClickListener на кнопку. внутри onClick() Вы можете начать работу в полноэкранном режиме (вы можете определить ее в полноэкранном режиме в AndroidManifest.xml или программно) или показать другой фрагмент, который имеет SimpleExoPlayerView занимая весь экран. Что касается вашего второго пункта, вы можете получить позицию воспроизведения следующим образом: playbackPosition = player.getCurrentPosition() и передайте его новому полноэкранному действию / фрагменту как дополнительное намерение. Затем в этом полноэкранном разделе "Деятельность / фрагмент" вы загружаете видео и извлекаете playbackPosition стоимость и вызов:

player.seekTo(playbackPosition);
player.setPlayWhenReady(true);

Вот файл макета элемента управления:

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

Решение с новым полноэкранным действием поверх текущего, передавая ему позицию воспроизведения

Другой ответ велик, и указывает на вас в правильном направлении, но это скорее теоретическая, и я все еще должен был заполнить некоторые пробелы и решить несколько вещей при написании кода. Постараюсь его дополнить.

Начните с копирования макета exo_playback_control_view.xml из библиотеки ExoPlayer в res/layout. Ссылка на файл: https://github.com/google/ExoPlayer/blob/release-v2/library/ui/src/main/res/layout/exo_playback_control_view.xml

Измените макет, чтобы добавить полноэкранную кнопку, которая может быть примерно такой:

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

Обратите внимание, что у вас могут быть разные макеты для разных PlayerViews с помощью атрибута app:controller_layout_id. Вы также можете удалить кнопки проигрывателя, если они вам не нужны. Это описано в документации: https://exoplayer.dev/ui-components.html.

Когда у вас есть кнопка полноэкранного режима, установите OnClickListener в теме:

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

Добавить 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)
    }

}

Добавьте активность в манифест:

<activity
    android:name=".ui.FullScreenVideoActivity"
    android:screenOrientation="landscape" <-- this is optional
    android:theme="@style/AppTheme.NoActionBar.FullScreen" />

Наконец добавьте тему в styles.xml:

<style name="AppTheme.NoActionBar.FullScreen">
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowContentOverlay">@null</item>
</style>

Вот и все! Надеюсь, это поможет.

Альтернативное решение в текущей деятельности

Вышеупомянутое решение отлично работает и тривиально. Однако это требует повторной загрузки фрагментов уже загруженного вами видео, что прерывает воспроизведение: /

Я пытался избежать этого, следуя этим указаниям. Вот что у меня есть на данный момент:

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)

Обратите внимание, что для этого вам нужно установить android:configChanges="orientation|screenSize|layoutDirection" к вашей деятельности в манифесте.

Теперь вы можете отобразить полноэкранную кнопку, установив прослушиватель кликов наStyledPlayerView:

      videoPlayer.setFullscreenButtonClickListener {}

Где вид:

      <com.google.android.exoplayer2.ui.StyledPlayerView/>

Я сделал для этого функцию расширения kotlin. Вам нужно два Exoplayer, и расширение будет переключаться между двумя игроками (полноэкранный и нормальный).

    @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)
}

Например, с этим макетом:

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

Вы можете использовать это так:

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()
    }
}

Если вы предпочитаете не копировать этот код, вы можете добавить эту библиотеку. То же самое: https://github.com/Norulab/android-exoplayer-fullscreen

Другие вопросы по тегам