Eventos de player

Como ouvir eventos de reprodução

Eventos, como mudanças de estado e erros de reprodução, são informados para instâncias Player.Listener registradas. Para registrar um listener e receber esses eventos:

Kotlin

// Add a listener to receive events from the player.
player.addListener(listener)

Java

// Add a listener to receive events from the player.
player.addListener(listener);

Player.Listener tem métodos padrão vazios, então você só precisa implementar os métodos em que tem interesse. Consulte o Javadoc para uma descrição completa dos métodos e quando eles são chamados. Alguns dos métodos mais importantes estão descritos com mais detalhes abaixo.

Os listeners têm a opção de implementar callbacks de eventos individuais ou um callback onEvents genérico que é chamado após a ocorrência de um ou mais eventos juntos. Consulte Individual callbacks vs onEvents para uma explicação sobre qual é recomendado para diferentes casos de uso.

Mudanças no estado da reprodução

As mudanças no estado do player podem ser recebidas implementando onPlaybackStateChanged(@State int state) em um Player.Listener registrado. O player pode estar em um dos quatro estados de reprodução:

  • Player.STATE_IDLE: é o estado inicial, quando o player é interrompido e quando a reprodução falhou. O jogador terá apenas recursos limitados nesse estado.
  • Player.STATE_BUFFERING: o jogador não pode jogar imediatamente na posição atual. Isso acontece principalmente porque mais dados precisam ser carregados.
  • Player.STATE_READY: o player pode tocar imediatamente da posição atual.
  • Player.STATE_ENDED: o player terminou de reproduzir todas as mídias.

Além desses estados, o jogador tem uma flag playWhenReady para indicar a intenção do usuário de jogar. As mudanças nessa flag podem ser recebidas implementando onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason).

Um player está sendo reproduzido, ou seja, a posição dele está avançando e a mídia está sendo apresentada ao usuário quando as três condições abaixo são atendidas:

  • O player está no estado Player.STATE_READY
  • playWhenReady é true
  • A reprodução não é suprimida por um motivo retornado por Player.getPlaybackSuppressionReason.

Em vez de verificar essas propriedades individualmente, é possível chamar Player.isPlaying. As mudanças nesse estado podem ser recebidas com a implementação de onIsPlayingChanged(boolean isPlaying):

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onIsPlayingChanged(isPlaying: Boolean) {
      if (isPlaying) {
        // Active playback.
      } else {
        // Not playing because playback is paused, ended, suppressed, or the player
        // is buffering, stopped or failed. Check player.playWhenReady,
        // player.playbackState, player.playbackSuppressionReason and
        // player.playerError for details.
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onIsPlayingChanged(boolean isPlaying) {
        if (isPlaying) {
          // Active playback.
        } else {
          // Not playing because playback is paused, ended, suppressed, or the player
          // is buffering, stopped or failed. Check player.getPlayWhenReady,
          // player.getPlaybackState, player.getPlaybackSuppressionReason and
          // player.getPlaybackError for details.
        }
      }
    });

Erros de reprodução

Erros que fazem com que a reprodução falhe podem ser recebidos implementando onPlayerError(PlaybackException error) em um Player.Listener registrado. Quando ocorre uma falha, esse método é chamado imediatamente antes da transição do estado de reprodução para Player.STATE_IDLE. Chame ExoPlayer.prepare para tentar novamente as reproduções com falha ou interrompidas.

Algumas implementações de Player transmitem instâncias de subclasses de PlaybackException para fornecer mais informações sobre a falha. Por exemplo, ExoPlayer transmite ExoPlaybackException, que tem type, rendererIndex e outros campos específicos do ExoPlayer.

O exemplo abaixo mostra como detectar quando uma reprodução falhou devido a um problema de rede HTTP:

Kotlin

player.addListener(
  object : Player.Listener {
    override fun onPlayerError(error: PlaybackException) {
      val cause = error.cause
      if (cause is HttpDataSourceException) {
        // An HTTP error occurred.
        val httpError = cause
        // It's possible to find out more about the error both by casting and by querying
        // the cause.
        if (httpError is InvalidResponseCodeException) {
          // Cast to InvalidResponseCodeException and retrieve the response code, message
          // and headers.
        } else {
          // Try calling httpError.getCause() to retrieve the underlying cause, although
          // note that it may be null.
        }
      }
    }
  }
)

Java

player.addListener(
    new Player.Listener() {
      @Override
      public void onPlayerError(PlaybackException error) {
        @Nullable Throwable cause = error.getCause();
        if (cause instanceof HttpDataSourceException) {
          // An HTTP error occurred.
          HttpDataSourceException httpError = (HttpDataSourceException) cause;
          // It's possible to find out more about the error both by casting and by querying
          // the cause.
          if (httpError instanceof HttpDataSource.InvalidResponseCodeException) {
            // Cast to InvalidResponseCodeException and retrieve the response code, message
            // and headers.
          } else {
            // Try calling httpError.getCause() to retrieve the underlying cause, although
            // note that it may be null.
          }
        }
      }
    });

Transições de playlists

Sempre que o player muda para um novo item de mídia na playlist, onMediaItemTransition(MediaItem mediaItem, @MediaItemTransitionReason int reason) é chamado em objetos Player.Listener registrados. O motivo indica se foi uma transição automática, uma busca (por exemplo, depois de chamar player.next()), uma repetição do mesmo item ou causada por uma mudança na playlist (por exemplo, se o item em reprodução foi removido).

