蒙版弹幕可以在弹幕经过画面中的主体区域(如人像)时将其隐藏,实现“弹幕绕人走”的效果,避免弹幕遮挡视频主体,效果如下图所示。本文介绍如何基于应用已有的弹幕渲染功能,接入 HarmonyOS NEXT 播放器 SDK 的蒙版弹幕能力。

蒙版弹幕由播放器 SDK 与业务弹幕渲染模块协作完成:SDK 按播放时间输出 SVG 蒙版,业务将 SVG 解析为路径,并在弹幕画布中完成坐标映射和图形合成。

上述 SVG 的可视化效果如下:黑色区域对应 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>
viewBox、g transform、path d 和 fill-rule,将路径转换为 HarmonyOS Canvas 可绘制的 Path2D 或等价路径对象。未声明 fill-rule 时按 nonzero 处理,同时须支持 fill-rule="evenodd"。viewBox 映射到视频内容的实际显示区域。存在黑边时需要计算内容偏移,不能直接使用播放器组件的全部尺寸;缩放和平移应使用一致的坐标单位,避免混用 px 和 vp。globalCompositeOperation 设置为 destination-in,绘制变换后的蒙版路径,仅保留蒙版有像素区域中的弹幕。恢复画布状态后,将合成结果叠加到视频渲染层上方。
说明
SDK 负责蒙版资源加载、时间轴同步和 SVG 回调,不负责创建弹幕、解析 SVG 或绘制弹幕。业务可以使用自研弹幕组件或选择第三方弹幕组件(推荐鸿蒙官网 “DanmakuFlameMaster” 弹幕组件)。
VePlayer 自有播放器内核,而非系统 AVPlayer。"NeedBarrageMask": true,详见通过临时播放 Token 播放。实现 MaskInfoListener,在回调中将 SVG 和 PTS 交给业务弹幕渲染模块。回调线程不应被假定为 UI 线程;更新 ArkUI 状态时,应切换到业务使用的 UI 调度上下文。
import { MaskInfoListener } from '@simplayer/simkit_api' class BusinessMaskInfoListener implements MaskInfoListener { onMaskInfoCallback( sourceID: string | undefined, code: number, pts: number, svg: string ): void { // 将当前蒙版帧交给业务弹幕渲染模块 updateBarrageMask(sourceID, code, pts, svg) } }
updateBarrageMask 为业务实现的方法,渲染流程示意请参见步骤 4:渲染 SVG 蒙版。
根据播放方式选择以下一种配置。
SDK 会从 VideoModel 的 BarrageMaskInfo 自动读取蒙版 URL 和文件头长度,业务无需手动设置蒙版 URL。
import { PlayOptions, VePlayer, VidSource } from '@simplayer/simkit_api' // vid 和 playAuthToken 由应用服务端下发 const source = new VidSource(vid, playAuthToken) const player = new VePlayer(source, PlayOptions.create()) player.setBarrageMaskInfoListener(new BusinessMaskInfoListener()) player.setBarrageMaskEnabled(true) player.play()
说明
若 PlayAuthToken 未签入 NeedBarrageMask,或视频没有生成蒙版文件,SDK 无法获得蒙版资源,此时调用 setBarrageMaskEnabled(true) 不会产生蒙版回调。
在创建 VePlayer 之前,通过 PlayUrlModel.setBarrageMaskURL 设置与视频对应的蒙版文件 URL。
import { PlayOptions, PlayRequest, PlayUrlModel, VePlayer, getVideoModelFromPlayRequest } from '@simplayer/simkit_api' // videoUrl、barrageMaskUrl 和 sourceID 由业务提供 const playUrlModel = new PlayUrlModel([videoUrl]) playUrlModel.setBarrageMaskURL(barrageMaskUrl) const playRequest = new PlayRequest(sourceID, playUrlModel) const source = getVideoModelFromPlayRequest(playRequest) const player = new VePlayer(source, PlayOptions.create()) player.setBarrageMaskInfoListener(new BusinessMaskInfoListener()) player.setBarrageMaskEnabled(true) player.play()
蒙版 URL 必须与视频内容、时长和时间轴匹配。播放器创建后再补充 URL,不能为当前播放实例补建蒙版处理链路。
setBarrageMaskEnabled 是运行时开关,默认关闭。它只控制蒙版帧是否继续输出,不负责创建蒙版处理链路。
// 开启蒙版帧输出 player.setBarrageMaskEnabled(true) // 关闭蒙版帧输出 player.setBarrageMaskEnabled(false)
关闭开关时,业务还需主动清空渲染层缓存的 SVG,避免继续显示最后一帧蒙版。
以下代码仅示意业务渲染流程。currentSourceID 表示当前播放源,videoContentRect 表示视频内容实际显示区域,danmakuCanvas 表示独立弹幕图层的画布上下文。parseSvgMask、calculateVideoContentTransform、drawDanmakus 和 drawMaskPath 均需由业务实现,不属于播放器 SDK API。
function updateBarrageMask( sourceID: string | undefined, code: number, pts: number, svg: string ): void { if (sourceID !== currentSourceID || svg.length === 0) { return } const mask = parseSvgMask(svg) const transform = calculateVideoContentTransform(mask.viewBox, videoContentRect) danmakuCanvas.save() drawDanmakus(danmakuCanvas, pts) danmakuCanvas.globalCompositeOperation = 'destination-in' drawMaskPath(danmakuCanvas, mask, transform) danmakuCanvas.restore() }
页面退出或不再消费蒙版信息时,关闭输出、解绑监听并释放播放器,同时由业务清理渲染层缓存的 SVG。
player.setBarrageMaskEnabled(false) player.setBarrageMaskInfoListener(undefined) player.release()
播放结束后,建议停止生成新弹幕并清空当前 SVG 蒙版。已经进入屏幕的弹幕可以继续完成剩余动画。循环播放开始下一轮时,再恢复蒙版监听和运行时开关。
请依次检查:
VePlayer 自有播放器内核,而非系统 AVPlayer。NeedBarrageMask: true,且视频已生成蒙版文件。MaskInfoListener,并调用 setBarrageMaskEnabled(true)。检查 SVG viewBox 与视频内容实际显示区域的映射。使用 AspectFit 等模式时,需要扣除上下或左右黑边,并统一 px 与 vp 的坐标单位。旋转屏幕、切换全屏或改变播放器尺寸后,重新计算变换矩阵。
检查合成方向。HarmonyOS 蒙版中有像素的区域是弹幕允许显示区,应使用 destination-in;使用 destination-out 会得到相反结果。同时检查是否正确解析 fill-rule="evenodd"。
运行时开关只停止后续输出,不会清理已绘制的画布。关闭开关或收到播放结束事件时,业务需要主动清空渲染层缓存的 SVG。