本文为您详细介绍 PlayerKit 的架构、核心模块以及核心类,着重介绍播放控件、播放控制器、播放源和播放器等核心类的配置及使用方式。
PlayerKit 包含 vod-player、vod-player-volcengine 和 vod-player-utils 三个核心模块,如下图所示:
各模块的详细介绍如下表:
模块 | 说明 |
|---|---|
vod-player | 播放器接口层。该层定义了 PlayerKit 的核心类及架构。关键类包括:
|
vod-player-volcengine | 火山引擎播放器 SDK 的实现层。该层基于火山引擎播放器 SDK,实现了播放器适配器 PlayerAdapter。内部还实现了抖音同款策略,提供预加载、预渲染、超分、智能 H.265 策略、ABR 起播选档等多项高性能最佳实践。关键类包括:
|
vod-player-utils | 工具类模块。包含各模块所需的常见工具类。关键类如下:
|
ttsdk-player_premium | 火山引擎播放器 SDK。 |
vod-player、vod-player-volcengine 和 vod-player-utils 模块的依赖关系如下图所示。
VideoView 类是 PlayerKit 中对播放控件的定义。本节为您介绍 VideoView 的配置与使用方式。
创建播放控件:
VideoView videoView = new VideoView(context);
设置渲染视图类型:
// 设置渲染 View 类型,支持: // - DisplayView.DISPLAY_VIEW_TYPE_TEXTURE_VIEW(兼容性好,推荐使用) // - DisplayView.DISPLAY_VIEW_TYPE_SURFACE_VIEW videoView.selectDisplayView(DisplayView.DISPLAY_VIEW_TYPE_TEXTURE_VIEW);
设置显示模式:
// 设置显示模式,支持: // DisplayModeHelper.DISPLAY_MODE_DEFAULT: 可能会变形;画面宽高都充满控件;画面不被裁剪;无黑边 // DisplayModeHelper.DISPLAY_MODE_ASPECT_FILL_X: 无变形;画面宽充满控件,高按视频比例适配;画面可能被裁剪;可能有黑边。 // DisplayModeHelper.DISPLAY_MODE_ASPECT_FILL_Y: 无变形;画面高充满控件,宽按视频比例适配;画面可能被裁剪;可能有黑边。 // DisplayModeHelper.DISPLAY_MODE_ASPECT_FIT: 无变形;画面长边充满控件,短边按比例适配;画面不被裁剪;可能有黑边 // DisplayModeHelper.DISPLAY_MODE_ASPECT_FILL: 无变形;画面短边充满控件,长边按比例适配;画面可能被裁剪;无黑边 videoView.setDisplayMode(DisplayModeHelper.DISPLAY_MODE_ASPECT_FIT);
PlayerKit 通过 VideoLayer 实现高性能的播放 UI。vod-demo 和 vod-scenekit 模块提供了大量的 UI 浮层标准模板,帮助开发者快速搭建常见场景的播放 UI。开发者可直接将模板复制到项目中,对 Layer 中的 UI 进行修改,并复用消息和控制逻辑。
在以下示例代码中,当场景被设定为 SCENE_SHORT(短视频场景)时,会自动展示该场景对应的浮层样式。
VideoLayerHost layerHost = new VideoLayerHost(context); // 以下这些 Layer 类的实现均位于 vod-scenekit 模块中 layerHost.addLayer(new CoverLayer()); // 封面 Layer layerHost.addLayer(new LoadingLayer()); // 加载图标 Layer layerHost.addLayer(new PauseLayer()); // 暂停按钮 Layer layerHost.addLayer(new SimpleProgressBarLayer()); // 进度条 layer if (BuildConfig.DEBUG){ layerHost.addLayer(new LogLayer()); // 日志 Layer,可以展示信息方便开发 } layerHost.attachToVideoView(videoView); // 设置场景,支持的场景见 PlayScene 常量类,可用于 VideoLayer 中根据场景展示不同状态。 videoView.setPlayScene(PlayScene.SCENE_SHORT);
播放 UI 层级示意图如下:
如果上述模板无法满足您的需求,您可以自定义开发 VideoLayer。VideoLayer 是一套强大且灵活的系统,通过将播放控件上的 UI 视图进行 Layer 化抽象,实现粒度可控、职责单一的播放浮层。VideoLayer 是 PlayerKit 中的基础组件,而 BaseLayer、AnimateLayer、DialogLayer 则是 SceneKit 提供的高级组件,开发者可以将其复制到项目中使用。具体说明如下:
对于有动画的浮层,建议继承 AnimateLayer 来实现;对于没有动画的浮层,建议继承 BaseLayer 来实现。
视频浮层的显示和隐藏通常需要带有动画。动画对于 VideoLayer 来说是一个基础且通用的使用场景。因此,vod-scenekit 中抽象出了 AnimateLayer 作为动画浮层的基类。使用说明如下:
示例代码如下:
带有动画的显示与隐藏:
// 是否正在执行显示动画 boolean isAnimateShowing = animateLayer.isAnimateShowing(); // 带动画显示 animateLayer.animateShow(true); // true 显示后 4 秒后自动隐藏 animateLayer.animateShow(false); // false 显示后,不自动隐藏 // 是否正在执行隐藏动画 boolean isAnimateDismiss = animateLayer.isAnimateDismissing(); // 带动画隐藏 animateLayer.animateDismiss();
监听动画事件:
// 监听显示动画执行 animateLayer.setAnimateShowListener(new Animator.AnimatorListener() { // ... }); // 监听隐藏动画执行 animateLayer.setAnimateDismissListener(new Animator.AnimatorListener() { // ... });
自定义动画:
public class PauseLayer extends AnimateLayer { private ObjectAnimator scaleXAnimator; private ObjectAnimator scaleYAnimator; @Override protected Animator createAnimator() { scaleXAnimator = new ObjectAnimator(); scaleYAnimator = new ObjectAnimator(); AnimatorSet set = new AnimatorSet(); set.playTogether(scaleXAnimator, scaleYAnimator); set.setInterpolator(new DecelerateInterpolator()); set.setDuration(150); return set; } @Override protected void initAnimateDismissProperty(Animator animator) { if (scaleXAnimator != null) { // Using scaleX animator animate alpha instead create a new animator instance. scaleXAnimator.setPropertyName("alpha"); scaleXAnimator.setFloatValues(1, 0); } if (scaleYAnimator != null) { scaleYAnimator.setPropertyName("scaleY"); scaleYAnimator.setFloatValues(1, 1); } } @Override protected void initAnimateShowProperty(Animator animator) { if (scaleXAnimator != null) { scaleXAnimator.setPropertyName("scaleX"); scaleXAnimator.setFloatValues(1.5f, 1); } if (scaleYAnimator != null) { scaleYAnimator.setPropertyName("scaleY"); scaleYAnimator.setFloatValues(1.5f, 1); } } @Override protected void resetViewAnimateProperty() { View view = getView(); if (view != null) { view.setScaleX(1); view.setScaleY(1); view.setAlpha(1); } } }
与普通的 VideoLayer 相比,DialogLayer 具有以下特性:
当 DialogLayer 显示时,会自动隐藏所有继承了 BaseLayer 的浮层。如果不想被隐藏,BaseLayer 的子类和 AnimateLayer 的子类可以分别重写 requestDismiss 和 requestAnimateDismiss 方法来拦截隐藏操作,并根据 reason 判断是否要隐藏。示例代码如下:
public class MyVideoLayer extends BaseLayer { // 重写 requestDismiss 方法 @Override public void requestDismiss(@NonNull String reason) { if (!TextUtils.equals(reason, REQUEST_DISMISS_REASON_DIALOG_SHOW)) { super.requestDismiss(reason); } } } public class MyVideoLayer extends AnimateLayer { // 重写 requestAnimateDismiss 方法 @Override public void requestAnimateDismiss(@NonNull String reason) { if (!TextUtils.equals(reason, REQUEST_DISMISS_REASON_DIALOG_SHOW)) { super.requestAnimateDismiss(reason); } } }
DialogLayer 默认具有回退栈,可以拦截 onBackPressed 事件。这需要在 Activity/Fragment 的 onBackPressed 方法中调用 mVideoView.layerHost().onBackPressed()。示例代码如下:
public class MainActivity extends AppCompatActivity { private VideoView mVideoView; @Override public void onBackPressed() { // 拦截 Activity 的回退事件,用于处理 VideoLayer 内部的返回逻辑 if (mVideoView.layerHost().onBackPressed()) { return; } super.onBackPressed(); } }
通过重写 createView 方法创建浮层 View。浮层 View 会在首次调用 show 后回调。
public class SimpleProgressBarLayer extends AnimateLayer { protected MediaSeekBar mSeekBar; /** * @param parent 为 VideoLayerHost 中的 FrameLayout */ @Nullable @Override protected View createView(@NonNull ViewGroup parent) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.vevod_simple_progress_layer, parent, false); mSeekBar = view.findViewById(R.id.mediaSeekBar); return view; } }
VideoLayer 本质上为一个 ViewHolder,提供 createView、show 及 dismiss 等方法,用于控制 View 的创建、展示与隐藏。
// 判断是否正在显示 boolean isShowing = videoLayer.isShowing(); // 显示 layer view,创建 view 并添加 VideoLayerHost 中的 FrameLayout 中。 layer.show(); // 隐藏 layer view,把 view 从视图树上移除。 layer.dismiss(); // gone layer view,简单 gone 掉 view。 layer.hide();
绑定数据回调:
public class MyVideoLayer extends AnimateLayer { @Override public void onVideoViewBindDataSource(MediaSource dataSource) { // 重写 VideoLayer 的 onVideoViewBindDataSource 方法,监听数据绑定。 } }
获取已绑定的数据:
MediaSource mediaSource = layer.dataSource();
业务 Model 存取。MediaSource 仅涵盖视频信息,您可将业务 Model 存于 MediaSource 内,以便于在 VideoLayer 中进行展示和使用。
VideoItem videoitem = ...; mediaSource.putExtra(EXTRA_VIDEO_ITEM, videoitem); VideoItem videoitem = mediaSource.getExtra(EXTRA_VIDEO_ITEM, VideoItem.class);
VideoLayer 的视图采用了懒加载机制,在 onVideoViewBindDataSource 回调时,视图可能尚未创建。建议在 onVideoViewBindDataSource 和 show 方法中同时执行数据绑定逻辑。完整示例代码如下:
public class MyVideoLayer extends AnimateLayer { public TextView mTitleView; @Override public void onVideoViewBindDataSource(MediaSource dataSource) { bindVideoTitle(); } @Override public void show() { super.show(); bindVideoTitle(); } /** * 推荐在 onVideoViewBindDataSource 和 show 中都调用一次绑定逻辑。因为 * VideoLayer 的采用 View 懒加载机制,show 方法调用后,mTitleView 才会赋值。 */ private void bindVideoTitle() { if (mTitleView != null) { // 获取已绑定的数据 MediaSource mediaSource = dataSource(); // 获取业务 Model,获得更多业务信息 VideoItem videoItem = mediaSource.getExtra(EXTRA_VIDEO_ITEM, VideoItem.class); // 使用业务 Model 信息,进行 View 展示 mTitleView.setText(videoItem == null ? "" : videoItem.videoTitle); } } }
VideoLayerHost 和 VideoLayer 均提供了用于控制锁定的 API 。
// UI 是否锁定 boolean isLocked = videoLayerHost.isLocked(); // 设置/切换锁定状态 // 设置为 true 后,VideoLayer 会回调 onLayerHostLockStateChanged 方法,并在该方法里调用 dismiss 方法隐藏 // 子类可重写 onLayerHostLockStateChanged 改变行为 videoLayerHost.setLocked(!isLocked); // 单个 Layer 设置忽略锁定 // 默认值 false,手动设置为 true 后,该 VideoLayer 在 onLayerHostLockStateChanged 方法中不会被隐藏。 layer.setIgnoreLock(true); // layer 是否忽略锁定 boolean isIgnoreLock = layer.isIgnoreLock(); // 单个 layer 是否锁定;如果 layer 已忽略锁定,那么 layerHost 锁定后,这里也返回 false boolean isLocked = layer.isLocked(); // 如果一个 layer 被锁定,即 layer.isLocked() 返回 true 时, // 调用 show 方法只会 createView,但不会显示。 layer.show();
锁定按钮示例:
public class LockLayer extends AnimateLayer { private ImageView mImageView; public LockLayer() { // 设置忽略锁定 setIgnoreLock(true); } @Override public String tag() { return "lock"; } @Nullable @Override protected View createView(@NonNull ViewGroup parent) { // 锁定按钮 View mImageView = new ImageView(parent.getContext()); mImageView.setImageDrawable(ResourcesCompat.getDrawable(parent.getResources(), R.drawable.vevod_lock_layer_ic_selector, null)); mImageView.setOnClickListener(v -> { VideoLayerHost layerHost = layerHost(); if (layerHost == null) return; // 点击锁定按钮时,切换锁定状态 layerHost.setLocked(!layerHost.isLocked()); }); return mImageView; } @Override protected void onLayerHostLockStateChanged(boolean locked) { super.onLayerHostLockStateChanged(locked); // 锁定状态有改变时,切换锁定按钮 View 的状态 syncLockedState(locked); } @Override public void show() { super.show(); // 显示锁定按钮 View 时,更新显示状态 syncLockedState(isLayerHostLocked()); } private boolean isLayerHostLocked() { VideoLayerHost layerHost = layerHost(); return layerHost != null && layerHost.isLocked(); } private void syncLockedState(boolean locked) { if (mImageView != null) { // 用 select 状态改变背景图 mImageView.setSelected(locked); } } }
调用 VideoLayer 的 onBindPlaybackController 和 onUnbindPlaybackController 方法注册与移除监听器,有效避免内存泄漏。
注意
PlaybackController 和 Player 都提供事件回调。PlaybackController 会代理其中绑定的 Player 对象的事件。使用 VideoView 的情况下,推荐使用 PlaybackController 的 addPlaybackListener 来统一监听的事件。事件常量定义,请参考常量类 PlayerEvent 和 PlaybackEvent。
public class MyVideoLayer extends BaseLayer { @Override protected void onBindPlaybackController(@NonNull PlaybackController controller) { controller.addPlaybackListener(mPlaybackListener); } @Override protected void onUnbindPlaybackController(@NonNull PlaybackController controller) { controller.removePlaybackListener(mPlaybackListener); } private final Dispatcher.EventListener mPlaybackListener = new Dispatcher.EventListener() { @Override public void onEvent(Event event) { switch (event.code()) { } } }; }
VideoLayer 实现了 VideoViewListener,重写相关方法即可实现监听。在与 VideoView 绑定时注册监听,解绑后移除监听。
public class MyVideoLayer extends VideoLayer { /** * 播放源绑定回调。在 VideoView bindDataSource 方法调用后回调 */ @Override public void onVideoViewBindDataSource(MediaSource dataSource) { } /** * VideoView click 点击后回调 */ @Override public void onVideoViewClick(VideoView videoView) { } /** * 显示 View 创建完成回调。displayView 是 TextureView/SurfaceView 实例。 */ @Override public void onVideoViewDisplayViewCreated(View displayView) { } /** * 显示 View 改变回调。在 VideoView selectDisplayView 方法调用后回调 */ @Override public void onVideoViewDisplayViewChanged(View oldView, View newView) { } /** * 显示模式改变回调。在 VideoView setDisplayMode 方法调用后回调 */ @Override public void onVideoViewDisplayModeChanged(@DisplayMode int fromMode, @DisplayMode int toMode) { } /** * 播放场景改变回调。在 VideoView setDisplayMode 方法被调用后回调 */ @Override public void onVideoViewPlaySceneChanged(int fromScene, int toScene) { } /** * startPlayback 方法被拦截后回调 */ @Override public void onVideoViewStartPlaybackIntercepted(VideoView videoView, String reason) { } /** * stopPlayback 方法被拦截后回调 */ @Override public void onVideoViewStopPlaybackIntercepted(VideoView videoView, String reason) { } /** * pausePlayback 方法被拦截后回调 */ @Override public void onVideoViewPausePlaybackIntercepted(VideoView videoView, String reason) { } /** * VideoView 系统方法 onConfigurationChanged 回调时回调 */ @Override public void onConfigurationChanged(Configuration newConfig) { } /** * VideoView 系统方法 onWindowFocusChanged 回调时回调 */ @Override public void onWindowFocusChanged(boolean hasWindowFocus) { } /** * TextureView/SurfaceView surface 可用时回调 */ @Override public void onSurfaceAvailable(Surface surface, int width, int height) { } /** * TextureView/SurfaceView surface 大小改变时回调 */ @Override public void onSurfaceSizeChanged(Surface surface, int width, int height) { } /** * TextureView surface 更新时回调 */ @Override public void onSurfaceUpdated(Surface surface) { } /** * TextureView/SurfaceView surface 销毁时回调 */ @Override public void onSurfaceDestroy(Surface surface) { } }
在 Activity/Fragment 的 onBackPressed 方法内调用 mVideoView.layerHost().onBackPressed()。
public class MainActivity extends AppCompatActivity { private VideoView mVideoView; @Override public void onBackPressed() { // 拦截 Activity 的回退事件,用于处理 VideoLayer 内部的返回逻辑 if (mVideoView.layerHost().onBackPressed()) { return; } super.onBackPressed(); } }
监听拦截回退事件:
public class MyVideoLayer extends AnimateLayer implements VideoLayerHost.BackPressedHandler { public static final int MY_BACK_PRESSED_PRIORITY = 0; @Override protected void onBindLayerHost(@NonNull VideoLayerHost layerHost) { // 注册回退事件监听器,更高的 priority 会优先执行 // 注意:相同 priority 会相互覆盖,推荐集中定义 priority。 layerHost.registerBackPressedHandler(this, MY_BACK_PRESSED_PRIORITY); } @Override protected void onUnbindLayerHost(@NonNull VideoLayerHost layerHost) { // 反注册回退事件监听器 layerHost.unregisterBackPressedHandler(this); } @Override public boolean onBackPressed() { // 返回 true 拦截回退事件 return false; } }
基于 VideoLayer 所实现的 UI 浮层具备可复用及可动态插拔的特性。VideoLayer 在 VideoLayerHost 中的添加顺序也是浮层的 Z 轴顺序以及 Event 事件的接收顺序。
管理方法:
// 添加 Layer videoLayerHost.addLayer(new CoverLayer()); // 查找 Layer CoverLayer layer = videoLayerHost.findLayer(CoverLayer.class); // Layer 个数 int layerSize = videoLayerHost.layerSize(); // Layer index int index = videoLayerHost.indexOf(layer); // 用实例移除单个 Layer videoLayerHost.removeLayer(layer); // 用 index 移除单个 Layer videoLayerHost.removeLayer(index); // 移除所有 Layer videoLayerHost.removeAllLayers();
管理回调:在调用 addLayer 和 removeLayer 之后,将会触发 VideoLayer 的相关回调,您可以重写相关方法以监听相应的回调。如下图所示:
回调相关方法如下:
public class VideoLayer { /** * <p>Called when the {@link VideoLayer} instance add to {@link VideoLayerHost} with * {@link VideoLayerHost#addLayer(VideoLayer)}. * * <p>Pair with {@link #onUnbindLayerHost(VideoLayerHost)}. * * @param layerHost {@link VideoLayerHost} instance */ protected void onBindLayerHost(@NonNull VideoLayerHost layerHost); /** * <p>Called when the {@link VideoLayer} instance remove from {@link VideoLayerHost} with * {@link VideoLayerHost#removeLayer(VideoLayer)}. * * <p>Pair with {@link #onBindLayerHost(VideoLayerHost)}. * * @param layerHost {@link VideoLayerHost} instance */ protected void onUnbindLayerHost(@NonNull VideoLayerHost layerHost); /** * <p>Called when the {@link VideoLayerHost} instance attached to {@link VideoView} * * <p>Pair with {@link #onUnBindVideoView(VideoView)} * * @param videoView {@link VideoView} instance */ protected void onBindVideoView(@NonNull VideoView videoView); /** * <p>Called when the {@link VideoLayerHost} instance detached from {@link VideoView} * * <p>Pair with {@link #onBindVideoView(VideoView)} * * @param videoView {@link VideoView} instance */ protected void onUnBindVideoView(@NonNull VideoView videoView); /** * <p>Called when {@link PlaybackController} bind {@link VideoView} with * {@link PlaybackController#bind(VideoView)} * * <p>Pair with {@link #onUnbindPlaybackController(PlaybackController)} * * @param controller {@link PlaybackController} instance */ protected void onBindPlaybackController(@NonNull PlaybackController controller); /** * <p>Called when {@link PlaybackController} unbind {@link VideoView} with * {@link PlaybackController#unbind()} or {@code PlaybackController#bind(null)} * * <p>Pair with {@link #onBindPlaybackController(PlaybackController)} * * @param controller {@link PlaybackController} instance */ protected void onUnbindPlaybackController(@NonNull PlaybackController controller); }
获取 VideoView、PlaybackController、Player、Context 等核心组件实例:
// 获取绑定的播放源实例 MediaSource mediaSource = layer.dataSource(); // 获取绑定的 controller 实例 PlaybackController controller = layer.controller(); // 获取绑定的 player 实例 Player player = layer.player(); // 获取绑定的 videoView 实例 VideoView videoView = layer.videoView(); // 获取 LayerHost // 通过 VideoLayerHost 的浮层管理方法可以和其他 Layer 交互 VideoLayerHost layerHost = layer.layerHost(); // 获取 context 实例 Context context = layer.context(); // 获取 Activity 实例 FragmentActivity activity = layer.activity();
调用 bindDataSource 方法绑定播放源。
// 获取已绑定的播放源 MediaSource mediaSource = videoView.getDataSource(); // 绑定播放源 videoView.bindDataSource(mediaSource);
以下示例代码展示在 onBindViewHolder 中绑定播放源以及在 onViewDetachedFromWindow 和 onViewRecycled 中停止播放,避免复用出错。
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> { // 在 onViewDetachedFromWindow 中停止播放 @Override public void onViewDetachedFromWindow(@NonNull ViewHolder holder) { super.onViewDetachedFromWindow(); holder.videoView.stopPlayback(); } // 在 onViewRecycled 中停止播放 @Override public void onViewRecycled(@NonNull ViewHolder holder) { super.onViewRecycled(); holder.videoView.stopPlayback(); } // 在 onBindViewHolder 中绑定播放源 @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { final VideoItem videoItem = mItems.get(position); final VideoView videoView = holder.videoView; MediaSource mediaSource = videoView.getDataSource(); if (mediaSource == null) { mediaSource = VideoItem.toMediaSource(videoItem); videoView.bindDataSource(mediaSource); } else { // 根据 MediaId 判断播放源是否改变 if (!TextUtils.equals(videoItem.getVid(), mediaSource.getMediaId())) { // 若播放源发生改变,则先停止播放再绑定播放源 videoView.stopPlayback(); mediaSource = VideoItem.toMediaSource(videoItem); videoView.bindDataSource(mediaSource); } else { // 若播放源没有改变,对未起播的 VideoView 更新播放源 if (videoView.player() == null) { mediaSource = VideoItem.toMediaSource(videoItem); videoView.bindDataSource(mediaSource); } else { // do nothing } } } } }
Player 实例是在 PlaybackController 的 startPlayback 方法执行流程中创建的。PlayerKit 的核心组件都能获取到播放器实例。
// 从 VideoView 对象获取 Player player = videoView.player(); // 从 VideoLayer 对象获取 Player player = videoLayer.player(); // 从 PlaybackController 对象获取 Player player = playbackController.player(); // 从 PlayerPool 中获取 Player player = PlayerPool.get(mediaSource);
PlaybackController 是 PlayerKit 中对播放流程控制器的定义,其主要作用包括:
PlaybackController playbackController = new PlaybackController();
// 绑定 VideoView,监听 VideoView 中的 Surface 事件 playbackController.bind(videoView1); // 动态切换 VideoView 实例,内部会先解绑 videoView1,再绑定 videoView2 playbackController.bind(videoView2); // 解绑当前 VideoView playbackController.bind(null); // 同时解绑 Player 和 VideoView, 避免内存泄漏 playbackController.unbind();
PlaybackController 不支持对 Player 实例的显示绑定。在调用 startPlayback 时,其内部会从 PlayerPool 中获取 Player 实例并进行绑定。
playbackController.startPlayback();
调用 unbindPlayer 解绑播放器。
playbackController.unbindPlayer();
调用 startPlayback 方法开始播放。
videoView.startPlayback();
调用后,PlaybackController 会调用 PlayerPool 的 acquire 方法获取播放器。若 PlayerPool 中不存在可复用的播放器,则会调用 Player.Factory 的 create 方法创建播放器。开始播放具体流程如下图所示:
调用 stopPlayback 方法停止播放。
videoView.stopPlayback();
停止播放流程如下图所示:
调用 pausePlayback 方法暂停播放。
videoView.pausePlayback();
VideoView 支持通过调用 addPlaybackInterceptor 和 removePlaybackInterceptor 方法添加和移除 VideoViewPlaybackActionInterceptor 实例,从而拦截 VideoView 的 startPlayback、pausePlayback 和 stopPlayback 调用。您可为拦截设置优先级,遵循“不同优先级按照从高到低的顺序排列,相同优先级则依照添加顺序排列”的原则回调拦截方法。一旦有一个拦截器成功完成拦截,后续拦截器将不再触发拦截。播放流程拦截功能在 VideoView 内部实现的,而 PlaybackController 的 startPlayback、pausePlayback 和 stopPlayback 不支持拦截。
public interface VideoViewPlaybackActionInterceptor { /** * startPlayback 拦截方法 * * @return Intercept reason. Not intercept return null */ default String onVideoViewInterceptStartPlayback(VideoView videoView) { return null; } /** * stopPlayback 拦截方法 * * @return Intercept reason. Not intercept return null */ default String onVideoViewInterceptStopPlayback(VideoView videoView) { return null; } /** * pausePlayback 拦截方法 * * @return Intercept reason. Not intercept return null */ default String onVideoViewInterceptPausePlayback(VideoView videoView) { return null; } } /** * 根据 priority 添加拦截器 */ public void addPlaybackInterceptor(int priority, VideoViewPlaybackActionInterceptor interceptor); /** * 根据 priority 移除拦截器 */ public void removePlaybackInterceptor(int priority, VideoViewPlaybackActionInterceptor interceptor); /** * 移除该 priority 的所有拦截器 */ public void removePlaybackInterceptor(int priority); /** * 移除所有拦截器 */ public void removeAllPlaybackInterceptor();
以下示例展示了在付费解锁场景中,拦截未解锁的视频调用 startPlayback 方法。
public static final String INTERCEPT_START_PLAYBACK_REASON_LOCKED = "locked"; VideoViewPlaybackActionInterceptor playbackInterceptor = new VideoViewPlaybackActionInterceptor() { /** `* 拦截 VideoView startPlayback 方法 * * @return Intercept reason. Not intercept return null */ public String onVideoViewInterceptStartPlayback(VideoView videoView) { final MediaSource mediaSource = videoView.getDataSource(); final VideoItem videoItem = VideoItem.get(mediaSource); if (videoItem.isLocked()) { Toast.makeText(activity(), "Video is Locked, please pay first!", Toast.LENGTH_SHORT).show(); return INTERCEPT_START_PLAYBACK_REASON_LOCKED; } return null; } }; // 添加拦截器 videoView.addPlaybackInterceptor(0, playbackInterceptor); // 移除拦截器 videoView.removePlaybackInterceptor(0, playbackInterceptor);
调用 PlaybackController 的 addPlaybackListener 和 removePlaybackListener 方法监听和移除事件回调。
说明
推荐在 VideoLayer 的 onBindPlaybackController 和 onUnbindPlaybackController 方法中注册和移除监听器,避免内存泄漏。
private final Dispatcher.EventListener mPlaybackListener = new Dispatcher.EventListener() { @Override public void onEvent(Event event) { switch (event.code()) { case PlaybackEvent.Action.START_PLAYBACK: break; case PlaybackEvent.Action.STOP_PLAYBACK: break; // ... } } }; playbackController.addPlaybackListener(mPlaybackListener); playbackController.removePlaybackListener(mPlaybackListener);
播放控制器事件 PlaybackEvent 分为行为事件 PlaybackEvent.Action 和状态事件 PlaybackEvent.State。
public interface PlaybackEvent { class Action { /** * @see ActionPreparePlayback */ public static final int PREPARE_PLAYBACK = 10001; /** * @see ActionStartPlayback */ public static final int START_PLAYBACK = 10002; /** * @see ActionStopPlayback */ public static final int STOP_PLAYBACK = 10003; } class State { /** * @see StateBindPlayer */ public static final int BIND_PLAYER = 20001; /** * @see StateUnbindPlayer */ public static final int UNBIND_PLAYER = 20002; /** * @see StateBindVideoView */ public static final int BIND_VIDEO_VIEW = 20003; /** * @see StateUnbindVideoView */ public static final int UNBIND_VIDEO_VIEW = 20004; } }
MediaSource 类是 PlayerKit 中对播放源的定义。支持 Vid、DirectUrl 和 VideoModel 三种播放源,您可根据自身业务需求,选择创建相应的播放源。
如果您已将视频上传至火山引擎视频点播服务,可使用 Vid 方式播放视频。您需要将 mediaId 参数设为视频点播服务生成的 Vid,将 playAuthToken 参数设为临时播放 Token。Vid 和临时播放 Token 由应用服务端下发的。应用客户端无需关心,调用应用服务端的接口获取即可,详情请见通过临时播放 Token 播放。
String mediaId = "your video id"; // 必传 String playAuthToken = "your video id's playAuthToken"; // 必传 MediaSource mediaSource = MediaSource.createIdSource(mediaId, playAuthToken); videoView.bindDataSource(mediaSource);
如果您使用 DirectUrl 方式播放视频,需将 url 参数设为视频播放地址。播放地址可以是第三方点播地址或视频点播服务生成的播放地址。此外,对于 DirectUrl 播放源,还可设置 mediaId 和 cacheKey 参数,说明如下:
示例代码如下:
设置单个清晰度的播放源:
String mediaId = "your video id"; // 选传 String url = "http://example.com/video_480p.mp4?expired=160898989089"; // 必传 String cacheKey = MD5.md5(new URL(url).getPath()); // 选传,一般使用 url 去掉时间戳的 path 部分作为缓存 key // media Id 和 cacheKey 若传 null,内部会自动生成 MediaSource mediaSource = MediaSource.createUrlSource(mediaId, url, cacheKey); videoView.bindDataSource(mediaSource);
设置多个清晰度的播放源:
Track track1 = new Track(); track1.setTrackType(Track.TRACK_TYPE_VIDEO); // 设置播放地址 track1.setUrl("http://example.com/video_360p.mp4"); // 一般使用 url 去掉时间戳的 path 部分的 md5 作为缓存 key track1.setFileHash("cache_key_360p"); track1.setQuality(new Quality(Quality.QUALITY_RES_360, "360P")); Track track2 = new Track(); track2.setTrackType(Track.TRACK_TYPE_VIDEO); track2.setUrl("http://example.com/video_480p.mp4"); track2.setFileHash("cache_key_480p"); track2.setQuality(new Quality(Quality.QUALITY_RES_480, "480P")); Track track3 = new Track(); track3.setTrackType(Track.TRACK_TYPE_VIDEO); track3.setUrl("http://example.com/video_720p.mp4"); track3.setFileHash("cache_key_720p"); track3.setQuality(new Quality(Quality.QUALITY_RES_720, "720P")); Track track4 = new Track(); track4.setTrackType(Track.TRACK_TYPE_VIDEO); track4.setUrl("http://example.com/video_1080p.mp4"); track4.setFileHash("cache_key_1080p"); track4.setQuality(new Quality(Quality.QUALITY_RES_1080, "1080P")); String mediaId = "your video id"; MediaSource mediaSource = new MediaSource(mediaId, MediaSource.SOURCE_TYPE_URL); mediaSource.setTracks(Arrays.asList(track1, track2, track3, track4)); videoView.bindDataSource(mediaSource);
使用 VideoModel 播放源可避免因调用 GetPlayInfo 接口带来的网络请求,缩短视频首帧耗时。当前 PlayerKit 未提供 VideoModel 类作为播放源构造方式,开发者需依据 GetPlayInfo 返回参数的 JSON 结构手动构建 VideoModel 播放源。
String mediaId = "your video id"; // 必传 String videoModelJson = "your video model json"; // 必传 MediaSource mediaSource = MediaSource.createModelSource(mediaId, videoModelJson); videoView.bindDataSource(mediaSource);
Player 接口是 PlayerKit 中对播放器组件的抽象,负责和 PlayerKit 的其他核心组件如 VideoView、MediaSource、 PlaybackController、VideoLayer 等进行交互。
PlayerKit 使用 Player 协议作为播放器的抽象,其实现类为 AVPlayer。AVPlayer 内部并不直接持有火山引擎播放器 TTVideoEngine,而是通过 PlayerAdapter 进行了一层抽象。如下图所示,VolcPlayer 实现了 PlayerAdapter 接口,其内部对火山引擎播放器 TTVideoEngine 进行了封装。AVPlayer 内部所持有的 mPlayer 为 VolcPlayer 对象。
Player.Factory 协议是 PlayerKit 中播放器工厂的定义,用于创建 Player 实例。VolcPlayerFactory 是 Player.Factory 的默认实现,用于构造 AVPlayer 对象。
VolcPlayerInit.config(new VolcPlayerInitConfig.Builder() .setPlayerKitConfig(new PlayerKitConfig.Builder() // 不指定内部默认使用 VolcPlayerFactory 实例 .setPlayerFactory(new VolcPlayerFactory()) .build()) // ... .build());
PlayerPool 协议是 PlayerKit 中播放器池子的抽象。通过 PlayerPool 可以全局管理播放器实例,也可以在任意时机获取播放器实例。DefaultPlayerPool 是 PlayerKit 中 PlayerPool 协议的默认实现,使用 mediaId 作为 key 存储 Player 实例,不限制 Player 实例个数。
VolcPlayerInit.config(new VolcPlayerInitConfig.Builder() .setPlayerKitConfig(new PlayerKitConfig.Builder() // 不指定内部默认使用 PlayerPool.DEFAULT 实例 .setPlayerPool(PlayerPool.DEFAULT) .build()) // ... .build());
推荐使用 PlayerPool 的 acquire 方法创建播放器实例。若 PlayerPool 中无可复用播放器,则调用 Player.Factory 的 create 方法创建播放器。
Player player = PlayerKit.config().playerPool.acquire(mediaSource, PlayerKit.config().playerFactory);
播放器创建的具体流程如下图所示:
VolcConfig 配置:建议在构造好 MediaSource 之后、调用 VideoView.bindDataSource 之前设置 VolcConfig。
VolcConfig volcConfig = new VolcConfig(); // ... 配置 volcConfig volcConfig.tag = "shortvideo"; volcConfig.subTag = "recommend"; VolcConfig.set(mediaSource, volcConfig); // 若之前设置过则返回设置的,若未设置过,返回 VolcConfig.DEFAULT,始终返回非 Null 值 VolcConfig volcConfig = VolcConfig.get(mediaSource);
VolcConfig 支持的配置如下表所示:
VolcConfig 配置 | 类型 | 说明 |
|---|---|---|
codecStrategyType | int | Codec 策略类型:
|
playerDecoderType | int | 解码器类型:
|
sourceEncodeType | int | 播放源编码类型:
|
enableAudioTrackVolume | boolean | 是否开启调节播放音量:
|
enableHlsSeamlessSwitch | boolean | 是否开启 HLS 平滑切换功能:
|
enableMP4SeamlessSwitch | boolean | 是否开启 MP4 平滑切换功能:
|
enableDash | boolean | 是否开启 DASH 播放功能:
|
enableEngineLooper | boolean | 是否开启播放内核异步调用。
|
enableSeekEnd | boolean | 是否开启 Seek 最后一秒。
|
enableFrameUpdateCallback | boolean | 是否开启帧回调。回调频繁影响性能,建议关闭。
|
firstFrameBufferingTimeoutMS | int | 首帧加载超时抛错时间:
|
playbackBufferingTimeoutMS | int | 播放中加载超时抛错时间。
|
enableTextureRender | boolean | 是否开启离屏渲染:
|
superResolutionConfig | VolcSuperResolutionConfig | 视频超分功能配置。默认关闭。具体请见进阶功能使用 - 视频超分。 |
qualityConfig | VolcQualityConfig | 选档功能配置。默认关闭。具体请见进阶功能使用 - ABR 起播选档。 |
enableSubtitle | boolean | 是否开启字幕:
|
subtitleLanguageIds | List | 设置字幕支持语言列表。默认为 null |
enable403SourceRefreshStrategy | boolean | 是否开启针对播放源 403 错误的自刷新策略:
|
tag | String | 用于播放埋点上报的 tag |
subTag | String | 用于播放埋点上报的 subTag |
TTVideoEngine 实例配置:VolcConfig 中不包含的配置可修改 TTVideoEngineFactoryDefault 的 setup 方法来直接配置。
TTVideoEngine 全局配置:在 VolcPlayerInit 中,添加在 Env.init 前即可。若需灵活控制,可在 VolcConfigGlobal 中添加静态变量控制。
注意
VolcConfig/VolcGlobalConfig 中不包含的配置,需要修改 vod-playerkit 模块源码。
注意
推荐使用播放控制器 PlaybackController 类控制播放流程,可保证开始播放、结束播放流程的播放器调用顺序,减少状态判断。
设置起播进度:
if (player.isIDLE() || player.isStopped()) { long startTimeInMs = 10000; // 从 10 秒处起播 player.setStartTime(startTimeInMs); }
准备播放:
if (player.isIDLE() || player.isStopped()) { player.prepare(mediaSource); }
开始播放:
if (player.isInPlaybackState()) { player.start(); }
暂停播放:
if (player.isInPlaybackState()) { player.pause(); }
Seek 到指定位置播放:
if (player.isInPlaybackState()) { player.seekTo(1000L); // seek 到 1 秒 }
释放播放器:
player.release();
播放器不同状态的常量及状态判断方法如下:
public interface Player { // 播放器初始化 int STATE_IDLE = 0; // 播放器准备中 int STATE_PREPARING = 1; // 播放器准备完成 int STATE_PREPARED = 2; // 播放器开始 int STATE_STARTED = 3; // 播放器暂停 int STATE_PAUSED = 4; // 播放器完成 int STATE_COMPLETED = 5; // 播放器出错 int STATE_ERROR = 6; // 播放器停止 int STATE_STOPPED = 7; // 播放器释放 int STATE_RELEASED = 8; @PlayerState int getState(); boolean isIDLE(); boolean isPreparing(); boolean isPrepared(); boolean isPlaying(); boolean isPaused(); boolean isCompleted(); boolean isStopped(); boolean isReleased(); boolean isError(); boolean isInPlaybackState(); }
强状态机检查方法仅可在特定状态下调用,否则将抛出 IllegalStateException 异常。
强状态机检查方法 | 有效状态 |
|---|---|
prepare | IDLE, STOPPED |
setStartTime | IDLE, STOPPED |
seekTo | PREPARED, STARTED, PAUSED, COMPLETED |
start | PREPARED, STARTED, PAUSED, COMPLETED |
pause | PREPARED, STARTED, PAUSED, COMPLETED |
reset | All states |
release | All states |
下图展示了播放状态转换的详细流程。
获取视频宽高:
int videoWidth = player.getVideoWidth(); int videoHeight = player.getVideoHeight();
获取视频时长:
long durationInMS = player.getDuration(); // 单位:MS
主动获取播放进度:
long currentPositionInMS = player.getCurrentPosition(); // 单位:MS
监听播放进度回调:
player.addPlayerListener(new EventListener() { @Override public void onEvent(Event event) { switch(event.code()) { case PlayerEvent.Info.PROGRESS_UPDATE:{ final InfoProgressUpdate e = event.cast(InfoProgressUpdate.class); long currentPositionInMS = e.currentPosition; long durationInMS = e.duration; break; } } } });
主动获取缓存进度:
long bufferedDurationInMs = player.getBufferedDuration(); // 获取缓存时长 int bufferedPercent = player.getBufferedPercentage(); // 获取缓存百分比 0-100
监听缓存进度回调:
player.addPlayerListener(new EventListener() { @Override public void onEvent(Event event) { switch (event.code()) { case PlayerEvent.Info.BUFFERING_UPDATE: { InfoBufferingUpdate e = event.cast(InfoBufferingUpdate.class); int bufferedPercent = e.percent; // 缓存百分比 0-100 break; } } } });
player.setVolume(1f, 1f); // 取值范围 [0, 1] float volume = player.getVolume(); // 获取音量
player.setMuted(true); // true 静音;false 取消静音 boolean isMuted = player.isMuted(); // 获取是否静音
player.setSpeed(1f); // 取值范围 (0, 3] float speed = player.getSpeed(); // 获取播放速度
player.setLooping(true); // true 开启循环播放;false 关闭循环播放 boolean isLooping = player.isLooping(); // 获取是否循环播放
起播选择档位:
final TrackSelector trackSelector = new TrackSelector() { @NonNull @Override public Track selectTrack(int type, int trackType, @NonNull List<Track> tracks, @NonNull MediaSource source) { // 从 sharedPreference 获取用户选择的清晰度 int qualityRes = VideoQuality.getUserSelectedQualityRes(source); // 若用户未选择,默认用 720P 起播 if (qualityRes <= 0) { qualityRes = Quality.QUALITY_RES_720; } for (Track track : tracks) { Quality quality = track.getQuality(); if (quality != null) { if (quality.getQualityRes() == qualityRes) { return track; } } } // 没找到 720P,则选择第 0 个清晰度 return tracks.get(0); } }; // 初始化传入 VolcPlayerInit.config(new VolcPlayerInitConfig.Builder() // ... .setTrackSelector(trackSelector) // ... .build());
播放中选择档位:
获取清晰度列表:
// 监听清晰度列表回调 player.addPlayerListener(new EventListener() { @Override public void onEvent(Event event) { final int code = event.code; switch(code) { case PlayerEvent.Info.TRACK_INFO_READY: { // 清晰度列表回调 InfoTrackInfoReady e = event.cast(InfoTrackInfoReady.class); // 清晰度列表,用于用户选择展示 final List<Track> tracks = e.tracks; break; } } } }); // 获取清晰度列表 // vid 播放源需要在 TRACK_INFO_READY 后才可获取 List<Track> tracks = player.getTracks(Track.TRACK_TYPE_VIDEO);
切换清晰度:
// 在 TRACK_INFO_READY 消息回调后,根据用户选择切换清晰度,切换成功回调 TRACK_CHANGED player.selectTrack(track.getTrackType(), track); // 监听清晰度切换成功回调 player.addPlayerListener(new EventListener() { @Override public void onEvent(Event event) { final int code = event.code; switch(code) { case PlayerEvent.Info.TRACK_CHANGED: { // 清晰度切换成功回调 InfoTrackChanged e = event.cast(InfoTrackChanged.class); Track pre = e.pre; // 切换前的清晰度 Track current = e.current; // 切换后当前清晰度 break; } } } });
获取当前清晰度:
// 档位未切换完成时,选中的清晰度和播放的清晰度会不一致 // 当前选中的档位 Track selectedTrack = player.getSelectedTrack(Track.TRACK_TYPE_VIDEO); // 当前播放的档位 Track currentTrack = player.getCurrentTrack(Track.TRACK_TYPE_VIDEO);
针对视频点播服务生成的帧对齐的 MP4、HLS 视频,支持在播放过程中平滑切换多个分辨率的播放地址,提升用户的播放体验。参考以下示例代码构造播放源:
MediaSource mediaSource = new MediaSource(UUID.randomUUID().toString(), MediaSource.SOURCE_TYPE_URL); @MediaProtocol int mediaProtocol = MEDIA_PROTOCOL_DEFAULT; // MEDIA_PROTOCOL_DASH or MEDIA_PROTOCOL_HLS mediaSource.setMediaProtocol(mediaProtocol); mediaSource.setMediaType(MEDIA_TYPE_VIDEO); // 必须开启 mediaSource.setSupportABR(true); Track track1 = new Track(); // 使用平滑切换时,每个 track 中的以下字段均为必填项 track1.setTrackType(Track.TRACK_TYPE_VIDEO); track1.setUrl("http://example.com/video_360p.mp4"); // 设置 cachekey 作为 fileHash track1.setFileHash("file unique id for cacheKey"); track1.setQuality(new Quality(Quality.QUALITY_RES_360, "360P")); // 设置封装格式类型,取值:Track.FORMAT_MP4、Track.FORMAT_HLS、Track.FORMAT_DASH track1.setFormat(Track.FORMAT_MP4); // 设置视频编码格式 // Track 列表中各个 Track 的视频编码格式需要一致,例如均为 H264 或均为 H265 track1.setEncoderType(Track.ENCODER_TYPE_H264); // Track.ENCODER_TYPE_H265 track1.setBitrate(700); // in Kbps track1.setVideoWidth(360); track1.setVideoHeight(640); Track track2 = new Track(); // ... 480p Track track3 = new Track(); // ... 720p Track track4 = new Track(); // ... 1080p mediaSource.setTracks(Arrays.asList(track1, track2, track3, track4));
调用 Player 的 addPlayerListener 和 removePlayerListener 方法监听和移除事件回调。
说明
在使用 PlaybackController 控制播放流程时,PlaybackController 会在其内部代理 Player 的事件,并统一向外分发。您只需为 PlaybackController 设置监听器,而无需为 Player 设置。若单独使用 Player 进行播放,可使用此 API。
final EventListener eventListener = new EventListener() { @Override public void onEvent(Event event) { final int code = event.code; // Event 类型码 // 下面列举常见 Event 码,更多请参考 PlayerEvent 类 switch(code) { case PlayerEvent.Action.PREPARE: // 调用 prepare 后触发 break; case PlayerEvent.State.PREPARED: // 播放器准备完成回调 break; case PlayerEvent.Info.VIDEO_RENDERING_START: // 播放器首帧渲染回调 break; } } }; // 设置监听 player.addPlayerListener(eventListener); // 移除监听 player.removePlayerListener(eventListener);
播放器事件分为 Action 事件、State 事件和 Info 事件三类,具体如下:
public interface PlayerEvent { /** * 播放器 Action 事件,在您调用对应的播放器方法时触发 * * @see Event#code() */ class Action { /** * 调用 setSurface 方法后触发 * * @see ActionSetSurface */ public static final int SET_SURFACE = 1001; /** * 调用 prepare 方法后触发 * * @see ActionPrepare */ public static final int PREPARE = 1002; /** * 调用 start 方法后触发 * * @see ActionStart */ public static final int START = 1003; /** * 调用 pause 方法后触发 * * @see ActionPause */ public static final int PAUSE = 1004; /** * 调用 release 方法后触发 * * @see ActionRelease */ public static final int RELEASE = 1006; /** * 调用 seekTo 方法后触发 * * @see ActionSeekTo */ public static final int SEEK_TO = 1007; /** * 调用 setLooping 方法后触发 * @see ActionSetLooping */ public static final int SET_LOOPING = 1008; /** * 调用 setSpeed 方法后触发 * @see ActionSetSpeed */ public static final int SET_SPEED = 1009; } /** * 播放器 State 事件,在播放器状态发生改变时触发。 * * @see Event#code() */ class State { /** * @see StateIDLE */ public static final int IDLE = 2001; /** * 调用 prepare 后,播放器处于 preparing 状态时回调 * * @see StatePreparing */ public static final int PREPARING = 2002; /** * 调用 prepare 后,播放器处于 prepared 状态时回调 * * @see StatePrepared */ public static final int PREPARED = 2003; /** * 调用 start 后,播放器处于 started 状态时回调 * * @see StateStarted */ public static final int STARTED = 2004; /** * 调用 pause 后,播放器处于 paused 状态时回调 * * @see StatePaused */ public static final int PAUSED = 2005; /** * 调用 release 后,播放器处于 released 状态时回调 * * @see StateReleased */ public static final int RELEASED = 2007; /** * 播放完成,播放器处于 completed 状态时回调 * * @see StateCompleted */ public static final int COMPLETED = 2008; /** * 播放出错,播放器处于 error 状态时回调 * * @see StateError */ public static final int ERROR = 2009; } /** * 播放器 Info 事件,会根据具体的事件类型触发。 * * @see Event#code() */ class Info { /** * 1. Vid 视频, 视频信息获取成功回调 * 2. Vid 字幕,字幕信息获取成功回调 * * @see InfoDataSourceRefreshed */ public static final int DATA_SOURCE_REFRESHED = 3001; /** * 获取到视频大小或视频大小改变时回调 * * @see InfoVideoSizeChanged */ public static final int VIDEO_SIZE_CHANGED = 3002; /** * 获取到视频像素比例时回调 * * @see InfoVideoSARChanged */ public static final int VIDEO_SAR_CHANGED = 3003; /** * 视频首帧渲染成功回调 * * @see InfoVideoRenderingStart */ public static final int VIDEO_RENDERING_START = 3004; /** * 音频首帧渲染成功回调 * * @see InfoAudioRenderingStart */ public static final int AUDIO_RENDERING_START = 3005; /** * 视频预渲染首帧回调 * * @see InfoVideoRenderingStartBeforeStart */ public static final int VIDEO_RENDERING_START_BEFORE_START = 3006; /** * 数据不足开始 loading 加载回调 * * @see InfoBufferingStart */ public static final int BUFFERING_START = 3007; /** * Loading 加载结束回调 * * @see InfoBufferingEnd */ public static final int BUFFERING_END = 3008; /** * 播放数据加载进度回调 * * @see InfoBufferingUpdate */ public static final int BUFFERING_UPDATE = 3009; /** * Seek 开始回调 * * @see InfoSeekingStart */ public static final int SEEKING_START = 3010; /** * Seek 渲染完成回调 * * @see InfoSeekComplete */ public static final int SEEK_COMPLETE = 3011; /** * 播放进度更新回调 * * @see InfoProgressUpdate */ public static final int PROGRESS_UPDATE = 3012; /** * 播放清晰度列表获取成功回调 * * @see InfoTrackInfoReady */ public static final int TRACK_INFO_READY = 3013; /** * 清晰度切换开始回调 * * @see InfoTrackWillChange */ public static final int TRACK_WILL_CHANGE = 3014; /** * 清晰度切换完成回调 * * @see InfoTrackChanged */ public static final int TRACK_CHANGED = 3015; /** * 起播命中缓存大小回调 * * @see InfoCacheUpdate */ public static final int CACHE_UPDATE = 3016; /** * 字幕显示状态改变回调 * * @see InfoSubtitleStateChanged */ public static final int SUBTITLE_STATE_CHANGED = 3017; /** * 字幕语言列表获取成功回调 * * @see InfoSubtitleInfoReady */ public static final int SUBTITLE_LIST_INFO_READY = 3018; /** * 字幕文件加载完成回调 * * @see InfoSubtitleFileLoadFinish */ public static final int SUBTITLE_FILE_LOAD_FINISH = 3019; /** * 字幕语言切换开始回调 * * @see InfoSubtitleWillChange */ public static final int SUBTITLE_WILL_CHANGE = 3020; /** * 字幕文字更新回调 * * @see InfoSubtitleTextUpdate */ public static final int SUBTITLE_TEXT_UPDATE = 3021; /** * 字幕语言切换完成回调 * * @see InfoSubtitleChanged */ public static final int SUBTITLE_CHANGED = 3022; /** * 视频帧信息回调 * * @see InfoFrameInfoUpdate */ public static final int FRAME_INFO_UPDATE = 3023; } }