Usar controles de transporte do Leanback

A biblioteca Leanback androidx tem novos controles de mídia que oferecem uma experiência do usuário aprimorada. Em apps de vídeo, os controles de transporte são compatíveis com a barra de progressão de vídeo com controles para frente/para trás. Ao deslizar, a tela mostra 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 interface com os controles de transporte do player que reproduz o vídeo. Isso é feito com dois componentes: um fragmento de suporte à reprodução para exibir os controles de transporte (e, opcionalmente, o vídeo) e um adaptador do player para encapsular um player de mídia.

Fragmento de reprodução

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

Você pode personalizar o ObjectAdapter de um fragmento para melhorar a interface. 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 escolher a implementação pré-criada da MediaPlayerAdapter ou escrever 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 vínculo:

agrupador de controle de transporte da leanback

Se você quiser que seu app ofereça suporte à barra de progressão de vídeo, use PlaybackTransportControlGlue.

Também é necessário especificar um "host de cola" que vincule o agrupador ao fragmento de reprodução, desenhe os controles de transporte na interface, mantenha o estado deles e transmita os eventos de controle de transporte de volta ao agrupador. O host precisa corresponder ao tipo do fragmento de reprodução. Use PlaybackSupportFragmentGlueHost com um PlaybackFragment e VideoSupportFragmentGlueHost com um VideoFragment.

Veja uma ilustração que mostra como as partes de um controle de transporte da Leanback se encaixam:

agrupador de controle de transporte da leanback

O código que une seu app precisa estar dentro da PlaybackSupportFragment ou do VideoSupportFragment que define a interface.

No exemplo abaixo, o app cria uma instância de PlaybackTransportControlGlue, nomeando-a como playerGlue, e conecta a VideoSupportFragment a uma MediaPlayerAdapter recém-criada. Como esse é um VideoSupportFragment, o código de configuração chama setHost() para anexar um VideoSupportFragmentGlueHost a playerGlue. O código está 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));
  }
}

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

Personalizar o agrupador da IU

É possível 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 de cima dos controles de reprodução, substitua 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 mostrados acima da barra de busca, e os controles do grupo secundário são mostrados abaixo da barra. Inicialmente, há apenas uma ação principal para o botão "Reproduzir/pausar" e nenhuma ação secundária.

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

É necessário 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 seu próprio PlaybackTransportRowPresenter para renderizar 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

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