预加载是指在开始播放之前提前下载即将播放视频的头部数据,以实现快速起播,从而提升播放体验。预加载适用于各种播放场景,它的接入成本低,效果明显。
本节介绍不同播放场景中预加载的规则和效果。通过选择合理精细的预加载时机和设置,可以提高预加载命中率,从而提升首帧体验。需要注意的是,过度粗放或不合理的预加载时机可能会导致流量浪费,增加成本。
说明
针对短视频场景,点播 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);