如何添加外挂字幕

阿里云播放器SDK支持添加WebVTT、SRT、ASS格式的外挂字幕流并实现解析和渲染,同时也支持M3U8文件内嵌字幕流的解析和渲染。本文介绍使用阿里云播放器SDK进行外挂字幕添加、解析和渲染的实现方式,以及在AndroidiOS平台上实现的具体技术方案。

重要

本文所有功能相关代码和实现细节,建议优先参考API-Example示例工程,按照最佳实践进行适配和扩展

具体实现可结合下文实现方案,并参考对应的 API-Example-AndroidAPI-Example-iOS外挂字幕演示与切换(ExternalSubtitle)模块源码。

使用限制

  • 外挂字幕仅支持播放器专业版,请参考获取播放器SDK License获取专业版授权后使用。

  • 目前仅支持添加WebVTT、SRT、ASS格式的外挂字幕流文件。

渲染示例

以下为播放器SDK渲染组件示例,支持普通字幕及自定义位置、字体、背景色、描边等高自由度渲染样式。image

如需打包字幕流实现字幕渲染,请参考多字幕转码打包最佳实践进行配置。

Android 端关键实现

外挂字幕渲染

  1. 导入依赖库。

    import com.aliyun.subtitle.SubTitleBase;
    import com.cicada.player.utils.webVtt.VttSubtitleView;
    import com.aliyun.player.IPlayer;
  2. 设置显示视图。

    vttSubtitleView = new VttSubtitleView(getContext());
    vttSubtitleView.setId(R.id.cicada_player_vtt_subtitle);
    
    
    // 设置字幕的显示位置 - 添加到布局中央
    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.WRAP_CONTENT,
            FrameLayout.LayoutParams.WRAP_CONTENT
    );
    params.gravity = Gravity.CENTER; // 添加到布局中央
    
    // 将VTT字幕View添加到根布局视图中
    mRootFrameLayout.addView(mVttSubtitleView, params);
  3. 设置回调。

    //下述示例为mVideoListPlayerlistPlayer,使用aliPlayer 也是一致的
    //1. 设置 OnSubtitleDisplayListener
    mVideoListPlayer.setOnSubtitleDisplayListener(new IPlayer.OnSubtitleDisplayListener() {
        @Override
        public void onSubtitleExtAdded(int trackIndex, String url) {
          //由于仅测试添加单个vtt文件,因此当vtt文件添加完毕时,直接选择该路字幕即可
            mVideoListPlayer.selectExtSubtitle(trackIndex, true);
        }
    
        @Override
        public void onSubtitleShow(int trackIndex, long id, String data) {
            if (vttSubtitleView != null) {
                vttSubtitleView.show(id, data);
            }
        }
    
        @Override
        public void onSubtitleHide(int trackIndex, long id) {
            if (vttSubtitleView != null) {
                vttSubtitleView.dismiss(id);
            }
        }
    
        @Override
        public void onSubtitleHeader(int trackIndex, String header) {
            if (vttSubtitleView != null) {
                vttSubtitleView.setVttHeader(header);
            }
        }
    });
    
    //2.设置VideoSizeChangedListener,下述流程为必要流程,需要将数据回传到vttSubtitleView中
    mVideoListPlayer.setOnVideoSizeChangedListener(new IPlayer.OnVideoSizeChangedListener() {
        @Override
        public void onVideoSizeChanged(int width, int height) {
            int viewWidth = getWidth();
            int viewHeight = getHeight();
            IPlayer.ScaleMode mode = mVideoListPlayer.getScaleMode();
            SubTitleBase.VideoDimensions videoDimensions = SubTitleBase.getVideoDimensionsWhenRenderChanged(width, height, viewWidth, viewHeight, mode);
            vttSubtitleView.setVideoRenderSize(videoDimensions.videoDisplayWidth, videoDimensions.videoDisplayHeight);
        }
    });
  4. 播放器执行prepare/moveTo/moveToNext/moveToPrev操作后,需清除显示视图。

    source = getSource(someParams);//伪代码
    
    mVideoListPlayer.moveTo(mCurrentUUID + "");
    if (vttSubtitleView != null) {
        vttSubtitleView.clearAll();
    }
    
    if (!source.getExtSubtitleUrl().isEmpty()) {
        mCurrentExtSubtitle = source.getExtSubtitleUrl();//获取外挂字幕 url
    }
  5. 播放器onPrepared后,添加字幕文件。

    //当前仅能在播放器 onprepared后,调用addExtSubtitle
    mVideoListPlayer.addExtSubtitle(mCurrentExtSubtitle);

