You need to enable JavaScript to run this app.
视频点播

视频点播

复制全文
进阶功能
多路流管理
复制全文
多路流管理

对于包含多种清晰度、多语言音轨或多语种字幕的视频,播放器 SDK 支持在播放的各个阶段进行精确的管理和切换。本文详细介绍多路流的实现方案,并指导您如何完成集成,以及如何优化切换体验。

方案概览

播放器 SDK 支持自研协议标准协议两大类多路流方案,您可以根据业务场景和媒资来源灵活选择。

协议类型

播放模式

核心原理

优点

缺点

适用场景

自研协议

Vid 模式

客户端通过 Vid 请求,由视频点播服务端智能下发包含所有可用清晰度的 VideoModel。

接入最简单,且安全性强。客户端逻辑清晰,服务端自动处理降级和媒资关联,支持帧对齐平滑切换。

依赖视频点播服务,对音轨/字幕的底层控制能力有限。

适用于媒资全部托管在火山引擎视频点播服务的场景。

VideoModel 模式

由您的业务服务端自行构造或从点播服务获取完整的 VideoModel,并下发给客户端。

性能最优,减少了一次客户端到点播服务的网络请求,首帧更快。对多路流的管理方式与 Vid 模式一致。

需自行实现构造 VideoModel 的逻辑,复杂度稍高。

对首帧性能有极致要求的场景,例如短视频信息流。

标准协议

HLS (Master M3U8)

客户端直接播放标准的 Master M3U8 文件。SDK 解析文件后,通过底层回调和 Option 设置,允许您对视频流、音轨、字幕进行精细化控制。

兼容性最强,适用于任意来源的 HLS 视频流。提供了最底层的控制能力,灵活性最高。

客户端实现逻辑最复杂,需要自行处理流信息的遍历、决策和切换。

需要播放第三方 HLS 视频流,或需要对音轨、字幕进行精细化管理的场景。

DASH

建设中,敬请期待

Vid/VideoModel 模式

对于客户端的多清晰度管理,Vid 模式和 VideoModel 模式共享一套简单、直观的 API。

前置准备

在进行多路流相关开发前,请确保已完成以下准备工作:

  1. 生成多清晰度视频且确保业务服务端正确下发视频播放信息:
    • 对于 Vid 模式:
      • 您必须通过视频点播媒体处理服务转码生成多种清晰度的视频流,详见音视频转码
      • 您的业务服务端在为客户端签发 PlayAuthToken 时,可不指定 Definition 参数。视频点播服务在响应播放请求时,将默认返回该视频所有可用清晰度的播放地址。
    • 对于 VideoModel 模式:视频数据来自火山引擎视频点播服务,也可以来自第三方服务或自定义数据。详见构造 VideoModel 播放源
  2. 完成 SDK 基础集成:请确保您已参考集成 SDK快速开始文档,完成了 SDK 的初始化、创建播放器实例、设置播放源等基础步骤。

获取可用清晰度列表

在播放器 SDK 触发 onFetchedVideoInfo 回调返回视频播放信息后,调用 supportedResolutionTypes() 获取所有可用的清晰度。

Resolution[] supportedResolutions;

ttVideoEngine.setVideoInfoListener(new VideoInfoListener() {
    @Overridepublic boolean onFetchedVideoInfo(VideoModel videoModel) {
        if (videoModel != null) {
            // 获取清晰度数组,用于在 UI 上构建清晰度选择列表
            supportedResolutions = ttVideoEngine.supportedResolutionTypes();
        }
        return false;
    }
});

设置起播清晰度

同样在 onFetchedVideoInfo 回调中,您可以根据业务逻辑(如用户历史选择、当前网络状况)设置起播的清晰度。

说明

如果您同时使用了起播选档功能,SDK 会自动选择最佳码率起播,您无需再手动调用 configResolution 设置起播清晰度。

// 在 onFetchedVideoInfo 回调中设置起播
@Override
public boolean onFetchedVideoInfo(VideoModel videoModel) {
    // 业务逻辑:期望以 360p (Standard) 起播
    Resolution defaultResolution = Resolution.Standard;
    
    // 由于播放源不一定包含 360p,调用 findDefaultResolution 找到最接近的一个可用清晰度
    Resolution startResolution = TTVideoEngine.findDefaultResolution(videoModel, defaultResolution);
    
    if (startResolution != null) {
        ttVideoEngine.configResolution(startResolution);
    }
    return false;
}

播放中切换清晰度

