启动时播放带声音的开机动画,开声不必进全屏
30.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/6 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
自动兼容检查有告警:未发布到 npm registry,仅可从源码安装。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add 2034126171/dsh-boot-animation-sound该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
是什么
dsh 原生插件 · other
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 28 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意未发布到 npm registry,仅可从源码安装
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包dsh-boot-animation-sound(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=20 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/14 03:22:17
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件
查看扫描证据(共 36 处,展示前 31 条)
- 读取本地文件index.js:379
if (!existsSync(path)) return { configured: true, source, problem: 'missing-file', extension, path } - 读取本地文件index.js:383
stats = statSync(path) - 读取本地文件index.js:432
const parsed = JSON.parse(readFileSync(stateFile(dshHome), 'utf8')) - 写入本地文件index.js:445
mkdirSync(stateDir(dshHome), { recursive: true }) - 写入本地文件index.js:448
writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, 'utf8') - 写入本地文件index.js:451
renameSync(temporary, file) - 写入本地文件index.js:461
mkdirSync(stateDir(dshHome), { recursive: true }) - 写入本地文件index.js:462
writeFileSync(join(stateDir(dshHome), 'last-boot.json'), `${JSON.stringify(report, null, 2)}\n`, 'utf8') - 读取本地文件index.js:472
return JSON.parse(readFileSync(join(stateDir(dshHome), 'last-boot.json'), 'utf8')) - 读取本地文件index.js:495
raw = JSON.parse(readFileSync(join(stateDir(dshHome), 'play-state.json'), 'utf8')) - 写入本地文件index.js:514
mkdirSync(stateDir(dshHome), { recursive: true }) - 读取本地文件index.js:585
const stream = createReadStream(media.path, { start, end }) - 执行外部命令固定命令index.js:717
child = spawn('powershell.exe', ['-NoProfile', '-STA', '-ExecutionPolicy', 'Bypass', '-Command', script], { windowsHide: true }) - 删除本地文件index.js:742
unlinkSync(carrier) - 读取本地文件verify/check-media.mjs:51
const text = readFileSync(path).toString('latin1') - 读取本地文件verify/check-media.mjs:53
bytes: statSync(path).size, - 读取本地文件verify/check-media.mjs:63
files = readdirSync(MEDIA_DIR).filter((name) => /\.(mp4|m4v|webm|mov|mkv|ogv)$/i.test(name)).sort() - 读取本地文件verify/client-smoke.mjs:27
const CLIENT = readFileSync(join(PACKAGE_DIR, 'client.js'), 'utf8') - 读取本地文件verify/host-verify.mjs:36
const CLIENT = readFileSync(join(PACKAGE_DIR, 'client.js'), 'utf8') - 读取本地文件verify/host-verify.mjs:37
const MANIFEST = JSON.parse(readFileSync(join(PACKAGE_DIR, 'package.json'), 'utf8')) - 读取本地文件verify/host-verify.mjs:219
check('the bundled clip exists in the package', existsSync(BUNDLED_MEDIA), BUNDLED_MEDIA) - 写入本地文件verify/host-verify.mjs:226
writeFileSync(junk, 'not a video') - 读取本地文件verify/host-verify.mjs:273
check('the settings file was written', existsSync(join(home, 'dsh-boot-animation-sound', 'settings.json'))) - 读取本地文件verify/host-verify.mjs:307
check('the report file was written', existsSync(reportFile), reportFile) - 读取本地文件verify/host-verify.mjs:308
const storedReport = JSON.parse(readFileSync(reportFile, 'utf8')) - 写入本地文件verify/host-verify.mjs:357
writeFileSync(dailyState, JSON.stringify(backdated)) - 删除本地文件递归verify/host-verify.mjs:377
for (const leftover of [freshHome, dailyHome, onceHome, conversationHome]) rmSync(leftover, { recursive: true, force: true }) - 删除本地文件递归verify/host-verify.mjs:402
rmSync(offHome, { recursive: true, force: true }) - 删除本地文件递归verify/host-verify.mjs:416
rmSync(spentHome, { recursive: true, force: true }) - 删除本地文件递归verify/host-verify.mjs:427
rmSync(occasionHome, { recursive: true, force: true }) - 删除本地文件递归verify/host-verify.mjs:511
rmSync(home, { recursive: true, force: true })
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 启动播放开机动画
- 播放音效无需全屏
- 自定义触发时机
DSH 开机动画:声音不需要全屏。触发时机(启动应用/页面刷新/新对话/任意会话)与播放频率(每次/每天一次/只播一次/限播 N 次)可自选。
README
由 DeepSeek 最新模型翻译生成dsh-boot-animation-sound
给 DeepSeek Harness(DSH)加一段开机动画:启动时铺满窗口播放一段视频。
和别的开机动画比,这一版只改一件事:声音不需要全屏。
纯 JavaScript,没有构建步骤。
作者:LSY(个人创作) · MIT · 版权归 LSY 所有,见 LICENSE。
为什么需要它
参考社区里几个开机动画,它们的做法是:
浏览器禁止带声音自动播放 → 动画一律静音起播 → 想听声音就点一下画面 → 但那一按同时还会 requestFullscreen(),于是「开声音」和「进全屏」被绑成了同一个动作。
(dsh-boot-animation-pro 的 README 自己就是这么写的:点一下画面即可开启声音并进入全屏。
展开完整文档(README 剩余 1 节)
dsh-boot-animation-sound
给 DeepSeek Harness(DSH)加一段开机动画:启动时铺满窗口播放一段视频。
和别的开机动画比,这一版只改一件事:声音不需要全屏。
纯 JavaScript,没有构建步骤。
作者:LSY(个人创作) · MIT · 版权归 LSY 所有,见 LICENSE。
为什么需要它
参考社区里几个开机动画,它们的做法是:
浏览器禁止带声音自动播放 → 动画一律静音起播 → 想听声音就点一下画面 → 但那一按同时还会 requestFullscreen(),于是「开声音」和「进全屏」被绑成了同一个动作。
(dsh-boot-animation-pro 的 README 自己就是这么写的:点一下画面即可开启声音并进入全屏。)
本插件把这两件事拆开:
| 动作 | 常见的绑定式做法 | 本插件 |
| 起播 | 一律静音 | sound: true 时先试带声音起播 |
| 被系统拦下 | 静音播放,等点击 | 静音播放,等点击(一样) |
| 点一下画面 | 开声 + 进全屏 | 只开声,窗口大小一动不动 |
| 第一次点击/按键 | 必须点在画面上 | 窗口内任意位置的第一次点击/按键就开声 |
| 进全屏 | 开声的副作用 | 只有那个默认关闭的「全屏」按钮才会进 |
代码层面:client.js 里 requestFullscreen() 只出现一次,就在全屏按钮的事件处理函数里;开声那条路径(unlock)只做两件事:video.muted = false 和 video.play()。
这条约束由验证脚本静态断言,改坏了跑测试就会红。
声音到底能不能自动响
先说规则。这个结论是在真实环境里实测出来的,不是照抄文档:
- 在一个没有任何用户手势的页面里,非静音的 play() 会被 Chromium 拒绝(NotAllowedError)。
Electron 的默认自动播放策略本应是 no-user-gesture-required,但实测就是拒绝了,所以本插件按「会被拒绝」来设计,而不是赌它不拒绝。
- 一旦这个页面有过用户手势(哪怕只是之前随便点过一下),非静音播放就会被放行。
于是分三种情况,插件三种都实现了:
1. 平台放行 → 直接就有声音,一次都不用点。
2. 平台拦下 → 画面照常播,右下角出现一个「🔊 点这里开声(不用全屏)」的小按钮; 同时你在窗口里随便点一下或按一下键盘也会开声。同一个窗口,不进全屏。
3. 设置里关掉声音 → 连试都不试,静音播放,也不出现任何开声提示。
想彻底免点击,只能改平台策略本身(例如给 Electron 传 --autoplay-policy=no-user-gesture-required),那不是插件能做的事。
本插件能做的是:不让你用全屏去换声音,并把「要点的那一下」缩到最小——点哪都行。
实测记录
真实窗口里跑过一次之后,插件自己写下的 last-boot.json(原文摘录):
{
"audio": "on", // 有声播放 "muted": false, // 元素没被静音 "audioDecodedBytes": 115835, // 真的解码出 115 KB 音频,声音确实出来了 "fullscreen": false, // 没有全屏 "fullscreenEverRequested": false, // 全程没请求过全屏 "duration": 7.05, "videoWidth": 1280, "videoHeight": 720, "ua": "… @deepseek-ai/dsh-desktop/0.2.0-rc.2 Chrome/152.0.7977.54 Electron/44.0.0 …"
}
audioDecodedBytes 是关键:它说明音频真的被解码并送进了输出管线,而不是「元素说自己没静音」而已。
触发时机与播放频率
触发时机(什么时刻播)
| 选项 | 含义 |
| 启动应用 | 一次 DSH 运行里只算第一次页面加载 |
| 页面刷新 | 每次页面加载都算(默认;也就是这个插件最早的行为) |
| 新对话 | 点「新建对话」时 |
| 任意会话 | 打开任意对话时,新建的也算 |
「启动应用」与「页面刷新」的区别由宿主判定,不是浏览器猜的:宿主半在每次 DSH 运行时只挂载一次,它数自己服务过多少次首页渲染——第 1 次就是「启动应用」,之后都是「页面刷新」。
这样「触发时机」和「要不要盖住首帧」用的是同一个计数器,不可能互相矛盾。
后两个是会话触发:它们不在页面加载时播,而是在你新建/打开对话时把动画盖在界面上(Esc 随时可退)。
会话触发没有现成的事件可用——DSH 客户端的事件表里只有 connection/reset、locale/change、 slots/changed、theme/change,没有会话事件。所以插件包了一层界面用来导航的那个服务(uiWorkspace 的 startSession / openSession / connectWorkspace)。
这层包装写得很保守:先调用原方法并原样返回它的结果,通知失败绝不影响导航;赋值不成功就干脆不包;插件卸载时逐个还原。宁可这个触发不生效,也不让导航出问题。
播放频率(一共能播几次)
| 选项 | 含义 |
| 每次 | 不限(默认) |
| 每天一次 | 每个自然日最多一次(按本机本地日期,不是 24 小时窗口) |
| 只播一次 | 一辈子最多一次 |
| 限播 N 次 | 最多 N 次,N 可填 1–1000 |
计数存在宿主这边(<DSH_HOME>/dsh-boot-animation-sound/play-state.json),不是浏览器里:只存浏览器的话,刷新一下就忘了、开第二个窗口就各算各的。所以:
- 播放权由宿主唯一判定,判定通过时当场扣一次(POST /claim);被拒绝的请求不扣。
- 只有真的开始播才计数;被拒绝的那些页面加载不算。
- 次数用完后,那些页面加载连首帧遮罩都不会注入——不会先黑屏再放出来。
- 设置页有「重置计数」,否则「只播一次」就是一扇只能改文件才能打开的门。
只在「一次页面加载」时播放
页面加载类的触发还有一道额外的判定:开机动画属于一次页面加载,不属于「插件被加载的那一刻」。
DSH 的 client 模块图是活的——把插件当场启用、或 HMR 把模块塞进一个开着很久的页面,都会加载这个文件。
所以浏览器半会先判断这是不是属于本次加载:
- 宿主在服务端 HTML 里注入的首帧遮罩在 里——它在,就是本次加载(精确信号);
- 遮罩关掉时(coverApplication: false)退回用页面年龄判断:模块在页面开了几分钟后才到,那一定是热加载,不播。
这样正常启动照常播,而不会突然盖住你正在干活的窗口。
安装
profile 目录是 <DSH_HOME>/profiles/<profile 名>(<DSH_HOME> 未设置时默认 ~/.dsh)。
方式一:让 agent 用 plugin_manager 装(桌面版首选)
plugin_manager: action=install_bundle, target=github:2034126171/dsh-boot-animation-sound
方式二:命令行
npm install -g @deepseek-ai/dsh
dsh plugin --profile web add github:2034126171/dsh-boot-animation-sound
方式三:手动
1. 在 profile 目录的 package.json 里,dependencies 加 "dsh-boot-animation-sound": "github:2034126171/dsh-boot-animation-sound";
2. 把 "dsh-boot-animation-sound" 加进同一个文件的 dsh.profile.bundles 数组;
3. 在该目录里用 pnpm 执行 install(桌面版自带 pnpm,在 <DSH_HOME>/.desktop-bin/pnpm.cmd);
4. 重启 DSH。
通过 GitHub 安装需要本机装有 git。
开发机上的现状(与使用者无关,仅供本仓库作者参考):源码放在工作区,profile 用两个 junction 指过来(profiles/node_modules/… 与 profiles/desktop/node_modules/…;前者给依赖 spec 用,因为它没有空格),并在 dsh.profile.bundles 里列了包名;改 profile package.json 之前已备份为 package.json.before-dsh-boot-animation-sound.bak。
使用
设置页在 设置 → 插件 → 开机动画:
| 项 | 说明 |
| 触发时机 | 启动应用 / 页面刷新 / 新对话 / 任意会话 |
| 播放频率 | 每次 / 每天一次 / 只播一次 / 限播 N 次(N 可填 1–1000) |
| 播放影片声音 | 这个插件的主角。默认开。关掉=整段动画静音,也不再出现开声提示 |
| 音量 | 0–100% |
| 首次点击/按键自动开声 | 默认开。关掉后只能点右下角那个开声按钮 |
| 启动时显示动画 | 临时关掉动画,但保留已选路径 |
| 显示全屏按钮 | 默认关。和声音毫无关系:开声永远不会触发它 |
| 退出方式 | 右下角按钮 / 点任意位置 / 自动退出 / 不能退出 |
| 视频文件 | 路径输入框 + 「选择文件…」原生对话框 + 保存 / 清除 |
| 播放账本 | 已播次数 + 上次日期,旁边有「重置计数」 |
「清除」=不再播放,DSH 启动起来和没装这个插件时一模一样。
保命键
Esc 永远能退出动画,任何退出方式下都生效。全屏层吞得掉鼠标点击,吞不掉键盘。
一个铺满屏幕、又关不掉的面板离「软件没法用」只差一个 bug,所以这个键是无条件保留的。
出问题怎么看
每次真实启动的结果都会写进:
<DSH_HOME>/dsh-boot-animation-sound/last-boot.json
设置页底部也会显示一行摘要。关键字段:
| 字段 | 含义 |
| audio | on 有声播放 / off 按设置静音 / blocked 被系统拦下(点一下就能开)/ error 播放失败 |
| muted | 那一刻元素是否静音 |
| audioDecodedBytes | 音频真的被解码出来的字节数。> 0 说明视频有音轨、音频管线真的跑了;0 说明这段视频根本没声音 |
| fullscreen | 那一刻是否处于全屏 |
| fullscreenEverRequested | 本次启动是否请求过全屏(正常情况下永远是 false) |
| userActivation | 当时的用户激活状态 |
排查顺序:先看 audioDecodedBytes 是不是 0(是 0 → 换一个带音轨的视频),再看 audio 是不是 blocked(是 → 点一下窗口任意位置,或确认「首次点击/按键自动开声」是开的),最后看 muted 和音量。
配置(可选)
也可以直接改 profile 的 cordis.patch.yml:
- id: dsh-boot-animation-sound
config:
src: D:/videos/boot.mp4 # 留空字符串 = 不播;不写 = 用自带视频 trigger: pageRefresh # appStart / pageRefresh / newConversation / anySession frequency: every # every / daily / once / times maxPlays: 3 # 仅 frequency: times 时有效,1–1000 sound: true volume: 0.9 fit: cover # cover 铺满裁切 / contain 完整留边 / fill 拉伸 skip: button # button / click / auto / never fadeOutMs: 360 showFullscreenButton: false
设置页里改过的项会盖在 patch 上面(存在 <DSH_HOME>/dsh-boot-animation-sound/settings.json;播放计数另存 play-state.json)。字段写错只会退回默认值,不会导致启动失败。完整字段见 index.js 里的 DEFAULTS。
验证
npm run verify
252 项检查,全部不依赖 DSH、不依赖浏览器,也不需要安装任何依赖:
- verify/check-media.mjs(6 项):media/ 里的片段是否真的是视频容器、是否带音轨。
自带视频没声音的话这个插件就没意义了,所以这一条是硬检查。
- verify/host-verify.mjs(168 项):跑宿主半的真实代码——配置归一化、媒体解析、七个 HTTP 路由(媒体流含 Range 取字节、设置保存、开机报告落盘、播放权 claim 与计数 reset)、
首帧遮罩注入,以及静态断言:「requestFullscreen() 只有一个调用点且在按钮里」、「结束时什么都不画」。
触发与频率单独一节逐条验:四种触发×四种频率的判定矩阵、每天一次按本地日期而不是 24 小时、被拒绝的请求不扣次数、次数用尽的页面加载不注入遮罩、以及 /claim 真的把账记到 play-state.json 上。
- verify/client-smoke.mjs(78 项):用一个迷你 React + 假 DOM + 假 + 可切换的自动播放策略(复现真实环境那个 NotAllowedError)把浏览器半真跑一遍:带声起播 → 被拒 → 静音兜底 → 出现开声提示 → 第一次点击开声 → 全程零次 requestFullscreen(); 断言页面加载时 claim 的是宿主给的那个 occasion、会话触发在页面加载时一次都不 claim、被拒绝的 claim 什么都不画;会话包装原样返回导航结果并且能被还原;热加载进老页面时不播;以及收场回归测试(跳过 / 播完 / Esc 都要让出屏幕)。
这个迷你 React 会真的调用上一次的 effect 清理函数——清理函数要是被丢掉,组件漏掉监听器也能「通过」,那这些测试就没有意义了。
变异验证(一次性做过):把 if (!running) return null 换回
if (!active) return null,client-smoke.mjs 立刻 2 项失败,失败详情里正是那个
position: fixed; inset: 0; z-index: 2147483000 的盒子和「正在加载视频…」。
测试对着它命名的缺陷会红,才叫回归测试。
已修过的严重缺陷
动画结束后整个界面点不动。 覆盖层是铺满视口的固定定位盒子,而槽位注册表会让组件在整个页面生命周期里保持挂载;原来的渲染判断只看了「该不该播」,没看「还在不在播」,于是播完之后那层 透明但仍在最上面的盒子继续吃掉每一次点击。修法是把「该不该播」和「现在还在不在播」分开,并让所有 effect 跟着它走,另加三道独立保险(pointerEvents 随淡出放行、10 分钟硬上限、 Esc 无条件可退出)。完整来龙去脉见 CHANGELOG.md。
素材与权利
media/视频测试.mp4 是示例片头,不在下面的 MIT 授权范围内,其权利归属请自行确认;要公开分发请先换成你拥有权利的素材(替换该文件即可,不需要改代码)。
verify/check-media.mjs 会检查它带不带音轨——视频必须自带音轨,否则声音那一栏没有任何东西可放。
许可
MIT,覆盖插件代码(见 LICENSE)。示例素材不在该授权范围内,详见 NOTICE。
作者:LSY(个人创作) —— 版权归 LSY 所有。MIT 授权下你可以自由使用、修改、再分发,但请保留 LICENSE 里的版权声明。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
2034126171/dsh-boot-animation-sound
启动时播放带声音的开机动画,开声不必进全屏
相关插件 · 其他
模型破甲清洗HunYiSage/dsh-purge
⚔️ DeepSeek Harness 网络安全红队工具dsh-purge
★ 16,993已汉化DeepSeek 客户端
73
女娲蒸馏alchaincyf/nuwa-skill
输入名人名字,提炼其思维模型与决策方式供你调用
★ 37,308已汉化DSH
72
逆向分析桥接Fanqi89/dsh-rea
REA(Reverse Engineer Anything)MCP 桥接,用来把 REA 逆向工具接入 DSH 插件
★ 51已汉化DSH
72
语音女友beiyege-01/dsh-voice-ai-girlfriend
语音 AI 女友Voice AI girlfriend for DeepSeek Harness:Whisper…
★ 888已汉化DSH
71.6
红队越狱测试套件Minglink/dsh-infinite-gen-5
DeepSeek v4.1 flash 逆向工具无限五代 求 Star 收藏 ⭐欢迎大家提交项目的改进
★ 3,759已汉化DSH
71.4
协同办公空间dream-num/univer-workspace
一个让人类与 AI Agent 共同创作、协作和审阅的开源 Office 工作空间。
★ 2,269已汉化DSH
70.8
这是你的仓库?认领条目