Cómo usar controles de transporte de Leanback

La biblioteca de Leanback de AndroidX tiene nuevos controles de reproducción que proporcionan una experiencia del usuario mejorada. Para las apps de video, los controles de transporte admiten limpieza de video con controles de retroceso y avance. Mientras se limpia el video, se muestran miniaturas para ayudarte a navegar por el video.

La biblioteca incluye clases abstractas además de implementaciones integradas que brindan un control más detallado para desarrolladores. Mediante las implementaciones compiladas previamente, puedes compilar una app llena de funciones sin tener que codificar demasiado. Si necesitas una mayor personalización, puedes ampliar cualquiera de los componentes compilados previamente de la biblioteca.

Controles y reproductor

La biblioteca de Leanback separa la IU que contiene los controles de transporte del reproductor del video. Esto se logra con dos componentes: un fragmento de reproducción para mostrar los controles de transporte (y, opcionalmente, el video) y un adaptador del reproductor para encapsular un reproductor multimedia.

Fragmento de reproducción

El objeto Activity de la IU de tu app debería usar PlaybackFragment o VideoFragment. Ambos contienen los controles de transporte de Leanback.

  • Un objeto PlaybackFragment anima sus controles de transporte para mostrarlos/ocultarlos según sea necesario.
  • Un objeto VideoFragment amplía PlaybackFragment y tiene un elemento SurfaceView para procesar video.

Puedes procesar el objeto ObjectAdapter de un fragmento para mejorar la IU. Por ejemplo, puedes usar setAdapter() para agregar la fila "videos relacionados".

PlayerAdapter

PlayerAdapter es una clase abstracta que controla el reproductor multimedia subyacente. Los desarrolladores pueden elegir la implementación del MediaPlayerAdapter compilado previamente o escribir su propia implementación de esta clase.

Cómo unir las piezas

Debes usar algún elemento de unión de controles a fin de conectar el fragmento de reproducción con el reproductor. La biblioteca de Leanback proporciona los siguientes dos tipos de unión:

unión de control de transporte de Leanback

Si quieres que tu app sea compatible con la limpieza de video, debes usar PlaybackTransportControlGlue.

También tienes que especificar un "host de unión" que vincule la unión con el fragmento de reproducción, que dibuje los controles de transporte de la IU y mantenga su estado, y que devuelva los eventos de control de transporte a la unión. El host debe coincidir con el tipo de fragmento de reproducción. Usa PlaybackFragmentGlueHost con un objeto PlaybackFragment y VideoFragmentGlueHost con un objeto VideoFragment.

A continuación, puedes ver una ilustración que muestra cómo se unen las piezas de un control de transporte de Leanback:

unión de control de transporte de Leanback

El código que une tu app debería estar dentro de PlaybackFragment o VideoFragment que define la IU.

En el siguiente ejemplo, la app crea una instancia de PlaybackTransportControlGlue, la llama playerGlue y conecta su objeto VideoFragment con un objeto MediaPlayerAdapter recientemente creado. Como se trata de un objeto VideoFragment, el código de configuración llama a setHost() para adjuntar un elemento VideoFragmentGlueHost a playerGlue. El código se incluye dentro de la clase que amplía VideoFragment.

Kotlin

    class MyVideoFragment : VideoFragment() {

      fun onCreate(savedInstanceState: Bundle) {
          super.onCreate(savedInstanceState)
          val playerGlue = PlaybackTransportControlGlue(getActivity(),
              MediaPlayerAdapter(getActivity()))
          playerGlue.setHost(VideoFragmentGlueHost(this))
          playerGlue.addPlayerCallback(object : PlaybackGlue.PlayerCallback() {
              override fun onPreparedStateChanged(glue: PlaybackGlue) {
                  if (glue.isPrepared()) {
                      playerGlue.seekProvider = MySeekProvider()
                      playerGlue.play()
                  }
              }
          })
          playerGlue.setSubtitle("Leanback artist")
          playerGlue.setTitle("Leanback team at work")
          val uriPath = "android.resource://com.example.android.leanback/raw/video"
          playerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath))
      }
    }
    

