danoniplus 41.4.13 → 41.4.15

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 +237 -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 : 2026/01/24
7
+ * Revised : 2026/01/26
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 41.4.13`;
12
- const g_revisedDate = `2026/01/24`;
11
+ const g_version = `Ver 41.4.15`;
12
+ const g_revisedDate = `2026/01/26`;
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 = null;
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,9 @@ 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
+ if (g_audioForMS) {
5064
+ g_audioForMS.volume = g_stateObj.bgmVolume / 100;
5065
+ }
5035
5066
  btnBgmVolume.textContent = `${g_stateObj.bgmVolume}${g_lblNameObj.percent}`;
5036
5067
  };
5037
5068
 
@@ -5047,7 +5078,6 @@ const titleInit = (_initFlg = false) => {
5047
5078
  createCss2Button(`btnStart`,
5048
5079
  `>`, () => {
5049
5080
  clearTimeout(g_timeoutEvtTitleId);
5050
- pauseBGM();
5051
5081
  g_handler.removeListener(wheelHandler);
5052
5082
  g_keyObj.prevKey = `Dummy${g_settings.musicIdxNum}`;
5053
5083
  g_langStorage.bgmVolume = g_stateObj.bgmVolume;
@@ -5108,7 +5138,9 @@ const titleInit = (_initFlg = false) => {
5108
5138
 
5109
5139
  // 初期表示用 (2秒後に選曲画面を表示)
5110
5140
  if (_initFlg && !g_headerObj.customTitleUse) {
5111
- g_audio.muted = true;
5141
+ if (g_audioForMS) {
5142
+ g_audioForMS.muted = true;
5143
+ }
5112
5144
  const mSelectTitleSprite = createEmptySprite(divRoot, `mSelectTitleSprite`,
5113
5145
  g_windowObj.mSelectTitleSprite, g_cssObj.settings_DifSelector);
5114
5146
  multiAppend(mSelectTitleSprite,
@@ -5127,8 +5159,15 @@ const titleInit = (_initFlg = false) => {
5127
5159
  if (_opacity <= 0) {
5128
5160
  clearTimeout(fadeOpacity);
5129
5161
  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;
5162
+ if (!g_stateObj.bgmMuteFlg && g_audioForMS) {
5163
+ g_audioForMS.muted = false;
5164
+ g_audioForMS.currentTime = g_headerObj.musicStarts[g_headerObj.musicIdxList[g_settings.musicIdxNum]] ?? 0;
5165
+ if (g_audioForMS instanceof AudioPlayer) {
5166
+ // AudioPlayerはシークを適用するために再起動が必要
5167
+ g_audioForMS.pause();
5168
+ g_audioForMS.play();
5169
+ }
5170
+ }
5132
5171
  } else {
5133
5172
  mSelectTitleSprite.style.opacity = _opacity;
5134
5173
  fadeOpacity = setTimeout(() => {
@@ -5421,17 +5460,17 @@ const getCreatorInfo = (_creatorList) => {
5421
5460
  * BGMの停止
5422
5461
  */
5423
5462
  const pauseBGM = () => {
5424
- if (g_audio) {
5463
+ if (g_audioForMS) {
5425
5464
  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();
5465
+ g_audioForMS.pause();
5466
+ if (!(g_audioForMS instanceof AudioPlayer)) {
5467
+ g_audioForMS.removeAttribute('src');
5468
+ g_audioForMS.load();
5430
5469
  }
5431
5470
  }
5432
5471
  [`bgmLooped`, `bgmFadeIn`, `bgmFadeOut`].forEach(id => {
5433
5472
  if (g_stateObj[id]) {
5434
- clearInterval(g_stateObj[id]);
5473
+ clearTimeout(g_stateObj[id]);
5435
5474
  g_stateObj[id] = null;
5436
5475
  }
5437
5476
  });
@@ -5453,122 +5492,208 @@ const playBGM = async (_num, _currentLoopNum = g_settings.musicLoopNum) => {
5453
5492
  const encodeFlg = listMatching(musicUrl, [`.js`, `.txt`], { suffix: `$` });
5454
5493
  const musicStart = g_headerObj.musicStarts?.[currentIdx] ?? 0;
5455
5494
  const musicEnd = g_headerObj.musicEnds?.[currentIdx] ?? 0;
5456
- const isTitle = () => g_currentPage === `title`;
5495
+ const isTitle = () => g_currentPage === `title` && _currentLoopNum === g_settings.musicLoopNum;
5496
+
5497
+ /**
5498
+ * 汎用フェード処理
5499
+ * @param {number} startVolume - 開始音量 (0〜1)
5500
+ * @param {number} endVolume - 終了音量 (0〜1)
5501
+ * @param {number} step - 1ステップの増減量
5502
+ * @param {function} onEnd - フェード完了時の処理
5503
+ * @param {function} isValid - フェード継続条件(true: 継続, false: 中断)
5504
+ * @returns {number} timeoutId
5505
+ */
5506
+ const fadeVolume = (startVolume, endVolume, step, onEnd, isValid) => {
5507
+
5508
+ // 開始時点で終了音量とイコールの場合は終了
5509
+ if (startVolume === endVolume || step === 0) {
5510
+ g_audioForMS.volume = endVolume;
5511
+ onEnd(true);
5512
+ return null;
5513
+ }
5514
+
5515
+ let volume = startVolume;
5516
+ g_audioForMS.volume = startVolume;
5517
+
5518
+ const stepFunc = () => {
5519
+ // 継続条件チェック
5520
+ if (!isValid()) {
5521
+ onEnd(false); // 中断
5522
+ return;
5523
+ }
5524
+
5525
+ // 終了判定
5526
+ const reached =
5527
+ (startVolume < endVolume && volume >= endVolume) ||
5528
+ (startVolume > endVolume && volume <= endVolume);
5529
+
5530
+ if (reached) {
5531
+ g_audioForMS.volume = endVolume;
5532
+ onEnd(true); // 正常終了
5533
+ return;
5534
+ }
5535
+
5536
+ // 音量更新
5537
+ volume += step;
5538
+ g_audioForMS.volume = Math.min(Math.max(volume, 0), 1);
5539
+
5540
+ // 次のステップへ
5541
+ setTimeout(stepFunc, FADE_INTERVAL_MS);
5542
+ };
5543
+
5544
+ return setTimeout(stepFunc, FADE_INTERVAL_MS);
5545
+ };
5546
+
5547
+ /**
5548
+ * 汎用ポーリング(監視)処理
5549
+ * @param {function} check - 条件チェック関数(true なら終了)
5550
+ * @param {function} onEnd - 終了時の処理
5551
+ * @param {function} isValid - 継続条件(true: 継続, false: 中断)
5552
+ * @returns {number} timeoutId
5553
+ */
5554
+ const poll = (check, onEnd, isValid) => {
5555
+ const step = () => {
5556
+ // 継続条件チェック
5557
+ if (!isValid()) {
5558
+ onEnd(false); // 中断
5559
+ return;
5560
+ }
5561
+
5562
+ // 条件成立
5563
+ if (check()) {
5564
+ onEnd(true); // 正常終了
5565
+ return;
5566
+ }
5567
+
5568
+ // 次のチェックへ
5569
+ setTimeout(step, FADE_INTERVAL_MS);
5570
+ };
5571
+
5572
+ return setTimeout(step, FADE_INTERVAL_MS);
5573
+ };
5457
5574
 
5458
5575
  /**
5459
5576
  * BGMのフェードアウトとシーク
5460
5577
  */
5461
5578
  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);
5579
+
5580
+ const start = g_audioForMS.volume;
5581
+ const end = 0;
5582
+
5583
+ g_stateObj.bgmFadeOut = fadeVolume(
5584
+ start,
5585
+ end,
5586
+ -FADE_STEP,
5587
+ /* onEnd */
5588
+ (finished) => {
5469
5589
  g_stateObj.bgmFadeOut = null;
5470
- g_audio.pause();
5471
- g_audio.currentTime = musicStart;
5590
+
5591
+ if (!finished) return; // 中断された
5592
+
5593
+ g_audioForMS.pause();
5594
+ g_audioForMS.currentTime = musicStart;
5472
5595
 
5473
5596
  // フェードイン開始
5474
5597
  if (isTitle()) {
5475
5598
  setTimeout(() => {
5476
5599
  fadeIn();
5477
- if (encodeFlg) {
5478
- // base64エンコード時はtimeupdateイベントが発火しないため、
5479
- // setIntervalで時間を取得する
5480
- repeatBGM();
5481
- }
5600
+ if (encodeFlg) repeatBGM();
5482
5601
  }, FADE_DELAY_MS);
5483
5602
  } else {
5484
5603
  pauseBGM();
5485
5604
  }
5486
- }
5487
- }, FADE_INTERVAL_MS);
5488
- g_stateObj.bgmFadeOut = fadeInterval;
5605
+ },
5606
+ /* isValid */
5607
+ () =>
5608
+ isTitle() &&
5609
+ g_stateObj.bgmFadeOut !== null
5610
+ );
5489
5611
  };
5490
5612
 
5491
5613
  /**
5492
5614
  * BGMのフェードイン
5493
5615
  */
5494
5616
  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);
5617
+ if (!(g_audioForMS instanceof AudioPlayer) && !g_audioForMS.src) return;
5618
+
5619
+ const start = 0;
5620
+ const end = g_stateObj.bgmVolume / 100;
5621
+
5622
+ g_audioForMS.volume = 0;
5623
+ g_audioForMS.play();
5624
+ g_stateObj.bgmFadeIn = fadeVolume(
5625
+ start,
5626
+ end,
5627
+ FADE_STEP,
5628
+ /* onEnd */
5629
+ () => {
5506
5630
  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;
5631
+ },
5632
+ /* isValid */
5633
+ () =>
5634
+ isTitle() &&
5635
+ g_stateObj.bgmFadeIn !== null &&
5636
+ currentIdx === g_headerObj.musicIdxList[g_settings.musicIdxNum]
5637
+ );
5517
5638
  };
5518
5639
 
5519
5640
  /**
5520
- * BGMのループ処理
5641
+ * BGMのループ処理 (base64エンコード時用)
5642
+ * - base64エンコード時はtimeupdateイベントが発火しないため、setIntervalで時間を取得する
5521
5643
  */
5522
5644
  const repeatBGM = () => {
5523
- if (encodeFlg) {
5524
- // base64エンコード時はtimeupdateイベントが発火しないため、setIntervalで時間を取得する
5525
- const repeatCheck = setInterval((num = g_settings.musicIdxNum) => {
5645
+ const numAtStart = g_settings.musicIdxNum;
5646
+
5647
+ g_stateObj.bgmLooped = poll(
5648
+ /* check */
5649
+ () => {
5526
5650
  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;
5651
+ return (
5652
+ g_audioForMS.elapsedTime >= musicEnd ||
5653
+ numAtStart !== g_settings.musicIdxNum
5654
+ );
5655
+ } catch {
5656
+ return true; // エラー時は終了扱い
5536
5657
  }
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) {
5658
+ },
5659
+ /* onEnd */
5660
+ (finished) => {
5661
+ g_stateObj.bgmLooped = null;
5662
+ if (finished) {
5543
5663
  fadeOutAndSeek();
5544
5664
  }
5545
- });
5546
- }
5665
+ },
5666
+ /* isValid */
5667
+ () => g_stateObj.bgmLooped !== null
5668
+ );
5547
5669
  };
5548
5670
 
5549
- const musicPlayCheck = () => _currentLoopNum !== g_settings.musicLoopNum || g_currentPage !== `title`;
5550
5671
  if (encodeFlg) {
5551
5672
  try {
5552
5673
  // base64エンコードは読込に時間が掛かるため、曲変更時のみ読込
5553
5674
  if (!hasVal(g_musicdata) || Math.abs(_num) % g_headerObj.musicIdxList.length !== 0) {
5675
+ if (g_audioForMS instanceof AudioPlayer) {
5676
+ g_audioForMS.close();
5677
+ }
5554
5678
  await loadScript2(url);
5555
5679
  musicInit();
5556
- if (musicPlayCheck()) {
5680
+ if (!isTitle()) {
5557
5681
  return;
5558
5682
  }
5559
5683
  const tmpAudio = new AudioPlayer();
5560
5684
  const array = Uint8Array.from(atob(g_musicdata), v => v.charCodeAt(0));
5561
5685
  await tmpAudio.init(array.buffer);
5562
- if (musicPlayCheck()) {
5686
+ if (!isTitle()) {
5563
5687
  tmpAudio.close();
5564
5688
  return;
5565
5689
  }
5566
- g_audio = tmpAudio;
5690
+ g_audioForMS = tmpAudio;
5567
5691
  }
5568
- g_audio.volume = g_stateObj.bgmVolume / 100;
5569
- if (g_currentPage === `title`) {
5570
- g_audio.currentTime = musicStart;
5571
- g_audio.play();
5692
+ g_audioForMS.volume = g_stateObj.bgmVolume / 100;
5693
+ if (g_currentPage === `title` && musicEnd > 0) {
5694
+ g_audioForMS.currentTime = musicStart;
5695
+ g_audioForMS.play();
5696
+ repeatBGM();
5572
5697
  }
5573
5698
  } catch (e) {
5574
5699
  // 音源の読み込みに失敗した場合、エラーを表示
@@ -5576,21 +5701,34 @@ const playBGM = async (_num, _currentLoopNum = g_settings.musicLoopNum) => {
5576
5701
  }
5577
5702
 
5578
5703
  } 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`, () => {
5704
+ // 既存の監視を解除し、AudioPlayer を確実にクローズ
5705
+ if (g_stateObj.bgmTimeupdateEvtId) {
5706
+ g_handler.removeListener(g_stateObj.bgmTimeupdateEvtId);
5707
+ g_stateObj.bgmTimeupdateEvtId = null;
5708
+ }
5709
+ if (g_audioForMS instanceof AudioPlayer) {
5710
+ g_audioForMS.close();
5711
+ }
5712
+ g_audioForMS = new Audio();
5713
+ g_audioForMS.src = url;
5714
+ g_audioForMS.autoplay = false;
5715
+ g_audioForMS.volume = g_stateObj.bgmVolume / 100;
5716
+ const loadedMeta = g_handler.addListener(g_audioForMS, `loadedmetadata`, () => {
5584
5717
  g_handler.removeListener(loadedMeta);
5585
- if (musicPlayCheck()) {
5718
+ if (!isTitle()) {
5586
5719
  return;
5587
5720
  }
5588
- g_audio.currentTime = musicStart;
5589
- g_audio.play();
5721
+ g_audioForMS.currentTime = musicStart;
5722
+ g_audioForMS.play();
5590
5723
  }, { once: true });
5591
- }
5592
- if (musicEnd > 0) {
5593
- repeatBGM();
5724
+
5725
+ if (musicEnd > 0) {
5726
+ g_stateObj.bgmTimeupdateEvtId = g_handler.addListener(g_audioForMS, "timeupdate", () => {
5727
+ if (g_audioForMS.currentTime >= musicEnd) {
5728
+ fadeOutAndSeek();
5729
+ }
5730
+ });
5731
+ }
5594
5732
  }
5595
5733
  };
