You need to enable JavaScript to run this app.
文档中心
视频点播

视频点播

复制全文
下载 pdf
含 UI 集成方案
核心类使用
复制全文
下载 pdf
核心类使用

本文为您详细介绍 PlayerKit 的架构、核心模块以及核心类,着重介绍播放控件、播放控制器、播放源和播放器等核心类的配置及使用方式。

PlayerKit 架构概览

模块构成

PlayerKit 包含 vod-player、vod-player-volcengine 和 vod-player-utils 三个核心模块,如下图所示:
Image
各模块的详细介绍如下表:

模块

说明

vod-player

播放器接口层。该层定义了 PlayerKit 的核心类及架构。关键类包括:

  • Player:播放器标准接口定义。
  • AVPlayer:播放器默认实现,内部依赖 PlayerAdapter 提供具体的播放器实现。
  • PlayerPool:播放器池子接口定义,便于管理播放器实例。
  • MediaSource:播放源定义。
  • VideoView:播放 View 定义,内部实现了 TextureView 和 SurfaceView。
  • VideoLayerHost:播放 UI Layer 管理器。
  • VideoLayer:播放 UI Layer 基类。用于实现灵活、高复用的播放 UI。
  • PlaybackController:播放流程定义。规定了一次起播流程中 Player 的标准调用流程,以及如何把播放三要素 Player、MediaSource、VideoView 串联起来。实现了状态安全的开始和结束播放方法。

vod-player-volcengine

火山引擎播放器 SDK 的实现层。该层基于火山引擎播放器 SDK,实现了播放器适配器 PlayerAdapter。内部还实现了抖音同款策略,提供预加载、预渲染、超分、智能 H.265 策略、ABR 起播选档等多项高性能最佳实践。关键类包括:

  • VolcPlayerInit:播放器 SDK 初始化类。内部封装了火山引擎播放器 SDK 的初始化逻辑。
  • VolcPlayer:PlayerAdapter 的火山引擎播放器(TTVideoEngine)实现类。
  • TTVideoEngineFactory:TTVideoEngine 工厂接口。
  • TTVideoEngineFactoryDefault:TTVideoEngine 工厂实现类,负责 TTVideoEngine 实例创建、option 设置管理。

vod-player-utils

工具类模块。包含各模块所需的常见工具类。关键类如下:

  • L:logcat 输出类。各组件中的日志输出都是经过精心设计的,便于问题排查。
  • Asserts:断言类。各组件使用断言保证状态和流程准确。

ttsdk-player_premium

火山引擎播放器 SDK。

依赖关系

vod-player、vod-player-volcengine 和 vod-player-utils 模块的依赖关系如下图所示。
Image

播放控件 VideoView

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);
    

快速搭建播放 UI

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 层级示意图如下:
Image

自定义开发 VideoLayer

如果上述模板无法满足您的需求,您可以自定义开发 VideoLayer。VideoLayer 是一套强大且灵活的系统,通过将播放控件上的 UI 视图进行 Layer 化抽象,实现粒度可控、职责单一的播放浮层。VideoLayer 是 PlayerKit 中的基础组件,而 BaseLayer、AnimateLayer、DialogLayer 则是 SceneKit 提供的高级组件,开发者可以将其复制到项目中使用。具体说明如下:

  • VideoLayer:作为播放浮层的基类,负责与 VideoLayerHost 进行交互。它提供了创建 View、显示和隐藏 View 以及事件分发的功能。
  • BaseLayer:继承自 VideoLayer,具备显示和隐藏 View 的拦截能力。
  • AnimateLayer:属于动画浮层,继承自 BaseLayer,提供了浮层 View 动画显示和隐藏的能力。
  • DialogLayer:作为 Dialog 浮层,继承自 AnimateLayer,具备显示后隐藏其他 Layer 的能力。

对于有动画的浮层,建议继承 AnimateLayer 来实现;对于没有动画的浮层,建议继承 BaseLayer 来实现。
Image

动画浮层

