监听播放事件
系统会将事件(例如状态更改和播放错误)报告给已注册的 Player.Listener
实例。如需注册监听器以接收此类事件,请执行以下操作:
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
包含空的默认方法,因此您只需实现感兴趣的方法。如需详细了解这些方法以及调用时机,请参阅 Javadoc。下文更详细地介绍了一些最重要的方法。
监听器可以选择实现单个事件回调,也可以选择在一个或多个事件一起发生后调用的通用 onEvents
回调。请参阅 Individual callbacks vs onEvents
,了解对于不同的用例应优先选择哪种方法。
播放状态更改
可以通过在已注册的 Player.Listener
中实现 onPlaybackStateChanged(@State int state)
来接收播放器状态的变化。播放器可能处于以下四种播放状态之一:
Player.STATE_IDLE
:这是初始状态、播放器停止时以及播放失败时的状态。在此状态下,播放器将仅保留有限的资源。Player.STATE_BUFFERING
:播放器无法立即从当前位置开始播放。这主要是因为需要加载更多数据。Player.STATE_READY
:播放器能够从当前位置立即播放。Player.STATE_ENDED
:播放器完整播放了所有媒体。
除了这些状态之外,玩家还有一个 playWhenReady
标志,用于表明用户有意玩游戏。可以通过实现 onPlayWhenReadyChanged(playWhenReady, @PlayWhenReadyChangeReason int reason)
来接收此标志中的更改。
当满足以下全部三个条件时,播放器正在播放(即其位置正在播放且媒体正在呈现):
- 播放器处于
Player.STATE_READY
状态 playWhenReady
为true
- 播放未因
Player.getPlaybackSuppressionReason
返回的原因禁止播放
可以调用 Player.isPlaying
,而不必单独检查这些属性。可以通过实现 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. } } });
播放错误
通过在已注册的 Player.Listener
中实现 onPlayerError(PlaybackException error)
,可以接收导致播放失败的错误。如果发生失败,系统会在播放状态转换为 Player.STATE_IDLE
之前立即调用此方法。可以通过调用 ExoPlayer.prepare
重试失败或已停止的播放。
请注意,某些 Player
实现会传递 PlaybackException
的子类的实例,以提供有关失败情况的更多信息。例如,ExoPlayer
传递 ExoPlaybackException
,其中包含 type
、rendererIndex
和其他 ExoPlayer 专用字段。
以下示例展示了如何检测因 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. } } } });
播放列表转换
每当播放器切换到播放列表中的新媒体项时,系统都会对已注册的 Player.Listener
对象调用 onMediaItemTransition(MediaItem mediaItem,
@MediaItemTransitionReason int reason)
。原因指明这是自动转换、搜寻(例如在调用 player.next()
之后)、重复播放同一项,还是由播放列表变更(例如,如果当前播放项被移除)导致。
元数据
从 player.getCurrentMediaMetadata()
返回的元数据可能会因多种原因而发生变化:播放列表转换、插播元数据更新或在播放过程中更新当前的 MediaItem
。
如果您对元数据更改(例如,为了更新显示当前标题的界面)感兴趣,可以监听 onMediaMetadataChanged
。
正在指定播放时间点
调用 Player.seekTo
方法会导致对已注册的 Player.Listener
实例进行一系列回调:
onPositionDiscontinuity
与reason=DISCONTINUITY_REASON_SEEK
。这是调用Player.seekTo
的直接结果。该回调包含用于定位跳转前后位置的PositionInfo
字段。onPlaybackStateChanged
- 与跳转相关的任何即时状态更改。请注意,可能没有这样的更改。
单个回调与 onEvents
监听器可以选择实现个别回调(如 onIsPlayingChanged(boolean isPlaying)
)或常规 onEvents(Player player, Events events)
回调。通用回调提供对 Player
对象的访问,并指定一起发生的一组 events
。系统始终会在与各个事件对应的回调之后调用此回调。
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); } }
在以下情况下,应首选单个事件:
- 监听器关注的是更改原因。例如,为
onPlayWhenReadyChanged
或onMediaItemTransition
提供的原因。 - 监听器仅作用于通过回调参数提供的新值,或者触发不依赖于回调参数的其他内容。
- 监听器实现倾向于在方法名称中提供清晰易读的指示,指明触发了相应事件。
- 监听器会向需要了解所有个别事件和状态更改的分析系统报告。
在以下情况下,应首选通用 onEvents(Player player, Events events)
:
- 监听器想为多个事件触发相同的逻辑。例如,更新
onPlaybackStateChanged
和onPlayWhenReadyChanged
的界面。 - 监听器需要访问
Player
对象以触发更多事件,例如在媒体内容转换后搜寻。 - 监听器旨在将通过单独的回调报告或与
Player
getter 方法结合使用的多个状态值。例如,只有在onEvents
回调中才能安全地将Player.getCurrentWindowIndex()
与onTimelineChanged
中提供的Timeline
一起使用。 - 监听器关注事件逻辑上是否一起发生。例如,因媒体内容转换而将
onPlaybackStateChanged
转换为STATE_BUFFERING
。
在某些情况下,监听器可能需要将各个回调与通用 onEvents
回调结合使用(例如,通过 onMediaItemTransition
记录媒体内容更改原因),但只有在所有状态变化都可以在 onEvents
中一起使用时,监听器才会执行该操作。
使用 AnalyticsListener
使用 ExoPlayer
时,可通过调用 addAnalyticsListener
向播放器注册 AnalyticsListener
。AnalyticsListener
实现能够监听可用于分析和日志记录的详细事件。如需了解详情,请参阅数据分析页面。
使用 EventLogger
EventLogger
是由库直接提供的用于日志记录的 AnalyticsListener
。将 EventLogger
添加到 ExoPlayer
即可通过一行代码启用实用的附加日志记录:
Kotlin
player.addAnalyticsListener(EventLogger())
Java
player.addAnalyticsListener(new EventLogger());
如需了解详情,请参阅调试日志记录页面。
在指定播放位置触发事件
某些用例要求在指定的播放位置触发事件。可使用 PlayerMessage
支持此操作。您可以使用 ExoPlayer.createMessage
创建 PlayerMessage
。可以使用 PlayerMessage.setPosition
设置应播放的播放位置。消息默认在播放线程上执行,但可以使用 PlayerMessage.setLooper
自定义。PlayerMessage.setDeleteAfterDelivery
可用于控制是在每次遇到指定的播放位置时执行消息(由于搜寻和重复模式,这种情况可能会发生多次),还是仅执行第一次。PlayerMessage
配置完成后,即可使用 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();