预加载是指在开始播放之前提前下载即将播放视频的头部数据,以实现快速起播,从而提升播放体验。预加载适用于各种播放场景,它的接入成本低,效果明显。
本节介绍不同播放场景中预加载的规则和效果。通过选择合理精细的预加载时机和设置,可以提高预加载命中率,从而提升首帧体验。需要注意的是,过度粗放或不合理的预加载时机可能会导致流量浪费,增加成本。
说明
针对短视频场景,点播 SDK 基于抖音亿级日活跃用户的真实反馈和大规模实践经验,封装了两大最佳策略:预加载策略和预渲染策略,帮助您快速搭建“抖音”同款短视频场景,实现“零首帧”的播放效果。详情请见抖音同款短视频最佳实践。
仅高级版或企业版 SDK 支持预加载功能。请确保您已购买高级版或企业版的 License 并添加高级版 SDK 依赖,详见以下文档:
预加载与播放使用相同的数据源结构。为保证播放能命中预加载缓存,需确保构造预加载数据源与构造播放数据源时使用的 vid
、url
和 cacheKey
一致。示例代码如下:
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
、playAuthToken
、encodeType
和 resolution
一致。
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);