Java

    public class MyVideoFragment extends VideoFragment {

      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          final PlaybackTransportControlGlue<MediaPlayerAdapter> playerGlue =
                  new PlaybackTransportControlGlue(getActivity(),
                          new MediaPlayerAdapter(getActivity()));
          playerGlue.setHost(new VideoFragmentGlueHost(this));
          playerGlue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
              @Override
              public void onPreparedStateChanged(PlaybackGlue glue) {
                  if (glue.isPrepared()) {
                      playerGlue.setSeekProvider(new MySeekProvider());
                      playerGlue.play();
                  }
              }
          });
          playerGlue.setSubtitle("Leanback artist");
          playerGlue.setTitle("Leanback team at work");
          String uriPath = "android.resource://com.example.android.leanback/raw/video";
          playerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath));
      }
    }
    

Ten en cuenta de que el código de configuración también define un elemento PlayerAdapter.Callback que permite administrar eventos del reproductor multimedia.

Cómo personalizar la unión de IU

Puedes personalizar los elementos PlaybackBannerControlGlue y PlaybackTrabsportControlGlue para cambiar el objeto PlaybackControlsRow.

Cómo personalizar el título y la descripción

Para personalizar el título y la descripción de la parte superior de los controles de reproducción, anula onCreateRowPresenter():

Kotlin

    override fun onCreateRowPresenter(): PlaybackRowPresenter {
        return super.onCreateRowPresenter().apply {
            (this as? PlaybackTransportRowPresenter)
                    ?.setDescriptionPresenter(MyCustomDescriptionPresenter())
        }
    }
    

Java

    @Override
    protected PlaybackRowPresenter onCreateRowPresenter() {
      PlaybackTransportRowPresenter presenter = (PlaybackTransportRowPresenter) super.onCreateRowPresenter();
      presenter.setDescriptionPresenter(new MyCustomDescriptionPresenter());
      return presenter;
    }
    

Cómo agregar controles

La unión de los controles muestra controles para acciones en un objeto PlaybackControlsRow.

Las acciones de PlaybackControlsRow se asignan a dos grupos: acciones primarias y acciones secundarias. Los controles del grupo primario aparecen sobre la barra de búsqueda y los controles del grupo secundario aparecen debajo de ella. Inicialmente, solo hay una acción primaria para el botón de reproducción/pausa, y no hay acciones secundarias.

Para agregar acciones a los grupos de acciones primarias y secundarias, anula onCreatePrimaryActions() y onCreateSecondaryActions().

Kotlin

    private lateinit var repeatAction: PlaybackControlsRow.RepeatAction
    private lateinit var pipAction: PlaybackControlsRow.PictureInPictureAction
    private lateinit var thumbsUpAction: PlaybackControlsRow.ThumbsUpAction
    private lateinit var thumbsDownAction: PlaybackControlsRow.ThumbsDownAction
    private lateinit var skipPreviousAction: PlaybackControlsRow.SkipPreviousAction
    private lateinit var skipNextAction: PlaybackControlsRow.SkipNextAction
    private lateinit var fastForwardAction: PlaybackControlsRow.FastForwardAction
    private lateinit var rewindAction: PlaybackControlsRow.RewindAction

    override fun onCreatePrimaryActions(primaryActionsAdapter: ArrayObjectAdapter) {
        // Order matters, super.onCreatePrimaryActions() will create the play / pause action.
        // Will display as follows:
        // play/pause, previous, rewind, fast forward, next
        //   > /||      |<        <<        >>         >|
        super.onCreatePrimaryActions(primaryActionsAdapter)
        primaryActionsAdapter.apply {
            add(skipPreviousAction)
            add(rewindAction)
            add(fastForwardAction)
            add(skipNextAction)
        }
    }

    override fun onCreateSecondaryActions(adapter: ArrayObjectAdapter?) {
        super.onCreateSecondaryActions(adapter)
        adapter?.apply {
            add(thumbsDownAction)
            add(thumbsUpAction)
        }
    }
    

