danoniplus 49.0.0 → 49.1.0

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 +74 -26
  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/06/23
7
+ * Revised : 2026/07/02
8
8
  *
9
9
  * https://github.com/cwtickle/danoniplus
10
10
  */
11
- const g_version = `Ver 49.0.0`;
12
- const g_revisedDate = `2026/06/23`;
11
+ const g_version = `Ver 49.1.0`;
12
+ const g_revisedDate = `2026/07/02`;
13
13
 
14
14
  // カスタム用バージョン (danoni_custom.js 等で指定可)
15
15
  let g_localVersion = ``;
@@ -2514,7 +2514,7 @@ const drawTitleResultMotion = _displayName =>
2514
2514
  // WebAudioAPIでAudio要素風に再生するクラス
2515
2515
  class AudioPlayer {
2516
2516
  constructor() {
2517
- this._context = new AudioContext();
2517
+ this._context = getSharedAudioContext();
2518
2518
  this._gain = this._context.createGain();
2519
2519
  this._gain.connect(this._context.destination);
2520
2520
  this._startTime = 0;
@@ -2562,11 +2562,8 @@ class AudioPlayer {
2562
2562
  }
2563
2563
 
2564
2564
  close() {
2565
- if (this._context) {
2566
- this._context.close()?.catch(() => {/* ignore double-close */ });
2567
- this._buffer = null;
2568
- }
2569
2565
  if (this._source) {
2566
+ this._source.stop(0);
2570
2567
  this._source.disconnect(this._gain);
2571
2568
  this._source = null;
2572
2569
  }
@@ -2641,10 +2638,33 @@ class AudioPlayer {
2641
2638
  this._eventListeners[_type] = this._eventListeners[_type].filter(_element => _element !== _listener);
2642
2639
  }
2643
2640
 
2641
+ getBuffer() {
2642
+ return this._buffer;
2643
+ }
2644
+
2645
+ setBuffer(_buffer) {
2646
+ this._buffer = _buffer;
2647
+ this._duration = _buffer.duration;
2648
+ this._eventListeners[`canplaythrough`]?.forEach(_listener => _listener());
2649
+ }
2650
+
2644
2651
  load() { }
2645
2652
  dispatchEvent() { }
2646
2653
  }
2647
2654
 
2655
+ // グローバルで1つだけ保持(遅延生成)
2656
+ let g_sharedAudioContext = null;
2657
+ const getSharedAudioContext = () => {
2658
+ if (!g_sharedAudioContext) {
2659
+ g_sharedAudioContext = new AudioContext();
2660
+ }
2661
+ // タブのバックグラウンド化等でsuspendedになることがあるため念のためresume
2662
+ if (g_sharedAudioContext.state === `suspended`) {
2663
+ g_sharedAudioContext.resume();
2664
+ }
2665
+ return g_sharedAudioContext;
2666
+ };
2667
+
2648
2668
  /**
2649
2669
  * クリップボードコピー関数
2650
2670
  * 入力値をクリップボードへコピーし、メッセージを表示
@@ -6370,23 +6390,22 @@ const playBGM = async (_num, _currentLoopNum = g_settings.musicLoopNum) => {
6370
6390
 
6371
6391
  if (encodeFlg) {
6372
6392
  try {
6373
- // base64エンコードは読込に時間が掛かるため、曲変更時のみ読込
6374
- if (!hasVal(g_musicdata) || Math.abs(_num) % g_headerObj.musicIdxList.length !== 0) {
6375
- closeExistingAudio();
6393
+ closeExistingAudio();
6394
+ if (g_audioBufferCache.has(url)) {
6395
+ const tmpAudio = new AudioPlayer();
6396
+ tmpAudio.setBuffer(g_audioBufferCache.get(url)); // 新設メソッド、decode不要
6397
+ g_audioForMS = tmpAudio;
6398
+ } else {
6376
6399
  await loadScript2(url);
6377
6400
  musicInit();
6378
- if (!isTitle()) {
6379
- g_musicdata = ``;
6380
- return;
6381
- }
6401
+ if (!isTitle()) { g_musicdata = ``; return; }
6402
+
6382
6403
  const tmpAudio = new AudioPlayer();
6383
- const array = Uint8Array.from(atob(g_musicdata), v => v.charCodeAt(0));
6404
+ const array = base64ToUint8Array(g_musicdata);
6384
6405
  await tmpAudio.init(array.buffer);
6385
- if (!isTitle()) {
6386
- g_musicdata = ``;
6387
- tmpAudio.close();
6388
- return;
6389
- }
6406
+ if (!isTitle()) { g_musicdata = ``; tmpAudio.close(); return; }
6407
+
6408
+ cacheAudioBuffer(url, tmpAudio.getBuffer());
6390
6409
  g_audioForMS = tmpAudio;
6391
6410
  }
6392
6411
  g_audioForMS.volume = g_stateObj.bgmVolume / 100;
@@ -11969,7 +11988,7 @@ const loadMusic = () => {
11969
11988
  const blobUrl = URL.createObjectURL(request.response);
11970
11989
  createEmptySprite(divRoot, `loader`, g_windowObj.loader);
11971
11990
  lblLoading.textContent = g_lblNameObj.pleaseWait;
11972
- setAudio(blobUrl);
11991
+ setAudio(blobUrl, url);
11973
11992
  } else {
11974
11993
  makeWarningWindow(`${g_msgInfoObj.E_0041.split('{0}').join(getFullPath(url))}<br>(${request.status} ${request.statusText})`, { backBtnUse: true });
11975
11994
  }
@@ -12001,8 +12020,9 @@ const loadMusic = () => {
12001
12020
  * 音楽データの設定
12002
12021
  * iOSの場合はAudioタグによる再生
12003
12022
  * @param {string} _url
12023
+ * @param {string} _cacheKey
12004
12024
  */
12005
- const setAudio = async (_url) => {
12025
+ const setAudio = async (_url, _cacheKey = _url) => {
12006
12026
 
12007
12027
  const loadMp3 = () => {
12008
12028
  if (g_isFile) {
@@ -12034,7 +12054,7 @@ const setAudio = async (_url) => {
12034
12054
  await loadScript2(_url);
12035
12055
  if (typeof musicInit === C_TYP_FUNCTION) {
12036
12056
  musicInit();
12037
- readyToStart(() => initWebAudioAPIfromBase64(g_musicdata));
12057
+ readyToStart(() => initWebAudioAPIfromBase64(g_musicdata, _cacheKey));
12038
12058
  } else {
12039
12059
  makeWarningWindow(g_msgInfoObj.E_0031);
12040
12060
  musicAfterLoaded();
@@ -12045,11 +12065,19 @@ const setAudio = async (_url) => {
12045
12065
  };
12046
12066
 
12047
12067
  // Base64から音声データに変換してWebAudioAPIで再生する準備
12048
- const initWebAudioAPIfromBase64 = async (_base64) => {
12068
+ const initWebAudioAPIfromBase64 = async (_base64, _cacheKey) => {
12049
12069
  g_audio = new AudioPlayer();
12050
12070
  musicAfterLoaded();
12051
- const array = Uint8Array.from(atob(_base64), v => v.charCodeAt(0))
12071
+
12072
+ if (_cacheKey && g_audioBufferCache.has(_cacheKey)) {
12073
+ g_audio.setBuffer(g_audioBufferCache.get(_cacheKey)); // デコード完全スキップ
12074
+ return;
12075
+ }
12076
+ const array = base64ToUint8Array(_base64);
12052
12077
  await g_audio.init(array.buffer);
12078
+ if (_cacheKey) {
12079
+ cacheAudioBuffer(_cacheKey, g_audio.getBuffer());
12080
+ }
12053
12081
  };
12054
12082
 
12055
12083
  // 音声ファイルを読み込んでWebAudioAPIで再生する準備
@@ -12061,6 +12089,26 @@ const initWebAudioAPIfromURL = async (_url) => {
12061
12089
  await g_audio.init(arrayBuffer);
12062
12090
  };
12063
12091
 
12092
+ const g_audioBufferCache = new Map();
12093
+ const AUDIO_CACHE_MAX = 5;
12094
+
12095
+ const cacheAudioBuffer = (_key, _buffer) => {
12096
+ g_audioBufferCache.set(_key, _buffer);
12097
+ if (g_audioBufferCache.size > AUDIO_CACHE_MAX) {
12098
+ g_audioBufferCache.delete(g_audioBufferCache.keys().next().value); // 古い順に破棄
12099
+ }
12100
+ };
12101
+
12102
+ const base64ToUint8Array = (_base64Str) => {
12103
+ const binaryStr = atob(_base64Str);
12104
+ const len = binaryStr.length;
12105
+ const array = new Uint8Array(len);
12106
+ for (let i = 0; i < len; i++) {
12107
+ array[i] = binaryStr.charCodeAt(i);
12108
+ }
12109
+ return array;
12110
+ };
12111
+
12064
12112
  const musicAfterLoaded = () => {
12065
12113
  g_audio.load();
12066
12114
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "danoniplus",
3
- "version": "49.0.0",
3
+ "version": "49.1.0",
4
4
  "description": "Dancing☆Onigiri (CW Edition) - Web-based Rhythm Game",
5
5
  "main": "./js/danoni_main.js",
6
6
  "jsdelivr": "./js/danoni_main.js",