dsh-boot-suite 2.15.15 → 2.15.17

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 CHANGED
@@ -1,3 +1,14 @@
1
+ ## [2.15.17] — 2026-11-02
2
+
3
+ - **字幕不再提前消失**:每句配音的中文台词与波形改为跟随该句音频的真实播放时钟(Audio 元素的 currentTime / ended),不再按动画时间轴推算;即使主线程卡顿导致音频晚开始,字幕也会和语音同时出现、同时结束。
4
+ - **掉线彩蛋字幕同步**:彩蛋里「警告:特工已离线。」同样跟随 voice-line4 的播放时钟,不会在语音结束前消失。
5
+ - 版本号 2.15.17,构建号 cw-65。
6
+
7
+ ## [2.15.16] — 2026-11-02
8
+
9
+ - **掉线彩蛋不再硬切配音**:如果掉线正好触发在某句配音播放期间,先等这句播完(含中文字幕)再弹出彩蛋,不会出现「语音没说完、字幕先消失」;有 0.9s 兜底超时与动画结束保护。
10
+ - 版本号 2.15.16,构建号 cw-64。
11
+
1
12
  ## [2.15.15] — 2026-11-02
2
13
 
3
14
  - **重连提示音接入插件配音效果**:`voice-reconnect` 现在与其余配音一样走「配音效果」(AI 机械音/机器人/无线电/老电视/低沉),不再是无效果的原声。