视频浮层的显示和隐藏通常需要带有动画。动画对于 VideoLayer 来说是一个基础且通用的使用场景。因此,vod-scenekit 中抽象出了 AnimateLayer 作为动画浮层的基类。使用说明如下:

  • 默认是 alpha 显示和 alpha 隐藏动画。开发者可通过重写 createAnimator、resetViewAnimateProperty、initAnimateShowProperty、initAnimateDismissProperty 来定制动画。
  • 开发者可调用 animateShow 和 animateDismiss 触发动画,通过 isAnimateShowing 和 isAnimateDismissing 来判断动画是否正在执行。

示例代码如下:

  • 带有动画的显示与隐藏:

    // 是否正在执行显示动画
    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);
            }
        }
    }
    

Dialog 浮层

与普通的 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();
        }
    }
    

创建浮层 View

通过重写 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;
    }
}

显示和隐藏浮层 View

VideoLayer 本质上为一个 ViewHolder,提供 createView、show 及 dismiss 等方法,用于控制 View 的创建、展示与隐藏。

  • show 方法提供了 View 的懒加载机制,能够确保 View 仅在需要展示时才创建,以此节省内存。
  • dismiss 方法并非单纯地隐藏 View,而是把它从父 View 中移除,这保障了视图树的简洁性,提高了流畅度。
  • 调用 show 将 view 添加至 VideoLayerHost 中的 hostView 时,会触发 onViewAddedToHostView 的回调。
  • 调用 dismiss 将 view 从 VideoLayerHost 的 hostView 中移除时,会触发 onViewRemovedFromHostView 的回调。
// 判断是否正在显示
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()) {
                
            }
        }
    };
}

监听 VideoView 事件

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) {
    }
}

监听回退事件

  1. 在 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();
        }
    }
    
  2. 监听拦截回退事件:

    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 的相关回调,您可以重写相关方法以监听相应的回调。如下图所示:
    Image
    回调相关方法如下:

    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);

RecyclerView 中使用 VideoView

以下示例代码展示在 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

PlaybackController 是 PlayerKit 中对播放流程控制器的定义,其主要作用包括:

  • 规范开始播放流程:
    • 管理 Player 的复用和创建流程,适用于 RecyclerView 中 VideoView 的复用场景。
    • 处理 VideoView 的 Surface 的复用、创建和标记保持。
    • 协调 Player 与 VideoView 的 MediaSource 之间的交互。
    • 确保 Player 在不同状态下安全开始播放。由于 AVPlayer 是强状态机实现,在不恰当的时机调用播放控制方法可能会抛出异常,而 PlaybackController 提供了状态安全的播放控制方法,以方便业务调用。
  • 规范停止播放流程:
    • 负责 Player 的回收。
    • 处理 VideoView 的 Surface 的标记保持和销毁。
  • 进行播放流程事件分发,代理播放器事件的分发。

创建播放控制器

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 方法创建播放器。开始播放具体流程如下图所示:
Image

停止播放

调用 stopPlayback 方法停止播放。

videoView.stopPlayback();

停止播放流程如下图所示:
Image

暂停播放

调用 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

MediaSource 类是 PlayerKit 中对播放源的定义。支持 Vid、DirectUrl 和 VideoModel 三种播放源,您可根据自身业务需求,选择创建相应的播放源。

Vid 播放源

如果您已将视频上传至火山引擎视频点播服务,可使用 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 播放源

如果您使用 DirectUrl 方式播放视频,需将 url 参数设为视频播放地址。播放地址可以是第三方点播地址或视频点播服务生成的播放地址。此外,对于 DirectUrl 播放源,还可设置 mediaId 和 cacheKey 参数,说明如下:

  • mediaId:视频的唯一标识,必须与视频源一一对应。mediaId 可以是您自己的视频管理系统中的唯一标识,也可以在 App 层自行生成一个 ID。此 ID 会用于播放器 SDK 的日志上报、预加载、预渲染等功能中。
  • cacheKey:推荐使用 URL 的 MD5 值作为缓存 key。缓存 key 也需能和视频资源文件一一对应,不带特殊字符,能作为文件名。

示例代码如下:

  • 设置单个清晰度的播放源:

    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 播放源

使用 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

Player 接口是 PlayerKit 中对播放器组件的抽象,负责和 PlayerKit 的其他核心组件如 VideoView、MediaSource、 PlaybackController、VideoLayer 等进行交互。

播放器协议

