Usar controles de transporte

Criar melhor 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 usuário do YouTube tem controles de reprodução que oferecem uma interface experiência do usuário. 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. Ao arrastar o marcador, 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 controle mais granular para os desenvolvedores. Usar o modelo você pode criar rapidamente um app repleto de recursos sem muita programação. Se precisar de mais personalização, você pode estender qualquer um dos componentes de solução.

Controles e player

O kit de ferramentas da interface Leanback separa a interface de controles de transporte do player que exibe 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 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 interface. Por exemplo, use setAdapter() para adicionar um "vídeos relacionados" linha de comando.

PlayerAdapter

PlayerAdapter é uma classe abstrata que controla o player de mídia subjacente. Os desenvolvedores podem escolher a 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. O botão de relaxamento fornece dois tipos de cola:

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, mantenha o estado deles e transmita os eventos de controle de transporte de volta para o agrupador. O host precisa corresponder ao tipo do fragmento de reprodução. Usar PlaybackSupportFragmentGlueHost com um PlaybackFragment e VideoSupportFragmentGlueHost com um VideoFragment.

Esta é uma ilustração que mostra como as peças do controle de transporte do Flix se encaixam:

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.

Nos seguintes exemplo, o app cria uma instância de PlaybackTransportControlGlue, dê o nome playerGlue a ele. 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 para playerGlue. O código é incluído na 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 o player de mídia.

Personalizar o agrupador da IU

Você pode personalizar PlaybackBannerControlGlue e PlaybackTransportControlGlue para alterar PlaybackControlsRow

Personalizar o título e a descrição

Para personalizar o título e a descrição na parte superior do controles de mídia, substituir 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 para o grupo principal aparecem acima da barra de busca e dos controles do grupo secundário aparecem abaixo da barra de busca. 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ê deve substituir 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 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 na App Leanback Showcase (link em inglês). ,