You need to enable JavaScript to run this app.
导航

接入蒙版弹幕

最近更新时间2023.10.11 19:17:23

首次发布时间2021.09.16 17:47:42

前言

本文档基于您已经在 APP 中实现了弹幕的基本渲染功能。

当您想要进一步提升弹幕的用户体验,做到弹幕播放时不遮挡重要信息,效果如下:

image.png

如上图所示,在弹幕经过画面重要信息时,弹幕将会自动“隐藏”,不影响观看。

您可以通过集成 TTSDK,并参考本文内容为 APP 添加此功能。

基本实现

原理介绍

image.png

  1. TTSDK 可以根据当前播放中的画面内容按照时间戳同步地输出当前画面的蒙版信息,格式为 SVG,以 string 形式提供。
  • SVG 示例如下
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
 width="455px" height="256px" viewBox="0 0 455 256"
 preserveAspectRatio="xMidYMid meet">
<g transform="translate(0,256) scale(0.1,-0.1)" fill="#000000">
<path d="M0 1280 l0 -1280 694 0 695 0 19 53 c11 28 36 84 55 122 20 39 45 104 56 145 18 67 48 157 96 290 9 25 22 52 30 60 42 49 89 220 99 363 7 100 51 230 89 264 12 11 63 33 112 47 50 15 117 35 150 45 56 17 60 20 63 51 3 30 -10 73 -61 195 -11 28 -24 70 -27 95 -4 25 -13 66 -21 92 -10 38 -11 61 0 125 14 91 17 187 7 224 -6 18 -3 31 9 42 20 21 68 22 84 3 25 -30 66 -29 133 5 112 57 218 33 288 -65 19 -27 61 -78 92 -113 36 -40 60 -77 64 -98 7 -37 -8 -121 -31 -169 -8 -17 -21 -59 -29 -94 -8 -34 -23 -77 -35 -95 -68 -108 -71 -126 -23 -164 20 -16 69 -47 107 -68 39 -20 106 -61 150 -90 44 -29 99 -62 122 -74 23 -11 48 -30 56 -41 11 -16 21 -19 40 -15 38 10 91 -11 132 -53 46 -47 57 -73 44 -110 -22 -65 -123 -81 -154 -24 l-14 27 -1 -35 c0 -19 7 -67 15 -105 8 -39 15 -86 15 -105 1 -33 4 -36 65 -64 119 -53 150 -110 120 -218 -17 -63 -87 -220 -111 -248 -12 -15 -114 -190 -114 -196 0 -2 331 -4 735 -4 l735 0 0 1280 0 1280 -2275 0 -2275 0 0 -1280z"/>
</g>
</svg>

  • 上述 SVG 效果图如下

image.png

  1. 在获取到蒙版的 SVG 信息后,将图片内容作为弹幕渲染视图的遮罩图层,即可控制弹幕渲染视图的显示区域

  2. 将蒙版图片作用于弹幕的渲染视图。

image.png

必要条件

  • 必须先在控制台中为视频节目生成相应的蒙版文件
  • AppServer 在下发的 PlayAuthToken 中需要签入 “NeedBarrageMask”,AppServer 可通过服务端SDK中的【获取临时安全凭证】(即 PlayAuthToken )文档进行接入。
  • App 端开启蒙版弹幕功能,确认以下两项正确配置按注释所说明的时机调用。
// TTVideoEngine 初始化时调用
[self.videoEngine setOptionForKey:VEKeyPlayerEnableBarrageMaskThread_BOOL value:@(GLOBAL_CONFIG.isBarrageMaskOn)];

// 视频播放过程中可以控制蒙版的开关
[self.videoEngine setOptionForKey:VEKKeyPlayerBarrageMaskEnabled_BOOL value:@(isEnabeld)];

实现过程

  1. 设置视频源及蒙版 url

设置direct play url 及蒙版 direct url 的方法示例

// 设置 direct url,key 建议使用 url md5 值
[self.videoEngine ls_setDirectURL:url key:url.md5String];

// 设置蒙版 url
[self.videoEngine mask_setBarrageMaskUrl:maskUrl];

  1. 设置视频播放地址及相应的蒙版 url 后还需要实现相关的代理方法TTVideoEngineMaskDelegate,以接收按时间戳回调的蒙版信息。在代理的回调中包含了当前画面所需要的弹幕蒙版信息(以字符串形式表示的 SVG 信息)。
