As mudanças no estado do player (como início da reprodução, armazenamento em buffer ou erros)
acionam eventos que são enviados para instâncias Player.Listener registradas. Esses
eventos são representados por constantes inteiras e definidos por Player.Event
e Player.Events.
Registrar um Player.Listener
Os eventos do player são informados às instâncias Player.Listener registradas. Para
registrar um listener para 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);
Se você estiver usando o Kotlin, também poderá usar as funções de extensão de suspensão
fornecidas pelo módulo media3-common-ktx para detectar eventos usando corrotinas.
Nesse caso, não é necessário registrar ou cancelar o registro de um Player.Listener
explicitamente.
Detectar eventos de reprodução usando Player.Listener
Player.Listener tem métodos padrão vazios, então você só precisa implementar os
métodos que quiser. Consulte o Javadoc para uma descrição completa dos
métodos e quando eles são chamados. Alguns dos métodos mais importantes são
descritos em mais detalhes posteriormente.
Os listeners podem implementar callbacks de eventos individuais ou um callback genérico onEvents que é chamado depois que um ou mais eventos ocorrem juntos. Consulte Individual callbacks vs onEvents para uma explicação de qual
deve ser preferido para diferentes casos de uso.
Mudanças no estado de 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: esse é o estado inicial, o estado em que o player é interrompido e quando a reprodução falha. O player vai manter apenas recursos limitados nesse estado.Player.STATE_BUFFERING: o player não pode iniciar a reprodução imediatamente da posição atual. Isso acontece principalmente porque mais dados precisam ser carregados.Player.STATE_READY: o player pode iniciar a reprodução imediatamente da posição atual.Player.STATE_ENDED: o player terminou de reproduzir todas as mídias.
Além desses estados, o player tem uma flag playWhenReady para indicar
a intenção do usuário de tocar. As mudanças nessa flag podem ser recebidas implementando
onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason).
Um player está em reprodução (ou seja, a posição dele está avançando e a mídia está sendo apresentada ao usuário) quando todas as três condições a seguir 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 implementando
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
Os erros que causam falhas na reprodução 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. As reproduções com falha ou interrompidas podem ser
repetidas chamando ExoPlayer.prepare.
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 a seguir 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 playlist
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 se foi causada por uma mudança na playlist
(por exemplo, se o item que está sendo reproduzido for 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 durante a reprodução.
Se você tiver interesse em mudanças de metadados, por exemplo, para atualizar uma interface que mostra
o título atual, use onMediaMetadataChanged.
Procurando
Chamar métodos Player.seekTo resulta em uma série de callbacks para instâncias Player.Listener registradas:
onPositionDiscontinuitycomreason=DISCONTINUITY_REASON_SEEK. Esse é o resultado direto de chamarPlayer.seekTo. O callback tem camposPositionInfopara a posição antes e depois da busca.onPlaybackStateChangedcom qualquer mudança de estado imediata relacionada à busca. Pode ser que não haja essa mudança.
Callbacks individuais x onEvents
Os listeners podem escolher entre implementar callbacks individuais, como
onIsPlayingChanged(boolean isPlaying), e o callback genérico onEvents(Player
player, Events events). O callback genérico fornece acesso ao objeto Player e especifica o conjunto de events que ocorreram juntos. Esse
callback é sempre chamado após os 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); } }
Os eventos individuais devem ser preferidos nos seguintes casos:
- O listener está interessado nos motivos das mudanças. Por exemplo, os motivos fornecidos para
onPlayWhenReadyChangedouonMediaItemTransition. - O listener só atua nos novos valores fornecidos pelos parâmetros de callback ou aciona algo que não depende 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 informa a 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 é preferível nos
seguintes casos:
- O listener quer acionar a mesma lógica para vários eventos. Por
exemplo, atualizar uma IU para
onPlaybackStateChangedeonPlayWhenReadyChanged. - O listener precisa acessar o objeto
Playerpara acionar outros eventos, por exemplo, buscar após uma transição de item de mídia. - O listener pretende usar vários valores de estado que são informados por
callbacks separados juntos ou em combinação com os métodos getter
Player. Por exemplo, usarPlayer.getCurrentWindowIndex()com oTimelinefornecido emonTimelineChangedsó é seguro no callbackonEvents. - O listener quer saber se os eventos ocorreram juntos de forma lógica.
Por exemplo, de
onPlaybackStateChangedparaSTATE_BUFFERINGdevido a uma transição de item de mídia.
Em alguns casos, os listeners podem precisar combinar os callbacks individuais com o
callback genérico onEvents, por exemplo, para registrar os motivos da mudança do item de mídia
com onMediaItemTransition, mas só agir quando todas as mudanças de estado puderem ser usadas
juntas em onEvents.
Detectar eventos de reprodução usando corrotinas
Como alternativa, é possível iniciar uma corrotina do Kotlin usando Player.listenTo e
especificar o Player.Event relevante:
Player.listen e Player.listenTo podem ser chamados em qualquer
linha de execução, enquanto o lambda de callback é sempre invocado na linha de execução associada
a Player.getApplicationLooper. Portanto, é seguro acessar os métodos e as propriedades de estado de Player dentro da lambda de callback, mesmo que a corrotina tenha sido iniciada em uma linha de execução diferente.
Mudanças no estado de reprodução
coroutineScope.launch { player.listenTo(Player.EVENT_IS_PLAYING_CHANGED) { // `Player` is a receiver scope for this trailing lambda if (isPlaying) { // Active playback. } else { // Not playing. } } }
Erros de reprodução
coroutineScope.launch { player.listenTo(Player.EVENT_PLAYER_ERROR) { val error = playerError ?: return@listenTo val cause = error.cause if (cause is HttpDataSourceException) { // An HTTP error occurred. if (cause is InvalidResponseCodeException) { // Retrieve the response code, message and headers } else { // Try calling cause.cause to retrieve the underlying cause } } } }
Callbacks individuais x onEvents
Ao detectar eventos do player em uma corrotina, você sempre vai fornecer
a implementação para o callback onEvents, em vez de um callback
individual. Você pode escolher entre
Player.listen e Player.listenTo, dependendo de quais eventos devem
acionar uma invocação do lambda. No entanto, as funções são equivalentes:
ouvir
coroutineScope.launch { player.listen { events -> // `Player` is a receiver scope for this trailing lambda if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) { // Access the player state directly from the receiver updateUi(playbackState) } if (events.contains(Player.EVENT_PLAYER_ERROR)) { // Access the error directly from the player handleError(playerError) } } }
listenTo
coroutineScope.launch { player.listenTo(Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYER_ERROR) { events -> // `Player` is a receiver scope for this trailing lambda if (events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) { // Access the player state directly from the receiver updateUi(playbackState) } if (events.contains(Player.EVENT_PLAYER_ERROR)) { // Access the error directly from the player handleError(playerError) } } }
Se você tiver interesse em vários tipos de evento, transmita uma lista de eventos para
Player.listenTo. A lambda será invocada sempre que qualquer um desses eventos
ocorrer, e você poderá inspecionar o parâmetro Events para verificar quais eventos realmente
foram acionados:
coroutineScope.launch { player.listenTo(Player.EVENT_PLAYBACK_STATE_CHANGED, Player.EVENT_PLAYER_ERROR) { events -> // Unclear which event got triggered without querying `events` parameter // The following function will fire whenever either one is caught updateUiAndHandleError(playbackState, playerError) } }
Como essas funções operam em onEvents, elas não dão acesso aos
argumentos temporários transmitidos para callbacks individuais, como o motivo em
onMediaItemTransition(..., int reason) ou o oldPosition em
onPositionDiscontinuity(...). Se a lógica depender desses argumentos específicos
(e eles não estiverem disponíveis como propriedades de estado no Player), use
a interface Player.Listener padrão.
Usar 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
registro. Consulte a página de análise para mais detalhes.
Usar EventLogger
EventLogger é um AnalyticsListener fornecido diretamente pela biblioteca para
fins de geração de registros. Adicione EventLogger a um ExoPlayer para ativar uma geração de registros
adicional útil com uma única linha:
Kotlin
player.addAnalyticsListener(EventLogger())
Java
player.addAnalyticsListener(new EventLogger());
Consulte a página de registro de depuração para mais detalhes.
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 com o uso de PlayerMessage. É possível criar um PlayerMessage usando
ExoPlayer.createMessage. A posição de 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 estiver 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();