Java

    private PlaybackControlsRow.RepeatAction repeatAction;
    private PlaybackControlsRow.PictureInPictureAction pipAction;
    private PlaybackControlsRow.ThumbsUpAction thumbsUpAction;
    private PlaybackControlsRow.ThumbsDownAction thumbsDownAction;
    private PlaybackControlsRow.SkipPreviousAction skipPreviousAction;
    private PlaybackControlsRow.SkipNextAction skipNextAction;
    private PlaybackControlsRow.FastForwardAction fastForwardAction;
    private PlaybackControlsRow.RewindAction rewindAction;

    @Override
    protected void onCreatePrimaryActions(ArrayObjectAdapter primaryActionsAdapter) {
        // Order matters, super.onCreatePrimaryActions() will create the play / pause action.
        // Will display as follows:
        // play/pause, previous, rewind, fast forward, next
        //   > /||      |<        <<        >>         >|
        super.onCreatePrimaryActions(primaryActionsAdapter);
        primaryActionsAdapter.add(skipPreviousAction);
        primaryActionsAdapter.add(rewindAction);
        primaryActionsAdapter.add(fastForwardAction);
        primaryActionsAdapter.add(skipNextAction);
    }

    @Override
    protected void onCreateSecondaryActions(ArrayObjectAdapter adapter) {
        super.onCreateSecondaryActions(adapter);
        adapter.add(thumbsDownAction);
        adapter.add(thumbsUpAction);
    }
    

Debes anular onActionClicked() para administrar las nuevas acciones.

Kotlin

    override fun onActionClicked(action: Action) {
        when(action) {
            rewindAction -> {
                // Handle Rewind
            }
            fastForwardAction -> {
                // Handle FastForward
            }
            thumbsDownAction -> {
                // Handle ThumbsDown
            }
            thumbsUpAction -> {
                // Handle ThumbsUp
            }
            else ->
                // The superclass handles play/pause and delegates next/previous actions to abstract methods,
                // so those two methods should be overridden rather than handling the actions here.
                super.onActionClicked(action)
        }
    }

    override fun next() {
        // Skip to next item in playlist.
    }

    override fun previous() {
        // Skip to previous item in playlist.
    }
    

Java

    @Override
    public void onActionClicked(Action action) {
        if (action == rewindAction) {
            // Handle Rewind
        } else if (action == fastForwardAction ) {
            // Handle FastForward
        } else if (action == thumbsDownAction) {
            // Handle ThumbsDown
        } else if (action == thumbsUpAction) {
            // Handle ThumbsUp
        } else {
            // The superclass handles play/pause and delegates next/previous actions to abstract methods,
            // so those two methods should be overridden rather than handling the actions here.
            super.onActionClicked(action);
        }
    }

    @Override
    public void next() {
        // Skip to next item in playlist.
    }

    @Override
    public void previous() {
        // Skip to previous item in playlist.
    }
    

En casos especiales, es posible que quieras implementar tu propio objeto PlaybackTransportRowPresenter para procesar controles personalizados y responder a acciones de búsqueda con PlaybackSeekUi.

Limpieza de video

Si tu app usa un elemento VideoFragment, es posible que quieras ofrecer compatibilidad con limpieza de video.

limpieza

Debes proporcionar una implementación de PlaybackSeekDataProvider. Este componente proporciona miniaturas del video durante el desplazamiento. Debes implementar tu propio proveedor al extender PlaybackSeekDataProvider. Consulta el ejemplo que aparece en la app de muestra de Android Leanback Showcase en el repositorio de GitHub de Android TV.