O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Usar controles de transporte do Leanback

A biblioteca Leanback androidx tem novos controles de reprodução que oferecem uma experiência do usuário aprimorada. Para apps de vídeo, os controles de transporte são compatíveis com a barra de progressão de vídeo com controles para avançar/voltar. Enquanto a barra é movida, a tela exibe miniaturas para ajudar a navegar pelo vídeo.

A biblioteca inclui classes abstratas, bem como implementações prontas para uso, que oferecem um controle mais granular para os desenvolvedores. Usando as implementações pré-compiladas, é possível criar um app repleto de recursos sem muita programação. Se você precisar de um nível mais alto de personalização, poderá estender qualquer um dos componentes pré-compilados da biblioteca.

Controles e player

A biblioteca leanback separa a IU com os controles de transporte do player que exibe o vídeo. Essa ação é realizada com dois componentes: um fragmento de suporte para reprodução para exibir os controles de transporte (e opcionalmente o vídeo) e o adaptador do player para encapsular um player de mídia.

Fragmento de reprodução

A atividade da IU do seu app precisa usar um PlaybackSupportFragment ou um VideoSupportFragment. Ambos contêm os controles de transporte da leanback:

É possível personalizar o ObjectAdapter de um fragmento para melhorar a IU. Por exemplo, use setAdapter() para adicionar uma linha de "vídeos relacionados".

PlayerAdapter

PlayerAdapter é uma classe abstrata que controla o player de mídia subjacente. Os desenvolvedores podem optar pela implementação da MediaPlayerAdapter pré-criada ou gravar a própria implementação dessa classe.

Agrupar as partes

É necessário usar algum "agrupador de controles" para conectar o fragmento de reprodução ao player. A biblioteca leanback oferece dois tipos de agrupadores:

agrupador de controle de transporte da leanback

Se você quer que o app seja compatível com a barra de progressão de vídeo, é necessário usar PlaybackTransportControlGlue.

Você também precisa especificar um "host de agrupador" que vincule o agrupador ao fragmento de reprodução, desenhe os controles de transporte na IU mantendo o estado deles e transmita os eventos de controle de transporte novamente para o agrupador. O host precisa corresponder ao tipo do fragmento de reprodução. Use PlaybackSupportFragmentGlueHost com PlaybackFragment e VideoSupportFragmentGlueHost com um VideoFragment.

Veja uma ilustração que mostra como as partes de um controle de transporte da leanback funcionam em conjunto:

agrupador de controle de transporte da leanback

O código que agrupa seu app precisa estar dentro do PlaybackSupportFragment ou VideoSupportFragment que define a IU.

No exemplo a seguir, o app cria uma instância de PlaybackTransportControlGlue, nomeando-a como playerGlue e conecta o VideoSupportFragment a um MediaPlayerAdapter recém-criado. Como este é um VideoSupportFragment, o código de configuração chama setHost() para anexar um VideoSupportFragmentGlueHost a um playerGlue. O código é incluído dentro da classe que estende o VideoSupportFragment.

Kotlin

    class MyVideoFragment : VideoSupportFragment() {

      fun onCreate(savedInstanceState: Bundle) {
          super.onCreate(savedInstanceState)
          val playerGlue = PlaybackTransportControlGlue(getActivity(),
              MediaPlayerAdapter(getActivity()))
          playerGlue.setHost(VideoSupportFragmentGlueHost(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 VideoSupportFragment {

      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          final PlaybackTransportControlGlue<MediaPlayerAdapter> playerGlue =
                  new PlaybackTransportControlGlue(getActivity(),
                          new MediaPlayerAdapter(getActivity()));
          playerGlue.setHost(new VideoSupportFragmentGlueHost(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));
      }
    }
    

Observe que o código da configuração também define um PlayerAdapter.Callback para processar eventos do player de mídia.

Personalizar o agrupador da IU

Você pode personalizar PlaybackBannerControlGlue e PlaybackTransportControlGlue para mudar o PlaybackControlsRow.

Personalizar o título e a descrição

Para personalizar o título e a descrição na parte superior dos controles de reprodução, modifique 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;
    }
    

Adicionar controles

O agrupador de controles exibe controles para ações em uma PlaybackControlsRow.

As ações em PlaybackControlsRow são atribuídas a dois grupos: ações principais e ações secundárias. Os controles do grupo principal são exibidos acima da barra de busca e os controles do grupo secundário são exibidos abaixo dessa barra. Inicialmente, há apenas uma ação principal para o botão assistir/pausar, sem qualquer ação secundária.

É possível adicionar ações aos grupos principal e secundário modificando onCreatePrimaryActions() e 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);
    }
    

Você precisa modificar onActionClicked() para processar as novas ações.

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

Em casos especiais, convém implementar sua própria PlaybackTransportRowPresenter para processar controles personalizados e responder a ações de busca usando o PlaybackSeekUi.

Barra de progressão de vídeo

Se seu app usa um VideoSupportFragment e você quer compatibilizar com barra de progressão de vídeo.

como arrastar o marcador

É necessário disponibilizar uma implementação de PlaybackSeekDataProvider. Esse componente exibe miniaturas no vídeo que são usadas ao mover a barra. Você precisa implementar o próprio provedor, estendendo PlaybackSeekDataProvider. Vejo um exemplo com a amostra do app Android Leanback Showcase no repositório do GitHub para Android TV (em inglês). .