You need to enable JavaScript to run this app.
导航
自定义预加载
最近更新时间:2024.08.21 11:48:49首次发布时间:2021.02.23 10:42:42

预加载是指在开始播放之前提前下载即将播放视频的头部数据,以实现快速起播,从而提升播放体验。预加载适用于各种播放场景,它的接入成本低,效果明显。

预加载规则说明

本节介绍不同播放场景中预加载的规则和效果。通过选择合理精细的预加载时机和设置,可以提高预加载命中率,从而提升首帧体验。需要注意的是,过度粗放或不合理的预加载时机可能会导致流量浪费,增加成本。
图片

短视频场景(类似抖音)

  • 场景特点:一屏只展示一个竖版视频,一次只滑动一个视频。
  • 预加载时机:当前视频缓冲时长超过 20 秒或全部缓冲完成后,可以开始向下进行预加载。
  • 预加载数量:推荐预加载 3 个视频。用户停留在当前视频位置观看视频,当前位置记为 index,向下预加载 n 个视频,预加载范围为 [index + 1, index+n]。
  • 预加载大小:建议预加载的大小设置 800K。
  • 取消预加载时机:滑动切换播放视频时,取消全部预加载,避免对当前播放视频带宽产生争抢,影响当前视频的首帧。

说明

针对短视频场景,点播 SDK 基于抖音亿级日活跃用户的真实反馈和大规模实践经验,封装了两大最佳策略:预加载策略预渲染策略,帮助您快速搭建“抖音”同款短视频场景,实现“零首帧”的播放效果。详情请见抖音同款短视频最佳实践

中视频场景(类似西瓜视频)

  • 场景特点:一屏中同时存在 2 个或以上视频。Feed 流可以快速滑动,一次可能划过多个视频。
  • 预加载时机:当前视频缓冲时长超过 20 秒或全部缓冲完成,可以开始向下进行预加载。
  • 预加载数量:推荐预加载 5 个视频。用户停留在当前视频位置观看视频,当前位置记为 index,向下预加载 n 个视频,预加载范围为 [index + 1, index + n]。
  • 预加载大小:建议预加载的大小设置 800K。
  • 取消预加载时机:滑动切换播放视频时,取消全部预加载,避免对当前播放视频带宽产生争抢,影响当前视频的首帧。

长视频场景

  • 场景特点:沉浸式的全屏播放场景和播放页场景,例如影视、综艺等长视频的播放场景。
  • 预加载时机:可根据具体场景,例如影视或综艺,在当前剧集将要播放完成时,预加载下一剧集。

前提条件

仅高级版或企业版 SDK 支持预加载功能。请确保您已购买高级版或企业版的 License 并添加高级版 SDK 依赖,详见以下文档:

DirectUrl 模式下自定义预加载

构造预加载数据源

预加载与播放使用相同的数据源结构。为保证播放能命中预加载缓存,需确保构造预加载数据源与构造播放数据源时使用的 vidurlcacheKey 一致。示例代码如下:

public static DirectUrlSource createDirectUrlSource() {
    final String vid = "video id"; // 视频源与 vid 必须一一对应
    final String url = "http://www.example.com/h264.mp4";
    // cacheKey 用作磁盘缓存的文件名,建议采用 url 中能标识视频文件的部分的 MD5 值
    final String cacheKey = TTVideoEngine.computeMD5(url); 
    DirectUrlSource directUrlSource = new DirectUrlSource.Builder()
            .setVid(vid)
            .addItem(new DirectUrlSource.UrlItem.Builder()
                    .setUrl(url)
                    .setCacheKey(cacheKey)
                    .build())
            .build();
    return directUrlSource;
}

添加预加载任务

示例代码如下:

