Article language
Original language: Chinese
You are reading a machine-translated version from Chinese to English.
Bilibili Danmaku anti-blocking feature realization analysis
其中,智能防挡弹幕的功能实现最令我着迷。在掌握了前端“三板斧”后,我迫切想弄明白的,便是这一功能背后的实现逻辑。
As a 356,441st registered user on Bilibili, I have witnessed its entire journey from a niche community for ACG enthusiasts, streaming anime resources watermarked with "For Preview Only," to a NASDAQ-listed company. I still clearly remember the day of the IPO; I was on a subway in Manhattan, rushing to an 8:00 AM Japanese class. Over the years, beyond the UI iterations, the expansion of content and user demographics, and the membership system that accompanied the licensing of anime, danmaku has remained the indispensable soul of Bilibili. Although its design inspiration originated from Niconico Douga, but the current danmaku ecosystem is far from what it was in 2007. Features such as colored danmaku, advanced danmaku, and fixed danmaku have emerged one after another. Among them, the implementation of the intelligent anti-blocking danmaku feature fascinates me the most. After mastering the "three axes" of front-end development, what I am eager to understand is the implementation logic behind this feature.
Exploration and Discovery
After some exploration, I found in the consoleElementA component was discovered at the same level as the player, stacked on<video>Above the element<div>Container, whose class name is bpx-player-render-dm-wrap. This container contains a key sub-container bpx-player-dm-mask-wrap, which incorporates something similar to mask-image CSS property. Those familiar with CSS will find this recognizable, as it is exactly what we know as "Mask" Technology. At this point, a rough implementation plan immediately surfaced in my mind. I was struck by how ingenious and concise it was; one cannot help but marvel at human ingenuity.

Conjecture on Implementation Mechanisms
While playing the video, I observed that this mask container changes in real-time with the video progress, leading me to speculate that it follows a "one frame, one mask" approach, where the system transmits mask data to the client in real-time. As for the source of this mask data, I hypothesize that during the compression phase after video upload, an algorithm (such as semantic segmentation) is used to directly identify and mark the positions of faces or bodies in each frame, with the data then stored in a database. Subsequently, a dedicated mask generation microservice transmits this data to the client in synchronization with the video playback, thereby preventing bullet comments from obscuring the main subjects and significantly enhancing the viewing experience. Upon carefully examining the transmission process of the mask data, I discovered in the Network tab of the console that the system is transmitting the required SVG mask for each frame in real-time. This high-frequency service request is most likely implemented through a real-time communication channel established via WebSocket.

