跳转到内容

工作原理

Danmaku Anywhere 是一个纯前端的浏览器插件(mv3),通过注入脚本的方式在网页上显示弹幕。

所有的用户数据都保存在浏览器中,不会上传到服务器。由于弹幕数据量一般会大于浏览器可同步的限制,弹幕数据只保存在当前设备上。

此插件基本只做两件事情:

  1. 获取弹幕
  2. 渲染弹幕

获取弹幕

弹幕数据保存在IndexedDB中,分为两种类型:用户上传和第三方源。

用户上传

用户上传作为最基本的获取弹幕的方式,保证插件可以在不联网的情况下使用。

在能够使用第三方弹幕源的情况下,用户上传的弹幕一般是用来补充第三方弹幕源的不足,比如暂不支持的网址,一些小众网站、自制视频等。

第三方源

除了用户上传之外,其他的获取方式均依赖第三方网站,而这些网站普遍不提供官方的 API,

浏览器插件可以通过declarativeNetRequest权限来更改请求头从而绕过cors问题。而且由于插件拥有非常宽松的host_permissions权限,发送的请求会自动带上用户的cookie,所以可以获取到登录状态下的弹幕,而且可以绕过一些风控。 而坏处是,由于每个请求都会附带用户cookie(登录的情况),滥用可能会影响到用户的账号安全。

从设计原则上来说,插件尽可能的最小化对第三方网站的访问,只获取必要的数据。本地缓存弹幕数据很大一部分是因为这个原因。

B站

在启用B站弹幕源时,插件会先GET https://www.bilibili.com/以保证cookie正常,然后再确认用户的登录状态,如果未登录则会提示登录。

腾讯

腾讯视频需要的cookie通过javascript注入,所以无法简单通过GET https://v.qq.com/来获取。启用时,如果发现cookie缺失,会要求用户手动前往腾讯视频页面获取cookie

渲染弹幕

渲染指在正确的网站的正确的视频上显示弹幕。

匹配网页

插件安装时会请求所有网站的权限,但并不是所有网站都需要弹幕,因此需要装填配置来告诉插件哪些网页需要弹幕。

通过白名单的方式,只有在配置中指定的网址模式才会注入脚本并显示弹幕。同时,装填配置可以用来关联网址和其他配置和规则,比如网页适配和弹幕样式(未实装)等。

具体是通过chrome.scripting.registerContentScripts来注册脚本,只有在配置中指定的网址模式下才会注入脚本。

最早的设计是在添加装填配置时请求配置中网址的权限,这样可以最小化权限的需求,但后来发现这样太麻烦了,用户体验不好,而且权限管理也很繁琐,就索性安装时一次性请求所有权限。

匹配视频

弹幕是和视频绑定的,会与视频的播放/暂停同步。插件始终只会渲染一个视频的弹幕,如果存在多个视频,就需要判断在哪个个视频上显示弹幕。

可能出现多个视频的情况有:

  • 广告
  • 视频预览,比如鼠标悬停时自动播放的视频
  • 隐藏的视频,有些网站会使用隐藏的视频元素来实现一些功能

一开始的设计是用装填配置中的视频元素来选择某个视频,如果选到了多个,就取第一个。但是要求用户正确配置视频元素存在技术门槛,而且有些网站的视频元素是动态的,会随着时间/版本变化,所以这个方法并不可靠。

现在的方案是默认网站上存在多个视频,插件会选择最后一个开始播放的视频。如果该视频暂停了,就会选择正在播放的视频中最后一个开始播放的视频,如果有的话。如果出现问题,用户依然可以通过装填配置来指定视频元素。

渲染

渲染弹幕主要的问题是如何保证弹幕始终显示在视频上方。

考虑的最坏情况是视频全屏,一般全屏是通过Top Layer实现的,所以弹幕也需要使用Top Layer,这样才能保证弹幕始终显示在视频上方。

这样做会导致无法实现弹幕互动,例如鼠标悬停、点击等。由于弹幕层在最上层,会遮挡下方的元素,比如视频控件等。如果要和下方的元素交互,就需要给弹幕层设置pointer-events: none,这样可以避免弹幕层拦截点击事件,但也导致弹幕无法接收用户互动。

限制

iframe

对于插件来说,网页和网页内的 iframe 是两个不同的网页。如果视频位于 iframe 中,而装填配置匹配到了网页但没有匹配到 iframe,插件将无法获取到视频元素,导致无法正常工作。

Shadow DOM

Shadow DOM 会阻止脚本访问元素。

如果一个网站的视频元素位于 Shadow DOM 中,插件无法直接访问到视频元素,导致无法正常工作。

目前无解,但也没有遇到过这种情况。