You need to enable JavaScript to run this app.
导航
Master M3U8 播放源的多清晰度和多音轨管理
最近更新时间:2025.09.24 17:24:27首次发布时间:2025.09.03 14:33:29
复制全文
我的收藏
有用
有用
无用
无用

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

起播时选择清晰度与音轨

通过设置 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;
    }
});