The specific CSS properties are as follows:
mask-image: linear-gradient(rgb(0, 0, 0), rgb(0, 0, 0) 0.0332447%, rgba(0, 0, 0, 0) 0.0332447%), linear-gradient(rgba(0, 0, 0, 0) 99.9668%, rgb(0, 0, 0) 99.9668%, rgb(0, 0, 0)), url(data:image/svg+xml;base64)among whichURLThe part is the Base64-encoded data of the SVG mask. With this, the hierarchical structure of the page becomes clear:
1.Lowest level:<video>Video element.
<div class="bpx-player-video-perch">
<div class="bpx-player-video-wrap">
<video crossorigin="anonymous" preload="auto" src="blob:https://www.bilibili.com/3c2bf5a3-a599-493d-9e56-6b49db76c63f">
</video>
</div>
</div>2.Middle tier: A bullet screen container with a mask applied.
<div class="bpx-player-render-dm-wrap">
<div
class="bpx-player-dm-mask-wrap"
style='mask-image: linear-gradient(rgb(0, 0, 0), rgb(0, 0, 0) 0.0332447%, rgba(0, 0, 0, 0) 0.0332447%), linear-gradient(rgba(0, 0, 0, 0) 99.9668%, rgb(0, 0, 0) 99.9668%, rgb(0, 0, 0)), url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKIHdpZHRoPSI2NjhweCIgc3R5bGU9InRyYW5zZm9ybTpzY2FsZSgxLDEuMDAxKTsiIGhlaWdodD0iMzc1Ljc1cHgiIHZpZXdCb3g9IjAgMCAzMjAuMDAwMDAwIDE4MC4wMDAwMDAiCiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMC4wMDAwMDAsMTgwLjAwMDAwMCkgc2NhbGUoMC4xMDAwMDAsLTAuMTAwMDAwKSIKZmlsbD0iIzAwMDAwMCIgc3Ryb2tlPSJub25lIj4KPHBhdGggZD0iTTAgOTA1IGwwIC04OTUgMzIwIDAgMzIwIDAgMCAyMyBjMCAzNSAtMzYgNjIgLTExOSA5MSAtNDEgMTQgLTgzIDM0IC05NCA0MwotNDcgNDMgLTgwIDE5MyAtNDIgMTkzIDkgMCAxNSA2IDE0IDEyIC0xIDcgMSAyNSA1IDM5IDUgMTkgMSA0MSAtMTMgNzIgLTEyCjI1IC0yMSA1MSAtMjEgNTcgMCA2IC05IDI4IC0yMCA1MCAtMTYgMzEgLTIwIDU5IC0yMCAxMzcgMCA1NCA0IDEwNCA5IDExMiA1CjcgNDQgMzQgODcgNjEgODYgNTIgODMgNDQgNjMgMTQ1IC03IDMzIC0xMyA3OCAtMTMgMTAwIC0xIDM5IDEgNDEgNTQgNjcgNDQKMjIgNjQgMjcgOTkgMjIgOTMgLTEyIDExMyAtNDYgMTEzIC0xOTcgbDEgLTEwNSA0NiAtNyBjNTggLTkgNjggLTE3IDEwMiAtODgKMjcgLTU4IDI3IC01OCAzNCAtMjggNCAyMCAxIDM4IC0xMSA1NSAtMTUgMjQgLTE1IDMwIC0yIDcxIGwxNSA0NCAtMjkgNiBjLTIwCjUgLTM0IDE3IC00NSAzOSAtMTQgMzAgLTE0IDM2IDIgNjUgOSAxOCAyMCA1NyAyNSA4NiAxMSA2OCAzMyAxMDEgODIgMTI2IDM2CjE5IDM5IDIyIDI1IDM3IC0xNCAxNiAtMjAgNjIgLTE4IDEzOSAxIDQ1IDQ0IDkzIDgzIDkzIDQwIDAgODggLTMwIDExMSAtNzAKMTUgLTI0IDE4IC00NyAxNSAtOTcgbC0zIC02NiAzOSAtMTQgYzQ5IC0xNiA3NSAtMzkgOTEgLTc4IGwxMyAtMjkgMTQgMzUgYzEyCjI2IDI4IDQxIDcyIDYyIGw1OCAyOSAtOCAzNiBjLTUgMjAgLTcgNjkgLTYgMTA3IDIgNTkgNiA3MyAyNSA4NyAxMiAxMCAyNyAyMgozNCAyOCAxNSAxMiAzMyAxMyAzMyAxIDAgLTUgMTMgLTE0IDI5IC0xOSAxNiAtNiAzNCAtMjAgNDAgLTMxIDE1IC0yOCAxNwotMTMxIDMgLTE1OCAtMjAgLTM2IC0xNSAtNDMgNTIgLTczIDcyIC0zMiA4MiAtNDUgOTQgLTEyMiA4IC01MCA5IC01MSAxNyAtMjMKMTYgNjIgNzMgMTE2IDE0NSAxMzUgMjQgNiAyNCA4IDIxIDg3IC0zIDg2IDMgMTA2IDQ1IDE0OCAyNCAyNCA4NCAzNiA4NCAxNiAwCi01IDEwIC0xMSAyMyAtMTUgMjUgLTYgNjcgLTcxIDY3IC0xMDMgMCAtMTEgLTE2IC00MSAtMzUgLTY2IC0xOSAtMjUgLTM1IC00OQotMzUgLTUyIDAgLTExIDQyIC00NCA4MSAtNjQgMTkgLTExIDQxIC0yNyA0OCAtMzUgMjAgLTI0IDUxIC0xOTMgNDEgLTIyNSAtNQotMTQgLTExIC00MiAtMTQgLTYyIC00IC0yNCAtMTQgLTQwIC0zMSAtNDkgLTE4IC05IC0yNSAtMjEgLTI0IC0zOSAxIC0xNCAtMgotNjQgLTYgLTExMSAtNyAtOTQgLTUgLTk0IDQ2IDAgNDQgODEgODIgMTMxIDExMyAxNDcgMjYgMTMgMjcgMTcgMjMgNzYgLTIgMzMKLTkgNjMgLTE2IDY1IC02IDIgLTExIDIxIC0xMSA0MyAwIDIxIC00IDM5IC0xMCAzOSAtMTQgMCAtMTIgMzYgMiA0OCA3IDUgMjkKMTMgNDggMTcgMTkgNCA1MiAxNiA3MiAyNiA1MSAyNiAxMjAgMjUgMTYzIC0zIDU5IC0zOSA2OCAtODMgMzUgLTE3MCAtMTEgLTI5Ci0yMCAtNjAgLTIwIC02OSAwIC0xOSAzMSAtNDkgNTEgLTQ5IDggLTEgNDggLTI3IDg5IC01OCA1NCAtNDEgNzggLTY3IDg3IC05Mgo3IC0yMCAxMiAtOTUgMTMgLTE3NSAwIC05OSAzIC0xMzYgMTEgLTEyNSA4IDEyIDkgMTAgNyAtMTAgLTIgLTE0IC04IC0yNCAtMTUKLTIzIC02IDEgLTEzIC0xNiAtMTcgLTM4IC00IC0yNSAtMTkgLTUzIC00MSAtNzcgLTUxIC01OCAtNTYgLTc5IC0zNCAtMTQzIDE1Ci00MCAxOCAtNjcgMTQgLTEwNyBsLTYgLTUyIDIwNSAwIDIwNiAwIDAgODk1IDAgODk1IC0xNjAwIDAgLTE2MDAgMCAwIC04OTV6Ii8+CjxwYXRoIGQ9Ik0xNzcxIDkzNSBjLTEgLTUgLTMgLTcxIC00IC0xNDUgLTIgLTEyNSAtNCAtMTQwIC0zMSAtMjAwIC0zNSAtNzkgLTM3IC0xMzQKLTggLTE4MiAzMSAtNTAgMjggLTU4IC0yMiAtNTggLTQwIDAgLTQ2IDMgLTY3IDM2IC0yMCAzMyAtMjEgNDEgLTEwIDgzIDExIDQ3CjkgMjgwIC0zIDMwMCAtNCA2IC0yNCAxNiAtNDYgMjIgLTMyIDEwIC00NSA5IC03MiAtMyAtMTggLTggLTQ0IC0xOSAtNTggLTI1Ci0yNCAtOSAtMjQgLTEyIC0yMSAtMTAzIDEgLTUyIDExIC0xMjcgMjIgLTE2OCAxMCAtNDEgMTcgLTc4IDE0IC04MiAtOSAtMTQKLTg5IC00OCAtMTIwIC01MiAtMzYgLTMgLTM2IDMgNSA3MyBsMzEgNTIgLTM5IDgxIGMtMjIgNDUgLTQzIDkyIC00NyAxMDQgLTcKMjMgLTggMjMgLTMzIC0xNSAtMzYgLTUzIC0zOSAtNTUgLTExMSAtNTAgbC02NSA1IC01OCAtNTkgYy0zMiAtMzIgLTY0IC01OQotNzIgLTU5IC04IDAgLTI1IC0xMyAtMzkgLTMwIC0yMiAtMjYgLTI0IC0zNCAtMTYgLTY3IDI3IC0xMTEgMzEgLTEzMiAzNgotMTcxIDUgLTQwIDcgLTQzIDI5IC0zOCAyMyA2IDI0IDQgMjYgLTQxIDEgLTI3IDAgLTY3IC0xIC05MCBsLTMgLTQzIDY5NiAwCjY5NiAwIDAgNDQgYzAgMjcgLTYgNDggLTE1IDU2IC0yMSAxNyAtMTE5IDE4IC0xMzkgMSAtOSAtNyAtMTYgLTEwIC0xNyAtNSAtMwo0MiAtMTAgNjkgLTE5IDc4IC02IDYgLTQ4IDExIC05NyAxMiAtODIgMCAtODcgMSAtOTYgMjUgLTE3IDQzIC0yNSA4OSAtMTYgODkKNSAwIDkgNCA5IDkgMCA0IC0xMSA2IC0yNCA0IC0yOSAtNiAtNDUgMjYgLTIyIDQzIDkgNiAxMyAxNCA5IDE4IC00IDQgMTMgNQozNyAyIDMwIC00IDM3IC0zIDIwIDIgLTIwIDcgLTI0IDE0IC0yMiAzNSA1IDQ3IC0zNCAxNzEgLTY1IDIwOCAtOCAxMCAtMTcgNDMKLTIwIDc1IC02IDYyIC0yOCA5OCAtNzAgMTE1IC0xNSA1IC0yMyAxNyAtMjMgMzMgMCAxMyAtNiAyOSAtMTQgMzYgLTggNiAtMTcKMjEgLTE5IDMzIC0yIDEyIC01IDE4IC02IDEyeiIvPgo8L2c+Cjwvc3ZnPgo=");'
>
<div class="bpx-player-adv-dm-wrap"></div>
<div
class="bpx-player-row-dm-wrap bili-danmaku-x-paused"
style="contain: paint;"
>
<div class="bili-danmaku-x-dm-rotate"></div>
<div
aria-live="polite"
role="comment"
class="bili-danmaku-x-dm bili-danmaku-x-roll bili-danmaku-x-show"
>
辣条王子有不同辣度的
</div>
</div>
</div>
</div>;Resource Consumption Assessment
I carefully analyzed the size of each SVG mask, and most of them are under 4KB. Based on a common video frame rate of 30 fps and the average duration of Bilibili videos (taking the average of 3 to 30 minutes, which is 13 minutes), the estimation is as follows:
●Frame rate: 30 FPS
●Duration: 13 minutes (780 seconds)
●Single-frame data size: 4 KB (SVG path or mask)
●Total frame count: 30×780=23,400
Theoretically, the total consumption is approximately 23400×4KB≈91.4MB. This data volume is almost unacceptable in a mobile (5G/4G) environment, as it is nearly equivalent to the size of a 720P short video. Therefore, optimization is inevitable. In reality, the occasional stuttering of the player after enabling smart anti-occlusion danmaku may be more due to the performance pressure on the GPU during real-time mask calculation, rather than just an issue with network bandwidth.
Optimization Proposal Concepts
To address the aforementioned resource consumption issues, I have envisioned the following optimization schemes:
●Keyframe sampling: Drawing inspiration from the frame-sampling approach used in NVIDIA's DLSS technology, we do not need to generate a mask for every frame. Assuming we only sample 5 keyframes per second to generate masks, the difference is almost imperceptible to the human eye in scenes where character movement is not intense. This can directly reduce the data volume to 1/6 of the original, or approximately 15.2MB.
●Simplify Path: SVG masks are essentially complex polygonal paths. By using algorithms to reduce unnecessary inflection points, you can effectively compress SVG file size while maintaining shape approximation by using the minimum number of points to describe the polygon.
●HTTP Data Compression: Enable HTTP compression such as Gzip. Due to the high redundancy of SVG code, compression is highly efficient, and it is expected to further reduce the size of each frame of SVG data packets by 40%-50%.
●Chunked loading: Mask data should not be downloaded all at once; instead, it should be streamed and requested on-demand, synchronized with the video playback progress.
After the aforementioned optimizations, the mask data required for a 13-minute video can be kept well under 5MB. Given current network bandwidth and client-side hardware performance, this cost is essentially negligible. The implementation of this feature is, in essence, a trade-off and a balancing act of costs between the server side (computational investment during upload) and the client side (rendering computational power during playback). Before 2018, consuming such significant resources for a single feature might not have been worthwhile, but under today's hardware conditions, this return on investment is clearly reasonable.

Please log in to post a comment.
Log in0 comments
Be the first to comment.