package/lib/client.js CHANGED
@@ -269,7 +269,7 @@ window.__ModuleLoader__.load({
269
269
  —— 桌面端走浏览器半侧自举时没有 1,就靠这一条
270
270
  3) 下面这个常量:两份都没拿到时的兜底,别让面板显示 undefined
271
271
  版本号本身只在 lib/index.js 里读一次,这里不重复维护。 */
272
- var FALLBACK_VERSION = '2.15.15'
272
+ var FALLBACK_VERSION = '2.15.17'
273
273
  /* 手动选包成功后置 true:让还在后台跑的“浏览器下载监听”不要再弹第二次更新器 */
274
274
  var __manualUpdateApplied = false
275
275
  function readInjectedVersion() {
@@ -2022,7 +2022,7 @@ window.__ModuleLoader__.load({
2022
2022
  /* 每次改客户端就把这个号 +1。
2023
2023
  有了它在控制台一眼就能判断"浏览器里跑的是不是最新那份" ——
2024
2024
  排查问题时,分不清是代码问题还是旧包,会白猜一轮。 */
2025
- var BUILD = 'cw-63'
2025
+ var BUILD = 'cw-65'
2026
2026
 
2027
2027
  function log() {
2028
2028
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-boot-suite",
3
- "version": "2.15.15",
3
+ "version": "2.15.17",
4
4
  "description": "两幕启动动画(默认档案暖白 + 鲸鱼娘扫描睁眼,2K/4K 可切),支持导入视频/图片、三段转场、11→6 套配音音色与 5 种声音效果、聊天背景/循环视频、内置 iframe 启动模板(全景大肥鱼-含彩蛋版)与更新前自动回滚。",
5
5
  "keywords": [
6
6
  "dsh",
@@ -77,7 +77,19 @@ function render(){
77
77
  vOn=true;
78
78
  }else{
79
79
  const vsched=(window.__VSCHED__&&window.__VSCHED__.length)?window.__VSCHED__:(window.VOICE_SCHED||[]).map(function(s){var atV=(s.at<=9.2)?s.at-3.4:5.8+(s.at-9.2)/1.5;return{key:s.key,at:atV,dur:s.dur};});
80
- for(const s of vsched){if(VT>=s.at-0.02&&VT<s.at+s.dur&&(!window.__DSH_AUDIO_ON__||window.__DSH_ACTIVE_VOICE__===s.key)){sub=(cfg.voice&&cfg.voice.subs&&cfg.voice.subs[s.key])||"";const e=(window.VOICE_ENV||{})[s.key];if(e&&e.length){const vt=VT-s.at;vh=Array.from({length:20},(_,k)=>{const st=Math.max(0,vt-(19-k)*0.055);const fi=st/0.008;const ai=Math.min(e.length-1,Math.floor(fi));const af=fi-Math.floor(fi);const a=((e[ai]||0)*(1-af)+(e[Math.min(e.length-1,ai+1)]||0)*af);return 4+Math.min(360,Math.pow(Math.max(0,a),0.9)*360)});vOn=true;}break;}}
80
+ /* v2.15.17:有配音时,字幕/波形只跟「真正在播的那句」走(音频时钟),
81
+ 避免时间轴窗口结束得比语音早、字幕提前消失。静音模式仍按时间轴显示。 */
82
+ const audioOn=!!window.__DSH_AUDIO_ON__, avNow=window.__DSH_ACTIVE_VOICE__||'', aNow=+window.__DSH_AUDIO_T__||0, aPlaying=!!window.__DSH_AUDIO_PLAYING__;
83
+ for(const s of vsched){
84
+ let vt=null;
85
+ if(audioOn){if(avNow===s.key&&aPlaying)vt=Math.max(0,aNow);}
86
+ else if(VT>=s.at-0.02&&VT<s.at+s.dur)vt=VT-s.at;
87
+ if(vt==null)continue;
88
+ sub=(cfg.voice&&cfg.voice.subs&&cfg.voice.subs[s.key])||"";
89
+ const e=(window.VOICE_ENV||{})[s.key];
90
+ if(e&&e.length){vh=Array.from({length:20},(_,k)=>{const st=Math.max(0,vt-(19-k)*0.055);const fi=st/0.008;const ai=Math.min(e.length-1,Math.floor(fi));const af=fi-Math.floor(fi);const a=((e[ai]||0)*(1-af)+(e[Math.min(e.length-1,ai+1)]||0)*af);return 4+Math.min(360,Math.pow(Math.max(0,a),0.9)*360)});vOn=true;}
91
+ break;
92
+ }
81
93
  }
82
94
  const h=vh||Array(20).fill(4);const voiceVisible=vOn?1:0;renderVoiceRow($('voice'),$('subtitle'),h,sub,voiceVisible);$('seek').value=t;$('time').textContent=(t-(+cfg.startAt||0)).toFixed(2)+' / '+(cfg.duration-(+cfg.startAt||0)).toFixed(2)+' s';
83
95
  }
@@ -118,14 +130,14 @@ $('prevFrame').onclick=()=>{pause();seek(t-1/60)};$('nextFrame').onclick=()=>{pa
118
130
  function bgmSig(on){try{if(parent&&parent!==window)parent.postMessage({type:'dsh-startup-bgm',on:!!on},'*')}catch(e){}}
119
131
  var sched=vc.sched||[[4.6,'orig1',2.65],[8.0,'line1',3.375],[12.8,'line2',3.232],[20.0,'line3',3.300],[25.45,'orig3',2.77]];
120
132
  var eggLineAt=+(vc.eggLineAt!=null?vc.eggLineAt:2.0), eggReplayWait=+(vc.eggReplayWait!=null?vc.eggReplayWait:2.0), line4Dur=+(vc.line4Dur||1.911);
121
- var aud={},played={},activeVoice=null,cueGate=false,cueFailTimer=null;window.__DSH_AUDIO_ON__=!mute;window.__DSH_ACTIVE_VOICE__='';
122
- sched.forEach(function(e){var k=e[1];if(!mute&&vc[k]){var a=new Audio(vc[k]);a.preload='auto';a.volume=voiceVol;a.addEventListener('ended',function(){if(activeVoice===k){activeVoice=null;window.__DSH_ACTIVE_VOICE__='';}voiceDuck(false)});aud[k]=a;played[k]=false;}});
133
+ var aud={},played={},activeVoice=null,cueGate=false,cueFailTimer=null;window.__DSH_AUDIO_ON__=!mute;window.__DSH_ACTIVE_VOICE__='';window.__DSH_AUDIO_T__=-1;window.__DSH_AUDIO_PLAYING__=false;
134
+ sched.forEach(function(e){var k=e[1];if(!mute&&vc[k]){var a=new Audio(vc[k]);a.preload='auto';a.volume=voiceVol;a.addEventListener('ended',function(){if(activeVoice===k){activeVoice=null;window.__DSH_ACTIVE_VOICE__='';}window.__DSH_AUDIO_PLAYING__=false;window.__DSH_AUDIO_T__=-1;voiceDuck(false)});aud[k]=a;played[k]=false;}});
123
135
  if(!mute&&vc.reconnect){var rc=new Audio(vc.reconnect);rc.preload='auto';rc.volume=voiceVol;rc.addEventListener('ended',function(){if(activeVoice==='reconnect'){activeVoice=null;window.__DSH_ACTIVE_VOICE__=''}window.__DSH_CUE__='';window.__DSH_CUE_UNTIL__=0;cueGate=false;voiceDuck(false)});aud.reconnect=rc;played.reconnect=true;}
124
136
  /* v2.15.14:重连提示音只播自己这一行;播完(ended)才解除 cueGate,后续 line2/line3/orig3 才允许开始 */
125
137
  function playReconnectCue(){if(mute||!aud.reconnect)return;cueGate=true;setTimeout(function(){try{attachVoiceFx('reconnect');aud.reconnect.currentTime=0;aud.reconnect.play().catch(function(){})}catch(e){}activeVoice='reconnect';window.__DSH_ACTIVE_VOICE__='';var txt=(cfg.voice&&cfg.voice.subs&&cfg.voice.subs.reconnect)||'';var d=isFinite(aud.reconnect.duration)&&aud.reconnect.duration>0?aud.reconnect.duration:2.82;window.__DSH_CUE__=txt;window.__DSH_CUE_UNTIL__=performance.now()+d*1000+150;voiceDuck(true);if(cueFailTimer)clearTimeout(cueFailTimer);
126
138
  /* 兜底:音频若因自动播放策略被拒,ended 不会触发;到时强制解除门控,避免后续配音全被卡住 */
127
139
  cueFailTimer=setTimeout(function(){if(activeVoice==='reconnect'){activeVoice=null;window.__DSH_ACTIVE_VOICE__='';window.__DSH_CUE__='';window.__DSH_CUE_UNTIL__=0;cueGate=false;voiceDuck(false)}},d*1000+1500)},500)}
128
- if(!mute&&vc.line4){var l4=new Audio(vc.line4);l4.preload='auto';l4.volume=voiceVol;l4.addEventListener('ended',function(){if(activeVoice==='line4'){activeVoice=null;window.__DSH_ACTIVE_VOICE__='';}voiceDuck(false)});aud.line4=l4;played.line4=false;}
140
+ if(!mute&&vc.line4){var l4=new Audio(vc.line4);l4.preload='auto';l4.volume=voiceVol;l4.addEventListener('ended',function(){if(activeVoice==='line4'){activeVoice=null;window.__DSH_ACTIVE_VOICE__='';}window.__DSH_AUDIO_PLAYING__=false;window.__DSH_AUDIO_T__=-1;voiceDuck(false)});aud.line4=l4;played.line4=false;}
129
141
  /* v2.15.5:模板内配音效果(与全局 5 个声音效果同参数);只在宿主注入 voiceFx 时生效。 */
130
142
  var VOICE_FX_PRESETS={'ai-mech':{hp:130,lp:4200,peak:{f:260,g:4,q:1.0},drive:0.15,ring:0},'robot':{hp:180,lp:3200,peak:{f:1500,g:3,q:0.9},drive:0.35,ring:42},'radio':{hp:420,lp:2800,peak:{f:1800,g:5,q:1.2},drive:0.6,ring:0},'oldtv':{hp:220,lp:2200,peak:null,drive:0.45,ring:0},'dark':{hp:90,lp:1200,peak:{f:180,g:5,q:0.8},drive:0.1,ring:0}};
131
143
  var voiceFxMode=String((cfg&&cfg.voiceFx)||'none');
@@ -167,21 +179,25 @@ $('prevFrame').onclick=()=>{pause();seek(t-1/60)};$('nextFrame').onclick=()=>{pa
167
179
  eggSub.setAttribute('font-family',"'DIN Alternate','Bahnschrift','Microsoft YaHei',sans-serif");
168
180
  eggSvg.appendChild(eggRow);eggSvg.appendChild(eggSub);eggEl.appendChild(eggSvg);
169
181
  }
170
- function eggVoice(vt){
182
+ /* v2.15.17:彩蛋里的中文台词改为跟随 voice-line4 的真实播放时钟,不再用墙钟推算。
183
+ 这样即使彩蛋的 setTimeout 被主线程拖慢,字幕也会和语音同时出现、同时结束。 */
184
+ function eggVoice(){
171
185
  ensureEggVoice();
186
+ var a=aud.line4, live=!!(window.__DSH_AUDIO_ON__&&a&&!a.paused&&!a.ended&&window.__DSH_ACTIVE_VOICE__==='line4');
187
+ var vt=live?Math.max(0,+a.currentTime||0):-1;
172
188
  var e=(window.VOICE_ENV||{}).line4||[], vh=Array(20).fill(4);
173
- if(vt>=0&&vt<line4Dur&&e.length){
189
+ if(live&&vt<line4Dur+0.25&&e.length){
174
190
  var fi=vt/0.008, ai=Math.min(e.length-1,Math.floor(fi)), af=fi-Math.floor(fi);
175
191
  var amp=((e[ai]||0)*(1-af)+(e[Math.min(e.length-1,ai+1)]||0)*af);
176
192
  vh=[];for(var k=0;k<20;k++){var tv2=vt-(19-k)*0.06;var f2=tv2/0.008,i2=Math.min(e.length-1,Math.floor(f2));var a2=((e[i2]||0)*(1-(f2-Math.floor(f2)))+(e[Math.min(e.length-1,i2+1)]||0)*(f2-Math.floor(f2)));vh.push(4+Math.min(440,Math.pow(Math.max(0,a2),0.85)*440))}
177
193
  }
178
- var show=(vt>=0&&vt<line4Dur&&window.__DSH_ACTIVE_VOICE__==='line4')?1:0;
194
+ var show=live?1:0;
179
195
  var st=show?((cfg.voice&&cfg.voice.subs&&cfg.voice.subs.line4)||''):'';
180
196
  renderVoiceRow(eggRow,eggSub,vh,st,show);
181
197
  }
182
- var shown=false,done=false,rolled=false,v3Ended=false,waitStart=0,v4Timer=null,replayTimer=null,eggShownAt=0;
198
+ var shown=false,done=false,rolled=false,v3Ended=false,waitStart=0,v4Timer=null,replayTimer=null,eggShownAt=0,eggWait=false,eggWaitTimer=null;
183
199
  var hit=q.get('egg')==='1'||Math.random()<EGG_CHANCE;
184
- function stopVoice(){voiceDuck(false);activeVoice=null;window.__DSH_ACTIVE_VOICE__='';window.__DSH_CUE__='';window.__DSH_CUE_UNTIL__=0;cueGate=false;if(cueFailTimer){clearTimeout(cueFailTimer);cueFailTimer=null}Object.keys(aud).forEach(function(k){var a=aud[k];try{a.pause();a.currentTime=0}catch(e){}});if(v4Timer){clearTimeout(v4Timer);v4Timer=null}if(replayTimer){clearTimeout(replayTimer);replayTimer=null}}
200
+ function stopVoice(){voiceDuck(false);activeVoice=null;window.__DSH_ACTIVE_VOICE__='';window.__DSH_AUDIO_PLAYING__=false;window.__DSH_AUDIO_T__=-1;window.__DSH_CUE__='';window.__DSH_CUE_UNTIL__=0;cueGate=false;if(cueFailTimer){clearTimeout(cueFailTimer);cueFailTimer=null}Object.keys(aud).forEach(function(k){var a=aud[k];try{a.pause();a.currentTime=0}catch(e){}});if(v4Timer){clearTimeout(v4Timer);v4Timer=null}if(replayTimer){clearTimeout(replayTimer);replayTimer=null}}
185
201
  function playKey(k,offset){var a=aud[k];if(a&&!played[k]){played[k]=true;attachVoiceFx(k);try{a.currentTime=Math.max(0,offset||0);a.play().catch(function(){});activeVoice=k;window.__DSH_ACTIVE_VOICE__=k}catch(e){}voiceDuck(true);}}
186
202
  function resetPlayedAfter(t0){
187
203
  Object.keys(played).forEach(function(k){played[k]=false});v3Ended=false;
@@ -193,16 +209,16 @@ $('prevFrame').onclick=()=>{pause();seek(t-1/60)};$('nextFrame').onclick=()=>{pa
193
209
  });
194
210
  }
195
211
  function showEgg(){
196
- if(shown||done)return; shown=true; rolled=true; playing=false;
212
+ if(shown||done)return; shown=true; rolled=true; playing=false;eggWait=false;if(eggWaitTimer){clearTimeout(eggWaitTimer);eggWaitTimer=null}
197
213
  stopVoice();
198
214
  bgmSig(false);
199
215
  eggEl.classList.add('on'); eggEl.setAttribute('aria-hidden','false'); try{document.body.classList.add('egg-on')}catch(e){}
200
- eggShownAt=performance.now(); eggVoice(-1);
216
+ eggShownAt=performance.now(); eggVoice();
201
217
  v4Timer=setTimeout(function(){playKey('line4')},eggLineAt*1000);
202
218
  /* v2.15.6:不再自动 reconnect——彩蛋必须等用户点【重新连接】,否则动画会自己往下走、声音叠在一起。 */
203
219
  render();
204
220
  }
205
- function eggFrame(sec){showEgg();stopVoice();window.__EGG_T=Math.max(0,sec);eggShownAt=performance.now()-window.__EGG_T*1000;if(sec>=eggLineAt)playKey('line4');eggVoice(window.__EGG_T-eggLineAt);}
221
+ function eggFrame(sec){showEgg();stopVoice();window.__EGG_T=Math.max(0,sec);eggShownAt=performance.now()-window.__EGG_T*1000;if(sec>=eggLineAt)playKey('line4');eggVoice();}
206
222
  function hideEgg(){eggEl.classList.remove('on');eggEl.setAttribute('aria-hidden','true');try{document.body.classList.remove('egg-on')}catch(e){}shown=false;}
207
223
  /* v2.15.14:重连后不再续播断句(原来会把 line1 从半句接着放,和 reconnect 叠在一起)。
208
224
  顺序:等待 0.5s → 只播 reconnect(独占一行)→ 播完解除 cueGate → line2/line3/orig3 按时间线继续。 */
@@ -220,9 +236,27 @@ $('prevFrame').onclick=()=>{pause();seek(t-1/60)};$('nextFrame').onclick=()=>{pa
220
236
  if(aud.orig3)aud.orig3.addEventListener('ended',function(){v3Ended=true});
221
237
  (function watch(){
222
238
  if(!done){
239
+ /* v2.15.17:把当前配音的真实播放状态镜像到 window,供 render()/eggVoice() 使用,
240
+ 让字幕与波形以音频时钟为准,而不是按时间轴推算。 */
241
+ var __av=activeVoice?aud[activeVoice]:null;
242
+ if(__av){window.__DSH_AUDIO_T__=+__av.currentTime||0;window.__DSH_AUDIO_DUR__=+__av.duration||0;window.__DSH_AUDIO_PLAYING__=!__av.paused&&!__av.ended;}
223
243
  if(playing&&!cueGate){sched.forEach(function(e){if(t>=e[0])playKey(e[1]);});}
224
- if(!rolled&&playing&&!shown&&t>=eggAt){rolled=true;if(hit)showEgg();}
225
- if(shown){var elap=(window.__EGG_T!=null?window.__EGG_T:(performance.now()-eggShownAt)/1000);eggVoice(elap-eggLineAt);}
244
+ /* v2.15.16:掉线彩蛋若正好赶上某句配音,先等这句播完再弹出,避免语音和字幕被硬切断(有兜底超时)。 */
245
+ if(!rolled&&playing&&!shown&&t>=eggAt){
246
+ if(!hit){rolled=true;}
247
+ else if(!eggWait&&activeVoice&&aud[activeVoice]&&!aud[activeVoice].paused&&!aud[activeVoice].ended){
248
+ var eggA=aud[activeVoice], eggLeft=(+eggA.duration||0)-(+eggA.currentTime||0);
249
+ if(isFinite(eggLeft)&&eggLeft>0.05&&t+eggLeft<=cfg.duration-0.25){
250
+ eggWait=true;
251
+ (function(a){
252
+ var fired=false,fin=function(){if(fired)return;fired=true;eggWait=false;eggWaitTimer=null;rolled=true;showEgg();};
253
+ a.addEventListener('ended',fin,{once:true});
254
+ eggWaitTimer=setTimeout(fin,eggLeft*1000+900);
255
+ })(eggA);
256
+ } else {rolled=true;showEgg();}
257
+ } else if(!eggWait){rolled=true;showEgg();}
258
+ }
259
+ if(shown){eggVoice();}
226
260
  if(!shown&&t>=cfg.duration-0.02){if(!waitStart){waitStart=performance.now();window.__WAIT_START__=waitStart;}var waitVoice=(!mute&&aud.orig3&&!v3Ended&&(performance.now()-waitStart<4000));if(!waitVoice)flashDone();}
227
261
  }
228
262
  requestAnimationFrame(watch);
@@ -115,4 +115,10 @@ if (!bigfishApp.includes('cueFailTimer')) throw new Error('重连提示音缺少
115
115
  if (!bigfishApp.includes("attachVoiceFx('reconnect')")) throw new Error('重连提示音未接入配音效果')
116
116
  if (!bigfishApp.includes("[20.0,'line3',3.300]")) throw new Error('app.js 默认时间线未把 line3 顺延到 20.0s')
117
117
  if (!bigfishCfg.includes("[20.0,'line3',3.300]")) throw new Error('config.js 时间线未把 line3 顺延到 20.0s')
118
+ /* v2.15.16:掉线彩蛋等当前配音播完再弹出,不硬切语音/字幕 */
119
+ if (!bigfishApp.includes('掉线彩蛋若正好赶上某句配音')) throw new Error('app.js 缺少彩蛋等待当前配音播完的逻辑')
120
+ if (!bigfishApp.includes('eggWait=true')) throw new Error('app.js 缺少 eggWait 等待状态')
121
+ /* v2.15.17:字幕/波形跟随配音真实播放时钟 */
122
+ if (!bigfishApp.includes('__DSH_AUDIO_T__')) throw new Error('app.js 缺少配音真实时钟镜像 __DSH_AUDIO_T__')
123
+ if (!bigfishApp.includes('function eggVoice(){')) throw new Error('app.js 彩蛋字幕未改为音频时钟驱动')
118
124
  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-63', '__BUILD = cw-63(排查浏览器跑的是不是新包)', String(mod.__BUILD))
466
+ ok(mod.__BUILD === 'cw-65', '__BUILD = cw-65(排查浏览器跑的是不是新包)', 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 导出')