5596
5734
 
@@ -5788,6 +5926,7 @@ const setWindowStyle = (_text, _bkColor, _textColor, _align = C_ALIGN_LEFT, { _x
5788
5926
 
5789
5927
  const dataMgtInit = () => {
5790
5928
  clearWindow(true);
5929
+ pauseBGM();
5791
5930
  const prevPage = g_currentPage;
5792
5931
  g_currentPage = `dataMgt`;
5793
5932
  let selectedKey = g_keyObj.currentKey;
@@ -6019,6 +6158,7 @@ const dataMgtInit = () => {
6019
6158
 
6020
6159
  const preconditionInit = () => {
6021
6160
  clearWindow(true);
6161
+ pauseBGM();
6022
6162
  const prevPage = g_currentPage;
6023
6163
  g_currentPage = `precondition`;
6024
6164
 
@@ -6186,6 +6326,7 @@ const makePlayButton = _func => createCss2Button(`btnPlay`, g_lblNameObj.b_play,
6186
6326
  const optionInit = () => {
6187
6327
 
6188
6328
  clearWindow(true);
6329
+ pauseBGM();
6189
6330
  const divRoot = document.getElementById(`divRoot`);
6190
6331
  g_currentPage = `option`;
6191
6332
  g_stateObj.filterKeys = ``;
@@ -9206,6 +9347,7 @@ const changeShuffleConfigColor = (_keyCtrlPtn, _vals, _j = -1) => {
9206
9347
  const loadMusic = () => {
9207
9348
 
9208
9349
  clearWindow(true);
9350
+ pauseBGM();
9209
9351
  g_currentPage = `loading`;
9210
9352
 
9211
9353
  const musicUrl = getMusicUrl(g_stateObj.scoreId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "danoniplus",
3
- "version": "41.4.13",
3
+ "version": "41.4.15",
4
4
  "description": "Dancing☆Onigiri (CW Edition) - Web-based Rhythm Game",
5
5
  "main": "./js/danoni_main.js",
6
6
  "scripts": {