/// 蒙版信息输出回调
/// @param videoEngine
/// @param svg 视频蒙版 SVG string.
/// @param pts 当前 pts 
/// - see: TTVideoEngnie+Mask.h
- (void)videoEngine:(TTVideoEngine *)videoEngine onMaskInfoCallBack:(NSString*)svg pts:(NSUInteger)pts {
    // pseudocode:
    // UIImage *img = convertToImage(svg);
    // dispatch_async(dispatch_get_main_queue(), ^ {
    //    maskView.layer.contents = (id)image.CGImage;
    //    renderView.maskView = maskView;
    // })
}

  1. 通过上述代理方法回调获得的 svg 信息需要开发者将其转换到 UIImage 或 CGImage,并将转换得到的 UIImage/CGImage 设置成 CALayer 的 contents:
_maskView.layer.contents = (id)image.CGImage;

  1. 将 _maskView 设置成 弹幕渲染视图(_renderView)maskView

要点:需要将 maskView 的frame 与视频内容(实际的视频内容而不是playerView)对齐,实现的蒙版效果会更精准。

// 设置 maskView 之前先根据视频内容更新 frame
// Frame 计算参考:
// x = (playerView.bounds.size.width - videoWidth) / 2
// y = (playerView.bounds.size.height - videoHeight) / 2
// convertRect:toView:
_maskView.frame = CGRectMake(x, y, videoWidth, videoHeight);
// renderView:弹幕的渲染视图
// _maskView: 上述获取到的包含蒙版信息的 mask view.
[renderView setMaskView:_maskView];

如上即可快速通过 TTSDK 实现蒙版弹幕的功能,下文将对使用中的一些细节进行介绍。

使用细节

视图层级

播放视图、弹幕渲染及弹幕蒙版、播放控制等视图之间的层级关系需要事先根据业务需求设计,开发者可选择适合当前业务需求的视图层级。

现提供以下两种视图层级( 由下到上 )供参考:

  • 第一种:( 推荐
PlayerView
|-----视频渲染内容
播放控制View
|-----弹幕渲染视图
      |-----MaskView
|-----顶部控制栏
      |-----功能按钮
|-----底部控制栏
      |-----功能按钮
|-----其他按钮

  • 优点:便于实现交互式弹幕,易扩展,视图层级较少,交互类控件和弹幕集中在播放控制 View中。

  • 缺点:和其他播放控制耦合较多。

  • 第二种:

PlayerView
|-----视频渲染内容
弹幕渲染视图
|-----MaskView
播放控制View
|-----顶部控制栏
      |-----功能按钮
|-----底部控制栏
      |-----功能按钮

  • 优点:易实现
  • 缺点:仅适用于非交互式弹幕

横竖屏切换和 PlayerView 发生 resize 时的处理

由于蒙版是需要与实际渲染的视频内容完全对齐的,所以在 PlayerView 发生 resize 时,需要更新 maskView 的 frame。

_maskView.frame = CGRectMake(x, y, videoWidth, videoHeight);

//必要时调用, renderView 是弹幕的渲染视图
[renderView setNeedsLayout];
[renderView layoutIfNeeded];


其他

SVG 解析

SVG String 的解析可以通过 SVGKit 实现,注意解析 SVG 的回调在设置 maskView 时要在主线程,示例代码如下:

/** Callback for did convert a svg string to a UIImage instance. */
typedef void(^SVGConvertedToImageHandler)(UIImage * _Nullable image);

/**
 Parse SVG and convert to UIImage
 */
@interface SVGParser : NSObject


/**
 Callback for did convert a svg string to a UIImage instance.
 */
@property (nonatomic, copy, nullable) SVGConvertedToImageHandler imageGeneratedCompletion;

/**
 Convert a svg string to CALayer
  - svgString: <svg /svg>
 */
- (void)convertToImageFromSVGString:(NSString *)svgString;


@end

#import "SVGParser.h"
#import <SVGKit/SVGKit.h>

@implementation SVGParser

+ (dispatch_queue_t)serialQueue {
  static dispatch_queue_t queue = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    queue = dispatch_queue_create("Barrage.SVGParser", DISPATCH_QUEUE_SERIAL);
  });
  return queue;
}

- (void)convertToImageFromSVGString:(NSString *)svgString {
  dispatch_async([SVGParser serialQueue], ^{
    @autoreleasepool {
      NSData *svgData = [svgString dataUsingEncoding:NSUTF8StringEncoding];
       if (!svgData.length) {
        // Empty svg data situations are suitable for:
        // There is no person, animal, or other main subject in a video frame.
        dispatch_async(dispatch_get_main_queue(), ^{
          if (self.imageGeneratedCompletion) {
            self.imageGeneratedCompletion(nil);
          }
        });
        return;
      }
      
      SVGKSource *source = [SVGKSourceNSData sourceFromData:svgData URLForRelativeLinks:nil];
      SVGKImage *svgkImage = [SVGKImage imageWithSource:source];
      dispatch_async(dispatch_get_main_queue(), ^{
        if (self.imageGeneratedCompletion) {
          self.imageGeneratedCompletion(svgkImage.UIImage);
        }
      });
    }
  });
}

@end