ExoPlayer 无需任何特殊配置即可开箱即用地播放大多数自适应直播。如需了解详情,请参阅“支持的格式”页面。
自适应直播会提供一个可用媒体窗口,该窗口会定期更新,以与当前实时时间同步。这意味着,播放位置将始终位于此窗口中的某个位置,在大多数情况下,该位置接近于正在生成直播流的当前实时时间。当前实时位置与播放位置之间的差值称为直播偏移量。
检测和监控实时播放
每次更新直播窗口时,已注册的 Player.Listener 实例都会收到 onTimelineChanged 事件。您可以通过查询各种 Player 和 Timeline.Window 方法来检索有关当前直播播放的详细信息,如下文所列和下图所示。
Player.isCurrentWindowLive指示当前播放的媒体文件是否为直播。即使直播已结束,此值仍为 true。Player.isCurrentWindowDynamic指示当前播放的媒体文件是否仍在更新。对于尚未结束的直播,通常会出现这种情况。请注意,在某些情况下,此标志对于非直播流也为 true。Player.getCurrentLiveOffset会返回当前实时时间与播放位置(如果有)之间的偏移量。Player.getDuration会返回当前直播时间范围的时长。Player.getCurrentPosition会返回相对于直播时间范围开始位置的播放位置。Player.getCurrentMediaItem会返回当前媒体文件,其中MediaItem.liveConfiguration包含应用提供的目标直播偏移量和直播偏移量调整参数的替换值。Player.getCurrentTimeline会返回Timeline中的当前媒体结构。您可以使用Player.getCurrentMediaItemIndex和Timeline.getWindow从Timeline中检索当前Timeline.Window。在Window中:Window.liveConfiguration包含目标直播偏移量和直播偏移量调整参数。这些值基于媒体中的信息以及MediaItem.liveConfiguration中设置的任何应用提供的替换值。Window.windowStartTimeMs是自 Unix 纪元以来直播窗口开始的时间。Window.getCurrentUnixTimeMs是从当前实时时间的 Unix 纪元开始计算的时间。此值可能会根据服务器和客户端之间已知的时钟差异进行更正。Window.getDefaultPositionMs是直播窗口中播放器默认开始播放的位置。
在直播中跳转
您可以使用 Player.seekTo 拖动到直播窗口中的任意位置。传递的跳转位置是相对于直播时间范围的开头。例如,seekTo(0) 将跳转到直播窗口的开头。播放器会尝试在跳转后保持与跳转到的位置相同的直播偏移量。
直播窗口还有一个默认位置,播放应该从该位置开始。此位置通常靠近直播边缘。您可以通过调用 Player.seekToDefaultPosition 跳转到默认位置。
直播播放界面
ExoPlayer 的默认界面组件会显示直播窗口的时长以及其中的当前播放位置。这意味着,每次更新直播窗口时,位置似乎都会向后跳。如果您需要不同的行为(例如显示 Unix 纪元时间或当前直播偏移量),可以对 PlayerControlView 进行分叉并根据需要进行修改。
配置直播播放参数
ExoPlayer 使用一些参数来控制播放位置与直播边缘的偏移量,以及可用于调整此偏移量的播放速度范围。
ExoPlayer 会从三个位置获取这些参数的值,优先级按降序排列(使用找到的第一个值):
- 传递给
MediaItem.Builder.setLiveConfiguration的每个MediaItem值。 - 全局默认值设置于
DefaultMediaSourceFactory。 - 直接从媒体中读取的值。
Kotlin
// Global settings. val player = ExoPlayer.Builder(context) .setMediaSourceFactory(DefaultMediaSourceFactory(context).setLiveTargetOffsetMs(5000)) .build() // Per MediaItem settings. val mediaItem = MediaItem.Builder() .setUri(mediaUri) .setLiveConfiguration( MediaItem.LiveConfiguration.Builder().setMaxPlaybackSpeed(1.02f).build() ) .build() player.setMediaItem(mediaItem)
Java
// Global settings. ExoPlayer player = new ExoPlayer.Builder(context) .setMediaSourceFactory( new DefaultMediaSourceFactory(context).setLiveTargetOffsetMs(5000)) .build(); // Per MediaItem settings. MediaItem mediaItem = new MediaItem.Builder() .setUri(mediaUri) .setLiveConfiguration( new MediaItem.LiveConfiguration.Builder().setMaxPlaybackSpeed(1.02f).build()) .build(); player.setMediaItem(mediaItem);
可用的配置值包括:
targetOffsetMs:目标直播偏移量。如果可能,播放器会在播放期间尝试接近此直播偏移量。minOffsetMs:允许的直播偏移量下限。即使根据当前网络状况调整偏移量,播放器也不会在播放期间尝试低于此偏移量。maxOffsetMs:允许的最大直播偏移量。即使根据当前网络状况调整了偏移量,播放器也不会在播放期间尝试超过此偏移量。minPlaybackSpeed:播放器在尝试达到目标直播偏移量时可以使用的最低播放速度。maxPlaybackSpeed:播放器在尝试达到目标直播偏移量时可用于追赶的最大播放速度。
播放速度调整
播放低延迟直播时,ExoPlayer 会通过略微改变播放速度来调整直播偏移。播放器会尝试匹配媒体或应用提供的目标直播偏移量,但也会尝试对不断变化的网络状况做出反应。例如,如果在播放期间发生重新缓冲,播放器会稍微减慢播放速度,以进一步远离直播边缘。如果网络随后变得足够稳定,可以再次支持更接近直播边缘的播放,播放器将加快播放速度,以移回目标直播偏移量。
如果不需要自动调整播放速度,可以通过将 minPlaybackSpeed 和 maxPlaybackSpeed 属性设置为 1.0f 来停用此功能。同样,您也可以通过将这些值明确设置为 1.0f 以外的值,为非低延迟直播启用此功能。如需详细了解如何设置这些属性,请参阅配置部分。
自定义播放速度调整算法
如果启用了速度调整,则 LivePlaybackSpeedControl 会定义所做的调整。您可以实现自定义 LivePlaybackSpeedControl,也可以自定义默认实现 DefaultLivePlaybackSpeedControl。在这两种情况下,都可以在构建播放器时设置实例:
Kotlin
val player = ExoPlayer.Builder(context) .setLivePlaybackSpeedControl( DefaultLivePlaybackSpeedControl.Builder().setFallbackMaxPlaybackSpeed(1.04f).build() ) .build()
Java
ExoPlayer player = new ExoPlayer.Builder(context) .setLivePlaybackSpeedControl( new DefaultLivePlaybackSpeedControl.Builder() .setFallbackMaxPlaybackSpeed(1.04f) .build()) .build();
DefaultLivePlaybackSpeedControl 的相关自定义参数如下:
fallbackMinPlaybackSpeed和fallbackMaxPlaybackSpeed:如果媒体和应用提供的MediaItem均未定义限制,则可用于调整的最低和最高播放速度。proportionalControlFactor:控制速度调整的平滑度。值越高,调整越突然、越灵敏,但也越容易被听到。值越小,速度之间的过渡越平滑,但代价是速度较慢。targetLiveOffsetIncrementOnRebufferMs:每当发生重新缓冲时,此值都会添加到目标直播偏移量中,以便更谨慎地继续播放。通过将值设置为 0,可以停用此功能。minPossibleLiveOffsetSmoothingFactor:指数平滑因子,用于根据当前缓冲的媒体跟踪可能的最小直播偏移量。值非常接近 1 表示估算更加谨慎,可能需要更长时间才能根据网络状况的改善进行调整,而值越低表示估算调整速度越快,但遇到重新缓冲的风险也越高。
BehindLiveWindowException 和 ERROR_CODE_BEHIND_LIVE_WINDOW
播放位置可能会落后于直播时间范围,例如,如果播放器暂停或缓冲的时间足够长。如果发生这种情况,播放将失败,并且系统会使用 Player.Listener.onPlayerError 报告错误代码为 ERROR_CODE_BEHIND_LIVE_WINDOW 的异常。您可能希望通过在默认位置恢复播放来处理此类错误。演示版应用的 PlayerActivity 举例说明了此方法。
Kotlin
override fun onPlayerError(error: PlaybackException) { if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) { // Re-initialize player at the live edge. player.seekToDefaultPosition() player.prepare() } else { // Handle other errors } }
Java
@Override public void onPlayerError(PlaybackException error) { if (error.errorCode == PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW) { // Re-initialize player at the live edge. player.seekToDefaultPosition(); player.prepare(); } else { // Handle other errors } }