PlayerKit 使用 Player 协议作为播放器的抽象,其实现类为 AVPlayer。AVPlayer 内部并不直接持有火山引擎播放器 TTVideoEngine,而是通过 PlayerAdapter 进行了一层抽象。如下图所示,VolcPlayer 实现了 PlayerAdapter 接口,其内部对火山引擎播放器 TTVideoEngine 进行了封装。AVPlayer 内部所持有的 mPlayer 为 VolcPlayer 对象。
Image

播放器工厂

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);

播放器创建的具体流程如下图所示:
Image

播放器配置

  • 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 策略类型:

    • VolcConfig.CODEC_STRATEGY_DISABLE = 0
    • VolcConfig.CODEC_STRATEGY_COST_SAVING_FIRST = 1
    • VolcConfig.CODEC_STRATEGY_HARDWARE_DECODE_FIRST = 2

    playerDecoderType

    int

    解码器类型:

    • Player.DECODER_TYPE_UNKNOWN = 0
    • Player.DECODER_TYPE_SOFTWARE = 1
    • Player.DECODER_TYPE_HARDWARE = 2

    sourceEncodeType

    int

    播放源编码类型:

    • Track.ENCODER_TYPE_UNKNOWN = 0
    • Track.ENCODER_TYPE_H264 = 1
    • Track.ENCODER_TYPE_H265 = 2
    • Track.ENCODER_TYPE_H266 = 3

    enableAudioTrackVolume

    boolean

    是否开启调节播放音量:

    • true:调节播放器音量
    • false:(默认值)调节系统音量

    enableHlsSeamlessSwitch

    boolean

    是否开启 HLS 平滑切换功能:

    • true:(默认值)开启
    • false:关闭

    enableMP4SeamlessSwitch

    boolean

    是否开启 MP4 平滑切换功能:

    • true:(默认值)开启
    • false:关闭

    enableDash

    boolean

    是否开启 DASH 播放功能:

    • true:(默认值)开启
    • false:关闭

    enableEngineLooper

    boolean

    是否开启播放内核异步调用。

    • true:(默认值)开启
    • false:关闭

    enableSeekEnd

    boolean

    是否开启 Seek 最后一秒。

    • true:(默认值)开启
    • false:关闭

    enableFrameUpdateCallback

    boolean

    是否开启帧回调。回调频繁影响性能,建议关闭。

    • true:开启
    • false:(默认值)关闭

    firstFrameBufferingTimeoutMS

    int

    首帧加载超时抛错时间:

    • 0:(默认值)关闭。
    • >0:开启。开启后,首帧若在指定时间内未回调,Player 会释放播放内核,主动抛错。

    playbackBufferingTimeoutMS

    int

    播放中加载超时抛错时间。

    • 0:(默认值)关闭。
    • >0:开启。开启后,播放中 bufferingStart 后,bufferingEnd 在指定时间内未回调,Player 内会释放播放内核,主动抛错。

    enableTextureRender

    boolean

    是否开启离屏渲染:

    • true:(默认值)开启
    • false:关闭

    superResolutionConfig

    VolcSuperResolutionConfig

    视频超分功能配置。默认关闭。具体请见进阶功能使用 - 视频超分。

    qualityConfig

    VolcQualityConfig

    选档功能配置。默认关闭。具体请见进阶功能使用 - ABR 起播选档。

    enableSubtitle

    boolean

    是否开启字幕:

    • true:开启。
    • false:(默认值)关闭

    subtitleLanguageIds

    List

    设置字幕支持语言列表。默认为 null

    enable403SourceRefreshStrategy

    boolean

    是否开启针对播放源 403 错误的自刷新策略:

    • true:开启。
    • false:(默认值)关闭

    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

播放状态转化

下图展示了播放状态转换的详细流程。
Image

获取播放信息

  • 获取视频宽高:

    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());
    
  • 播放中选择档位:

    1. 获取清晰度列表:

      // 监听清晰度列表回调
      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);
      
    2. 切换清晰度:

      // 在 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;
                  }
              }
          }
      });
      
    3. 获取当前清晰度:

      // 档位未切换完成时,选中的清晰度和播放的清晰度会不一致
      
      // 当前选中的档位
      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;
    }
}
最近更新时间:2026.10.21 13:17:49
这个页面对您有帮助吗?
有用
有用
无用
无用