dsh-boot-suite 2.16.5 → 2.16.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/lib/assets/builtin-background-snowgirl.png +0 -0
- package/lib/client.js +23 -8
- package/lib/host-startup.js +4 -1
- package/lib/host-wallpaper.js +40 -18
- package/lib/splash.js +27 -5
- package/package.json +1 -1
- package/templates/division-bigfish-codex/assets/egg-bg.png +0 -0
- package/test/smoke-package.mjs +26 -1
- package/test/smoke-wallpaper-client.mjs +1 -1
- package/test/smoke-wallpaper-host.mjs +9 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## [2.16.8] — 2026-11-04
|
|
2
|
+
|
|
3
|
+
- 聊天背景新增第二张内置背景「黑发雪夜战术少女」(`lib/assets/builtin-background-snowgirl.png`):设置 → 聊天背景 → 背景图里会多一张可点选,默认仍用原默认背景,不覆盖用户已选。
|
|
4
|
+
- 聊天背景设置面板:内置背景条目支持多张(`id` / `builtinId`),当前选中项按 id 精确高亮;「当前状态」显示对应内置名称。
|
|
5
|
+
- 掉线彩蛋底图沿用上一版替换后的图片。
|
|
6
|
+
- 版本 2.16.8,构建号 cw-74。
|
|
7
|
+
|
|
8
|
+
## [2.16.7] — 2026-11-04
|
|
9
|
+
|
|
10
|
+
- 新增「视频原声」开关:第一幕、第二幕各一个,可分别决定是否播放视频自带的配音/音乐(默认静音,保持原有行为)。适合把配好音的导入动画当第二幕使用;打开后视频原声与背景音乐同时响,建议只开一个。
|
|
11
|
+
- 未静音视频若被浏览器自动播放策略拦下,会自动退回静音再播,保证画面不会卡住。
|
|
12
|
+
- 掉线彩蛋底图换成「黑发雪夜战术少女」。
|
|
13
|
+
- 版本 2.16.7,构建号 cw-73。
|
|
14
|
+
|
|
15
|
+
## [2.16.6] — 2026-11-04
|
|
16
|
+
|
|
17
|
+
- 修复「第一幕是 iframe 模板(全景大肥鱼-含彩蛋版)时,第二幕永远不播」:`lib/splash.js` 的 iframe 分支此前在模板结束后直接收尾,绕过了 `playSecondTemplate()`;现在 iframe 收尾前先接管第二幕,再走退场转场。
|
|
18
|
+
- 无第二幕时行为不变(直接退场);第二幕的衔接/退场转场仍按设置生效。
|
|
19
|
+
- 版本 2.16.6,构建号 cw-72。
|
|
20
|
+
|
|
1
21
|
## [2.16.5] — 2026-11-04
|
|
2
22
|
|
|
3
23
|
- 下拉菜单加不透明底:皮肤把 `--dsw-alias-bg-layer-2` 定义成半透明,菜单会透出背后的说明文字。现在改用近不透明的 `--dsw-alias-bg-overlay` 双层叠加(≈99%,跟随浅/深主题)+ 轻微背景模糊,选项清晰可读。
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -362,7 +362,8 @@ window.__ModuleLoader__.load({
|
|
|
362
362
|
colorMode: 'color',
|
|
363
363
|
bgmEnabled: false, bgmFile: '', bgmFile2: '', bgmVolume: 0.5, bgmBpm: 100, bgmBeats1: 16, bgmBeats2: 32,
|
|
364
364
|
bgm1Start: 0, bgm2Start: 0, bgm1FadeIn: 1, bgm1FadeOut: 1, bgm2FadeIn: 1, bgm2FadeOut: 2,
|
|
365
|
-
bgmCrossfade: 1200, bgmLoop: true, bgm2Loop: false, bgmDuck: true
|
|
365
|
+
bgmCrossfade: 1200, bgmLoop: true, bgm2Loop: false, bgmDuck: true,
|
|
366
|
+
act1VideoSound: false, act2VideoSound: false
|
|
366
367
|
}
|
|
367
368
|
function num(v, d) {
|
|
368
369
|
var n = Number(v)
|
|
@@ -375,7 +376,7 @@ window.__ModuleLoader__.load({
|
|
|
375
376
|
—— 桌面端走浏览器半侧自举时没有 1,就靠这一条
|
|
376
377
|
3) 下面这个常量:两份都没拿到时的兜底,别让面板显示 undefined
|
|
377
378
|
版本号本身只在 lib/index.js 里读一次,这里不重复维护。 */
|
|
378
|
-
var FALLBACK_VERSION = '2.16.
|
|
379
|
+
var FALLBACK_VERSION = '2.16.8'
|
|
379
380
|
/* 手动选包成功后置 true:让还在后台跑的“浏览器下载监听”不要再弹第二次更新器 */
|
|
380
381
|
var __manualUpdateApplied = false
|
|
381
382
|
function readInjectedVersion() {
|
|
@@ -1266,6 +1267,12 @@ window.__ModuleLoader__.load({
|
|
|
1266
1267
|
}, '删除该动画')
|
|
1267
1268
|
)
|
|
1268
1269
|
),
|
|
1270
|
+
h(Field, {
|
|
1271
|
+
label: '第一幕视频原声',
|
|
1272
|
+
hint: '第一幕使用视频/导入动画时,是否播放视频自带的声音;默认静音。'
|
|
1273
|
+
},
|
|
1274
|
+
h(Check, { checked: draft.act1VideoSound === true, onChange: (v) => set('act1VideoSound', v), label: '播放第一幕视频原声' })
|
|
1275
|
+
),
|
|
1269
1276
|
h(Field, {
|
|
1270
1277
|
label: '启动动画第二幕',
|
|
1271
1278
|
hint: '第一幕播完接着播这一段;选「不播放」就第一幕结束直接进聊天。'
|
|
@@ -1294,6 +1301,12 @@ window.__ModuleLoader__.load({
|
|
|
1294
1301
|
}, '删除该动画')
|
|
1295
1302
|
)
|
|
1296
1303
|
),
|
|
1304
|
+
h(Field, {
|
|
1305
|
+
label: '第二幕视频原声',
|
|
1306
|
+
hint: '第二幕视频里自带配音/音乐时打开;默认静音。打开后会和背景音乐同时响。'
|
|
1307
|
+
},
|
|
1308
|
+
h(Check, { checked: draft.act2VideoSound === true, onChange: (v) => set('act2VideoSound', v), label: '播放第二幕视频原声' })
|
|
1309
|
+
),
|
|
1297
1310
|
h('div', { className: '__ss_row' },
|
|
1298
1311
|
h('button', {
|
|
1299
1312
|
className: '__ss_btn',
|
|
@@ -2128,7 +2141,7 @@ window.__ModuleLoader__.load({
|
|
|
2128
2141
|
/* 每次改客户端就把这个号 +1。
|
|
2129
2142
|
有了它在控制台一眼就能判断"浏览器里跑的是不是最新那份" ——
|
|
2130
2143
|
排查问题时,分不清是代码问题还是旧包,会白猜一轮。 */
|
|
2131
|
-
var BUILD = 'cw-
|
|
2144
|
+
var BUILD = 'cw-74'
|
|
2132
2145
|
|
|
2133
2146
|
function log() {
|
|
2134
2147
|
try {
|
|
@@ -3216,6 +3229,8 @@ window.__ModuleLoader__.load({
|
|
|
3216
3229
|
images: images.images || [],
|
|
3217
3230
|
current: images.current || null,
|
|
3218
3231
|
imageSource: images.imageSource || '',
|
|
3232
|
+
builtinId: images.builtinId || '',
|
|
3233
|
+
builtinName: images.builtinName || '',
|
|
3219
3234
|
dropDir: images.dropDir || '',
|
|
3220
3235
|
dropDirExists: !!images.dropDirExists,
|
|
3221
3236
|
veil: images.veil || { light: 0.8, dark: 0.5 },
|
|
@@ -3318,14 +3333,14 @@ window.__ModuleLoader__.load({
|
|
|
3318
3333
|
|
|
3319
3334
|
var thumb = function (im) {
|
|
3320
3335
|
var selected = im.builtin
|
|
3321
|
-
? data.imageSource === 'builtin'
|
|
3336
|
+
? (im.builtinId ? data.builtinId === im.builtinId : (im.selected != null ? im.selected : data.imageSource === 'builtin'))
|
|
3322
3337
|
: (data.current && data.current.split(/[\\/]/).pop() === im.name)
|
|
3323
3338
|
return cwH('button', {
|
|
3324
3339
|
key: im.name,
|
|
3325
3340
|
type: 'button',
|
|
3326
|
-
title: im.builtin ? '
|
|
3341
|
+
title: im.builtin ? ('内置背景:' + im.name + '(随插件安装;删不掉)') : (im.name + ' · ' + im.sizeKB + ' KB · ' + im.mtime),
|
|
3327
3342
|
disabled: busy,
|
|
3328
|
-
onClick: function () { apply({ image: im.builtin ? BUILTIN_IMAGE_ID : im.name }, true) },
|
|
3343
|
+
onClick: function () { apply({ image: im.builtin ? (im.builtinId || BUILTIN_IMAGE_ID) : im.name }, true) },
|
|
3329
3344
|
style: {
|
|
3330
3345
|
position: 'relative', padding: 0, border: '2px solid ' + (selected ? 'var(--dsw-alias-state-business-primary,#3964fe)' : 'var(--dsw-alias-border-l2)'),
|
|
3331
3346
|
borderRadius: 8, background: 'var(--dsw-alias-bg-layer-3)', cursor: 'pointer', overflow: 'hidden', width: 132, height: 76
|
|
@@ -3343,7 +3358,7 @@ window.__ModuleLoader__.load({
|
|
|
3343
3358
|
cwH('div', { className: '__ss_groupTitle' }, '当前状态'),
|
|
3344
3359
|
cwH('div', { className: '__ss_hint' },
|
|
3345
3360
|
'背景图:' + (data.imageSource === 'builtin'
|
|
3346
|
-
? '默认背景(内置)'
|
|
3361
|
+
? (data.builtinName || '默认背景(内置)')
|
|
3347
3362
|
: (data.image ? data.image.split(/[\\/]/).pop() : '(没有背景图,使用 DSH 默认底色)')),
|
|
3348
3363
|
cwH('br'),
|
|
3349
3364
|
'循环视频:' + (data.video && data.video.items.length ? ('已扫描 ' + data.video.items.length + ' 段' + (data.video.bgMode === 'video' && data.video.currentId ? ',正在播放' : ',未启用')) : '(视频目录里还没有视频)'),
|
|
@@ -3367,7 +3382,7 @@ window.__ModuleLoader__.load({
|
|
|
3367
3382
|
|
|
3368
3383
|
cwH('div', { className: '__ss_group' },
|
|
3369
3384
|
cwH('div', { className: '__ss_groupTitle' }, '背景图'),
|
|
3370
|
-
cwH('div', { className: '__ss_hint' }, '
|
|
3385
|
+
cwH('div', { className: '__ss_hint' }, '点一下立刻换。前两张是插件自带的内置背景(默认 / 黑发雪夜战术少女),开箱就有;把图丢进上面的目录也会出现在这里,按修改时间取最新那张。'),
|
|
3371
3386
|
data.images.length
|
|
3372
3387
|
? cwH('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } }, data.images.map(thumb))
|
|
3373
3388
|
: cwH('div', { className: '__ss_hint' }, '目录里还没有图。点「打开图片文件夹」,把 jpg / png / webp 丢进去,再点刷新。只扫描这一层,子文件夹里的不算(支持 jpg/png/webp/gif/avif/bmp)。'),
|
package/lib/host-startup.js
CHANGED
|
@@ -115,6 +115,9 @@ const DEFAULTS = {
|
|
|
115
115
|
glitchB: '#2971b8',
|
|
116
116
|
template: 'archive-warm', // 第一幕默认:档案暖白
|
|
117
117
|
act2: 'whale-girl-scan', // 第二幕默认:鲸鱼娘扫描睁眼(2K);另有 whale-girl-scan-4k;'' = 不播
|
|
118
|
+
/* v2.16.7:视频原声(第一幕 / 第二幕分开控制;默认静音) */
|
|
119
|
+
act1VideoSound: false,
|
|
120
|
+
act2VideoSound: false,
|
|
118
121
|
/* v2.12.0 三段转场:第一幕入场 / 两幕衔接 / 第二幕退场。默认全部交叉叠化。 */
|
|
119
122
|
transitionIn: 'dissolve',
|
|
120
123
|
transitionJoin: 'dissolve',
|
|
@@ -166,7 +169,7 @@ const ENUMS = {
|
|
|
166
169
|
transitionOut: TRANSITION_TYPES,
|
|
167
170
|
transitionDir: TRANSITION_DIRS
|
|
168
171
|
}
|
|
169
|
-
const BOOL_KEYS = ['enabled', 'requireInteraction', 'allowSkip', 'sound', 'bedNoise', 'voice', 'voiceCue', 'bgmEnabled', 'bgmLoop', 'bgm2Loop', 'bgmDuck']
|
|
172
|
+
const BOOL_KEYS = ['enabled', 'requireInteraction', 'allowSkip', 'sound', 'bedNoise', 'voice', 'voiceCue', 'bgmEnabled', 'bgmLoop', 'bgm2Loop', 'bgmDuck', 'act1VideoSound', 'act2VideoSound']
|
|
170
173
|
/** 数值字段的合法区间(越界即夹取)。 */
|
|
171
174
|
const RANGES = {
|
|
172
175
|
welcomeDelay: [0, 3000],
|
package/lib/host-wallpaper.js
CHANGED
|
@@ -21,13 +21,26 @@ const HOME = process.env.DSH_HOME ?? join(homedir(), '.dsh')
|
|
|
21
21
|
const CONFIG_FILE = join(HOME, 'chat-wallpaper.json')
|
|
22
22
|
|
|
23
23
|
/* v2.13.1:插件自带一张默认背景图(随安装包上传,新装用户开箱就有底图)。
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
v2.16.8:新增第二张内置背景(可选,默认仍是第一张);两张都随包分发、删不掉。
|
|
25
|
+
哨兵值写进 config.image:
|
|
26
|
+
__default__ = 明确选内置默认图(用户自己的图不参与自动挑选)
|
|
27
|
+
__snowgirl__ = 明确选内置第二张「黑发雪夜战术少女」
|
|
28
|
+
__none__ = 明确不要背景图(否则空字符串在"丢图目录里有图"时会自动挑最新那张) */
|
|
29
|
+
const ASSETS_DIR = join(dirname(fileURLToPath(import.meta.url)), 'assets')
|
|
30
|
+
const BUILTIN_IMAGES = [
|
|
31
|
+
{ id: '__default__', name: '默认背景(内置)', file: join(ASSETS_DIR, 'default-background.jpg') },
|
|
32
|
+
{ id: '__snowgirl__', name: '黑发雪夜战术少女(内置)', file: join(ASSETS_DIR, 'builtin-background-snowgirl.png') },
|
|
33
|
+
]
|
|
34
|
+
const BUILTIN_IMAGE = BUILTIN_IMAGES[0].file
|
|
35
|
+
const BUILTIN_IMAGE_ID = BUILTIN_IMAGES[0].id
|
|
29
36
|
const NO_IMAGE_ID = '__none__'
|
|
30
37
|
|
|
38
|
+
/** 按哨兵 id 找内置图 */
|
|
39
|
+
function builtinById(id) {
|
|
40
|
+
if (!id) return null
|
|
41
|
+
return BUILTIN_IMAGES.find((b) => b.id === id) || null
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
/** 默认配置。字段与客户端半边一一对应。 */
|
|
32
45
|
const DEFAULTS = {
|
|
33
46
|
/* 聊天底图:往 ~/.dsh/chat-wallpaper/ 里丢张图就自动换背景。
|
|
@@ -158,13 +171,17 @@ function isImageFile(p) {
|
|
|
158
171
|
}
|
|
159
172
|
function resolveImage(cfg) {
|
|
160
173
|
if (cfg.image === NO_IMAGE_ID) return { file: null, source: 'none' }
|
|
161
|
-
|
|
162
|
-
|
|
174
|
+
const picked = builtinById(cfg.image)
|
|
175
|
+
if (picked) {
|
|
176
|
+
return isImageFile(picked.file)
|
|
177
|
+
? { file: picked.file, source: 'builtin', builtinId: picked.id, builtinName: picked.name }
|
|
178
|
+
: { file: null, source: 'none' }
|
|
163
179
|
}
|
|
164
180
|
if (cfg.image && isImageFile(cfg.image)) return { file: cfg.image, source: 'config' }
|
|
165
181
|
const list = listImages()
|
|
166
182
|
if (list.length) return { file: list[0].file, source: 'your-library' }
|
|
167
|
-
|
|
183
|
+
const fallback = BUILTIN_IMAGES[0]
|
|
184
|
+
if (isImageFile(fallback.file)) return { file: fallback.file, source: 'builtin', builtinId: fallback.id, builtinName: fallback.name }
|
|
168
185
|
return { file: null, source: 'none' }
|
|
169
186
|
}
|
|
170
187
|
|
|
@@ -374,12 +391,13 @@ export default {
|
|
|
374
391
|
let file = null
|
|
375
392
|
if (want) {
|
|
376
393
|
const wanted = String(want)
|
|
377
|
-
|
|
378
|
-
|
|
394
|
+
const picked = builtinById(wanted)
|
|
395
|
+
if (picked) {
|
|
396
|
+
if (isImageFile(picked.file)) file = picked.file
|
|
379
397
|
} else {
|
|
380
398
|
const cand = join(wallpaperImageDir(), basename(wanted))
|
|
381
399
|
if (isImageFile(cand)) file = cand
|
|
382
|
-
/* 兼容旧 URL
|
|
400
|
+
/* 兼容旧 URL:只有用户目录没有同名文件时才回落到内置默认图 */
|
|
383
401
|
else if (wanted === 'default-background.jpg' && isImageFile(BUILTIN_IMAGE)) file = BUILTIN_IMAGE
|
|
384
402
|
}
|
|
385
403
|
} else {
|
|
@@ -416,18 +434,22 @@ export default {
|
|
|
416
434
|
dropDirExists: existsSync(dir),
|
|
417
435
|
current: cur.file ? cur.file.replace(/\\/g, '/') : null,
|
|
418
436
|
imageSource: cur.source,
|
|
437
|
+
builtinId: cur.builtinId || '',
|
|
438
|
+
builtinName: cur.builtinName || '',
|
|
419
439
|
imageConfig: cfg.image || '',
|
|
420
440
|
banner: cfg.banner || '',
|
|
421
441
|
bannerOpacity: cfg.bannerOpacity == null ? 0.12 : cfg.bannerOpacity,
|
|
422
442
|
images: [
|
|
423
|
-
...(existsSync(
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
443
|
+
...BUILTIN_IMAGES.filter((b) => existsSync(b.file)).map((b) => ({
|
|
444
|
+
id: b.id,
|
|
445
|
+
builtinId: b.id,
|
|
446
|
+
name: b.name,
|
|
447
|
+
url: '/chat-wallpaper/image?name=' + encodeURIComponent(b.id),
|
|
448
|
+
sizeKB: Math.round(statSync(b.file).size / 1024),
|
|
427
449
|
mtime: '',
|
|
428
450
|
builtin: true,
|
|
429
|
-
selected: cur.source === 'builtin',
|
|
430
|
-
}
|
|
451
|
+
selected: cur.source === 'builtin' && cur.builtinId === b.id,
|
|
452
|
+
})),
|
|
431
453
|
...listImages().map((c) => {
|
|
432
454
|
const name = c.file.split(/[\\/]/).pop()
|
|
433
455
|
return {
|
|
@@ -503,7 +525,7 @@ export default {
|
|
|
503
525
|
if (k === 'image') {
|
|
504
526
|
/* 只允许:空字符串(自动/内置默认)、两个哨兵、丢图目录里的文件名。
|
|
505
527
|
不接受任意绝对路径(避免成为本地任意文件读取原语)。 */
|
|
506
|
-
if (v === '' || v === NO_IMAGE_ID || v
|
|
528
|
+
if (v === '' || v === NO_IMAGE_ID || builtinById(String(v))) {
|
|
507
529
|
cfg.image = String(v || '')
|
|
508
530
|
applied.image = cfg.image
|
|
509
531
|
continue
|
package/lib/splash.js
CHANGED
|
@@ -489,6 +489,8 @@
|
|
|
489
489
|
}
|
|
490
490
|
|
|
491
491
|
var C = cfg()
|
|
492
|
+
/* v2.16.7:当前正在构建哪一幕的时间线(1=第一幕,2=第二幕),用于视频原声开关 */
|
|
493
|
+
var TIMELINE_ACT = 1
|
|
492
494
|
|
|
493
495
|
/* 模板的配色。两条规矩:
|
|
494
496
|
1) 用户手动挑过的颜色优先 —— 只有配置还是默认值时才让模板改;
|
|
@@ -1982,7 +1984,7 @@
|
|
|
1982
1984
|
- 同源时顺手把作者编辑器侧栏藏掉,只留动画舞台。 */
|
|
1983
1985
|
var iframeCleanup = null
|
|
1984
1986
|
|
|
1985
|
-
function runIframeTemplate(tpl) {
|
|
1987
|
+
function runIframeTemplate(tpl, afterIframe) {
|
|
1986
1988
|
diagRun('iframe template ' + tpl.id)
|
|
1987
1989
|
var entry = String(tpl.entry || 'index.html').replace(/^\/+/, '')
|
|
1988
1990
|
var wrap = document.createElement('div')
|
|
@@ -2052,6 +2054,9 @@
|
|
|
2052
2054
|
finishOnce()
|
|
2053
2055
|
}
|
|
2054
2056
|
return done.then(function () {
|
|
2057
|
+
/* v2.16.6:第一幕是 iframe 模板时,收尾前先把控制权交给调用方(接第二幕);
|
|
2058
|
+
没有 afterIframe 时保持原行为——直接走退场转场。 */
|
|
2059
|
+
if (typeof afterIframe === 'function') return afterIframe()
|
|
2055
2060
|
/* v2.15.11:模板不再自己做白闪;结尾由插件退场转场接管。
|
|
2056
2061
|
这里不提前卸 iframe / 停 BGM,交给被转场盖住的 finish() 统一清理。 */
|
|
2057
2062
|
return transOut(transType('Out'), finish)
|
|
@@ -2064,7 +2069,15 @@
|
|
|
2064
2069
|
if (TEMPLATE && TEMPLATE.engine === 'iframe' && TEMPLATE.id) {
|
|
2065
2070
|
/* v2.15.8:iframe 模板也支持用户自备 BGM(父页面播,模板自己演) */
|
|
2066
2071
|
try { bgm.start() } catch (eBgmIframe) { /* 静默失败,绝不阻塞启动 */ }
|
|
2067
|
-
|
|
2072
|
+
/* v2.16.6:iframe 第一幕结束后接第二幕(此前直接 finish,配了第二幕也永远不播) */
|
|
2073
|
+
return runIframeTemplate(TEMPLATE, function () {
|
|
2074
|
+
if (iframeCleanup) { try { iframeCleanup() } catch (eCleanIframe) { /* noop */ } iframeCleanup = null }
|
|
2075
|
+
return playSecondTemplate()
|
|
2076
|
+
.then(function () { return secondTransitionDone ? finish() : transOut(transType('Out'), finish) })
|
|
2077
|
+
}).catch(function (errIframe) {
|
|
2078
|
+
try { console.warn('[dsh-startup-screen] iframe+第二幕异常,直接收尾:', errIframe) } catch (e2) {}
|
|
2079
|
+
return finish()
|
|
2080
|
+
})
|
|
2068
2081
|
}
|
|
2069
2082
|
diagRun('run enter timeline=' + (TIMELINE ? 'v2' : 'v1') + ' tpl=' + ((TEMPLATE && TEMPLATE.id) || '?') + ' tpl2=' + ((TEMPLATE2 && TEMPLATE2.id) || 'none'))
|
|
2070
2083
|
audio.bedStart() /* 本地补丁:开机即"开通道",底噪一路铺到揭幕 */
|
|
@@ -2453,8 +2466,10 @@
|
|
|
2453
2466
|
var vnode = document.createElement('video')
|
|
2454
2467
|
vnode.className = 'dsu-tl-layer dsu-tl-video'
|
|
2455
2468
|
vnode.setAttribute('data-layer', String(L.id || ('video-' + i)))
|
|
2456
|
-
|
|
2457
|
-
|
|
2469
|
+
/* v2.16.7:视频原声开关——第一幕用 act1VideoSound,第二幕用 act2VideoSound;默认静音 */
|
|
2470
|
+
var vSoundOn = TIMELINE_ACT === 2 ? (C.act2VideoSound === true) : (C.act1VideoSound === true)
|
|
2471
|
+
vnode.muted = !vSoundOn
|
|
2472
|
+
vnode.defaultMuted = !vSoundOn
|
|
2458
2473
|
vnode.autoplay = true
|
|
2459
2474
|
vnode.playsInline = true
|
|
2460
2475
|
vnode.preload = 'auto'
|
|
@@ -2495,7 +2510,13 @@
|
|
|
2495
2510
|
diagRun('video append ' + vfile + ' vids=' + videoCount())
|
|
2496
2511
|
try {
|
|
2497
2512
|
var vp0 = vnode.play()
|
|
2498
|
-
if (vp0 && vp0.catch) vp0.catch(function () {
|
|
2513
|
+
if (vp0 && vp0.catch) vp0.catch(function (eAudio) {
|
|
2514
|
+
if (!vSoundOn) return
|
|
2515
|
+
/* v2.16.7:未静音自动播放可能被浏览器策略拦下——退回静音再播,保证画面不卡 */
|
|
2516
|
+
try { vnode.muted = true; vnode.defaultMuted = true } catch (eMute) {}
|
|
2517
|
+
try { var vpM = vnode.play(); if (vpM && vpM.catch) vpM.catch(function () {}) } catch (eMute2) {}
|
|
2518
|
+
diagRun('video audio blocked, fallback muted ' + vfile)
|
|
2519
|
+
})
|
|
2499
2520
|
} catch (ePlay) { /* autoplay blocked */ }
|
|
2500
2521
|
if (vnode.requestVideoFrameCallback) {
|
|
2501
2522
|
var vFrames = 0, vT0 = 0
|
|
@@ -3184,6 +3205,7 @@
|
|
|
3184
3205
|
/* 第二幕分支是否已经自己处理过「第二幕→桌面」退场(v2 主链路据此决定要不要再包 transOut) */
|
|
3185
3206
|
var secondTransitionDone = false
|
|
3186
3207
|
function playSecondTemplate() {
|
|
3208
|
+
TIMELINE_ACT = 2
|
|
3187
3209
|
if (secondStarted) { diagRun('second skipped (duplicate)'); return Promise.resolve() }
|
|
3188
3210
|
secondStarted = true
|
|
3189
3211
|
diagRun('second enter tlSwap=' + !!tlSwap)
|
package/package.json
CHANGED
|
Binary file
|
package/test/smoke-package.mjs
CHANGED
|
@@ -85,7 +85,8 @@ for (const f of ['index.html', 'render.html']) {
|
|
|
85
85
|
if (bigfishApp.includes('flashEl')) throw new Error('app.js 仍引用模板白闪 flashEl')
|
|
86
86
|
if (bigfishApp.includes('flashEl')) throw new Error('app.js 仍残留白闪动画')
|
|
87
87
|
if (skipCss.includes('#flash')) throw new Error('style.css 仍残留 #flash 样式')
|
|
88
|
-
if (!/
|
|
88
|
+
if (!/if \(typeof afterIframe === 'function'\) return afterIframe\(\)/.test(splashJs)) throw new Error('iframe 模板收尾未支持第二幕回调')
|
|
89
|
+
if (!/return done\.then\(function \(\) \{[\s\S]{0,420}transOut\(transType\('Out'\), finish\)/.test(splashJs)) throw new Error('iframe 模板收尾未交给插件退场转场')
|
|
89
90
|
/* v2.15.12:结尾等配音时圆环继续转 */
|
|
90
91
|
if (!bigfishApp.includes('const rt=t+waitSec')) throw new Error('结尾圆环未使用连续旋转时钟 rt')
|
|
91
92
|
if (!bigfishApp.includes('rotate(${rt*5})')) throw new Error('外圈未使用 rt 旋转')
|
|
@@ -176,4 +177,28 @@ if (!clientSrc.includes('aria-haspopup')) throw new Error('client.js 自绘下
|
|
|
176
177
|
if (!clientSrc.includes('function CwSelect(')) throw new Error('client.js 聊天背景缺少 CwSelect')
|
|
177
178
|
if (clientSrc.includes("h('select',")) throw new Error('client.js 仍残留原生 select(设置页)')
|
|
178
179
|
if (clientSrc.includes("cwH('select',")) throw new Error('client.js 仍残留原生 select(聊天背景)')
|
|
180
|
+
/* v2.16.6:第一幕是 iframe 模板时也要接第二幕 */
|
|
181
|
+
if (!splashSrc.includes('function runIframeTemplate(tpl, afterIframe)')) throw new Error('splash.js 未给 iframe 模板留第二幕回调')
|
|
182
|
+
if (!splashSrc.includes('runIframeTemplate(TEMPLATE, function () {')) throw new Error('splash.js iframe 首幕未接第二幕')
|
|
183
|
+
if (!splashSrc.includes('afterIframe')) throw new Error('splash.js 缺少 afterIframe 处理')
|
|
184
|
+
/* v2.16.7:视频原声开关 + 掉线彩蛋底图 */
|
|
185
|
+
const hostSrcV2167 = fs.readFileSync(path.join(root, 'lib/host-startup.js'), 'utf8')
|
|
186
|
+
if (!hostSrcV2167.includes('act1VideoSound: false')) throw new Error('host-startup 缺少第一幕视频原声默认值')
|
|
187
|
+
if (!hostSrcV2167.includes('act2VideoSound: false')) throw new Error('host-startup 缺少第二幕视频原声默认值')
|
|
188
|
+
if (!hostSrcV2167.includes("'act1VideoSound'") || !hostSrcV2167.includes("'act2VideoSound'")) throw new Error('host-startup 的 BOOL_KEYS 缺少视频原声开关')
|
|
189
|
+
if (!splashSrc.includes('act2VideoSound === true')) throw new Error('splash.js 缺少第二幕视频原声分支')
|
|
190
|
+
if (!splashSrc.includes('fallback muted')) throw new Error('splash.js 缺少未静音自动播放失败回退')
|
|
191
|
+
if (!clientSrc.includes('第一幕视频原声') || !clientSrc.includes('第二幕视频原声')) throw new Error('client.js 缺少视频原声设置项')
|
|
192
|
+
const eggBg = path.join(root, 'templates/division-bigfish-codex/assets/egg-bg.png')
|
|
193
|
+
if (!fs.existsSync(eggBg)) throw new Error('缺少掉线彩蛋底图 egg-bg.png')
|
|
194
|
+
if (fs.statSync(eggBg).size < 1000000) throw new Error('掉线彩蛋底图疑似未替换(文件过小)')
|
|
195
|
+
/* v2.16.8:聊天背景第二张内置背景(可选,不改变默认) */
|
|
196
|
+
const wallSrcV2168 = fs.readFileSync(path.join(root, 'lib/host-wallpaper.js'), 'utf8')
|
|
197
|
+
if (!wallSrcV2168.includes('BUILTIN_IMAGES')) throw new Error('host-wallpaper 缺少多张内置背景表')
|
|
198
|
+
if (!wallSrcV2168.includes("'__snowgirl__'")) throw new Error('host-wallpaper 缺少第二张内置背景哨兵')
|
|
199
|
+
if (!wallSrcV2168.includes('builtinName')) throw new Error('host-wallpaper 缺少内置背景名称回传')
|
|
200
|
+
if (!clientSrc.includes('builtinName')) throw new Error('client.js 缺少内置背景名称显示')
|
|
201
|
+
const snowBg = path.join(root, 'lib/assets/builtin-background-snowgirl.png')
|
|
202
|
+
if (!fs.existsSync(snowBg)) throw new Error('缺少第二张内置背景图 builtin-background-snowgirl.png')
|
|
203
|
+
if (fs.statSync(snowBg).size < 1000000) throw new Error('第二张内置背景图疑似不完整')
|
|
179
204
|
console.log('ok package.json version=' + pkg.version + ',无 BOM,关键文件齐全(含更新前回滚 checkpoint)');
|
|
@@ -463,7 +463,7 @@ ok(intervals.every((t) => t.cleared) || intervals.length === 0, '旧的 interval
|
|
|
463
463
|
intervals.map((t) => (t.cleared ? 'cleared' : 'live')).join(','))
|
|
464
464
|
|
|
465
465
|
console.log('\n=== ⑦ v2.13.0 测试钩子 / BUILD ===')
|
|
466
|
-
ok(mod.__BUILD === 'cw-
|
|
466
|
+
ok(mod.__BUILD === 'cw-74', '__BUILD = cw-68(排查浏览器跑的是不是新包)', String(mod.__BUILD))
|
|
467
467
|
ok(mod.__VIDEO_ID === 'dsh-cw-video', '__VIDEO_ID = dsh-cw-video', String(mod.__VIDEO_ID))
|
|
468
468
|
ok(typeof mod.__startShuffle === 'function', '__startShuffle 导出')
|
|
469
469
|
ok(typeof mod.__stopShuffle === 'function', '__stopShuffle 导出')
|
|
@@ -196,10 +196,12 @@ try {
|
|
|
196
196
|
sortKeys(r.json))
|
|
197
197
|
check('/images 的 dropDir 指向临时 DSH_HOME',
|
|
198
198
|
r.json && r.json.dropDir.includes(HOME.replace(/\\/g, '/')), r.json && r.json.dropDir)
|
|
199
|
-
check('★ /images
|
|
200
|
-
r.json && Array.isArray(r.json.images) && r.json.images.length ===
|
|
199
|
+
check('★ /images 默认列出两张内置背景(默认图 selected,第二张可选)',
|
|
200
|
+
r.json && Array.isArray(r.json.images) && r.json.images.length === 2 &&
|
|
201
201
|
r.json.images[0].builtin === true && r.json.images[0].selected === true &&
|
|
202
|
-
r.json.images[0].url.includes('__default__')
|
|
202
|
+
r.json.images[0].url.includes('__default__') &&
|
|
203
|
+
r.json.images[1].builtin === true && r.json.images[1].selected === false &&
|
|
204
|
+
r.json.images[1].url.includes('__snowgirl__'),
|
|
203
205
|
JSON.stringify(r.json && r.json.images))
|
|
204
206
|
check('/images.imageSource = builtin、imageConfig 为空',
|
|
205
207
|
r.json && r.json.imageSource === 'builtin' && r.json.imageConfig === '',
|
|
@@ -323,9 +325,10 @@ try {
|
|
|
323
325
|
r.json && r.json.imageSource === 'your-library', r.json && r.json.imageSource)
|
|
324
326
|
|
|
325
327
|
r = await callJson('exact /chat-wallpaper/images')
|
|
326
|
-
check('★ 丢图后 /images 列出
|
|
327
|
-
r.json && Array.isArray(r.json.images) && r.json.images.length ===
|
|
328
|
-
r.json.images[0].builtin === true && r.json.images[1].
|
|
328
|
+
check('★ 丢图后 /images 列出 3 张:两张内置在前、用户的在后',
|
|
329
|
+
r.json && Array.isArray(r.json.images) && r.json.images.length === 3 &&
|
|
330
|
+
r.json.images[0].builtin === true && r.json.images[1].builtin === true &&
|
|
331
|
+
r.json.images[2].name === 'smoke-bg.png' && r.json.images[2].selected === true,
|
|
329
332
|
JSON.stringify(r.json && r.json.images))
|
|
330
333
|
check('丢图后 /images.current 指向它、imageSource = your-library',
|
|
331
334
|
r.json && typeof r.json.current === 'string' && r.json.current.endsWith('smoke-bg.png') &&
|