当用户在 UI 上选择新的清晰度后,只需调用 configResolution 进行切换,并通过 onVideoStreamBitrateChanged 回调监听切换结果。

// 假设用户从 UI 上点击了 resolutions 列表中的第 i 项
Resolution selectedResolution = supportedResolutions[i];

// 调用接口切换
ttVideoEngine.configResolution(selectedResolution);

// 监听切换结果
ttVideoEngine.setVideoEngineCallback(new VideoEngineCallback() {
    /**
     * 当码率(清晰度)发生变化时回调
     * @param resolution 切换后的清晰度枚举
     * @param bitrate 切换后的码率
     */
    @Override
    public void onVideoStreamBitrateChanged(Resolution resolution, int bitrate) {
        // 清晰度切换成功
        Log.d("VideoPlay", "Stream switched successfully to: " + resolution + ", bitrate: " + bitrate);
    }
});

外挂多语言字幕(仅 Vid 模式)

Vid 模式下,您可以通过外挂的方式为视频添加和管理多语言字幕。字幕文件与视频 Vid 在视频点播服务端进行绑定。客户端通过 VidPlayAuthToken 请求播放,再通过 SubtitleToken 请求字幕信息。实现流程如下:

  1. 将字幕上传到视频点播服务并绑定到 Vid,或直接通过视频点播服务生成字幕。详细操作步骤,请见视频字幕
  2. 服务端为客户端签发 PlayAuthTokenSubtitleToken
  3. 客户端使用 Vid 和 Token 播放,SDK 内部会自动拉取与该 Vid 关联的所有字幕信息列表。

详细集成步骤请参见添加外挂字幕

HLS Master M3U8 模式

对于标准的 HLS Master M3U8 播放源,播放器 SDK 提供底层的控制能力,允许开发者在播放生命周期的不同阶段介入,精确选择要使用的视频和音频流。

核心概念

一个 Master M3U8 文件就像一个“节目单”,本身不包含视频数据,而是定义了这个节目包含了哪些可用的流。

  • 视频流 (Variant Stream):代表不同的清晰度(如 720p, 1080p)。由 #EXT-X-STREAM-INF 定义。每个标签都描述了一个特定码率 BANDWIDTH、分辨率 RESOLUTION 的视频流,并指向其对应的子 M3U8 文件。
  • 媒体流 (Rendition):代表附加的音轨TYPE=AUDIO)或字幕TYPE=SUBTITLES)。由 #EXT-X-MEDIA 定义。
  • 关联关系:视频流和媒体流通过 GROUP-ID 属性进行关联。一个视频流可以指定它属于哪个音频组和字幕组,从而决定了在该清晰度下有哪些可选的音轨和字幕。

在以下 M3U8 文件示例中,1080p 和 720p 两个清晰度都关联了名为 audio_group 的音频组,这意味着无论在哪种清晰度下,用户都可以选择“英文”或“中文”音轨。

#EXTM3U

# --- 定义媒体流 (音轨) ---
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio_group",NAME="English",LANGUAGE="en",URI="audio_en.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio_group",NAME="Chinese",LANGUAGE="zh",URI="audio_zh.m3u8"

# --- 定义视频流 (清晰度),并关联音轨 ---
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,AUDIO="audio_group"
video_1080p.m3u8

#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720,AUDIO="audio_group"
video_720p.m3u8

起播时选择清晰度与音轨

您可以通过设置 setPlayerHLSChooseStreamCallback 回调,在播放器 SDK 解析完 Master M3U8 文件后、正式开始下载媒体数据前,介入并指定使用哪个视频流和音频流进行起播。以下示例代码展示了如何按 720p 分辨率和英文音轨起播:

private String mHlsAudioLanguage = "en"; // 目标音轨语言
private int mVideoStartupResolution = 720; // 目标视频分辨率

private TTVideoEngineMasterPlaylist mMasterPlaylist;
private int mHlsVideoVariantIndex;
private int mHlsAudioRenditionInfoId;

