Bilibili-Evolved 快捷键扩展实战:剖析“开关灯“插件从注册到触发播放器灯光的完整链路
2026/9/20 1:49:04 网站建设 项目流程
  • 前端
  • 音视频

【免费下载链接】Bilibili-Evolved

强大的哔哩哔哩增强脚本

项目地址:https://gitcode.com/gh_mirrors/bi/Bilibili-Evolved
点击查看免费下载

本篇技术指南以 Bilibili-Evolved 仓库中的快捷键扩展插件 keymap-toggle-player-light 为切入点,深入讲解该插件如何在"快捷键扩展(keymap)"组件的动作列表中添加一个"开关灯"动作,以及该动作如何经由播放器适配层最终切换 B 站播放器的灯光模式。读完本文,你将掌握 Bilibili-Evolved 中"插件注册快捷键动作 + 预设默认键位"的完整机制,并能举一反三,为自己的功能扩展编写同类插件。

插件文档与插件本体

该插件的说明文档 index.md 非常简短,只有一句话:

在快捷键的动作列表里添加一个 "开关灯"。

这寥寥数语描述的,其实是一个完整的"插件式扩展"案例:它不修改 keymap 组件的任何源码,而是通过 Bilibili-Evolved 的插件数据总线(addData),向"快捷键扩展"组件动态注册一个新的动作条目,并为其配置默认快捷键。插件的全部实现都在 index.ts 中,全篇约 20 行,结构清晰:

import { PluginMetadata } from '@/plugins/plugin' import { toggleLight } from '@/components/video/player-light' import type { KeyBindingAction } from '../../../components/utils/keymap/bindings' export const plugin: PluginMetadata = { name: 'keymap.actions.togglePlayerLight', displayName: '快捷键扩展 - 开关灯', setup: ({ addData }) => { addData('keymap.actions', (actions: Record<string, KeyBindingAction>) => { actions.togglePlayerLight = { displayName: '开关灯', run: async () => { toggleLight() }, } }) addData('keymap.presets', (presetBase: Record<string, string>) => { presetBase.togglePlayerLight = 'shift l' }) }, }

从源码结构可以看到,这是一个标准插件(PluginMetadata)的完整形态:

  • namekeymap.actions.togglePlayerLight,是插件在注册表中的唯一标识;
  • displayName快捷键扩展 - 开关灯,展示在用户组件的插件管理界面中;
  • setup:接收addData函数,向两个数据槽(data slot)注入内容。

数据槽机制:插件如何"接管"组件的内部数据

理解这个插件,首先要理解addDataregisterAndGetData这对配套机制。在 Bilibili-Evolved 中,组件可以通过 registerAndGetData 声明"可被插件扩展的数据槽",插件则通过addData向这些槽位追加数据。

在 keymap 组件中,共有两个与本文相关的数据槽:

  1. keymap.actions:承载动作集合Record<string, KeyBindingAction>。keymap 组件在 actions.ts 中通过registerAndGetData('keymap.actions', builtInActions)注册内置动作(如全屏、宽屏、静音、投币、收藏、暂停/播放等),插件即可向该集合追加自己的动作。
  2. keymap.presets:承载预设键位。在 presets.ts 中通过registerAndGetData('keymap.presets', presetBase, builtInPresets)注册默认键位表与内置预设(Default、YouTube、HTML5Player、PotPlayer)。

插件keymap-toggle-player-light正是向这两个槽位各注入一份数据:

  • keymap.actions追加togglePlayerLight动作,其displayName为"开关灯",run回调直接调用播放器灯光切换函数;
  • keymap.presetspresetBase追加默认键位togglePlayerLight = 'shift l',即默认按下Shift + L即可开关灯(也可在设置中修改,见下文)。

这种设计的好处在于:插件与组件完全解耦,keymap 组件本身并不知道"开关灯"动作的存在,两者通过数据总线松耦合连接,插件可以独立启用/禁用,且不影响组件主体。

动作执行的底层链路:从快捷键到播放器灯光

动作的run回调只有一行:toggleLight(),它来自 src/components/video/player-light.ts。该文件对外导出三个函数:

export const lightOn = setLight(true) export const lightOff = setLight(false) export const toggleLight = setLight()

setLight工厂函数做了两件事:

  1. 页面匹配检查:通过playerUrls.some(url => matchUrlPattern(url))判断当前页面是否为播放器页面,非播放器页面直接返回空值(不触发);
  2. 惰性加载设置面板并切换灯光:在播放器页面中,先调用loadLazyPlayerSettingsPanel(buttons.settings.selector, settings.wrap.selector)确保播放器设置面板资源已就绪,再调用playerAgentInstance.toggleLight(on)执行实际切换。

其中toggleLight未传参数,表示"无指定参数时直接翻转当前状态"。

再往下,灯光切换的真正执行者是播放器 Agent 的 toggleLight 方法:

