danoniplus 41.4.12 → 41.4.14

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.
Files changed (2) hide show
  1. package/js/danoni_main.js +231 -95
  2. package/package.json +1 -1
package/js/danoni_main.js CHANGED
@@ -4,12 +4,12 @@
4
4
  *
5
5
  * Source by tickle
6
6
  * Created : 2018/10/08
7
- * Revised : 2025/12/30
7
+ * Revised : 2026/01/25
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 41.4.12`;
12
- const g_revisedDate = `2025/12/30`;
11
+ const g_version = `Ver 41.4.14`;
12
+ const g_revisedDate = `2026/01/25`;
13
13
 
14
14
  // カスタム用バージョン (danoni_custom.js 等で指定可)
15
15
  let g_localVersion = ``;
@@ -223,6 +223,7 @@ const g_wordObj = {
223
223
 
224
224
  // オーディオ設定・タイマー管理
225
225
  let g_audio = new Audio();
226
+ let g_audioForMS = new Audio();
226
227
  let g_timeoutEvtId = 0;
227
228
  let g_timeoutEvtTitleId = 0;
228
229
  let g_timeoutEvtResultId = 0;
@@ -2309,6 +2310,8 @@ class AudioPlayer {
2309
2310
  this._fadeinPosition = 0;
2310
2311
  this._eventListeners = {};
2311
2312
  this.playbackRate = 1;
2313
+ this._muted = false;
2314
+ this._savedVolume = 1;
2312
2315
  }
2313
2316
 
2314
2317
  async init(_arrayBuffer) {
@@ -2371,11 +2374,17 @@ class AudioPlayer {
2371
2374
  }
2372
2375
 
2373
2376
  get volume() {
2374
- return this._gain.gain.value;
2377
+ // ミュート中でも設定されている音量を返す
2378
+ return this._muted ? this._savedVolume : this._gain.gain.value;
2375
2379
  }
2376
2380
 
2377
2381
  set volume(_volume) {
2378
- this._gain.gain.value = _volume;
2382
+ if (this._muted) {
2383
+ // ミュート中でも音量設定は保存
2384
+ this._savedVolume = _volume;
2385
+ } else {
2386
+ this._gain.gain.value = _volume;
2387
+ }
2379
2388
  }
2380
2389
 
2381
2390
  get duration() {
@@ -2390,6 +2399,26 @@ class AudioPlayer {
2390
2399
  }
2391
2400
  }
2392
2401
 
2402
+ get muted() {
2403
+ return this._muted;
2404
+ }
2405
+
2406
+ set muted(_muted) {
2407
+ if (this._muted === _muted) {
2408
+ return;
2409
+ }
2410
+ this._muted = _muted;
2411
+
2412
+ if (_muted) {
2413
+ // ミュート時:現在の音量を保存してゲインを0に
2414
+ this._savedVolume = this._gain.gain.value;
2415
+ this._gain.gain.value = 0;
2416
+ } else {
2417
+ // ミュート解除時:保存した音量を復元
2418
+ this._gain.gain.value = this._savedVolume;
2419
+ }
2420
+ }
2421
+
2393
2422
  addEventListener(_type, _listener) {
2394
2423
  this._eventListeners[_type]?.push(_listener) || (this._eventListeners[_type] = [_listener]);
2395
2424
  }
@@ -5031,7 +5060,7 @@ const titleInit = (_initFlg = false) => {
5031
5060
  const setBGMVolume = (_num = 1) => {
5032
5061
  g_settings.bgmVolumeNum = nextPos(g_settings.bgmVolumeNum, _num, g_settings.volumes.length);
5033
5062
  g_stateObj.bgmVolume = g_settings.volumes[g_settings.bgmVolumeNum];
5034
- g_audio.volume = g_stateObj.bgmVolume / 100;
5063
+ g_audioForMS.volume = g_stateObj.bgmVolume / 100;
5035
5064
  btnBgmVolume.textContent = `${g_stateObj.bgmVolume}${g_lblNameObj.percent}`;
5036
5065
  };
5037
5066
 
@@ -5108,7 +5137,7 @@ const titleInit = (_initFlg = false) => {
5108
5137
 
5109
5138
  // 初期表示用 (2秒後に選曲画面を表示)
5110
5139
  if (_initFlg && !g_headerObj.customTitleUse) {
5111
- g_audio.muted = true;
5140
+ g_audioForMS.muted = true;
5112
5141
  const mSelectTitleSprite = createEmptySprite(divRoot, `mSelectTitleSprite`,
5113
5142
  g_windowObj.mSelectTitleSprite, g_cssObj.settings_DifSelector);
5114
5143
  multiAppend(mSelectTitleSprite,
@@ -5127,8 +5156,15 @@ const titleInit = (_initFlg = false) => {
5127
5156
  if (_opacity <= 0) {
5128
5157
  clearTimeout(fadeOpacity);
5129
5158
  mSelectTitleSprite.style.display = C_DIS_NONE;
5130
- g_audio.muted = false;
5131
- g_audio.currentTime = g_headerObj.musicStarts[g_headerObj.musicIdxList[g_settings.musicIdxNum]] ?? 0;
5159
+ if (!g_stateObj.bgmMuteFlg) {
5160
+ g_audioForMS.muted = false;
5161
+ g_audioForMS.currentTime = g_headerObj.musicStarts[g_headerObj.musicIdxList[g_settings.musicIdxNum]] ?? 0;
5162
+ if (g_audioForMS instanceof AudioPlayer) {
5163
+ // AudioPlayerはシークを適用するために再起動が必要
5164
+ g_audioForMS.pause();
5165
+ g_audioForMS.play();
5166
+ }
5167
+ }
5132
5168
  } else {
5133
5169
  mSelectTitleSprite.style.opacity = _opacity;
5134
5170
  fadeOpacity = setTimeout(() => {
@@ -5421,17 +5457,17 @@ const getCreatorInfo = (_creatorList) => {
5421
5457
  * BGMの停止
5422
5458
  */
5423
5459
  const pauseBGM = () => {
5424
- if (g_audio) {
5460
+ if (g_audioForMS) {
5425
5461
  g_handler.removeListener(g_stateObj.bgmTimeupdateEvtId);
5426
- g_audio.pause();
5427
- if (!(g_audio instanceof AudioPlayer)) {
5428
- g_audio.removeAttribute('src');
5429
- g_audio.load();
5462
+ g_audioForMS.pause();
5463
+ if (!(g_audioForMS instanceof AudioPlayer)) {
5464
+ g_audioForMS.removeAttribute('src');
5465
+ g_audioForMS.load();
5430
5466
  }
5431
5467
  }
5432
5468
  [`bgmLooped`, `bgmFadeIn`, `bgmFadeOut`].forEach(id => {
5433
5469
  if (g_stateObj[id]) {
5434
- clearInterval(g_stateObj[id]);
5470
+ clearTimeout(g_stateObj[id]);
5435
5471
  g_stateObj[id] = null;
5436
5472
  }
5437
5473
  });
@@ -5453,122 +5489,208 @@ const playBGM = async (_num, _currentLoopNum = g_settings.musicLoopNum) => {
5453
5489
  const encodeFlg = listMatching(musicUrl, [`.js`, `.txt`], { suffix: `$` });
5454
5490
  const musicStart = g_headerObj.musicStarts?.[currentIdx] ?? 0;
5455
5491
  const musicEnd = g_headerObj.musicEnds?.[currentIdx] ?? 0;
5456
- const isTitle = () => g_currentPage === `title`;
5492
+ const isTitle = () => g_currentPage === `title` && _currentLoopNum === g_settings.musicLoopNum;
5493
+
5494
+ /**
5495
+ * 汎用フェード処理
5496
+ * @param {number} startVolume - 開始音量 (0〜1)
5497
+ * @param {number} endVolume - 終了音量 (0〜1)
5498
+ * @param {number} step - 1ステップの増減量
5499
+ * @param {function} onEnd - フェード完了時の処理
5500
+ * @param {function} isValid - フェード継続条件(true: 継続, false: 中断)
5501
+ * @returns {number} timeoutId
5502
+ */
5503
+ const fadeVolume = (startVolume, endVolume, step, onEnd, isValid) => {
5504
+
5505
+ // 開始時点で終了音量とイコールの場合は終了
5506
+ if (startVolume === endVolume || step === 0) {
5507
+ g_audioForMS.volume = endVolume;
5508
+ onEnd(true);
5509
+ return null;
5510
+ }
5511
+
5512
+ let volume = startVolume;
5513
+ g_audioForMS.volume = startVolume;
5514
+
5515
+ const stepFunc = () => {
5516
+ // 継続条件チェック
5517
+ if (!isValid()) {
5518
+ onEnd(false); // 中断
5519
+ return;
5520
+ }
5521
+
5522
+ // 終了判定
5523
+ const reached =
5524
+ (startVolume < endVolume && volume >= endVolume) ||
5525
+ (startVolume > endVolume && volume <= endVolume);
5526
+
5527
+ if (reached) {
5528
+ g_audioForMS.volume = endVolume;
5529
+ onEnd(true); // 正常終了
5530
+ return;
5531
+ }
5532
+
5533
+ // 音量更新
5534
+ volume += step;
5535
+ g_audioForMS.volume = Math.min(Math.max(volume, 0), 1);
5536
+
5537
+ // 次のステップへ
5538
+ setTimeout(stepFunc, FADE_INTERVAL_MS);
5539
+ };
5540
+
5541
+ return setTimeout(stepFunc, FADE_INTERVAL_MS);
5542
+ };
5543
+
5544
+ /**
5545
+ * 汎用ポーリング(監視)処理
5546
+ * @param {function} check - 条件チェック関数(true なら終了)
5547
+ * @param {function} onEnd - 終了時の処理
5548
+ * @param {function} isValid - 継続条件(true: 継続, false: 中断)
5549
+ * @returns {number} timeoutId
5550
+ */
5551
+ const poll = (check, onEnd, isValid) => {
5552
+ const step = () => {
5553
+ // 継続条件チェック
5554
+ if (!isValid()) {
5555
+ onEnd(false); // 中断
5556
+ return;
5557
+ }
5558
+
5559
+ // 条件成立
5560
+ if (check()) {
5561
+ onEnd(true); // 正常終了
5562
+ return;
5563
+ }
5564
+
5565
+ // 次のチェックへ
5566
+ setTimeout(step, FADE_INTERVAL_MS);
5567
+ };
5568
+
5569
+ return setTimeout(step, FADE_INTERVAL_MS);
5570
+ };
5457
5571
 
5458
5572
  /**
5459
5573
  * BGMのフェードアウトとシーク
5460
5574
  */
5461
5575
  const fadeOutAndSeek = () => {
5462
- let volume = g_audio.volume;
5463
- const fadeInterval = setInterval(() => {
5464
- if (volume > FADE_STEP && isTitle()) {
5465
- volume -= FADE_STEP;
5466
- g_audio.volume = Math.max(volume, 0);
5467
- } else {
5468
- clearInterval(fadeInterval);
5576
+
5577
+ const start = g_audioForMS.volume;
5578
+ const end = 0;
5579
+
5580
+ g_stateObj.bgmFadeOut = fadeVolume(
5581
+ start,
5582
+ end,
5583
+ -FADE_STEP,
5584
+ /* onEnd */
5585
+ (finished) => {
5469
5586
  g_stateObj.bgmFadeOut = null;
5470
- g_audio.pause();
5471
- g_audio.currentTime = musicStart;
5587
+
5588
+ if (!finished) return; // 中断された
5589
+
5590
+ g_audioForMS.pause();
5591
+ g_audioForMS.currentTime = musicStart;
5472
5592
 
5473
5593
  // フェードイン開始
5474
5594
  if (isTitle()) {
5475
5595
  setTimeout(() => {
5476
5596
  fadeIn();
5477
- if (encodeFlg) {
5478
- // base64エンコード時はtimeupdateイベントが発火しないため、
5479
- // setIntervalで時間を取得する
5480
- repeatBGM();
5481
- }
5597
+ if (encodeFlg) repeatBGM();
5482
5598
  }, FADE_DELAY_MS);
5483
5599
  } else {
5484
5600
  pauseBGM();
5485
5601
  }
5486
- }
5487
- }, FADE_INTERVAL_MS);
5488
- g_stateObj.bgmFadeOut = fadeInterval;
5602
+ },
5603
+ /* isValid */
5604
+ () =>
5605
+ isTitle() &&
5606
+ g_stateObj.bgmFadeOut !== null
5607
+ );
5489
5608
  };
5490
5609
 
5491
5610
  /**
5492
5611
  * BGMのフェードイン
5493
5612
  */
5494
5613
  const fadeIn = () => {
5495
- if (!(g_audio instanceof AudioPlayer) && !g_audio.src) {
5496
- return;
5497
- }
5498
- let volume = 0;
5499
- g_audio.play();
5500
- const fadeInterval = setInterval(() => {
5501
- if (volume < g_stateObj.bgmVolume / 100 && isTitle()) {
5502
- volume += FADE_STEP;
5503
- g_audio.volume = Math.min(volume, 1);
5504
- } else {
5505
- clearInterval(fadeInterval);
5614
+ if (!(g_audioForMS instanceof AudioPlayer) && !g_audioForMS.src) return;
5615
+
5616
+ const start = 0;
5617
+ const end = g_stateObj.bgmVolume / 100;
5618
+
5619
+ g_audioForMS.volume = 0;
5620
+ g_audioForMS.play();
5621
+ g_stateObj.bgmFadeIn = fadeVolume(
5622
+ start,
5623
+ end,
5624
+ FADE_STEP,
5625
+ /* onEnd */
5626
+ () => {
5506
5627
  g_stateObj.bgmFadeIn = null;
5507
- }
5508
-
5509
- // フェードイン中に楽曲が変更された場合は停止
5510
- if (currentIdx !== g_headerObj.musicIdxList[g_settings.musicIdxNum]) {
5511
- pauseBGM();
5512
- clearInterval(fadeInterval);
5513
- g_stateObj.bgmFadeIn = null;
5514
- }
5515
- }, FADE_INTERVAL_MS);
5516
- g_stateObj.bgmFadeIn = fadeInterval;
5628
+ },
5629
+ /* isValid */
5630
+ () =>
5631
+ isTitle() &&
5632
+ g_stateObj.bgmFadeIn !== null &&
5633
+ currentIdx === g_headerObj.musicIdxList[g_settings.musicIdxNum]
5634
+ );
5517
5635
  };
5518
5636
 
5519
5637
  /**
5520
- * BGMのループ処理
5638
+ * BGMのループ処理 (base64エンコード時用)
5639
+ * - base64エンコード時はtimeupdateイベントが発火しないため、setIntervalで時間を取得する
5521
5640
  */
5522
5641
  const repeatBGM = () => {
5523
- if (encodeFlg) {
5524
- // base64エンコード時はtimeupdateイベントが発火しないため、setIntervalで時間を取得する
5525
- const repeatCheck = setInterval((num = g_settings.musicIdxNum) => {
5642
+ const numAtStart = g_settings.musicIdxNum;
5643
+
5644
+ g_stateObj.bgmLooped = poll(
5645
+ /* check */
5646
+ () => {
5526
5647
  try {
5527
- if (((g_audio.elapsedTime >= musicEnd) ||
5528
- num !== g_settings.musicIdxNum) && g_stateObj.bgmLooped !== null) {
5529
- clearInterval(repeatCheck);
5530
- g_stateObj.bgmLooped = null;
5531
- fadeOutAndSeek();
5532
- }
5533
- } catch (e) {
5534
- clearInterval(repeatCheck);
5535
- g_stateObj.bgmLooped = null;
5648
+ return (
5649
+ g_audioForMS.elapsedTime >= musicEnd ||
5650
+ numAtStart !== g_settings.musicIdxNum
5651
+ );
5652
+ } catch {
5653
+ return true; // エラー時は終了扱い
5536
5654
  }
5537
- }, FADE_INTERVAL_MS);
5538
- g_stateObj.bgmLooped = repeatCheck;
5539
-
5540
- } else {
5541
- g_stateObj.bgmTimeupdateEvtId = g_handler.addListener(g_audio, "timeupdate", () => {
5542
- if (g_audio.currentTime >= musicEnd) {
5655
+ },
5656
+ /* onEnd */
5657
+ (finished) => {
5658
+ g_stateObj.bgmLooped = null;
5659
+ if (finished) {
5543
5660
  fadeOutAndSeek();
5544
5661
  }
5545
- });
5546
- }
5662
+ },
5663
+ /* isValid */
5664
+ () => g_stateObj.bgmLooped !== null
5665
+ );
5547
5666
  };
5548
5667
 
5549
- const musicPlayCheck = () => _currentLoopNum !== g_settings.musicLoopNum || g_currentPage !== `title`;
5550
5668
  if (encodeFlg) {
5551
5669
  try {
5552
5670
  // base64エンコードは読込に時間が掛かるため、曲変更時のみ読込
5553
5671
  if (!hasVal(g_musicdata) || Math.abs(_num) % g_headerObj.musicIdxList.length !== 0) {
5672
+ if (g_audioForMS instanceof AudioPlayer) {
5673
+ g_audioForMS.close();
5674
+ }
5554
5675
  await loadScript2(url);
5555
5676
  musicInit();
5556
- if (musicPlayCheck()) {
5677
+ if (!isTitle()) {
5557
5678
  return;
5558
5679
  }
5559
5680
  const tmpAudio = new AudioPlayer();
5560
5681
  const array = Uint8Array.from(atob(g_musicdata), v => v.charCodeAt(0));
5561
5682
  await tmpAudio.init(array.buffer);
5562
- if (musicPlayCheck()) {
5683
+ if (!isTitle()) {
5563
5684
  tmpAudio.close();
5564
5685
  return;
5565
5686
  }
5566
- g_audio = tmpAudio;
5687
+ g_audioForMS = tmpAudio;
5567
5688
  }
5568
- g_audio.volume = g_stateObj.bgmVolume / 100;
5569
- if (g_currentPage === `title`) {
5570
- g_audio.currentTime = musicStart;
5571
- g_audio.play();
5689
+ g_audioForMS.volume = g_stateObj.bgmVolume / 100;
5690
+ if (g_currentPage === `title` && musicEnd > 0) {
5691
+ g_audioForMS.currentTime = musicStart;
5692
+ g_audioForMS.play();
5693
+ repeatBGM();
5572
5694
  }
5573
5695
  } catch (e) {
5574
5696
  // 音源の読み込みに失敗した場合、エラーを表示
@@ -5576,21 +5698,34 @@ const playBGM = async (_num, _currentLoopNum = g_settings.musicLoopNum) => {
5576
5698
  }
5577
5699
 
5578
5700
  } else {
5579
- g_audio = new Audio();
5580
- g_audio.src = url;
5581
- g_audio.autoplay = false;
5582
- g_audio.volume = g_stateObj.bgmVolume / 100;
5583
- const loadedMeta = g_handler.addListener(g_audio, `loadedmetadata`, () => {
5701
+ // 既存の監視を解除し、AudioPlayer を確実にクローズ
5702
+ if (g_stateObj.bgmTimeupdateEvtId) {
5703
+ g_handler.removeListener(g_stateObj.bgmTimeupdateEvtId);
5704
+ g_stateObj.bgmTimeupdateEvtId = null;
5705
+ }
5706
+ if (g_audioForMS instanceof AudioPlayer) {
5707
+ g_audioForMS.close();
5708
+ }
5709
+ g_audioForMS = new Audio();
5710
+ g_audioForMS.src = url;
5711
+ g_audioForMS.autoplay = false;
5712
+ g_audioForMS.volume = g_stateObj.bgmVolume / 100;
5713
+ const loadedMeta = g_handler.addListener(g_audioForMS, `loadedmetadata`, () => {
5584
5714
  g_handler.removeListener(loadedMeta);
5585
- if (musicPlayCheck()) {
5715
+ if (!isTitle()) {
5586
5716
  return;
5587
5717
  }
5588
- g_audio.currentTime = musicStart;
5589
- g_audio.play();
5718
+ g_audioForMS.currentTime = musicStart;
5719
+ g_audioForMS.play();
5590
5720
  }, { once: true });
5591
- }
5592
- if (musicEnd > 0) {
5593
- repeatBGM();
5721
+
5722
+ if (musicEnd > 0) {
5723
+ g_stateObj.bgmTimeupdateEvtId = g_handler.addListener(g_audioForMS, "timeupdate", () => {
5724
+ if (g_audioForMS.currentTime >= musicEnd) {
5725
+ fadeOutAndSeek();
5726
+ }
5727
+ });
5728
+ }
5594
5729
  }
5595
5730
  };
5596
5731
 
@@ -6843,7 +6978,7 @@ const makeHighScore = _scoreId => {
6843
6978
 
6844
6979
  let tweetFrzJdg = ``;
6845
6980
  let tweetMaxCombo = `${g_localStorage.highscores?.[scoreName]?.maxCombo}`;
6846
- if (g_allFrz > 0) {
6981
+ if (sumData(g_detailObj.frzCnt[_scoreId]) > 0) {
6847
6982
  tweetFrzJdg = `${g_localStorage.highscores?.[scoreName]?.kita}-${g_localStorage.highscores?.[scoreName]?.iknai}`;
6848
6983
  tweetMaxCombo += `-${g_localStorage.highscores?.[scoreName]?.fmaxCombo}`;
6849
6984
  }
@@ -9206,6 +9341,7 @@ const changeShuffleConfigColor = (_keyCtrlPtn, _vals, _j = -1) => {
9206
9341
  const loadMusic = () => {
9207
9342
 
9208
9343
  clearWindow(true);
9344
+ g_audioForMS.pause();
9209
9345
  g_currentPage = `loading`;
9210
9346
 
9211
9347
  const musicUrl = getMusicUrl(g_stateObj.scoreId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "danoniplus",
3
- "version": "41.4.12",
3
+ "version": "41.4.14",
4
4
  "description": "Dancing☆Onigiri (CW Edition) - Web-based Rhythm Game",
5
5
  "main": "./js/danoni_main.js",
6
6
  "scripts": {