对于包含多种清晰度、多语言音轨或多语种字幕的视频,播放器 SDK 支持在播放的各个阶段进行精确的管理和切换。本文详细介绍多路流的实现方案,并指导您如何完成集成,以及如何优化切换体验。
播放器 SDK 支持自研协议与标准协议两大类多路流方案,您可以根据业务场景和媒资来源灵活选择。
协议类型 | 播放模式 | 核心原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|---|
自研协议 | Vid 模式 | 客户端通过 Vid 请求,由视频点播服务端智能下发包含所有可用清晰度的 VideoModel。 | 接入最简单,且安全性强。客户端逻辑清晰,服务端自动处理降级和媒资关联,支持帧对齐平滑切换。 | 依赖视频点播服务,对音轨/字幕的底层控制能力有限。 | 适用于媒资全部托管在火山引擎视频点播服务的场景。 |
VideoModel 模式 | 由您的业务服务端自行构造或从点播服务获取完整的 VideoModel,并下发给客户端。 | 性能最优,减少了一次客户端到点播服务的网络请求,首帧更快。对多路流的管理方式与 Vid 模式一致。 | 需自行实现构造 VideoModel 的逻辑,复杂度稍高。 | 对首帧性能有极致要求的场景,例如短视频信息流。 | |
标准协议 | HLS (Master M3U8) | 客户端直接播放标准的 Master M3U8 文件。SDK 解析文件后,通过底层回调和 Option 设置,允许您对视频流、音轨、字幕进行精细化控制。 | 兼容性最强,适用于任意来源的 HLS 视频流。提供了最底层的控制能力,灵活性最高。 | 客户端实现逻辑最复杂,需要自行处理流信息的遍历、决策和切换。 | 需要播放第三方 HLS 视频流,或需要对音轨、字幕进行精细化管理的场景。 |
DASH | 建设中,敬请期待 | ||||
对于客户端的多清晰度管理,Vid 模式和 VideoModel 模式共享一套简单、直观的 API。
在进行多路流相关开发前,请确保已完成以下准备工作:
PlayAuthToken 时,可不指定 Definition 参数。视频点播服务在响应播放请求时,将默认返回该视频所有可用清晰度的播放地址。在播放器 SDK 通过 videoEngine:fetchedVideoModel: 代理回调返回视频信息后,通过 supportedResolutionTypes 属性获取所有可用的清晰度。
// 实现 TTVideoEngineDelegate 的代理方法 - (void)videoEngine:(TTVideoEngine *)videoEngine fetchedVideoModel:(TTVideoEngineModel *)videoModel { // 获取清晰度枚举的 NSNumber 数组,用于构建 UI 上的选择列表 NSArray<NSNumber *> *allResolutions = [self.engine supportedResolutionTypes]; // 可以在此处根据 allResolutions 更新您的清晰度选择 UI }
在调用 play 方法前设置 configResolution:,即可指定视频的起播清晰度。如果您设置的清晰度在 supportedResolutionTypes 列表中不存在,SDK 会自动选择一个当前已支持的、不高于您设定值的最接近档位进行播放。
说明
如果您同时使用了起播选档功能,SDK 会自动选择最佳码率起播,您无需再手动调用 configResolution 设置起播清晰度。
[self.engine configResolution:TTVideoEngineResolutionTypeFullHD];
当用户在 UI 上选择新的清晰度后,调用 configResolution:withCompletion: 进行切换,并在 completion 回调中处理切换结果。
// 假设 allResolutions 是您在 fetchedVideoModel 回调中获取的可用清晰度列表 // 假设用户点击了列表中的第一项作为目标清晰度 TTVideoEngineResolutionType targetResolution = [[allResolutions objectAtIndex:0] integerValue]; // 调用接口发起切换,并设置回调 block [self.engine configResolution:targetResolution withCompletion:^(BOOL success, NSError * _Nullable err, TTVideoEngineResolutionType completeResolution) { // 注意:该回调可能在非主线程触发,任何UI更新操作都需要 dispatch 到主线程 dispatch_async(dispatch_get_main_queue(), ^{ if (success) { // 切换成功,completeResolution 即为当前实际播放的清晰度 NSLog(@"清晰度切换成功,当前清晰度: %ld", (long)completeResolution); // 可以在此更新 UI 状态 } else { // 切换失败,可以从 NSError 对象中获取详细信息 // err.code 为 -1 表示因播放器状态异常导致切换请求未被执行 // 其他错误码含义请参考官网文档 NSLog(@"清晰度切换失败,错误码:%ld, 错误信息: %@", (long)err.code, err.localizedDescription); } }); }];
在播放过程中的任何时刻,您都可以通过访问 currentResolution 属性来获取当前正在播放的清晰度。
// 同步获取当前正在播放的清晰度 TTVideoEngineResolutionType currentType = self.engine.currentResolution; NSLog(@"当前播放清晰度为: %ld", (long)currentType);
Vid 模式下,您可以通过外挂的方式为视频添加和管理多语言字幕。字幕文件与视频 Vid 在视频点播服务端进行绑定。客户端通过 Vid 和 PlayAuthToken 请求播放,再通过 SubtitleToken 请求字幕信息。实现流程如下:
PlayAuthToken 和 SubtitleToken。Vid 和 Token 播放,SDK 内部会自动拉取与该 Vid 关联的所有字幕信息列表。详细集成步骤请参见添加外挂字幕。
对于标准的 Master M3U8 播放源,播放器 SDK 提供了精细的控制能力,允许开发者在播放生命周期的不同阶段介入,精确选择要使用的视频流(清晰度)和音频流(音轨)。
通过设置 masterPlaylistDelegate,您可以在播放器 SDK 解析完 M3U8 文件后、开始下载媒体数据前,通过代理方法返回您期望的视频流和音轨的索引,从而控制起播。具体工作流程如下:
设置代理:为播放器实例设置 masterPlaylistDelegate 并遵循 TTVideoEngineMasterPlaylistDelegate 协议。
接收流信息:当 M3U8 解析完成后,SDK 触发 onMasterPlaylist 回调。您可以在此获取并保存包含所有视频流 variantStreams 和音频流 renditions 的 masterPlaylist 对象。
// 遵循 TTVideoEngineMasterPlaylistDelegate 协议 // @interface YourViewController () <TTVideoEngineMasterPlaylistDelegate> // 设置代理 self.videoEngine.masterPlaylistDelegate = self; #pragma mark - TTVideoEngineMasterPlaylistDelegate /** * M3U8 解析完成的回调。 * 在此处保存 masterPlaylist 对象,以便在播放中切换清晰度或音轨时使用。 */ - (void)videoEngine:(TTVideoEngine *)videoEngine onMasterPlaylist:(TTVideoEngineMasterPlaylist *)masterPlaylist { self.masterPlaylist = masterPlaylist; NSLog(@"M3U8 解析完成,可用视频流为:%ld", (long)masterPlaylist.variantStreams.count); }
返回决策索引:通过 indexOfVariantOnPlayMasterPlaylist 和 indexOfRenditionOnPlayMasterPlaylist 返回您选定的视频流和音频流的索引。以下示例代码展示了以 720p 分辨率和英文音轨起播:
// 返回起播的视频流的序号,-1 表示自动选择 - (int)videoEngine:(TTVideoEngine *)videoEngine indexOfVariantOnPlayMasterPlaylist:(TTVideoEngineMasterPlaylist *)masterPlaylist { if (self.masterPlaylistPlayTargetIndex >= -1 && self.masterPlaylistPlayTargetIndex < (int)masterPlaylist.variantStreams.count) { return self.masterPlaylistPlayTargetIndex; } NSLog(@"清晰度选择错误:当前视频支持: -1 ~ %d,将播放默认清晰度", (int)(masterPlaylist.variantStreams.count - 1)); return -1; } // 返回起播的音频流的 streamId,-1 表示自动选择 - (int)videoEngine:(TTVideoEngine *)videoEngine indexOfRenditionOnPlayMasterPlaylist:(TTVideoEngineMasterPlaylist *)masterPlaylist { if (self.masterPlaylistAudioTrackIndex == -1) { return -1; } if (self.masterPlaylistAudioTrackIndex >= 0 && self.masterPlaylistAudioTrackIndex < (int)masterPlaylist.renditions.count) { return [[self.masterPlaylist.renditions objectAtIndex:self.masterPlaylistAudioTrackIndex].inStreamId intValue];; } NSLog(@"清晰度选择错误(rendition):当前视频支持: -1 ~ %d,将播放默认 rendition", (int)(masterPlaylist.renditions.count - 1)); return -1; }
要切换视频清晰度,您需要获取目标流的带宽值,并调用 setIntOption 方法,将 PLAYER_OPTION_SET_MASTER_M3U8_VIDEO_BANDWIDTH 作为键,带宽值作为值传入。
NSString* inputBitrate = [_inputBitrateTextView.text stringByTrim]; // 通过 setOptionForKey 切换视频流 [self.player.videoEngine setOptionForKey:VEKKeySetMasterm3u8VideoBandwidth_NSInteger value:@(inputBitrate.intValue)];
要切换音轨,您需要获取目标音轨的 InfoId,并调用 setOptionForKey:value: 方法,将 VEKKeyPlayerSwitchAudioInfoId_NSInteger 作为键,InfoId 作为值传入。
NSString* inputStreamId = [_inputStreamIdTextView.text stringByTrim]; // 通过 setOptionForKey 切换音频流 [self.player.videoEngine setOptionForKey:VEKKeyPlayerSwitchAudioInfoId_NSInteger value:@(inputStreamId.intValue)];
如果您使用 SDK 提供的预加载策略,同样可以介入并指定预加载哪些流。通过设置 TTVideoEnginePreloadDelegate,您可以在预加载任务解析完 M3U8 后,通过代理方法返回要预加载的流的索引。
// 设置预加载代理 // @interface YourAppDelegate () <TTVideoEnginePreloadDelegate> [TTVideoEngine ls_setPreloadDelegate:self]; #pragma mark - TTVideoEnginePreloadDelegate // 返回预加载的视频流的序号,-1 表示自动选择 - (int)indexOfVariantOnPreloadMasterPlaylist:(TTVideoEngineMasterPlaylist *)masterPlaylist { if (self.masterPlaylistPreloadTargetIndex >= -1 && self.masterPlaylistPreloadTargetIndex < (int)masterPlaylist.variantStreams.count) { return self.masterPlaylistPreloadTargetIndex; } NSLog(@"清晰度选择错误:当前视频支持: -1 ~ %d,将预加载默认清晰度", (int)(masterPlaylist.variantStreams.count - 1)); return -1; } // 返回预加载的音频流的序号,-1 表示自动选择 - (int)indexOfRendtionOnPreloadMasterPlaylist:(TTVideoEngineMasterPlaylist *)masterPlaylist { if (masterPlaylist.renditions.count == 0) { return -1; } if (self.masterPlaylistPreloadTargetIndex >= -1 && self.masterPlaylistPreloadTargetIndex < (int)masterPlaylist.renditions.count) { return self.masterPlaylistPreloadTargetIndex; } NSLog(@"清晰度选择错误(rendition):当前视频支持: -1 ~ %d,将预加载默认 rendition", (int)(masterPlaylist.renditions.count - 1)); return -1; }
清晰度平滑切换(或称无缝切换)是指播放器在不同清晰度之间切换时,画面过渡平滑、无黑屏、无明显卡顿的技术。
该功能依赖于视频源本身是帧对齐的。
说明
如需了解如何转码生成帧对齐视频以及在 DirectUrl 模式下开启平滑切换功能,可提交工单联系火山引擎技术支持。
通过 setIntOption 方法开启相应的平滑切换开关。
// HLS 播放源平滑切换 [self.videoEngine setOptionForKey:VEKKeyPlayerHLSSeamlessSwitchEnable_BOOL value:@(YES)]; [self.videoEngine setOptionForKey:VEKKeyMasterm3u8OptimizeEnable_BOOL value:@(YES)]; // MP4 播放源平滑切换 [self.videoEngine setOptionForKey:VEKKeyPlayerBashEnabled_BOOL value:@(YES)]; [self.videoEngine setOptionForKey:VEKeyPlayerSegmentFormatFlag value:@(TTVideoEngineDashSegmentFlagFormatMp4 | TTVideoEngineDashSegmentFlagFormatFMP4)];
TTVideoEngineResolutionType 枚举值 | 对应清晰度 | 视频描述 |
|---|---|---|
TTVideoEngineResolutionTypeSD | 360P | 标清 |
TTVideoEngineResolutionTypeHD | 480P | 高清 |
TTVideoEngineResolutionTypeFullHD | 720P | 超清 |
TTVideoEngineResolutionType1080P | 1080P | 1080P |
TTVideoEngineResolutionType2K | 2K | 2K |
TTVideoEngineResolutionType4K | 4K | 4K |
TTVideoEngineResolutionTypeAuto | 自动 | 自动选择 |