videoEngine.setPlayerHLSChooseStreamCallback(new TTVideoEnginePlayHLSChooseStreamCallback() {
    // 当 M3U8 解析完成后触发,返回包含所有视频流 variantStreams 和音频流 renditions 的 masterPlaylist 对象。您需要在此方法中遍历并决策出要使用的流的索引或 ID。
    @Override
    public void streamInfos(TTVideoEngineMasterPlaylist ttVideoEngineMasterPlaylist) {
        // 1. 遍历视频流,找到最匹配目标分辨率的 videoVariant
        mMasterPlaylist = ttVideoEngineMasterPlaylist;
        L.d(this, "streamInfos " + new Gson().toJson(mMasterPlaylist));
        for (int i = 0; i < ttVideoEngineMasterPlaylist.variantStreams.length; i++) {
            TTVideoEngineVariantStream videoVariant = ttVideoEngineMasterPlaylist.variantStreams[i];
            if (videoVariant.resolution.contains(mVideoStartupResolution + "x")) { // 简单演示用 720P 起播, 业务代码中可以根据业务情况写成一个范围比如:[700, 720] 具体看源是否规整
                mHlsVideoVariantIndex = i;
                break;
            }
        }
        if (mHlsVideoVariantIndex == -1) {
            mHlsVideoVariantIndex = 0;
        }
    }

    // 播放器调用此方法获取您决策出的视频流带宽。您需要返回 streamInfos 中选定流的带宽值
    @Override
    public int chooseVariantBandWidth() {
        // 2. 返回第一步中找到的 videoVariant 的带宽
        if (mMasterPlaylist == null) {
            return -1;
        }
        if (mMasterPlaylist.variantStreams == null ||  mMasterPlaylist.variantStreams.length == 0) {
            return -1;
        }
        if (mHlsVideoVariantIndex >= mMasterPlaylist.variantStreams.length)  {
            return -1;
        }
        TTVideoEngineVariantStream videoVariant = mMasterPlaylist.variantStreams[mHlsVideoVariantIndex];
        if (videoVariant == null) {
            return -1;
        }
        L.d(this, "chooseVariantBandWidth ", new Gson().toJson(videoVariant));
        return videoVariant.bandwidth;
    }

    // 播放器调用此方法获取您决策出的音轨 ID。您需要返回匹配的音轨 ID
    @Override
    public int chooseRenditionInfoId(int videoVariantIndex) {
        // 3. 根据第一步中找到的 videoVariant 的 audioGroupId
        // 在所有音轨中查找语言匹配的音轨,并返回其 infoId
        L.d(this, "chooseRenditionInfoId " + videoVariantIndex);
        if (mMasterPlaylist == null) {
            return -1;
        }
        if (mMasterPlaylist.variantStreams == null ||  mMasterPlaylist.variantStreams.length == 0) {
            return -1;
        }
        if (videoVariantIndex >= mMasterPlaylist.variantStreams.length)  {
            return -1;
        }
        TTVideoEngineVariantStream videoVariant = mMasterPlaylist.variantStreams[videoVariantIndex];
        if (videoVariant == null) {
            return -1;
        }
        if (mMasterPlaylist.renditions == null || mMasterPlaylist.renditions.length == 0) {
            return -1;
        }
        for (int i = 0; i < mMasterPlaylist.renditions.length; i++) {
            TTVideoEngineMasterPlaylist.TTVideoEngineRendition rendition =  mMasterPlaylist.renditions[i];
            if (TextUtils.equals(videoVariant.audioGroupId, rendition.groupId)
                    && TextUtils.equals(rendition.type, "AUDIO")) {
                if (TextUtils.equals(rendition.language, mHlsAudioLanguage)) {
                    mHlsAudioRenditionInfoId = rendition.infoId;
                    return mHlsAudioRenditionInfoId;
                }
            }
        }
        return -1;
    }
});

播放中切换清晰度与音轨

在播放过程中,您可以通过设置 PLAYER_OPTION 动态切换音视频流。

切换视频流(按 Bandwidth)

要切换视频清晰度,您需要获取到目标清晰度流的带宽值,并通过 setIntOption 设置。

// 假设 mMasterPlaylist 是您在 streamInfos 回调中保存的对象
// 假设用户选择了第 i 个视频流
TTVideoEngineVariantStream targetStream = mMasterPlaylist.variantStreams[i];
int targetBandwidth = targetStream.bandwidth;

// 设置 option 来切换
videoEngine.setIntOption(PLAYER_OPTION_SET_MASTER_M3U8_VIDEO_BANDWIDTH, targetBandwidth);

// 监听切换结果
videoEngine.setVideoEngineCallback(new VideoEngineCallback() {
    @Override
    public void onVideoStreamBitrateChanged(Resolution resolution, int bitrate) {
        // 对于 Master M3U8 播放,切换成功后 resolution 可能为 null,
        // 您应该以 bitrate 为准,判断是否切换到了目标带宽。
        Log.d("VideoPlay", "Switched to bitrate: " + bitrate);
    }
});

