本文档基于您已经在 APP 中实现了弹幕的基本渲染功能。
当您想要进一步提升弹幕的用户体验,做到弹幕播放时不遮挡重要信息,效果如下:
如上图所示,在弹幕经过画面重要信息时,弹幕将会自动“隐藏”,不影响观看。
您可以通过集成 TTSDK
,并参考本文内容为 APP 添加此功能。
<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 信息后,将图片内容作为弹幕渲染视图的遮罩图层,即可控制弹幕渲染视图的显示区域
将蒙版图片作用于弹幕的渲染视图。
// TTVideoEngine 初始化时调用 [self.videoEngine setOptionForKey:VEKeyPlayerEnableBarrageMaskThread_BOOL value:@(GLOBAL_CONFIG.isBarrageMaskOn)]; // 视频播放过程中可以控制蒙版的开关 [self.videoEngine setOptionForKey:VEKKeyPlayerBarrageMaskEnabled_BOOL value:@(isEnabeld)];
设置direct play url 及蒙版 direct url 的方法示例
// 设置 direct url,key 建议使用 url md5 值 [self.videoEngine ls_setDirectURL:url key:url.md5String]; // 设置蒙版 url [self.videoEngine mask_setBarrageMaskUrl:maskUrl];
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; // }) }
_maskView.layer.contents = (id)image.CGImage;
要点:需要将 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 时,需要更新 maskView 的 frame。
_maskView.frame = CGRectMake(x, y, videoWidth, videoHeight); //必要时调用, renderView 是弹幕的渲染视图 [renderView setNeedsLayout]; [renderView layoutIfNeeded];
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