/** true 开灯,false 关灯 */ async toggleLight(on?: boolean) { if (!this.nativeApi) { return null } const isCurrentLightOff = this.nativeApi.getLightOff() // 无指定参数, 直接 toggle if (on === undefined) { this.nativeApi.setLightOff(!isCurrentLightOff) return !isCurrentLightOff } // 关灯状态 && 要开灯 -> 开灯 if (on && isCurrentLightOff) { this.nativeApi.setLightOff(false) return true } if (!on && !isCurrentLightOff) { this.nativeApi.setLightOff(true) return false } // ... }

这里体现了完整的三层调用链:

快捷键 Shift+L 按下 → keymap 动作 togglePlayerLight.run() → player-light.ts 的 toggleLight()(页面匹配 + 惰性加载设置面板) → playerAgent.toggleLight(on)(无参即翻转) → nativeApi.getLightOff() / setLightOff()(读写 B 站原生播放器灯光状态)

需要特别说明的是:playerAgent是适配了 B 站多版本播放器的统一代理(见 src/components/video/player-agent/),nativeApi会随播放器版本(如 v2/v3/v4、BPX)解析出对应的原生接口。因此插件无需关心播放器内部实现差异,统一通过playerAgent这一抽象层操作即可。

快捷键触发与按键匹配机制

动作注册后,如何被"按出来"?这由 keymap 组件的 bindings.ts 负责。其核心逻辑如下:

  • KeyBindingAction接口定义了动作的结构:displayName(动作显示名)、run(执行函数)、prevent(是否阻止默认行为)、ignoreTyping(是否在打字时忽略)等;
  • loadKeyBindingsdocument.body与所有被观察的 shadow DOM 上以capture: true方式监听keydown
  • 每次按键事件,会依次经过多道过滤:是否启用、打字状态忽略(默认ignoreTyping !== false时跳过)、聚焦元素判断(允许播放器控制按钮、设置项输入框等场景下继续响应)、全景视频禁用 WASD;
  • 修饰键匹配支持可选修饰键语法binding.keys中写[shift]表示该修饰键按或不按均可(见 bindings.ts 的optionalModifyKey处理);
  • 主键匹配同时支持e.key(按键字符)与e.code(物理按键码),例如lL、方向键arrowUp等均能正确命中;
  • 动作执行后,若返回非空值或显式声明prevent: true,则调用stopImmediatePropagation()preventDefault()阻止页面默认行为(如按空格暂停播放等场景)。

插件注册的togglePlayerLight动作未设置preventignoreTyping,因此行为为默认:按键在打字时被忽略,匹配成功且动作返回非空值时阻止默认事件。

默认键位、预设与自定义键位

keymap 组件的预设系统分三层合并(见 presets.ts 与 index.ts):

最终键位 = presetBase(基础默认) ⊕ presets[preset](用户选中的预设,如 Default / YouTube / HTML5Player / PotPlayer) ⊕ settings.options.customKeyBindings(用户自定义键位)

插件向presetBase注入的shift l属于第一层,意味着:

  • 默认情况下Shift + L即可开关灯;
  • 若用户切换到 YouTube 等预设且预设中覆盖了togglePlayerLight,则以预设值为准;
  • 若用户在"快捷键设置"面板中手动自定义了该动作的键位,则最终以自定义值为准(自定义优先级最高)。

键位字符串以空格分隔,例如shift l会被解析为['shift', 'l']两个按键的组合(见 index.ts 的parseBindings)。因此该插件注册的键位语义是"同时按下 Shift 与 L"。

在设置面板中查看与修改

keymap 组件本身是一个可配置组件,其设置界面由 settings/ 目录下的 Vue 组件提供:

  • KeymapSettings.vue:快捷键设置弹窗主体,列出全部动作与当前绑定键位;
  • KeymapSettingsRow.vue:单个动作的键位行,支持录制/修改按键;
  • vm.ts:提供loadKeymapSettingstoggleKeymapSettings,其中toggleKeymapSettings可由启动栏(Launch Bar)动作keymapSettings触发(见 index.ts)。

keymap 组件的核心选项定义在 index.ts 中:

选项默认值说明
longJumpSeconds85长前进/长后退的跳跃秒数(校验最小值为 1)
volumeStep10音量调整幅度,范围 1–100
showSeekShortcutstrue是否显示跳转快捷键
customKeyBindings{}用户自定义键位表(隐藏项)
presetDefault键位预设(隐藏项)

启用该插件后,打开播放器页面,在"快捷键扩展"设置面板中即可看到新增的"开关灯"动作及其默认键位Shift + L,也可录制自定义键位覆盖之。

小结与扩展思路

从这一句话的文档出发,我们还原了一个完整的功能链路:

  1. 插件通过addData('keymap.actions', ...)注册名为togglePlayerLight的快捷键动作,通过addData('keymap.presets', ...)注册默认键位shift l
  2. 动作触发后调用 player-light.ts 的toggleLight(),经页面匹配与设置面板惰性加载后,委托播放器 Agent 的toggleLight方法;
  3. Agent 再通过nativeApi.getLightOff()/setLightOff()读写 B 站原生播放器灯光状态,完成开灯/关灯切换。

对于希望为 Bilibili-Evolved 编写同类扩展的开发者,本插件是一个极佳的模板:只需实现PluginMetadata,在setup中向对应数据槽addData注入动作与键位即可,无需改动 keymap 组件本体。动作的run可以是任意异步逻辑——切换灯光、跳转进度、调用playerAgent的其他能力(如fullscreentogglePlaychangeTime等,详见 actions.ts 的内置动作)——从而实现与 B 站原生气播放器无缝集成的自定义快捷键体系。

  • 前端
  • 音视频

【免费下载链接】Bilibili-Evolved

强大的哔哩哔哩增强脚本

项目地址:https://gitcode.com/gh_mirrors/bi/Bilibili-Evolved
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询