M3U8打包渲染

重要

M3U8打包与外挂字幕不建议混合渲染。

  1. 导入依赖库。

    import com.aliyun.subtitle.SubTitleBase;
    import com.cicada.player.utils.webVtt.VttSubtitleView;
    import com.aliyun.player.IPlayer;
  2. 设置显示视图。

    vttSubtitleView = new VttSubtitleView(getContext());
    vttSubtitleView.setId(R.id.cicada_player_vtt_subtitle);
    
    
    // 设置字幕的显示位置 - 添加到布局中央
    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.WRAP_CONTENT,
            FrameLayout.LayoutParams.WRAP_CONTENT
    );
    params.gravity = Gravity.CENTER; // 添加到布局中央
    
    // 将VTT字幕View添加到根布局视图中
    mRootFrameLayout.addView(mVttSubtitleView, params);
  3. 设置回调。

    //下述示例为mVideoListPlayerlistPlayer,使用aliPlayer 也是一致的
    //1. 设置 OnSubtitleDisplayListener
    mVideoListPlayer.setOnSubtitleDisplayListener(new IPlayer.OnSubtitleDisplayListener() {
        @Override
        public void onSubtitleExtAdded(int trackIndex, String url) {
          //由于仅测试添加单个vtt文件,因此当vtt文件添加完毕时,直接选择该路字幕即可
            mVideoListPlayer.selectExtSubtitle(trackIndex, true);
        }
    
        @Override
        public void onSubtitleShow(int trackIndex, long id, String data) {
            if (vttSubtitleView != null) {
                vttSubtitleView.show(id, data);
            }
        }
    
        @Override
        public void onSubtitleHide(int trackIndex, long id) {
            if (vttSubtitleView != null) {
                vttSubtitleView.dismiss(id);
            }
        }
    
        @Override
        public void onSubtitleHeader(int trackIndex, String header) {
            if (vttSubtitleView != null) {
                vttSubtitleView.setVttHeader(header);
            }
        }
    });
    
    //2.设置VideoSizeChangedListener,下述流程为必要流程,需要将数据回传到vttSubtitleView中
    mVideoListPlayer.setOnVideoSizeChangedListener(new IPlayer.OnVideoSizeChangedListener() {
        @Override
        public void onVideoSizeChanged(int width, int height) {
            int viewWidth = getWidth();
            int viewHeight = getHeight();
            IPlayer.ScaleMode mode = mVideoListPlayer.getScaleMode();
            SubTitleBase.VideoDimensions videoDimensions = SubTitleBase.getVideoDimensionsWhenRenderChanged(width, height, viewWidth, viewHeight, mode);
            vttSubtitleView.setVideoRenderSize(videoDimensions.videoDisplayWidth, videoDimensions.videoDisplayHeight);
        }
    });
  4. 播放器执行prepare/moveTo/moveToNext/moveToPrev操作后,需清除显示视图。

    source = getSource(someParams);//伪代码
    
    mVideoListPlayer.moveTo(mCurrentUUID + "");
    if (vttSubtitleView != null) {
        vttSubtitleView.clearAll();
    }
    
    if (!source.getExtSubtitleUrl().isEmpty()) {
        mCurrentExtSubtitle = source.getExtSubtitleUrl();//获取外挂字幕 url
    }
  5. 设置onTrackReady回调获取子流信息。

    mAliPlayer.setOnTrackReadyListener(new IPlayer.OnTrackReadyListener() {
        @Override
        public void onTrackReady(MediaInfo mediaInfo) {
            List<TrackInfo>  trackInfos = mediaInfo.getTrackInfos();
            for (TrackInfo trackInfo : trackInfos) {
                if (trackInfo.getType() == TrackInfo.Type.TYPE_SUBTITLE) {
                //todo 
                }
            }
        }
    });
  6. 播放器onPrepared后,调用selectTrack切换字幕。

    //index为目标字幕流的TrackIndex
    mAliPlayer.selectTrack(index);

