Usar controles de transporte

Criar da melhor forma com o Compose
Crie interfaces incríveis com o mínimo de código usando o Jetpack Compose para o SO do Android TV.

O kit de ferramentas de interface do Leanback tem controles de reprodução que oferecem uma experiência do usuário melhorada. Para apps de vídeo, os controles de transporte são compatíveis com a barra de progressão de vídeo com os controles para avançar e voltar. Enquanto passa o cursor, a tela mostra miniaturas para ajudar a navegar pelo vídeo.

A biblioteca inclui classes abstratas, bem como implementações pré-criadas e prontas para uso, que oferecem um controle mais granular para desenvolvedores. Com as implementações pré-criadas, é possível criar rapidamente um app repleto de recursos sem muita programação. Se precisar de mais personalização, você pode estender qualquer um dos componentes pré-criados da biblioteca.

Controles e player

O kit de ferramentas de interface do Leanback separa a interface de 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 um PlaybackSupportFragment ou um 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. Os desenvolvedores podem escolher a implementação pré-criada de MediaPlayerAdapter ou criar a própria implementação dessa classe.

Agrupar as partes

É necessário usar algum "cola de controles" para conectar o fragmento de reprodução ao player. A biblioteca flexível oferece dois tipos de ligação:

agrupador de controle de transporte da leanback

Se você quiser que seu app seja compatível com a barra de progressão de vídeo, use PlaybackTransportControlGlue.

Também é necessário especificar um "host de agrupador" que vincula o agrupador ao fragmento de reprodução, desenha os controles de transporte na interface, mantém o estado e transmite os eventos de controle de transporte de volta para o agrupador. O host precisa corresponder ao tipo do fragmento de reprodução. Use PlaybackSupportFragmentGlueHost com um PlaybackFragment e VideoSupportFragmentGlueHost com um VideoFragment.

Esta é uma ilustração que mostra como as partes de um controle de transporte do ChromeVox se encaixam:

agrupador de controle de transporte da leanback

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

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

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

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 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 no 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 aparecem abaixo dela. Inicialmente, há apenas uma ação principal para o botão assistir/pausar, sem ações secundárias.

Para adicionar ações aos grupos principal e secundário, substitua 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);
}

Substitua 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

É necessário fornecer uma implementação de PlaybackSeekDataProvider. Esse componente exibe miniaturas no vídeo que são usadas ao mover a barra. Você precisa implementar seu próprio provedor estendendo PlaybackSeekDataProvider. Confira o exemplo no app YouTube Showcase.