- 前端
- 音视频
【免费下载链接】Bilibili-Evolved
强大的哔哩哔哩增强脚本
导读
视频末尾的"充电鸣谢"列表会打断观看节奏,尤其在连续追剧时非常烦人。Bilibili-Evolved(哔哩哔哩增强脚本)内置了"跳过充电鸣谢"组件,可在视频播放结束后自动跳过 UP 主充电鸣谢展示,并支持隐藏播放器内的充电鸣谢面板。本文以该组件的功能文档为起点,结合仓库源码逐层拆解其"CSS 隐藏 + 事件触发自动点击"的双重实现机制、页面匹配范围、生命周期管理与功能边界,帮助读者理解增强脚本如何以最小侵入方式干预 B 站播放器,并掌握该组件在 Bilibili-Evolved 中的启用与行为控制方式。
一、功能概述与使用场景
该组件的功能描述定义在 registry/lib/components/video/player/skip-charge-list/index.md:
自动跳过视频结尾的充电鸣谢。注意:不包括番剧承包鸣谢。
这段描述虽然简短,但明确了三个关键事实:
- 自动跳过:无需手动点击,视频播放结束后组件会自动触发跳过操作;
- 跳过对象是"充电鸣谢":即视频播放完毕、画面结束后展示的 UP 主充电(打赏)用户名单;
- 功能边界:番剧(Bangumi)承包鸣谢不在此组件处理范围内,二者是 B 站两套不同的鸣谢机制。
从使用场景看,该组件主要服务于:追番/看视频时不想被片尾鸣谢列表打断,希望在视频结束的第一时间自动跳过,进入推荐流或下一集。组件属于"视频播放器"类别(tags: [componentsTags.video]),可在 Bilibili-Evolved 的设置面板中按组件名skipChargeList查找并开关。
二、双重实现机制:隐藏面板与自动点击
跳过充电鸣谢并非单一的 JS 逻辑,而是由样式隐藏与事件监听两条链路共同完成,二者分别承载"静态隐藏"与"动态跳过"两个职责。
2.1 样式层:直接隐藏充电鸣谢面板
组件通过 SCSS 文件 charge-list.scss 定义隐藏规则:
body:not(.skip-charge-list-disable) .bilibili-player .bilibili-player-electric-panel, body:not(.skip-charge-list-disable) #bilibili-player .bpx-player-electric-panel { display: none !important; }该规则同时覆盖了 B 站两代播放器的充电鸣谢面板:
.bilibili-player .bilibili-player-electric-panel:老版(2.X)播放器的充电鸣谢面板;#bilibili-player .bpx-player-electric-panel:新版(3.X,即 bpx)播放器的充电鸣谢面板。
选择器以body:not(.skip-charge-list-disable)作为前置条件,意味着当body上存在skip-charge-list-disable类时,隐藏规则失效——这正是组件卸载(unload)时恢复原状的开关,详见下文生命周期分析。
2.2 逻辑层:监听 ended 事件自动点击跳过按钮
样式隐藏适用于面板"已经出现"的情况,但如果播放器在视频结束后立即弹出充电鸣谢并阻塞播放流程,仅靠隐藏 DOM 不够彻底,组件因此在 index.ts 中实现了自动点击逻辑:
videoChange(async () => { const video = await select('video') video?.addEventListener('ended', async () => { if (document.body.classList.contains(SkipChargeListDisabledClass)) { return } // 选择2.X或3.X的跳过按钮 const jumpButton = (await select( '.bilibili-player-electric-panel-jump,.bpx-player-electric-jump', )) as HTMLElement jumpButton?.click() }) })核心流程分为三步:
- 等待视频变化:通过
videoChange监听当前页面的视频切换(换集、换 P 等场景),每次视频变化后重新绑定监听; - 等待 video 元素就绪:使用
select('video')轮询获取播放器中的<video>元素; - 监听 ended 事件:视频播放结束时,轮询查找"跳过按钮"并触发
.click()。按钮选择器同样兼容两代播放器——.bilibili-player-electric-panel-jump(2.X)与.bpx-player-electric-jump(3.X)。
轮询机制:select 的底层实现
select来自 src/core/spin-query.ts,其本质是基于setTimeout的轮询查询器(spin-query)。默认轮询配置定义在源码第 11-14 行:
const defaultConfig: Required<SpinQueryConfig> = { maxRetry: 15, // 最大重试次数 queryInterval: 1000, // 重试间隔(ms) }即:每隔 1 秒查询一次选择器结果,最多重试 15 次;查询期间若页面处于后台标签页,会通过waitForForeground等待页面重新获得焦点后再继续重试(见 spin-query.ts)。这种"带前台等待的轮询"避免了后台标签页空转消耗资源,也保证了切换回页面时组件能及时响应。
select还带有查询缓存:同一选择器在短时间内重复调用会复用上一次的 Promise(源码selectCache),减少重复轮询开销;查询结束后缓存自动清除,防止拿到过期的 DOM 引用。
事件驱动:videoChange 的视频变化监听
videoChange定义在 src/core/observer.ts,它通过allMutations(MutationObserver 封装)持续观察页面 DOM 变化,并检测全局unsafeWindow.cid的变更来判定"视频是否切换"。源码中还对 B 站的一个特殊行为做了兼容——更换 cid 时 B 站代码会临时把 cid 改成一个数组,监听逻辑会忽略这种异常值,仅在 cid 正常变化时触发videoChange事件(见 observer.ts)。
这意味着:在普通视频页、分 P 切换、连续播放下一集等场景下,组件都会重新绑定ended监听,确保每一段视频的结尾都能被自动跳过,而不是只在首次加载时生效。
三、组件生命周期:reload 与 unload 的正确开关
组件在元数据中显式声明了reload与unload钩子(见 index.ts):
reload: () => document.body.classList.remove(SkipChargeListDisabledClass), unload: () => document.body.classList.add(SkipChargeListDisabledClass),其中SkipChargeListDisabledClass = 'skip-charge-list-disable'(源码第 5 行)。这套设计非常精巧:
- 卸载(unload)时:向
body添加skip-charge-list-disable类。由于 SCSS 中所有规则都以body:not(.skip-charge-list-disable)为前置条件,样式隐藏立即失效,充电鸣谢面板恢复显示;同时 JS 逻辑在ended回调开头检查该类,一旦存在就直接return,不再自动点击跳过按钮。两条链路被一个类名同时关停,且无需移除已注入的<style>标签,代价极低、切换瞬时完成; - 重载(reload)时:移除该类,两条链路即刻恢复。
与之对比,styled-component.ts 中提供的通用toggleStyle工具(源码第 22-45 行)采用"动态创建/移除<style>标签"的方式实现样式开关。skip-charge-list 组件选择"类名开关"方案而非移除样式标签,既避免了频繁增删 DOM 节点,也保证了样式隐藏与 JS 跳过逻辑在开关状态上始终一致——这是该组件在工程实现上一个值得借鉴的细节。
四、页面匹配范围:仅在播放器页面生效
组件元数据中声明了urlInclude: playerUrls(见 index.ts),限定组件只在包含播放器的页面注入。playerUrls定义于 src/core/utils/urls.ts:
export const playerUrls = [ '//player.bilibili.com', '//www.bilibili.com/html/player.html', ...videoUrls, // 普通视频页 ...bangumiUrls, // 番剧页 ...cheeseUrls, // 课程页 ]即组件覆盖四类页面:
| 页面类型 | 匹配模式 | 说明 |
|---|---|---|
| 嵌入式播放器 | //player.bilibili.com | 嵌入第三方页面的播放器 iframe |
| 播放器独立页 | //www.bilibili.com/html/player.html | 播放器独立页面 |
| 普通视频页 | videoUrls | www.bilibili.com/video/等 |
| 番剧/课程页 | bangumiUrls、cheeseUrls | 番剧与付费课程播放页 |
页面匹配通过 urls.ts 的matchCurrentPage实现,videoChange内部也会先调用matchCurrentPage(playerUrls)做一次页面预检,非播放器页面直接返回false,避免无意义的监听注册(见 observer.ts)。
五、组件注册方式与扩展阅读
从组件定义结构看(defineComponentMetadata定义于 src/components/define.ts),skipChargeList是一个典型的 Bilibili-Evolved 标准组件,具备名称、显示名、分类标签、入口、生命周期钩子与 URL 匹配规则等完整元数据。其入口entry经由styledComponentEntry包装(styled-component.ts 实际上是 src/components/styled-component.ts),该包装器会先动态加载组件 SCSS 并注入全局样式,再执行真正的逻辑入口——也就是说,样式注入与逻辑挂载被统一封装,任何采用styledComponentEntry的组件都遵循"先样式、后逻辑"的加载顺序。
读者如果希望深入理解本文涉及的底层能力,可以按以下路径继续阅读仓库源码:
- registry/lib/components/video/player/skip-charge-list/index.ts:组件完整实现(入口、选择器、生命周期钩子);
- registry/lib/components/video/player/skip-charge-list/charge-list.scss:样式隐藏规则(两代播放器面板选择器);
- src/core/spin-query.ts:
select轮询器实现(重试次数、间隔、缓存与前台等待策略); - src/core/observer.ts:
videoChange视频变化监听机制(cid 钩子与数组异常值兼容); - src/core/utils/urls.ts:
playerUrls页面匹配列表定义; - src/components/styled-component.ts:
styledComponentEntry样式注入封装与toggleStyle工具对比。
六、小结:一个"小而完整"的增强组件范本
"跳过充电鸣谢"虽然功能描述只有一句话,但它的实现浓缩了 Bilibili-Evolved 组件体系的多项核心能力:样式与逻辑的双通道协作(SCSS 隐藏面板 + ended 事件自动点击)、跨播放器版本兼容(2.X 与 3.X 选择器双覆盖)、低开销的开关机制(body 类名统一控制两条链路)、事件驱动的动态绑定(videoChange 跟随视频切换重挂监听)以及页面范围精确限定(playerUrls)。对于希望理解 Bilibili-Evolved 组件如何与 B 站播放器交互、或参考其模式编写新组件的开发者而言,这是一个麻雀虽小、五脏俱全的范本。使用时只需在脚本设置面板中找到"跳过充电鸣谢"并保持开启即可,组件会自动完成其余工作。
- 前端
- 音视频
【免费下载链接】Bilibili-Evolved
强大的哔哩哔哩增强脚本
相关推荐
Intro Skipper终极指南:如何自动跳过电视节目片头片尾
Intro Skipper终极指南:如何自动跳过电视节目片头片尾 想要告别重复观看电视剧的片头片尾吗?Intro Skipper 是一个专为 Jellyfin
音视频音频处理视频处理插件系统Bilibili-Evolved 的 BiliPlus 跳转支持组件:视频、番剧与空间页的一键跳转实现解析
Bilibili Evolved 的 BiliPlus 跳转支持组件:视频、番剧与空间页的一键跳转实现解析 BiliPlus 是一个提供 B 站视频信息增强查询
前端音视频Bilibili-Evolved 动态分组过滤组件深度解析:按关注分组筛选动态的实现原理与使用指南
Bilibili Evolved 动态分组过滤组件深度解析:按关注分组筛选动态的实现原理与使用指南 Bilibili Evolved 的动态首页( t.bili
前端音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考