public static void preloadUrlSource() {
    // 1. 构造播放源
    final DirectUrlSource source = createDirectUrlSource();
    final long preloadSize = 800 * 1024; // 预加载大小 800K    
    // 2. 构造预加载 Item
    final PreloaderURLItem preloadItem = new PreloaderURLItem(source, preloadSize);
    preloadItem.setCallBackListener(new IPreLoaderItemCallBackListener() {
        @Override
        public void preloadItemInfo(PreLoaderItemCallBackInfo info) {
            if (info == null) return;
            final int key = info.getKey();
            switch (key) {
                case PreLoaderItemCallBackInfo.KEY_IS_PRELOAD_END_SUCCEED: {
                    // 预加载成功
                    DataLoaderHelper.DataLoaderTaskProgressInfo cacheInfo = info.preloadDataInfo;
                    if (cacheInfo == null) return;
                    String vid = cacheInfo.mVideoId; // 传入的 vid
                    String cacheKey = cacheInfo.mKey; // 预加载的视频文件的 fileHash
                    String cachePath = cacheInfo.mLocalFilePath; // 缓存视频文件路径
                    long mediaSize = cacheInfo.mMediaSize; // 缓存视频文件总大小
                    long cachedSize = cacheInfo.mCacheSizeFromZero; // 已缓存大小
                    Log.d("VideoPlay", "[preload] result success."
                            + " vid = " + vid
                            + ", cacheKey = " + cacheKey
                            + ", mediaSize = " + mediaSize
                            + ", cachedSize = " + cachedSize);
                    break;
                }
                case PreLoaderItemCallBackInfo.KEY_IS_PRELOAD_END_FAIL: {
                    // 预加载失败
                    Log.d("VideoPlay", "[preload] result failed."
                            + " vid = " + source.vid()
                            + ", error = " + info.preloadError);
                    break;
                }
                case PreLoaderItemCallBackInfo.KEY_IS_PRELOAD_END_CANCEL: {
                    // 预加载取消
                    Log.d("VideoPlay", "[preload] result canceled."
                            + " vid = " + source.vid());
                    break;
                }
                default: {
                    break;
                }
            }
        }
    });
    // 3. 添加预加载任务
    TTVideoEngine.addTask(preloadItem);
}
    

取消预加载任务

取消任务,对没开始执行的任务和正在下载的任务有影响,对已经完成的任务没有影响。示例代码如下:

// 取消单个预加载任务
// cacheKey 为构造 DirectUrlSource 播放源时传入的 cacheKey
TTVideoEngine.cancelPreloadTask(cacheKey); 

// 取消全部预加载任务
TTVideoEngine.cancelAllPreloadTasks();

Vid 模式下自定义预加载

构造播放源

预加载与播放使用相同的播放源结构。为保证播放能命中预加载缓存,需确保预加载与播放构造的播放源 vidplayAuthTokenencodeTyperesolution 一致。

private static VidPlayAuthTokenSource createVidSource() {
    final String videoId = "your video id"; // 由应用服务端下发
    final String playAuthToken = "your video id's play auth token"; // 由应用服务端下发
    final String encodeType = Source.EncodeType.H264;
    // final String encodeType = Source.EncodeType.h265;
    // final String encodeType = Source.EncodeType.h266;
    final Resolution resolution = Resolution.High; // 起播/预加载清晰度

    VidPlayAuthTokenSource vidSource = new VidPlayAuthTokenSource.Builder()
            .setVid(videoId)
            .setPlayAuthToken(playAuthToken)
            .setEncodeType(encodeType) // 设置 Codec 类型(h264,h265、h266),不传则使用默认值 h264
            .setResolution(resolution)
            .build();
    return vidSource;
}
    

添加预加载任务