切换音频流(按 Rendition ID)

切换音轨则需要获取目标音轨的 infoId。

// 假设用户选择了第 j 个音轨
TTVideoEngineRendition targetRendition = mMasterPlaylist.renditions[j];
int targetInfoId = targetRendition.infoId;

videoEngine.setIntOption(PLAYER_OPTION_SET_AUDIO_INFO_ID, targetInfoId);

UI 实现示例

以下代码展示了如何根据 masterPlaylist 中的信息,动态生成一个可供用户点击切换的清晰度和音轨列表。建议在 streamInfos 回调触发后调用此方法来构建或更新您的 UI。

// 假设 mHlsVideoVariants 是从 MasterPlaylist 解析出的视频流列表
// 假设 context 是当前的上下文环境

// --- 1. 构建清晰度选择 UI ---
LinearLayout videoResolutionSelectLayout = new LinearLayout(context);
// 遍历所有可用的视频流
for (int i = 0; i < mHlsVideoVariants.size(); i++) {
    // 获取单个视频流对象
    final HLSChooseBestStream.Variant videoVariant = mHlsVideoVariants.get(i);
    // 创建一个 TextView 用于显示清晰度
    final TextView textView = new TextView(context);
    // 设置显示的文本,例如 "1280x720"。您可以根据业务需求映射为“高清”、“超清”等。
    textView.setText(videoVariant.mWidth + "x" + videoVariant.mHeight);
    // 将 TextView 添加到布局中
    videoResolutionSelectLayout.addView(textView);

    // 为每个清晰度选项设置点击事件监听器
    textView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            // 当用户点击时,记录下所选的清晰度索引
            mHlsVideoVariantIndex = i;
            // 调用 setIntOption,传入目标视频流的带宽,以执行切换
            videoEngine.setIntOption(TTVideoEngine.PLAYER_OPTION_SET_MASTER_M3U8_VIDEO_BANDWIDTH, videoVariant.mBandWidth);
        }
    });
}

// --- 2. 构建音轨选择 UI ---
LinearLayout audioLanguageSelectLayout = new LinearLayout(context);
// 获取当前正在播放的视频流对象
HLSChooseBestStream.Variant videoVariants = mHlsVideoVariants.get(mHlsVideoVariantIndex);
// 遍历当前视频流关联的播放列表
for (int i = 0; i < videoVariants.mPlaylists.length; i++) {
    HLSChooseBestStream.Playlist playlist = videoVariants.mPlaylists[i];
    // 遍历播放列表中的所有 Rendition (通常包含音轨、字幕等)
    for (int j = 0; j < playlist.mRenditions.length; j++) {
        HLSChooseBestStream.Rendition rendition = playlist.mRenditions[i];
        // 判断当前 Rendition 是否为音轨
        if (rendition != null && rendition.mMediaTrackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
            // 创建一个 TextView 用于显示音轨信息
            final TextView textView = new TextView(context);
            // 设置显示的文本,例如 "音轨:en"。您可以根据业务需求映射为“英文”、“中文”等。
            textView.setText("音轨:" + rendition.mLanguage);
            // 将 TextView 添加到布局中
            audioLanguageSelectLayout.addView(textView);
            
            // 为每个音轨选项设置点击事件监听器
            textView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View view) {
                    // 当用户点击时,记录下所选音轨的 infoId
                    mHlsAudioRenditionInfoId = rendition.mInfoId;
                    // 调用 setIntOption,传入目标音轨的 infoId,以执行切换
                    videoEngine.setIntOption(TTVideoEngine.PLAYER_OPTION_SET_AUDIO_INFO_ID, rendition.infoId);
                }
            });
        }
    }
}

预加载时选择清晰度与音轨

如果您使用 SDK 提供的预加载策略,同样可以介入并指定预加载哪些流,以优化起播速度和节省带宽。通过设置 setPreloadChooseUrlCallback,您可以在预加载任务解析完 M3U8 文件后,返回一个包含具体流 URL 和预加载大小的列表。以下代码展示了如何预加载 720p 视频流及其关联的音频流:

