播放器 SDK 支持下载功能。用户可在有网络的条件下将视频下载至本地,随后在无网络的环境下观看。播放器 SDK 支持下载 DirectUrl 视频源和 Vid 视频源,并提供开始、暂停、恢复、删除等控制下载任务的方法。本文介绍如何接入播放器 SDK 的下载功能,包含功能介绍、前提条件、集成步骤等内容。
注意
如需下载 HLS 视频,请集成 Android 播放器 SDK 1.36.2.11 或以上版本。
使用下载功能前,您需要先在播放器 SDK 初始化过程中对下载功能进行一些必要的配置,例如是否开启下载 HLS 视频的能力、设置下载路径。此外您还可以设置最大并发数和空闲磁盘空间大小的限制等。
注意
需要特别注意的是,初始化代码的时序对下载功能是否能正常工作有直接影响:
示例代码如下:
//(非必需)自定义下载文件存储路径,默认路径:"/data/user/0/包名/cache/video_cache/mdlDownload"。 // 如需自定义下载文件存储路径,必须在播放器 SDK 初始化之前调用。 TTVideoEngine.setStringValue(DATALOADER_KEY_STRING_DOWNLOAD_DIR, getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()); // 开启 HLS 下载功能。可选,如业务中无需下载 HLS 视频,可跳过。 TTVideoEngine.setIntValue(DataLoaderHelper.DATALOADER_KEY_INT_ENABLE_HLS,1); // 开启 HLS TTVideoEngine.setIntValue(DataLoaderHelper.DATALOADER_KEY_ENABLE_HLS_PROXY,1); // 开启 HLS proxy // 播放器 SDK 初始化,详见快速开始文档 Env.init(...); // Vid 下载时,会有下载数量限制,以下两种方式按需选择一种,不要同时调用 // 方式一:去除下载数量限制,存入 db 数据库直至磁盘空间不足 VideoModelDBManager.setEnableSizeLimit(false); // 方式二:保留数量限制,例如最多缓存 500 个视频;按业务需求调整 VideoModelDBManager.setEnableSizeLimit(true); VideoModelDBManager.setCacheSize(500); //(非必需)设置最大并发下载数,默认值为 1。 Downloader.getInstance().setMaxDownloadOperationCount(1); //(非必需)设置空闲磁盘空间大小的限制(单位为 Byte)。默认值为 1 GB,如果剩余磁盘大小不足 1 GB,则返回 ERROR_CODE_DOWNLOAD_DISK_FULL错误。 Downloader.getInstance().setLimitFreeDiskSize(1024 * 1024 * 1024);
初始化后,调用 loadAllTasks 方法获取已有下载任务,包括已完成的任务和未完成的任务。执行完成后,SDK 会触发 downloaderDidLoadAllTask 回调。
说明
downloaderDidLoadAllTask 回调之后,才能创建和添加新任务。
// 加载已有下载任务,包括已完成的任务和未完成的任务。 // 执行完成会触发 downloaderDidLoadAllTask 回调。 // 初始化后即可调用此接口。 // 可用来做持久化:app 退出重新进入,通过调用此接口,可获取已下载完成和未完成的下载任务列表。 Downloader.getInstance().loadAllTasks(this.getApplicationContext());
播放器 SDK 支持播放 DirectUrl 视频源和 Vid 视频源。与之对应,播放器 SDK 也提供了不同数据源的下载能力。
说明
视频源的参数说明详见快速开始 - 设置播放数据源。
适用于下载来自火山引擎或第三方 CDN 的视频 URL 地址。通过传入视频的直接播放地址来指定播放内容。除了 url,您还必须提供以下两个关键参数:
vid:视频的唯一标识。可以是您自己的视频管理系统中的唯一标识,也可以在 App 层自行生成一个 ID。此 ID 会用于播放器 SDK 的日志上报、预加载、预渲染等功能中。cacheKey:视频的缓存索引 Key。为确保唯一性,推荐使用 URL 的 MD5 值。为确保播放器缓存和下载功能的稳定性,强烈建议您遵循以下规范来设置 cacheKey:
cacheKey 的取值应仅包含字母 (a-z, A-Z) 和数字 (0-9)。cacheKey 中使用任何特殊符号,例如 _、-、/、?、& 等。// 视频源 String[] urls = { httpUrl }; // 缓存 key,不可为空,需与视频源一一对应,比如使用 HTTP URL 的 MD5 值 String cacheKey = TTVideoEngine.computeMD5(httpUrl); String vid = "videoID"; DownloadTask downloadTask = Downloader.getInstance().urlTask(urls, cacheKey, vid);
适用于下载已上传至火山引擎视频点播服务的视频。通过视频的 Vid 和其对应的临时播放凭证 PlayAuthToken 来指定播放内容。这两个参数通常由您的业务服务端下发,客户端直接使用即可。详情请见通过临时播放 Token 播放。
// 视频 ID String videoId = "your video id"; // 临时播放 token String playAuthToken = "your video id's play auth token"; // 分辨率 Resolution resolution = Resolution.High; DownloadTask downloadTask = Downloader.getInstance().vidTask(videoId, resolution, playAuthToken);
通过以下方法控制单个下载任务:
// 开始/恢复单个下载任务。 // 执行完毕后,SDK 会触发 downloaderDidResume 回调。 // 支持断点续传。 downloadTask.resume(); // 暂停单个下载任务。 downloadTask.suspend(); // 删除单个下载任务。 // 删除过程为异步,删除完成后 SDK 会触发 downloaderDidComplete 回调。回调参数 error 不为空,error.code 为 -9995。 downloadTask.invalidateAndCancel();
通过以下方法控制所有下载任务:
// 开始/恢复所有下载任务 Downloader.getInstance().resumeAllTasks(); // 暂停所有下载任务 Downloader.getInstance().suspendAllTasks(); // 删除所有下载任务 Downloader.getInstance().invalidateAndCancelAllTasks();
注意
下载任务删除后,该任务对应的视频文件也会被删除。
为了让您的应用能够实时响应下载过程中的各种变化,例如在 UI 上展示进度条、更新任务状态或处理下载结果,您需要实现 IDownloaderListener 接口。SDK 会在任务进度更新、状态变更(如等待、运行中、暂停)以及最终完成或失败时,自动调用您实现的回调方法。
Downloader.getInstance().setListener(new IDownloaderListener() { /** * 下载任务完成回调。 * * @param task 当前下载任务。 * @param error 错误信息。如果 error 为 null,表示下载成功;否则表示下载失败。 */ @Override public void downloaderDidComplete(Downloader downloader, DownloadTask task, @Nullable Error error) { // 在此处理下载完成或失败的逻辑。 } /** * @deprecated 该回调已废弃,请使用 downloaderOnProgress() 替代。 * 对于 HLS 视频,其 receivedSize/totalSize 返回的是 TS 分片计数。 */ @Deprecated @Override public void downloaderProgress(Downloader downloader, DownloadTask task, long receivedSize, long totalSize) { } /** * (1.48.2 新增)下载任务进度回调。 * * @param task 当前下载任务。 * @param receivedSize 已下载的文件大小,单位为 Byte。对于 HLS,此值可以正确返回已下载的字节数。 * @param totalSize 文件总大小,单位为 Byte。对于 HLS,由于总大小未知,此值在下载过程中为 -1,下载结束后才能获取。 */ @Override public void downloaderOnProgress(Downloader downloader, DownloadTask task, long receivedSize, long totalSize) { // 推荐调用 task.getProgress() 获取所有视频格式(包括 HLS)的精确下载百分比。 float progress = task.getProgress(); // progress 范围为 [0.0, 1.0]。 int percent = (int) (progress * 100); // 您现在可以使用 receivedSize 参数向用户展示 HLS 视频已下载的具体大小(例如 MB)。 // 在此更新您的进度条 UI。 // Log.d("Downloader", String.format("任务 %s 进度: %d%%, 已下载: %.2f MB", // task.getVid(), percent, receivedSize / 1024.0 / 1024.0)); } /** * 下载任务开始或恢复时回调。 * * @param task 当前下载任务。 * @param fileOffset 文件偏移量,代表已下载的数据量。 * @param expectedTotalBytes 任务预期要下载的总数据量。 */ @Override public void downloaderDidResume(Downloader downloader, DownloadTask task, long fileOffset, long expectedTotalBytes) { // 在此处理任务开始或恢复的逻辑。 } /** * 所有下载任务完成回调。 * * @param allTasks 所有任务列表,包含已完成的和未完成的。 */ @Override public void downloaderDidLoadAllTask(Downloader downloader, @Nullable List<DownloadTask> allTasks, @Nullable Error error) { // 在此可以获取持久化的下载任务列表。 } /** * 下载任务状态变更回调。 * * @param task 当前下载任务。 * @param state 当前下载任务状态: * - DOWNLOAD_TASK_STATE_INIT(0):初始化。 * - DOWNLOAD_TASK_STATE_WAITING(1):等待中。 * - DOWNLOAD_TASK_STATE_RUNNING(2):运行中。 * - DOWNLOAD_TASK_STATE_SUSPENDED(3):暂停。 * - DOWNLOAD_TASK_STATE_COMPLETED(4):完成。 * - DOWNLOAD_TASK_STATE_FAILED(5):失败。 */ @Override public void downloaderTaskStateDidChange(Downloader downloader, DownloadTask task, int state) { // 在此可以根据任务状态更新 UI。 } });
SDK 内部已处理下载缓存与播放的关联关系。SDK 会优先播放已经下载的视频,即使它们尚未完全下载完成,因此您播放时无需额外设置。
说明
// 播放已下载的 HLS 视频前必须设置以下 option ttVideoEngine.setIntOption(PLAYER_OPTION_INT_ALLOW_ALL_EXTENSIONS, 1); final String vid = "video id"; // 视频源与 vid 必须一一对应 final String url = "http://www.example.com/h264.mp4"; // 播放 cacheKey 与下载 cacheKey 保持一致 final String cacheKey = TTVideoEngine.computeMD5(url); // 1.组装 DirectUrl 视频源 StrategySource directUrlSource = new DirectUrlSource.Builder() .setVid(vid) .addItem(new DirectUrlSource.UrlItem.Builder() .setUrl(url) .setCacheKey(cacheKey) .build()) .build(); // 2.设置播放源 ttVideoEngine.setStrategySource(directUrlSource); // 3.播放 ttVideoEngine.play();
if (/* 该 vid 的视频已下载完成 */) { // 离线播放已下载的文件需开启强制使用 VideoModel 缓存 ttVideoEngine.setIntOption(PLAYER_OPTION_USE_VIDEOMODEL_CACHE_FORCE, 1); } // 播放已下载的 HLS 视频前必须设置以下 option ttVideoEngine.setIntOption(PLAYER_OPTION_INT_ALLOW_ALL_EXTENSIONS, 1); ttVideoEngine.setIntOption(PLAYER_OPTION_USE_VIDEOMODEL_CACHE, 1); final String vid = "your video id"; // appServer 下发 final String playAuthToken = "your video id's play auth token"; // appServer 下发 final int encodeType = TTVideoEngine.CODEC_TYPE_H264; // final int encodeType = TTVideoEngine.CODEC_TYPE_H265; // final int encodeType = TTVideoEngine.CODEC_TYPE_H266; // 1.组装 Vid 播放源 StrategySource vidSource = new VidPlayAuthTokenSource.Builder() .setVid(vid) .setPlayAuthToken(playAuthToken) // 设置 Codec 类型(h264、h265、h266),不传则使用默认值 h264 .setEncodeType(encodeType) // 预期播放的分辨率,需和下载时设置的分辨率一致 .setResolution(Resolution.High) .build(); // 2.设置播放源 ttVideoEngine.setStrategySource(vidSource); // 3.播放 ttVideoEngine.play();
播放器 SDK 支持在下载视频时一并下载外挂字幕,覆盖 DirectUrl 和 Vid 两种视频源。SDK 先下载视频,再从字幕列表中选择一条字幕下载,供后续离线播放时加载。
注意
外挂字幕下载自 Android 播放器 SDK 1.51.3.9 起支持,基础版、高级版和企业版均可使用。请集成 1.51.3.9 或以上版本。
DirectUrl 方式下,应用服务端下发的字幕信息使用以下结构。示例中的 URL 需替换为实际可下载的字幕地址。
{ "list": [ { "language_id": 1, "language": "cmn-Hans-CN", "format": "webvtt", "url": "https://example.com/subtitles/chinese.vtt", "sub_id": 128934090 } ] }
| 字段 | 类型 | 说明 |
|---|---|---|
| list | Array | 字幕列表,可包含多条字幕。一个下载任务选择其中一条下载。 |
| list[].language_id | Integer | 必填,字幕语言 ID,取值详见语言。 |
| list[].language | String | 可选,语言标识。 |
| list[].format | String | 必填,字幕格式,使用 webvtt 或 srt。 |
| list[].url | String | 必填,可下载的字幕文件地址。 |
| list[].sub_id | Integer | 必填,字幕 ID,用于选择下载的字幕;它与语言 ID、列表位置不同。 |
| list[].cache_key | String | 可选,字幕缓存标识。未提供或为空字符串时由 SDK 生成;业务自定义时,下载和播放应使用相同值。 |
通过 SubDesInfoModel 构建字幕源,再调用带字幕参数的 urlTask 创建任务。以下代码在已有下载任务加载成功后执行。subtitleInfoJson 是应用服务端下发的字幕信息 JSON 字符串;为 null 或空字符串时,仅下载视频。
String videoUrl = "YOUR_VIDEO_URL"; String videoId = "YOUR_VIDEO_ID"; // 视频缓存标识应与后续播放使用的 cacheKey 一致。 String cacheKey = TTVideoEngine.computeMD5(videoUrl); SubDesInfoModel subtitleModel = null; if (!TextUtils.isEmpty(subtitleInfoJson)) { try { subtitleModel = new SubDesInfoModel(new JSONObject(subtitleInfoJson)); } catch (JSONException e) { // 字幕信息不是有效 JSON,业务提示错误或重新获取数据。 return; } } // null:下载字幕列表的第一条。 // 下载指定字幕时,传入该字幕的 sub_id;未匹配到时仍选择第一条。 Integer subtitleId = null; DownloadURLTask task = Downloader.getInstance().urlTask( new String[] {videoUrl}, cacheKey, videoId, subtitleModel, subtitleId); if (task == null) { // 创建失败,检查视频地址、cacheKey 和 SDK 授权。 return; } // 默认 false:字幕下载失败会影响整体任务结果。 // 可选:允许视频成功、字幕失败时整体任务仍成功,再取消下面的注释。 // task.setIgnoreSubtitleDownloadFailure(true); task.resume();
subtitleModel 为 null 或字幕列表为空时,仅下载视频。只设置 subtitleId 不会触发字幕下载。
使用 VideoModel 数据源时,先按选中的视频档位取得 URL 和视频 cacheKey,再连同字幕模型调用同一个 urlTask 接口;字幕下载仍由 DownloadURLTask 完成。
向 vidTask 传入视频播放鉴权 Token 和字幕鉴权 Token。SDK 获取字幕列表后,按 subtitleId 选择一条字幕下载。以下代码在已有下载任务加载成功后执行。
String videoId = "YOUR_VIDEO_ID"; String playAuthToken = "YOUR_PLAY_AUTH_TOKEN"; String subtitleAuthToken = "YOUR_SUBTITLE_AUTH_TOKEN"; Resolution resolution = Resolution.High; // 已知目标字幕的 sub_id 时可指定该 ID。 // null 或未匹配到时,SDK 下载字幕列表的第一条。 Integer subtitleId = null; DownloadVidTask task = Downloader.getInstance().vidTask( videoId, resolution, playAuthToken, subtitleAuthToken, subtitleId); if (task == null) { // 创建失败,检查视频参数和 SDK 授权。 return; } // 默认 false。业务允许忽略字幕失败时,再取消下面的注释。 // task.setIgnoreSubtitleDownloadFailure(true); task.resume();
playAuthToken 用于视频鉴权,subtitleAuthToken 用于字幕鉴权,分别由应用服务端签发。subtitleAuthToken 为 null 或空字符串时,仅下载视频。
上述五参数接口默认下载 H.264 视频,并关闭 baseDashEnable。如需指定编码、DASH 或 HTTPS,使用以下重载,并与后续播放的清晰度和编码保持一致:
DownloadVidTask task = Downloader.getInstance().vidTask( videoId, resolution, TTVideoEngine.CODEC_TYPE_H265, // 示例为 H.265,按实际播放配置选择。 false, // baseDashEnable,按视频源配置。 true, // httpsEnable。 playAuthToken, subtitleAuthToken, subtitleId); if (task != null) { task.resume(); }
downloaderSubtitleDidComplete 表示字幕阶段结果,downloaderDidComplete 表示整体下载结果,业务应分别处理。将以下方法加入已有的 IDownloaderListener 实现:
@Override public void downloaderSubtitleDidComplete( Downloader downloader, DownloadTask task, Error error) { if (error == null) { // 选中的字幕已下载成功。 } else if (error.code == Error.SubNoSubtitle) { // 该视频无可用字幕,属于正常情况,整体任务仍成功。 } else { // 字幕下载失败。 // 即使已开启“忽略字幕下载失败”,这里仍返回真实错误。 } } @Override public void downloaderDidComplete( Downloader downloader, DownloadTask task, Error error) { if (error == null) { // 整体下载成功。 // 字幕是否实际下载成功,按字幕完成回调的结果判断。 } else { // 按错误信息处理任务失败或结束原因。 } }
| 情况 | 字幕完成回调 | 整体完成回调 | 后续处理 |
|---|---|---|---|
| 视频、选中的字幕均下载成功 | error == null | error == null | 可离线播放视频及该字幕。 |
| 未配置字幕,或 DirectUrl 字幕列表为空 | 不触发字幕回调 | 视频下载成功后,error == null | 仅下载视频。 |
| VID 字幕接口返回无可用字幕 | error.code == Error.SubNoSubtitle | error == null | 按无字幕处理。 |
| 字幕下载失败,保留默认 false | 返回字幕错误 | 返回错误 | 处理错误后调用 task.resume(),重试字幕下载。 |
| 字幕下载失败,设置为 true | 仍返回字幕错误 | error == null | 视频可用,但失败的字幕不可用;对该已成功收尾的任务再次调用 resume() 不会补下字幕。 |
| 视频下载失败 | 尚未进入字幕阶段,不触发字幕回调 | 返回视频下载错误 | 处理视频下载错误。 |
setIgnoreSubtitleDownloadFailure 在创建任务后、resume() 前设置,只影响字幕失败时的整体任务结果,不会忽略视频下载错误。
如需区分视频和字幕的下载阶段,可监听 downloaderVideoDidComplete 和 downloaderSubtitleWriteData:前者仅在视频下载成功、即将尝试下载字幕时触发;后者提供字幕下载的字节数和时间间隔。VID 传入非空字幕 Token 时,即使后续查询发现视频没有字幕,也会先触发视频完成回调;DirectUrl 字幕模型为 null 或列表为空时,不触发该回调。
视频进度达到 100% 后,字幕仍可能正在下载,整体结果以 downloaderDidComplete 为准。
视频播放源按播放视频配置:DirectUrl 保持视频 cacheKey 一致;VID 保持下载时的清晰度和编码设置,并启用视频信息缓存。
字幕仍需按添加外挂字幕配置功能开关、字幕源和内容回调。SDK 返回字幕文本,业务通过自己的 UI 展示。以下几点需要额外注意:
DownloadURLTask.getSubtitleModel() 或 DownloadVidTask.getSubtitleModel() 获取任务保存的字幕列表,在设置视频播放源之后、调用 play() 之前,用 setSubDesInfoModel 设置给播放器。VID 任务的字幕列表在获取字幕信息成功后才可用;模型非空不代表字幕文件已下载成功。PLAYER_OPTION_ENABLE_OPEN_SUB_THREAD 和 PLAYER_OPTION_SUB_ENABLE_MDL;输出字幕时开启 PLAYER_OPTION_ENABLE_OPEN_SUB。通过 PLAYER_OPTION_SWITCH_SUB_ID 选择本次实际下载的 sub_id。subtitleId 为 null 或未匹配到时,实际下载的是列表第一条。离线播放应选择同一条字幕;列表中的其他字幕尚未因本任务下载。cacheKey 应相同。字幕模型带有非空 cache_key 时,两侧均优先使用该值;没有该字段或值为空时,下载侧使用 MD5(字幕 URL 的 path) + "_sub" 生成。cache_key 时,保持播放器默认的字幕 cacheKey 生成方式。若通过 setMDLCacheKeyGeneratorForSubModel 自定义了播放侧算法,下载侧不会自动采用该算法,离线播放可能无法命中字幕缓存。DirectUrl/VideoModel 可在字幕模型中提供一致的 cache_key;VID 模型无该字段时尤其需要核对生成方式。