public static void preloadVidSource() {
    // 1. 构造播放源 
    final VidPlayAuthTokenSource source = createVidSource();
    final long preloadSize = 800 * 1024; // 预加载大小 800K    
    // 2. 构造预加载 Item
    final PreloaderVidItem preloadItem = new PreloaderVidItem(source, preloadSize);
    preloadItem.setCallBackListener(new IPreLoaderItemCallBackListener() {
        @Override
        public void preloadItemInfo(PreLoaderItemCallBackInfo info) {
            if (info == null) return;
            final int key = info.getKey();
            switch (key) {
                case PreLoaderItemCallBackInfo.KEY_IS_FETCH_END_VIDEOMODEL: {
                    VideoModel videoModel = info.fetchVideoModel;
                    if (videoModel == null) return;
                    // getPlayInfo 接口成功获取到视频播放信息
                    Log.d("VideoPlay", "[preload] preloadItemInfo videoModel fetched."
                            + " vid = " + source.vid()
                            + ", resolution = " + source.resolution()
                            + ", all = " + Arrays.toString(videoModel.getSupportResolutions()));
                    break;
                }
                case PreLoaderItemCallBackInfo.KEY_IS_PRELOAD_END_SUCCEED: {
                    // 预加载成功
                    DataLoaderHelper.DataLoaderTaskProgressInfo cacheInfo = info.preloadDataInfo;
                    if (cacheInfo != null) {
                        String vid = cacheInfo.mVideoId; // 传入的 video id
                        Resolution resolution = cacheInfo.mResolution; // 预加载视频文件的清晰度
                        String cacheKey = cacheInfo.mKey; // 预加载的视频文件的 fileHash
                        String cachePath = cacheInfo.mLocalFilePath; // 缓存视频文件路径
                        long mediaSize = cacheInfo.mMediaSize; // 缓存视频文件总大小
                        long cachedSize = cacheInfo.mCacheSizeFromZero; // 已缓存大小

                        Log.d("VideoPlay", "[preload] result success."
                                + " vid = " + vid
                                + ", resolution = " + resolution
                                + ", cacheKey = " + cacheKey
                                + ", mediaSize = " + mediaSize
                                + ", cachedSize = " + cachedSize);
                    }
                    break;
                }
                case PreLoaderItemCallBackInfo.KEY_IS_PRELOAD_END_FAIL: {
                    // 预加载失败
                    Log.d("VideoPlay", "[preload] result failed."
                            + " error = " + info.preloadError);
                    break;
                }
                case PreLoaderItemCallBackInfo.KEY_IS_PRELOAD_END_CANCEL: {
                    // 预加载取消
                    Log.d("VideoPlay", "[preload] result canceled.");
                    break;
                }
                default: {
                    break;
                }
            }
        }
    });    
    // 3. 添加预加载任务
    TTVideoEngine.addTask(preloadItem);

取消预加载任务

取消任务,对没开始执行的任务和正在下载的任务有影响,对已经完成的任务没有影响。示例代码如下:

// 取消单个预加载任务
// 通过 videoId 取消预加载
TTVideoEngine.cancelPreloadTaskByVideoId(source.vid()); 

// 取消全部预加载任务
TTVideoEngine.cancelAllPreloadTasks();

验证是否命中预加载

通过播放器监听回调确认是否命中预加载。示例代码如下:

// TTVideoEngine 实例设置监听
ttVideoEngine.setVideoEngineInfoListener(new VideoEngineInfoListener() {
    @Override
    public void onVideoEngineInfos(VideoEngineInfos videoEngineInfos) {
        if (videoEngineInfos == null) {
            return;
        }
          
        if (videoEngineInfos.getKey().equals(USING_MDL_HIT_CACHE_SIZE)) {
            // 起播命中缓存判断
            String taskKey = videoEngineInfos.getUsingMDLPlayTaskKey();// 使用的 key 信息
            long cacheSize = videoEngineInfos.getUsingMDLHitCacheSize();// 命中缓存文件 size
            
            // cacheSize > 0 , 表示命中缓存
            // cacheSize = 0 , 未命中缓存,请排查预加载任务是否成功,如果是vid方式,请检查播放和预加载的reslution是否一致
        }
    }
});

说明

  • 默认情况下,播放器支持边下载边播放。如果某个视频是第一次播放,命中的缓存是预加载下载的数据。第二次播放,可能是第一次播放的缓存。
  • 如果您想要测试预加载缓存的命中率,需要确保每个视频都是第一次播放,建议首次安装进行测试。

设置预加载任务并发数

// 设置预加载任务的并发数,默认值为 1,串行执行预加载任务
TTVideoEngine.setIntValue(DataLoaderHelper.DATALOADER_KEY_INT_PARALLEL_NUM, 1);