// 设置预加载选档回调
TTVideoEngine.setPreloadChooseUrlCallback(new TTVideoEnginePreloadHLSChooseUrlCallback() {
    /**
     * 在此回调中实现自定义的预加载选档逻辑。
     * @param masterPlaylist 解析后的 Master M3U8 对象,包含所有流信息。
     * @return 返回一个列表,告知预加载模块具体要缓存哪些流。
     */
    @Override
    public List<TTVideoEnginePreloadUrlInfo> chooseUrls(TTVideoEngineMasterPlaylist masterPlaylist) {
        Log.v("VideoPlay", "chooseUrls:" + new Gson().toJson(masterPlaylist));

        if (masterPlaylist.variantStreams == null) {
            return null;
        }
        List<TTVideoEnginePreloadUrlInfo> urlInfos = new ArrayList<>();
        for (int i = 0; i < masterPlaylist.variantStreams.length; i++) {
            TTVideoEngineMasterPlaylist.TTVideoEngineVariantStream videoVariantStream = masterPlaylist.variantStreams[i];
            
            // 1. 根据业务需求(如分辨率)找到要预加载的目标视频流
            if (videoVariantStream != null && videoVariantStream.resolution.equals("720x1280")) {
                
                TTVideoEnginePreloadUrlInfo videoUrlInfo = new TTVideoEnginePreloadUrlInfo();
                videoUrlInfo.uri = videoVariantStream.uri;
                videoUrlInfo.preloadSize = 500 * 1024; // 设置预加载大小
                urlInfos.add(videoUrlInfo);

                if (masterPlaylist.renditions != null) {
                    for (int j = 0; j < masterPlaylist.renditions.length; j++) {
                        TTVideoEngineMasterPlaylist.TTVideoEngineRendition rendition = masterPlaylist.renditions[j];
                        if (rendition != null) {
                            // 2. 查找与视频流关联的音轨 (类型为 AUDIO 且 groupId 匹配)
                            if (TextUtils.equals(rendition.type, "AUDIO")
                                    && TextUtils.equals(videoVariantStream.audioGroupId, rendition.groupId)) {
                                
                                TTVideoEnginePreloadUrlInfo audioUrlInfo = new TTVideoEnginePreloadUrlInfo();
                                audioUrlInfo.uri = rendition.uri;
                                audioUrlInfo.preloadSize = 300 * 1024;
                                urlInfos.add(audioUrlInfo);
                                break;
                            }
                        }
                    }
                }
                break;
            }
        }
        Log.v("VideoPlay", "[preload] streams " + new Gson().toJson(urlInfos));
        return urlInfos;
    }
});

清晰度切换优化:平滑切换

清晰度平滑切换(或称无缝切换)是指播放器在不同清晰度之间切换时,画面过渡平滑、无黑屏、无明显卡顿的技术。

前置准备

该功能依赖于视频源本身是帧对齐的

  • 对于火山引擎视频点播服务转码生成的 MP4 和 HLS 视频,默认已支持帧对齐。
  • 对于第三方视频源,您需要确保您的媒资也经过了帧对齐处理。

说明

如需了解如何转码生成帧对齐视频以及在 DirectUrl 模式下开启平滑切换功能,可提交工单联系火山引擎技术支持。

开启平滑切换

通过 setIntOption 方法开启相应的平滑切换开关。

// 开启 HLS 平滑切换的核心开关
ttVideoEngine.setIntOption(TTVideoEngine.PLAYER_OPTION_ENABLE_HLS_SEAMLESS_SWITCH, 1); 
// 建议同时开启,以获得最佳的 Master M3U8 解析和调度性能
ttVideoEngine.setIntOption(TTVideoEngine.PLAYER_OPTION_ENABLE_MASTER_M3U8_OPTIMIZE, 1);

// MP4 播放源平滑切换
ttVideoEngine.setIntOption(PLAYER_OPTION_SEGMENT_FORMAT_FLAG, (1 << SEGMENT_FORMAT_FMP4) | (1 << SEGMENT_FORMAT_MP4));
ttVideoEngine.setIntOption(TTVideoEngine.PLAYER_OPTION_ENABLE_BASH, 1);

ttVideoEngine.setVideoEngineCallback(new VideoEngineCallback() {
    /**
     * 当码率(清晰度)发生变化时回调
     * @param resolution 切换后的清晰度枚举
     * @param bitrate 切换后的码率
     */
    @Override
    public void onVideoStreamBitrateChanged(Resolution resolution, int bitrate) {
        Log.d("VideoPlay", "Stream switched successfully to: " + resolution + ", bitrate: " + bitrate);
    }
});
最近更新时间:2025.12.01 17:55:52
这个页面对您有帮助吗?
有用
有用
无用
无用