Metadados

Os metadados retornados de player.getCurrentMediaMetadata() podem mudar por vários motivos: transições de playlist, atualizações de metadados in-stream ou atualização do MediaItem atual no meio da reprodução.

Se você tiver interesse em mudanças de metadados, por exemplo, para atualizar uma interface que mostre o título atual, ouça onMediaMetadataChanged.

Procurando

A chamada de métodos Player.seekTo resulta em uma série de callbacks para instâncias Player.Listener registradas:

  1. onPositionDiscontinuity com reason=DISCONTINUITY_REASON_SEEK. Esse é o resultado direto da chamada de Player.seekTo. O callback tem campos PositionInfo para a posição antes e depois da busca.
  2. onPlaybackStateChanged com qualquer mudança de estado imediata relacionada à busca. Talvez não exista essa mudança.

Callbacks individuais x onEvents

Os listeners podem escolher entre implementar callbacks individuais, como onIsPlayingChanged(boolean isPlaying), e o callback onEvents(Player player, Events events) genérico. O callback genérico fornece acesso ao objeto Player e especifica o conjunto de events que ocorreram juntos. Esse callback é sempre chamado depois dos callbacks que correspondem aos eventos individuais.

Kotlin

override fun onEvents(player: Player, events: Player.Events) {
  if (
    events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED) ||
      events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)
  ) {
    uiModule.updateUi(player)
  }
}

Java

@Override
public void onEvents(Player player, Events events) {
  if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)
      || events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)) {
    uiModule.updateUi(player);
  }
}

Dê preferência a eventos individuais nos seguintes casos:

  • O listener está interessado nos motivos das mudanças. Por exemplo, os motivos fornecidos para onPlayWhenReadyChanged ou onMediaItemTransition.
  • O listener só age com base nos novos valores fornecidos pelos parâmetros de callback ou aciona algo que não dependa deles.
  • A implementação do listener prefere uma indicação clara e legível do que acionou o evento no nome do método.
  • O listener é informado para um sistema de análise que precisa saber sobre todos os eventos individuais e mudanças de estado.

O onEvents(Player player, Events events) genérico precisa ter preferência nos seguintes casos:

  • O listener quer acionar a mesma lógica em vários eventos. Por exemplo, atualizar uma interface para onPlaybackStateChanged e onPlayWhenReadyChanged.
  • O listener precisa acessar o objeto Player para acionar outros eventos, por exemplo, buscar após uma transição de item de mídia.
  • O listener pretende usar diversos valores de estado que são informados por callbacks separados ou em combinação com métodos getter Player. Por exemplo, o uso de Player.getCurrentWindowIndex() com o Timeline fornecido em onTimelineChanged é seguro apenas dentro do callback onEvents.
  • O listener está interessado em saber se os eventos ocorreram logicamente juntos. Por exemplo, onPlaybackStateChanged para STATE_BUFFERING devido a uma transição de item de mídia.

Em alguns casos, os listeners podem precisar combinar os callbacks individuais com o callback onEvents genérico, por exemplo, para registrar motivos de mudança de item de mídia com onMediaItemTransition, mas agir apenas quando todas as mudanças de estado puderem ser usadas em conjunto em onEvents.

Como usar o AnalyticsListener

Ao usar ExoPlayer, um AnalyticsListener pode ser registrado com o player chamando addAnalyticsListener. As implementações de AnalyticsListener podem detectar eventos detalhados que podem ser úteis para fins de análise e geração de registros. Consulte a página de análise para mais detalhes.

Como usar o EventLogger

EventLogger é um AnalyticsListener fornecido diretamente pela biblioteca para fins de geração de registros. Adicione EventLogger a um ExoPlayer para ativar a geração de registros adicionais úteis com uma única linha:

Kotlin

player.addAnalyticsListener(EventLogger())

Java

player.addAnalyticsListener(new EventLogger());

Consulte a página de geração de registros de depuração para saber mais.

Como disparar eventos em posições de reprodução especificadas

Alguns casos de uso exigem o disparo de eventos em posições de reprodução especificadas. Isso é compatível usando PlayerMessage. Um PlayerMessage pode ser criado usando ExoPlayer.createMessage. A posição da reprodução em que ele precisa ser executado pode ser definida usando PlayerMessage.setPosition. As mensagens são executadas na linha de execução de reprodução por padrão, mas isso pode ser personalizado usando PlayerMessage.setLooper. PlayerMessage.setDeleteAfterDelivery pode ser usado para controlar se a mensagem será executada sempre que a posição de reprodução especificada for encontrada (isso pode acontecer várias vezes devido aos modos de busca e repetição) ou apenas na primeira vez. Depois que o PlayerMessage for configurado, ele poderá ser programado usando PlayerMessage.send.

Kotlin

player
  .createMessage { messageType: Int, payload: Any? -> }
  .setLooper(Looper.getMainLooper())
  .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120000)
  .setPayload(customPayloadData)
  .setDeleteAfterDelivery(false)
  .send()

Java

player
    .createMessage(
        (messageType, payload) -> {
          // Do something at the specified playback position.
        })
    .setLooper(Looper.getMainLooper())
    .setPosition(/* mediaItemIndex= */ 0, /* positionMs= */ 120_000)
    .setPayload(customPayloadData)
    .setDeleteAfterDelivery(false)
    .send();