预加载是指在开始播放之前,提前下载即将播放视频的头部数据,播放时达到快速起播,大幅优化播放体验。预加载在各播放场景都可以使用,接入成本低,效果明显。
预加载策略
预加载使用原则是不影响当前视频播放,选择合理精细的预加载时机,可以有效的提高预加载命中率,提升首帧体验。如果粗放不合理的预加载时机,会出现浪费流量的现象,导致成本增加。
如下介绍 3 个常见场景中使用预加载策略的效果和推荐简单使用规则。
![alt](https://lf3-volc-editor.volccdn.com/obj/volcfe/sop-public/upload_1bf1dc79c50abf0bf44395eb9f359d17.png)
短视频(竖屏类抖音)
场景特点:类似抖音场景,一屏只展示一个竖版视频,一次滑动只能滑动一个视频。
- 预加载时机:当前视频缓冲时长超过 20秒或全部缓冲完成,可以开始向下进行预加载。
- 预加载数量:推荐预加载视频数量设置为 3个。用户停留在当前视频位置观看视频,当前位置记为 index,向下预加载 n 个视频,预加载范围:[index + 1,index+n]。
- 预加载大小:建议预加载的大小设置 800K。
- 取消:滑动切换播放视频时,取消全部预加载,避免对当前播放视频带宽产生争抢,影响当前视频的首帧。
中视频(横屏类西瓜)
场景特点: Feed 一屏中同时存在 2个以上视频。Feed 流可以快速滑动,一次滑动可能划过多个视频。
- 预加载时机: 当前视频缓冲时长超过 20秒或全部缓冲完成,可以开始向下进行预加载。
- 预加载数量: 推荐预加载视频数量设置为 5个。用户停留在当前视频位置观看视频,当前位置记为 index,向下预加载 n 个视频,预加载范围:[index + 1, index + n]。
- 预加载大小: 建议预加载的大小设置 800K。
- 取消: 滑动切换播放视频时,取消全部预加载,避免对当前播放视频带宽产生争抢,影响当前视频的首帧。
长视频
场景特点:沉浸式的全屏播放场景和播放页播放场景,例如影视、综艺等长视频的播放场景。
可根据具体场景,例如影视综艺,可以在当前剧集将要播放完成,预加载下一剧集。
接入预加载
在开始添加任务之前,需要明确 SDK 启动配置信息是否符合预期。
注意
预加载为高级版 SDK 功能,若您目前使用的是基础版 SDK,请先在控制台中购买高级版 License,在高级版 SDK中进行接入。
接入方式
接入预加载的方式有 2种:URL 方式接入、Vid 方式接入。
预加载与播放使用相同的播放源结构。为保证播放能命中预加载缓存,需确保预加载与播放构造的播放源 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() {
// Step 1: 构造播放源
final DirectUrlSource source = createDirectUrlSource();
final long preloadSize = 800 * 1024; // 预加载大小 800K
// Step2: 构造预加载 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;
}
}
}
});
// Step3: 添加预加载任务
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"; // appServer 下发
final String playAuthToken = "your video id's play auth token"; // appServer 下发
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() {
// Step 1: 构造播放源
final VidPlayAuthTokenSource source = createVidSource();
final long preloadSize = 800 * 1024; // 预加载大小 800K
// Step2: 构造预加载 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;
}
}
}
});
// Step3: 添加预加载任务
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);