danoniplus 49.1.0 → 49.2.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.
- package/js/danoni_main.js +288 -159
- package/js/lib/danoni_constants.js +3 -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/07/
|
|
7
|
+
* Revised : 2026/07/07
|
|
8
8
|
*
|
|
9
9
|
* https://github.com/cwtickle/danoniplus
|
|
10
10
|
*/
|
|
11
|
-
const g_version = `Ver 49.
|
|
12
|
-
const g_revisedDate = `2026/07/
|
|
11
|
+
const g_version = `Ver 49.2.0`;
|
|
12
|
+
const g_revisedDate = `2026/07/07`;
|
|
13
13
|
|
|
14
14
|
// カスタム用バージョン (danoni_custom.js 等で指定可)
|
|
15
15
|
let g_localVersion = ``;
|
|
@@ -177,6 +177,10 @@ let g_maxScore = 1000000;
|
|
|
177
177
|
let g_gameOverFlg = false;
|
|
178
178
|
let g_finishFlg = true;
|
|
179
179
|
|
|
180
|
+
// 音源のAudioContext管理、リトライ中フラグ
|
|
181
|
+
let g_sharedAudioContext = null;
|
|
182
|
+
let g_retryInProgress = false;
|
|
183
|
+
|
|
180
184
|
/** 共通オブジェクト */
|
|
181
185
|
const g_loadObj = {};
|
|
182
186
|
const g_rootObj = {};
|
|
@@ -2060,10 +2064,9 @@ const makeBgCanvas = (_ctx, { w = g_sWidth, h = g_sHeight } = {}) => {
|
|
|
2060
2064
|
* - divオブジェクト(ボタンなど)はdivRoot配下で管理しているため、子要素のみを全削除している。
|
|
2061
2065
|
* - dicRoot自体を削除しないよう注意すること。
|
|
2062
2066
|
* - 再描画時に共通で表示する箇所はここで指定している。
|
|
2063
|
-
* @param {boolean} [_redrawFlg=false] 画面横幅を再定義し、Canvas背景を再描画するかどうか
|
|
2064
2067
|
* @param {string} [_customDisplayName=''] 画面名(メイン画面: 'Main', それ以外: 空)
|
|
2065
2068
|
*/
|
|
2066
|
-
const clearWindow = (
|
|
2069
|
+
const clearWindow = (_customDisplayName = ``) => {
|
|
2067
2070
|
closeDisplayPreview();
|
|
2068
2071
|
resetKeyControl();
|
|
2069
2072
|
resetTransform();
|
|
@@ -2110,37 +2113,33 @@ const clearWindow = (_redrawFlg = false, _customDisplayName = ``) => {
|
|
|
2110
2113
|
getLayerWithClear(`layer2`);
|
|
2111
2114
|
}
|
|
2112
2115
|
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
makeBgCanvas(l0ctx, { w: g_sWidth + diffX });
|
|
2119
|
-
}
|
|
2116
|
+
// 画面背景を指定 (background-color)
|
|
2117
|
+
$id(`canvas-frame`).width = wUnit(g_sWidth + diffX);
|
|
2118
|
+
layer0.width = g_sWidth + diffX;
|
|
2119
|
+
if (!g_headerObj[`customBack${_customDisplayName}Use`]) {
|
|
2120
|
+
makeBgCanvas(l0ctx, { w: g_sWidth + diffX });
|
|
2120
2121
|
}
|
|
2121
2122
|
}
|
|
2122
2123
|
|
|
2123
2124
|
// 背景を再描画
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
g_cxtDeleteFlg = {};
|
|
2125
|
+
g_btnAddFunc = {};
|
|
2126
|
+
g_btnDeleteFlg = {};
|
|
2127
|
+
g_cxtAddFunc = {};
|
|
2128
|
+
g_cxtDeleteFlg = {};
|
|
2129
2129
|
|
|
2130
|
-
|
|
2131
|
-
|
|
2130
|
+
if (document.getElementById(`layer0`) === null ||
|
|
2131
|
+
(!g_headerObj[`customBack${_customDisplayName}Use`] && !g_headerObj.defaultSkinFlg)) {
|
|
2132
2132
|
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2133
|
+
$id(`canvas-frame`).width = wUnit(g_sWidth + diffX);
|
|
2134
|
+
createEmptySprite(divRoot, `divBack`, { w: g_sWidth + diffX });
|
|
2135
|
+
}
|
|
2136
2136
|
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2137
|
+
// CSSスタイルの初期化
|
|
2138
|
+
Object.keys(g_cssBkProperties).forEach(prop =>
|
|
2139
|
+
document.documentElement.style.setProperty(prop, g_cssBkProperties[prop]));
|
|
2140
2140
|
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
}
|
|
2141
|
+
Object.keys(g_headerObj).filter(val => val.startsWith(`--`) && hasVal(g_headerObj[val])).forEach(prop =>
|
|
2142
|
+
document.documentElement.style.setProperty(prop, getCssCustomProperty(prop, g_headerObj[prop])));
|
|
2144
2143
|
};
|
|
2145
2144
|
|
|
2146
2145
|
/**
|
|
@@ -2653,7 +2652,6 @@ class AudioPlayer {
|
|
|
2653
2652
|
}
|
|
2654
2653
|
|
|
2655
2654
|
// グローバルで1つだけ保持(遅延生成)
|
|
2656
|
-
let g_sharedAudioContext = null;
|
|
2657
2655
|
const getSharedAudioContext = () => {
|
|
2658
2656
|
if (!g_sharedAudioContext) {
|
|
2659
2657
|
g_sharedAudioContext = new AudioContext();
|
|
@@ -3254,7 +3252,6 @@ const loadChartFile = async (_scoreId = g_stateObj.scoreId) => {
|
|
|
3254
3252
|
|
|
3255
3253
|
await loadScript2(`${filename}?${Date.now()}`, false, charset);
|
|
3256
3254
|
if (typeof externalDosInit === C_TYP_FUNCTION) {
|
|
3257
|
-
deleteDiv(divRoot, `lblLoading`);
|
|
3258
3255
|
|
|
3259
3256
|
// 外部データを読込(ファイルが見つからなかった場合は譜面追記をスキップ)
|
|
3260
3257
|
externalDosInit();
|
|
@@ -5660,7 +5657,7 @@ const setKeyDfVal = _ptnName => {
|
|
|
5660
5657
|
*/
|
|
5661
5658
|
const titleInit = (_initFlg = false) => {
|
|
5662
5659
|
|
|
5663
|
-
clearWindow(
|
|
5660
|
+
clearWindow();
|
|
5664
5661
|
g_currentPage = `title`;
|
|
5665
5662
|
g_stateObj.settingSummaryVisible = false;
|
|
5666
5663
|
|
|
@@ -6391,9 +6388,10 @@ const playBGM = async (_num, _currentLoopNum = g_settings.musicLoopNum) => {
|
|
|
6391
6388
|
if (encodeFlg) {
|
|
6392
6389
|
try {
|
|
6393
6390
|
closeExistingAudio();
|
|
6394
|
-
|
|
6391
|
+
const cachedBuffer = getAudioBufferFromCache(url);
|
|
6392
|
+
if (cachedBuffer !== undefined) {
|
|
6395
6393
|
const tmpAudio = new AudioPlayer();
|
|
6396
|
-
tmpAudio.setBuffer(
|
|
6394
|
+
tmpAudio.setBuffer(cachedBuffer);
|
|
6397
6395
|
g_audioForMS = tmpAudio;
|
|
6398
6396
|
} else {
|
|
6399
6397
|
await loadScript2(url);
|
|
@@ -6668,7 +6666,7 @@ const setWindowStyle = (_text, _bkColor, _textColor, _align = C_ALIGN_LEFT, { _x
|
|
|
6668
6666
|
/*-----------------------------------------------------------*/
|
|
6669
6667
|
|
|
6670
6668
|
const dataMgtInit = () => {
|
|
6671
|
-
clearWindow(
|
|
6669
|
+
clearWindow();
|
|
6672
6670
|
pauseBGM();
|
|
6673
6671
|
const prevPage = g_currentPage;
|
|
6674
6672
|
g_currentPage = `dataMgt`;
|
|
@@ -6906,7 +6904,7 @@ const dataMgtInit = () => {
|
|
|
6906
6904
|
/*-----------------------------------------------------------*/
|
|
6907
6905
|
|
|
6908
6906
|
const preconditionInit = () => {
|
|
6909
|
-
clearWindow(
|
|
6907
|
+
clearWindow();
|
|
6910
6908
|
pauseBGM();
|
|
6911
6909
|
const prevPage = g_currentPage;
|
|
6912
6910
|
g_currentPage = `precondition`;
|
|
@@ -7134,7 +7132,7 @@ const makePlayButton = _func => createCss2Button(`btnPlay`, g_lblNameObj.b_play,
|
|
|
7134
7132
|
*/
|
|
7135
7133
|
const optionInit = () => {
|
|
7136
7134
|
|
|
7137
|
-
clearWindow(
|
|
7135
|
+
clearWindow();
|
|
7138
7136
|
pauseBGM();
|
|
7139
7137
|
const divRoot = document.getElementById(`divRoot`);
|
|
7140
7138
|
g_currentPage = `option`;
|
|
@@ -9212,7 +9210,7 @@ const resetGroupList = (_type, _keyCtrlPtn) => {
|
|
|
9212
9210
|
|
|
9213
9211
|
const settingsDisplayInit = () => {
|
|
9214
9212
|
|
|
9215
|
-
clearWindow(
|
|
9213
|
+
clearWindow();
|
|
9216
9214
|
const divRoot = document.getElementById(`divRoot`);
|
|
9217
9215
|
g_currentPage = `settingsDisplay`;
|
|
9218
9216
|
|
|
@@ -10019,7 +10017,7 @@ const interlockingButton = (_headerObj, _name, _current, _next, _buttonFlg = fal
|
|
|
10019
10017
|
/*-----------------------------------------------------------*/
|
|
10020
10018
|
|
|
10021
10019
|
const exSettingInit = () => {
|
|
10022
|
-
clearWindow(
|
|
10020
|
+
clearWindow();
|
|
10023
10021
|
g_currentPage = `exSetting`;
|
|
10024
10022
|
|
|
10025
10023
|
multiAppend(divRoot,
|
|
@@ -10180,7 +10178,7 @@ const createGeneralSettingEx = (_spriteList, _name, { defaultList = [C_FLG_OFF],
|
|
|
10180
10178
|
*/
|
|
10181
10179
|
const keyConfigInit = (_kcType = g_kcType, _initFlg = false) => {
|
|
10182
10180
|
|
|
10183
|
-
clearWindow(
|
|
10181
|
+
clearWindow();
|
|
10184
10182
|
const divRoot = document.getElementById(`divRoot`);
|
|
10185
10183
|
g_kcType = _kcType;
|
|
10186
10184
|
g_currentPage = `keyConfig`;
|
|
@@ -11956,9 +11954,9 @@ const changeShuffleConfigColor = (_keyCtrlPtn, _vals, _j = -1) => {
|
|
|
11956
11954
|
/* Scene : LOADING [strawberry] */
|
|
11957
11955
|
/*-----------------------------------------------------------*/
|
|
11958
11956
|
|
|
11959
|
-
const loadMusic = () => {
|
|
11957
|
+
const loadMusic = async () => {
|
|
11960
11958
|
|
|
11961
|
-
clearWindow(
|
|
11959
|
+
clearWindow();
|
|
11962
11960
|
pauseBGM();
|
|
11963
11961
|
g_currentPage = `loading`;
|
|
11964
11962
|
|
|
@@ -11971,127 +11969,228 @@ const loadMusic = () => {
|
|
|
11971
11969
|
const lblLoading = getLoadingLabel();
|
|
11972
11970
|
divRoot.appendChild(lblLoading);
|
|
11973
11971
|
|
|
11974
|
-
//
|
|
11975
|
-
|
|
11976
|
-
|
|
11977
|
-
|
|
11972
|
+
// 音源準備Promise(ローカル/オンライン双方を吸収)
|
|
11973
|
+
const audioReadyPromise = loadAndSetupAudio(url, lblLoading);
|
|
11974
|
+
|
|
11975
|
+
// 譜面データ読込・変換処理Promise(g_audioに依存しない部分)
|
|
11976
|
+
const chartReadyPromise = loadChartFile().then(() => prepareScoreData());
|
|
11977
|
+
|
|
11978
|
+
// 両方の完了を待つ
|
|
11979
|
+
let loadSucceeded = true;
|
|
11980
|
+
try {
|
|
11981
|
+
await Promise.all([audioReadyPromise, chartReadyPromise]);
|
|
11982
|
+
} catch (e) {
|
|
11983
|
+
console.warn(`Loading error: ${e}`);
|
|
11984
|
+
loadSucceeded = false;
|
|
11985
|
+
} finally {
|
|
11986
|
+
deleteDiv(divRoot, `lblLoading`);
|
|
11987
|
+
}
|
|
11988
|
+
|
|
11989
|
+
if (loadSucceeded) {
|
|
11990
|
+
mainInit(); // 音源・譜面変換双方の完了後にまとめて呼ぶ
|
|
11978
11991
|
}
|
|
11992
|
+
};
|
|
11993
|
+
|
|
11994
|
+
/**
|
|
11995
|
+
* 音源の取得とセットアップ
|
|
11996
|
+
* - エンコード形式(base64)か通常の音声ファイルかを判定し、それぞれの準備処理に振り分ける
|
|
11997
|
+
* - iOSの場合はユーザー操作(ジェスチャー)を待ってから再生準備を行う(readyToStart経由)
|
|
11998
|
+
* - キャッシュヒット時はダウンロード自体を行わないよう、取得処理はsetupWebAudioへ
|
|
11999
|
+
* コールバックとして渡し、キャッシュミス時にのみ評価されるようにしている
|
|
12000
|
+
* @param {string} _url 音源の取得元URL
|
|
12001
|
+
* @param {HTMLDivElement} _lblLoading ローディング表示用のDiv要素
|
|
12002
|
+
* @returns {Promise<void>} 再生準備(canplaythrough相当)が完了したら解決するPromise
|
|
12003
|
+
*/
|
|
12004
|
+
const loadAndSetupAudio = async (_url, _lblLoading) => {
|
|
12005
|
+
|
|
12006
|
+
/**
|
|
12007
|
+
* iOSの場合はユーザー操作(ジェスチャー)を待ってから_funcを実行する
|
|
12008
|
+
* - AudioContextの制約上、iOSはジェスチャーを起点にしないと音声再生が許可されないため
|
|
12009
|
+
* @param {() => Promise<void>} _func 実行する音源準備処理
|
|
12010
|
+
* @returns {Promise<void>}
|
|
12011
|
+
*/
|
|
12012
|
+
const readyToStart = _func => {
|
|
12013
|
+
if (!g_isIos) {
|
|
12014
|
+
return _func(); // 通常環境はそのまま実行するだけ
|
|
12015
|
+
}
|
|
12016
|
+
return new Promise((resolve, reject) => {
|
|
12017
|
+
g_currentPage = `loadingIos`;
|
|
12018
|
+
_lblLoading.textContent = `Click to Start!`;
|
|
12019
|
+
divRoot.appendChild(makePlayButton(evt => {
|
|
12020
|
+
getSharedAudioContext().resume();
|
|
12021
|
+
g_currentPage = `loading`;
|
|
12022
|
+
resetKeyControl();
|
|
12023
|
+
divRoot.removeChild(evt.target);
|
|
12024
|
+
_func().then(resolve).catch(reject);
|
|
12025
|
+
}));
|
|
12026
|
+
setShortcutEvent(g_currentPage);
|
|
12027
|
+
});
|
|
12028
|
+
};
|
|
12029
|
+
|
|
12030
|
+
// 音源準備処理そのものを、条件に応じて組み立てる
|
|
12031
|
+
const setupAudioFunc = (() => {
|
|
12032
|
+
|
|
12033
|
+
// エンコードなし & ローカル実行: Audio要素で直接再生
|
|
12034
|
+
if (!g_musicEncodedFlg && g_isFile) {
|
|
12035
|
+
return () => {
|
|
12036
|
+
g_audio = new Audio();
|
|
12037
|
+
g_audio.src = _url;
|
|
12038
|
+
return musicAfterLoaded();
|
|
12039
|
+
};
|
|
12040
|
+
}
|
|
12041
|
+
|
|
12042
|
+
// エンコードなし & オンライン: WebAudioAPI経由(URLからfetch)
|
|
12043
|
+
if (!g_musicEncodedFlg) {
|
|
12044
|
+
return () => setupWebAudio(async () => {
|
|
12045
|
+
const blobUrl = await fetchMusicBlobUrl(_url, _lblLoading);
|
|
12046
|
+
try {
|
|
12047
|
+
const response = await fetch(blobUrl);
|
|
12048
|
+
return await response.arrayBuffer();
|
|
12049
|
+
} finally {
|
|
12050
|
+
URL.revokeObjectURL(blobUrl);
|
|
12051
|
+
}
|
|
12052
|
+
}, _url);
|
|
12053
|
+
}
|
|
12054
|
+
|
|
12055
|
+
// エンコードあり: スクリプト読込・musicInit実行を経てWebAudioAPI準備
|
|
12056
|
+
return () => setupWebAudio(async () => {
|
|
12057
|
+
const scriptSrc = g_isFile ? _url : await fetchMusicBlobUrl(_url, _lblLoading);
|
|
12058
|
+
try {
|
|
12059
|
+
await loadScript2(scriptSrc);
|
|
12060
|
+
} finally {
|
|
12061
|
+
if (!g_isFile) {
|
|
12062
|
+
URL.revokeObjectURL(scriptSrc);
|
|
12063
|
+
}
|
|
12064
|
+
}
|
|
12065
|
+
if (typeof musicInit !== C_TYP_FUNCTION) {
|
|
12066
|
+
makeWarningWindow(g_msgInfoObj.E_0031, { backBtnUse: true });
|
|
12067
|
+
throw new Error(`musicInit is not defined`);
|
|
12068
|
+
}
|
|
12069
|
+
musicInit();
|
|
12070
|
+
return base64ToUint8Array(g_musicdata).buffer;
|
|
12071
|
+
}, _url);
|
|
12072
|
+
})();
|
|
12073
|
+
|
|
12074
|
+
return readyToStart(setupAudioFunc);
|
|
12075
|
+
};
|
|
11979
12076
|
|
|
11980
|
-
|
|
12077
|
+
/**
|
|
12078
|
+
* XHRによる音源ファイルのダウンロード(Promise化)
|
|
12079
|
+
* - ダウンロードして Blob URL を返す
|
|
12080
|
+
* @param {string} _url 音源ファイルのURL
|
|
12081
|
+
* @param {HTMLDivElement} _lblLoading ローディング表示用のDiv要素
|
|
12082
|
+
* @returns {Promise<string>} Blob URL
|
|
12083
|
+
*/
|
|
12084
|
+
const fetchMusicBlobUrl = (_url, _lblLoading) => new Promise((resolve, reject) => {
|
|
11981
12085
|
const request = new XMLHttpRequest();
|
|
11982
|
-
request.open(`GET`,
|
|
12086
|
+
request.open(`GET`, _url, true);
|
|
11983
12087
|
request.responseType = `blob`;
|
|
11984
12088
|
|
|
12089
|
+
const STALL_TIMEOUT_MS = 30000; // 30秒間、進捗がなければ停滞とみなす
|
|
12090
|
+
let stallTimer = null;
|
|
12091
|
+
|
|
12092
|
+
// 停滞タイマーをリセット
|
|
12093
|
+
const resetStallTimer = () => {
|
|
12094
|
+
clearTimeout(stallTimer);
|
|
12095
|
+
stallTimer = setTimeout(() => {
|
|
12096
|
+
request.abort();
|
|
12097
|
+
makeWarningWindow(g_msgInfoObj.E_0033, { backBtnUse: true });
|
|
12098
|
+
reject(new Error(`stalled`));
|
|
12099
|
+
}, STALL_TIMEOUT_MS);
|
|
12100
|
+
};
|
|
12101
|
+
|
|
11985
12102
|
// 読み込み完了時
|
|
11986
12103
|
request.addEventListener(`load`, () => {
|
|
12104
|
+
clearTimeout(stallTimer);
|
|
11987
12105
|
if (request.status >= 200 && request.status < 300) {
|
|
11988
12106
|
const blobUrl = URL.createObjectURL(request.response);
|
|
11989
12107
|
createEmptySprite(divRoot, `loader`, g_windowObj.loader);
|
|
11990
|
-
|
|
11991
|
-
|
|
12108
|
+
_lblLoading.textContent = g_lblNameObj.pleaseWait;
|
|
12109
|
+
resolve(blobUrl);
|
|
11992
12110
|
} else {
|
|
11993
|
-
makeWarningWindow(`${g_msgInfoObj.E_0041.split('{0}').join(getFullPath(
|
|
12111
|
+
makeWarningWindow(`${g_msgInfoObj.E_0041.split('{0}').join(getFullPath(_url))}<br>(${request.status} ${request.statusText})`, { backBtnUse: true });
|
|
12112
|
+
reject(new Error(`HTTP ${request.status}`));
|
|
11994
12113
|
}
|
|
11995
12114
|
});
|
|
11996
12115
|
|
|
11997
12116
|
// 進捗時
|
|
11998
12117
|
request.addEventListener(`progress`, _event => {
|
|
11999
|
-
|
|
12118
|
+
resetStallTimer(); // 進捗があるたびにタイマーをリセット
|
|
12119
|
+
const lblLoadingElem = document.getElementById(`lblLoading`);
|
|
12120
|
+
if (lblLoadingElem === null) return; // 並列処理で先に削除されている場合の防御
|
|
12000
12121
|
|
|
12001
12122
|
if (_event.lengthComputable) {
|
|
12002
12123
|
const rate = _event.loaded / _event.total;
|
|
12003
12124
|
createEmptySprite(divRoot, `loader`, { y: g_sHeight - 10, h: 10, w: g_sWidth * rate, backgroundColor: `#eeeeee` });
|
|
12004
|
-
|
|
12125
|
+
lblLoadingElem.textContent = `${g_lblNameObj.nowLoading} ${Math.floor(rate * 100)}%`;
|
|
12005
12126
|
} else {
|
|
12006
|
-
|
|
12127
|
+
lblLoadingElem.textContent = `${g_lblNameObj.nowLoading} ${_event.loaded}Bytes`;
|
|
12007
12128
|
}
|
|
12008
12129
|
// ユーザカスタムイベント
|
|
12009
12130
|
safeExecuteCustomHooks(`g_customJsObj.progress`, g_customJsObj.progress, _event);
|
|
12010
12131
|
});
|
|
12011
12132
|
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12133
|
+
request.addEventListener(`error`, () => {
|
|
12134
|
+
clearTimeout(stallTimer);
|
|
12135
|
+
makeWarningWindow(g_msgInfoObj.E_0034, { backBtnUse: true });
|
|
12136
|
+
reject(new Error(`network error`));
|
|
12137
|
+
});
|
|
12015
12138
|
|
|
12139
|
+
resetStallTimer(); // 初回(最初のprogressが来るまで)のタイマーも開始
|
|
12016
12140
|
request.send();
|
|
12017
|
-
};
|
|
12141
|
+
});
|
|
12018
12142
|
|
|
12019
12143
|
/**
|
|
12020
|
-
*
|
|
12021
|
-
*
|
|
12022
|
-
*
|
|
12023
|
-
* @param {
|
|
12144
|
+
* WebAudioAPIによる音源再生の準備(共通処理)
|
|
12145
|
+
* - AudioPlayerを生成し、canplaythrough/errorの発火待ちを開始した上で、
|
|
12146
|
+
* ArrayBufferを取得してデコードする(キャッシュヒット時はデコードをスキップ)
|
|
12147
|
+
* @param {() => Promise<ArrayBuffer>} _fetchArrayBuffer 未キャッシュの場合にArrayBufferを取得する関数
|
|
12148
|
+
* @param {string} [_cacheKey] AudioBufferキャッシュ照合用のキー(省略時はキャッシュを使わない)
|
|
12149
|
+
* @returns {Promise<void>} 再生準備(canplaythrough相当)が完了したら解決するPromise
|
|
12024
12150
|
*/
|
|
12025
|
-
const
|
|
12026
|
-
|
|
12027
|
-
const
|
|
12028
|
-
if (g_isFile) {
|
|
12029
|
-
g_audio = new Audio();
|
|
12030
|
-
g_audio.src = _url;
|
|
12031
|
-
musicAfterLoaded();
|
|
12032
|
-
} else {
|
|
12033
|
-
initWebAudioAPIfromURL(_url);
|
|
12034
|
-
}
|
|
12035
|
-
};
|
|
12036
|
-
|
|
12037
|
-
const readyToStart = _func => {
|
|
12038
|
-
if (g_isIos) {
|
|
12039
|
-
g_currentPage = `loadingIos`;
|
|
12040
|
-
lblLoading.textContent = `Click to Start!`;
|
|
12041
|
-
divRoot.appendChild(makePlayButton(evt => {
|
|
12042
|
-
g_currentPage = `loading`;
|
|
12043
|
-
resetKeyControl();
|
|
12044
|
-
divRoot.removeChild(evt.target);
|
|
12045
|
-
_func();
|
|
12046
|
-
}));
|
|
12047
|
-
setShortcutEvent(g_currentPage);
|
|
12048
|
-
} else {
|
|
12049
|
-
_func();
|
|
12050
|
-
}
|
|
12051
|
-
};
|
|
12151
|
+
const setupWebAudio = async (_fetchArrayBuffer, _cacheKey) => {
|
|
12152
|
+
g_audio = new AudioPlayer();
|
|
12153
|
+
const loadedPromise = musicAfterLoaded(); // canplaythrough/errorの発火をここで待つ
|
|
12052
12154
|
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
musicInit();
|
|
12057
|
-
readyToStart(() => initWebAudioAPIfromBase64(g_musicdata, _cacheKey));
|
|
12058
|
-
} else {
|
|
12059
|
-
makeWarningWindow(g_msgInfoObj.E_0031);
|
|
12060
|
-
musicAfterLoaded();
|
|
12061
|
-
}
|
|
12155
|
+
const cachedBuffer = _cacheKey ? getAudioBufferFromCache(_cacheKey) : undefined;
|
|
12156
|
+
if (cachedBuffer !== undefined) {
|
|
12157
|
+
g_audio.setBuffer(cachedBuffer);
|
|
12062
12158
|
} else {
|
|
12063
|
-
|
|
12159
|
+
const arrayBuffer = await _fetchArrayBuffer();
|
|
12160
|
+
await g_audio.init(arrayBuffer);
|
|
12161
|
+
if (_cacheKey) {
|
|
12162
|
+
cacheAudioBuffer(_cacheKey, g_audio.getBuffer());
|
|
12163
|
+
}
|
|
12064
12164
|
}
|
|
12165
|
+
return loadedPromise;
|
|
12065
12166
|
};
|
|
12066
12167
|
|
|
12067
|
-
|
|
12068
|
-
const
|
|
12069
|
-
g_audio = new AudioPlayer();
|
|
12070
|
-
musicAfterLoaded();
|
|
12168
|
+
const g_audioBufferCache = new Map();
|
|
12169
|
+
const AUDIO_CACHE_MAX = 5;
|
|
12071
12170
|
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12171
|
+
/**
|
|
12172
|
+
* デコード済みAudioBufferのキャッシュからの取得
|
|
12173
|
+
* - Mapは挿入順を保持するのみでアクセス順を保持しないため、
|
|
12174
|
+
* ヒット時にエントリを一度削除して再挿入し、最新として扱う(LRU方式)
|
|
12175
|
+
* @param {string} _key キャッシュキー(楽曲の実URL)
|
|
12176
|
+
* @returns {AudioBuffer|undefined} キャッシュされたAudioBuffer。存在しない場合はundefined
|
|
12177
|
+
*/
|
|
12178
|
+
const getAudioBufferFromCache = (_key) => {
|
|
12179
|
+
if (!g_audioBufferCache.has(_key)) {
|
|
12180
|
+
return undefined;
|
|
12080
12181
|
}
|
|
12182
|
+
const buffer = g_audioBufferCache.get(_key);
|
|
12183
|
+
g_audioBufferCache.delete(_key);
|
|
12184
|
+
g_audioBufferCache.set(_key, buffer); // 末尾(最新)に再挿入
|
|
12185
|
+
return buffer;
|
|
12081
12186
|
};
|
|
12082
12187
|
|
|
12083
|
-
|
|
12084
|
-
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
await g_audio.init(arrayBuffer);
|
|
12090
|
-
};
|
|
12091
|
-
|
|
12092
|
-
const g_audioBufferCache = new Map();
|
|
12093
|
-
const AUDIO_CACHE_MAX = 5;
|
|
12094
|
-
|
|
12188
|
+
/**
|
|
12189
|
+
* デコード済みAudioBufferのキャッシュへの登録
|
|
12190
|
+
* - 直近 AUDIO_CACHE_MAX 件を保持するLRU方式。上限を超えた場合は最も古いエントリから破棄する
|
|
12191
|
+
* @param {string} _key キャッシュキー(楽曲の実URL)
|
|
12192
|
+
* @param {AudioBuffer} _buffer デコード済みのAudioBuffer
|
|
12193
|
+
*/
|
|
12095
12194
|
const cacheAudioBuffer = (_key, _buffer) => {
|
|
12096
12195
|
g_audioBufferCache.set(_key, _buffer);
|
|
12097
12196
|
if (g_audioBufferCache.size > AUDIO_CACHE_MAX) {
|
|
@@ -12099,6 +12198,13 @@ const cacheAudioBuffer = (_key, _buffer) => {
|
|
|
12099
12198
|
}
|
|
12100
12199
|
};
|
|
12101
12200
|
|
|
12201
|
+
/**
|
|
12202
|
+
* base64文字列をUint8Arrayに変換
|
|
12203
|
+
* - Uint8Array.from(atob(str), callback)によるコールバック呼び出し形式は
|
|
12204
|
+
* 大容量データで変換オーバーヘッドが大きいため、forループによる直接代入で高速化している
|
|
12205
|
+
* @param {string} _base64Str base64エンコードされた文字列
|
|
12206
|
+
* @returns {Uint8Array} 変換後のバイト配列
|
|
12207
|
+
*/
|
|
12102
12208
|
const base64ToUint8Array = (_base64Str) => {
|
|
12103
12209
|
const binaryStr = atob(_base64Str);
|
|
12104
12210
|
const len = binaryStr.length;
|
|
@@ -12109,34 +12215,41 @@ const base64ToUint8Array = (_base64Str) => {
|
|
|
12109
12215
|
return array;
|
|
12110
12216
|
};
|
|
12111
12217
|
|
|
12112
|
-
|
|
12218
|
+
/**
|
|
12219
|
+
* 音源の再生準備完了を待つ
|
|
12220
|
+
* - g_audio が Audio要素の場合は canplaythrough/error イベントの発火を待つ
|
|
12221
|
+
* - g_audio が AudioPlayer の場合、readyState が既に4(デコード済み、キャッシュヒット時など)であれば即時解決する
|
|
12222
|
+
* - canplaythrough/error のどちらが発火しても、もう一方のリスナーも確実に削除する(cleanup)
|
|
12223
|
+
* @returns {Promise<void>} 再生準備が完了したら解決し、読込エラー時は例外を投げて拒否するPromise
|
|
12224
|
+
*/
|
|
12225
|
+
const musicAfterLoaded = () => new Promise((resolve, reject) => {
|
|
12113
12226
|
g_audio.load();
|
|
12114
12227
|
|
|
12115
12228
|
if (g_audio.readyState === 4) {
|
|
12116
|
-
|
|
12117
|
-
loadingScoreInit();
|
|
12229
|
+
resolve();
|
|
12118
12230
|
} else {
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
loadingScoreInit();
|
|
12123
|
-
})(), false);
|
|
12124
|
-
|
|
12125
|
-
// エラー時
|
|
12126
|
-
g_audio.addEventListener(`error`, (() => function f() {
|
|
12127
|
-
g_audio.removeEventListener(`error`, f, false);
|
|
12231
|
+
const onCanPlay = () => { cleanup(); resolve(); };
|
|
12232
|
+
const onError = () => {
|
|
12233
|
+
cleanup();
|
|
12128
12234
|
makeWarningWindow(g_msgInfoObj.E_0041.split(`{0}`).join(g_audio.src), { backBtnUse: true });
|
|
12129
|
-
|
|
12235
|
+
reject(new Error(`audio load error`));
|
|
12236
|
+
};
|
|
12237
|
+
const cleanup = () => {
|
|
12238
|
+
g_audio.removeEventListener(`canplaythrough`, onCanPlay, false);
|
|
12239
|
+
g_audio.removeEventListener(`error`, onError, false);
|
|
12240
|
+
};
|
|
12241
|
+
g_audio.addEventListener(`canplaythrough`, onCanPlay, false);
|
|
12242
|
+
g_audio.addEventListener(`error`, onError, false);
|
|
12130
12243
|
}
|
|
12131
|
-
};
|
|
12244
|
+
});
|
|
12132
12245
|
|
|
12133
12246
|
/**
|
|
12134
|
-
*
|
|
12247
|
+
* 譜面データの変換処理
|
|
12248
|
+
* - 音源データの状態に依存しない部分のみを担う(g_audio非参照)
|
|
12249
|
+
* - loadMusic経由(並行フロー)、executeRetry経由(曲中リトライ)の両方から呼ばれる
|
|
12135
12250
|
*/
|
|
12136
|
-
const
|
|
12251
|
+
const prepareScoreData = () => {
|
|
12137
12252
|
|
|
12138
|
-
// 譜面データの読み込み
|
|
12139
|
-
await loadChartFile();
|
|
12140
12253
|
const tkObj = getKeyInfo();
|
|
12141
12254
|
const [keyCtrlPtn, keyNum] = [tkObj.keyCtrlPtn, tkObj.keyNum];
|
|
12142
12255
|
g_headerObj.blankFrameDef = setVal(g_headerObj.blankFrameDefs[g_stateObj.scoreId], g_headerObj.blankFrameDefs[0]);
|
|
@@ -12284,8 +12397,6 @@ const loadingScoreInit = async () => {
|
|
|
12284
12397
|
|
|
12285
12398
|
// ユーザカスタムイベント
|
|
12286
12399
|
safeExecuteCustomHooks(`g_customJsObj.loading`, g_customJsObj.loading);
|
|
12287
|
-
|
|
12288
|
-
mainInit();
|
|
12289
12400
|
};
|
|
12290
12401
|
|
|
12291
12402
|
/**
|
|
@@ -14243,9 +14354,6 @@ const getArrowSettings = () => {
|
|
|
14243
14354
|
g_workObj.frzReturnTimerId = null;
|
|
14244
14355
|
}
|
|
14245
14356
|
|
|
14246
|
-
// AutoRetryの初期化
|
|
14247
|
-
g_workObj.autoRetryFlg = false;
|
|
14248
|
-
|
|
14249
14357
|
// Camoufrageの設定
|
|
14250
14358
|
if (g_stateObj.camoufrage !== C_FLG_OFF) {
|
|
14251
14359
|
|
|
@@ -14422,7 +14530,7 @@ const setKeyCtrl = (_localStorage, _keyNum, _keyCtrlPtn) => {
|
|
|
14422
14530
|
* メイン画面初期化
|
|
14423
14531
|
*/
|
|
14424
14532
|
const mainInit = () => {
|
|
14425
|
-
clearWindow(
|
|
14533
|
+
clearWindow(`Main`);
|
|
14426
14534
|
const divRoot = document.getElementById(`divRoot`);
|
|
14427
14535
|
document.oncontextmenu = () => false;
|
|
14428
14536
|
g_currentPage = `main`;
|
|
@@ -14875,7 +14983,7 @@ const mainInit = () => {
|
|
|
14875
14983
|
};
|
|
14876
14984
|
|
|
14877
14985
|
// キー操作イベント
|
|
14878
|
-
document.onkeydown = evt => {
|
|
14986
|
+
document.onkeydown = async evt => {
|
|
14879
14987
|
evt.preventDefault();
|
|
14880
14988
|
const setCode = transCode(evt);
|
|
14881
14989
|
|
|
@@ -14887,17 +14995,16 @@ const mainInit = () => {
|
|
|
14887
14995
|
|
|
14888
14996
|
// 曲中リトライ、タイトルバック
|
|
14889
14997
|
if (setCode === g_kCdN[g_headerObj.keyRetry]) {
|
|
14890
|
-
g_audio.pause();
|
|
14891
|
-
clearTimeout(g_timeoutEvtId);
|
|
14892
14998
|
|
|
14893
14999
|
if (g_isMac && keyIsShift()) {
|
|
14894
15000
|
// Mac OS、IPad OSはDeleteキーが無いためShift+BSで代用
|
|
15001
|
+
g_audio.pause();
|
|
15002
|
+
clearTimeout(g_timeoutEvtId);
|
|
14895
15003
|
titleInit();
|
|
14896
15004
|
|
|
14897
15005
|
} else {
|
|
14898
15006
|
// その他の環境では単にRetryに対応するキーのみで適用
|
|
14899
|
-
|
|
14900
|
-
musicAfterLoaded();
|
|
15007
|
+
await executeRetry(`Retry`);
|
|
14901
15008
|
}
|
|
14902
15009
|
|
|
14903
15010
|
} else if (setCode === g_kCdN[g_headerObj.keyTitleBack]) {
|
|
@@ -16179,6 +16286,32 @@ const executeFrzReturn = (_seq, _idx, _axis) => {
|
|
|
16179
16286
|
g_workObj.frzReturnTimerId = setTimeout(() => executeFrzReturn(_seq, _idx + 1, _axis), 20);
|
|
16180
16287
|
};
|
|
16181
16288
|
|
|
16289
|
+
/**
|
|
16290
|
+
* 曲中リトライの共通処理
|
|
16291
|
+
* - 手動リトライ、AutoRetryの両方から呼ばれる
|
|
16292
|
+
* - g_retryInProgressによる多重実行防止、失敗時のログ出力・フラグ復帰を一括で行う
|
|
16293
|
+
* @param {string} [_logLabel='Retry'] エラーログに表示するラベル(手動/自動の区別用)
|
|
16294
|
+
*/
|
|
16295
|
+
const executeRetry = async (_logLabel = `Retry`) => {
|
|
16296
|
+
if (g_retryInProgress) {
|
|
16297
|
+
return;
|
|
16298
|
+
}
|
|
16299
|
+
g_retryInProgress = true;
|
|
16300
|
+
try {
|
|
16301
|
+
g_audio.pause();
|
|
16302
|
+
clearTimeout(g_timeoutEvtId);
|
|
16303
|
+
clearWindow(`Main`);
|
|
16304
|
+
await musicAfterLoaded();
|
|
16305
|
+
await loadChartFile();
|
|
16306
|
+
prepareScoreData();
|
|
16307
|
+
mainInit();
|
|
16308
|
+
} catch (e) {
|
|
16309
|
+
console.warn(`${_logLabel} audio load error: ${e}`);
|
|
16310
|
+
} finally {
|
|
16311
|
+
g_retryInProgress = false;
|
|
16312
|
+
}
|
|
16313
|
+
};
|
|
16314
|
+
|
|
16182
16315
|
/**
|
|
16183
16316
|
* AutoRetryの設定
|
|
16184
16317
|
* @param {string} _retryCondition リトライ基準となるAutoRetry名
|
|
@@ -16188,13 +16321,9 @@ const quickRetry = (_retryCondition) => {
|
|
|
16188
16321
|
if (retryNum < 0) {
|
|
16189
16322
|
return;
|
|
16190
16323
|
}
|
|
16191
|
-
if (g_settings.autoRetryNum >= retryNum && !
|
|
16192
|
-
|
|
16193
|
-
|
|
16194
|
-
g_audio.pause();
|
|
16195
|
-
clearTimeout(g_timeoutEvtId);
|
|
16196
|
-
clearWindow();
|
|
16197
|
-
musicAfterLoaded();
|
|
16324
|
+
if (g_settings.autoRetryNum >= retryNum && !g_retryInProgress) {
|
|
16325
|
+
setTimeout(async () => {
|
|
16326
|
+
await executeRetry(`AutoRetry`);
|
|
16198
16327
|
}, 16);
|
|
16199
16328
|
}
|
|
16200
16329
|
};
|
|
@@ -16795,7 +16924,7 @@ const finishViewing = () => {
|
|
|
16795
16924
|
*/
|
|
16796
16925
|
const resultInit = () => {
|
|
16797
16926
|
|
|
16798
|
-
clearWindow(
|
|
16927
|
+
clearWindow();
|
|
16799
16928
|
g_currentPage = `result`;
|
|
16800
16929
|
|
|
16801
16930
|
// 結果画面用フレーム初期化
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Source by tickle
|
|
7
7
|
* Created : 2019/11/19
|
|
8
|
-
* Revised : 2026/
|
|
8
|
+
* Revised : 2026/07/04 (v49.1.1)
|
|
9
9
|
*
|
|
10
10
|
* https://github.com/cwtickle/danoniplus
|
|
11
11
|
*/
|
|
@@ -145,7 +145,8 @@ const g_localeObj = {
|
|
|
145
145
|
};
|
|
146
146
|
|
|
147
147
|
const g_userAgent = window.navigator.userAgent.toLowerCase(); // msie, edge, chrome, safari, firefox, opera
|
|
148
|
-
const g_isIos = listMatching(g_userAgent, [`iphone`, `ipad`, `ipod`])
|
|
148
|
+
const g_isIos = listMatching(g_userAgent, [`iphone`, `ipad`, `ipod`]) ||
|
|
149
|
+
(navigator.maxTouchPoints > 1 && /macintosh/i.test(g_userAgent));
|
|
149
150
|
const g_isMac = listMatching(g_userAgent, [`iphone`, `ipad`, `ipod`, `mac os`]);
|
|
150
151
|
|
|
151
152
|
// 変数型
|