iOS端关键实现

外挂字幕渲染

  1. 设置回调。

    /**
     @brief 外挂字幕被添加
     @param player 播放器player指针
     @param trackIndex 字幕显示的索引号
     @param URL 字幕url
     */
    - (void)onSubtitleExtAdded:(AliPlayer*)player trackIndex:(int)trackIndex URL:(NSString *)URL {
        NSLog(@"onSubtitleExtAdded: %@, trackIndex: %d", URL, trackIndex);
        [self.listPlayer selectExtSubtitle:trackIndex enable:YES];
    }
  2. 播放器onPrepared后,添加字幕文件。

    if (self.currentModel.extSubtitleUrl != Nil) {
        [self.listPlayer addExtSubtitle:self.currentModel.extSubtitleUrl];
    }

M3U8打包渲染

重要

M3U8打包与外挂字幕不建议混合渲染。

  1. 设置回调。

    /**
     @brief 外挂字幕被添加
     @param player 播放器player指针
     @param trackIndex 字幕显示的索引号
     @param URL 字幕url
     */
    - (void)onSubtitleExtAdded:(AliPlayer*)player trackIndex:(int)trackIndex URL:(NSString *)URL {
        NSLog(@"onSubtitleExtAdded: %@, trackIndex: %d", URL, trackIndex);
        [self.listPlayer selectExtSubtitle:trackIndex enable:YES];
    }
  2. 设置onTrackReady回调获取子流信息。

    - (void)onTrackReady:(AliPlayer*)player info:(NSArray<AVPTrackInfo*>*)info {
      //
    }
  3. 播放器prepare done后,调用selectTrack切换字幕。

    //index为目标字幕流的TrackIndex
    [self.player selectTrack:index];

基于WebVTT流实现自定义渲染

阿里云播放器支持标准WebVTT流的双端解析渲染,可读取CSS样式并按标准实现双端统一字幕样式。并支持单文件多文本样式,适用于角色对话区分、重点内容强调及特殊视觉效果等场景。

以下为示例WebVTT文件:

  • REGION:显示区域定义。

  • STYLE:字体样式定义(首项为默认样式)。

WEBVTT

REGION
id:bottom
width:70.000000%
lines:3
viewportanchor:15.000000%,95.000000%
regionanchor:0.000000%,100.000000%
scroll:up

STYLE
::cue {
  color: white;
  font-size: 70%;
  font-family: Noto Sans;
  font-weight: bold;
  background-color: rgba(0, 0, 0, 0.2);
}
::cue(.font1) {
  color: rgba(255, 255, 255, 1);
  font-weight: bold;
  font-family: Noto Sans;
  outline-width: 3px;
  outline-color: rgba(0, 0, 0, 1);
}
::cue(.font2) {
  color: rgba(252, 255, 101, 1);
  font-style: bold;
  font-family: Noto Sans;
  outline-width: 3px;
  outline-color: rgba(0, 0, 0, 1);
}
::cue(.font3) {
  color: rgba(255, 191  23, 1);
  font-style: bold;
  font-family: Noto Sans;
  outline-width: 3px;
  outline-color: rgba(183, 28, 28, 1);
}

00:00:00.200 --> 00:00:01.800 region:bottom align:center
被告人薄寒时

00:00:01.800 --> 00:00:03.200 region:bottom align:center
你还有什么话要说

00:00:04.100 --> 00:00:04.800 region:bottom align:center
我

00:00:11.200 --> 00:00:12.200 region:bottom align:center
<v.font1>我无话可说</v.font1>

00:00:14.500 --> 00:00:16.200 region:bottom align:center
被告人薄寒时

...

为文字片段指定特定样式,请参照如下设置:

<v.font1>我无话可说</v.font1>