diff --git a/index.html b/index.html
index ac33af0..7c02dd0 100644
--- a/index.html
+++ b/index.html
@@ -671,29 +671,38 @@ async function tryVoicevox(text) {
if (!apiKey) return false;
const ctx = getAudioCtx();
- const res = await fetch(TTS_API, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
- body: JSON.stringify({ text: preprocessText(text), speaker: ttsSpeaker }),
- });
- if (!res.ok) {
- const msg = res.status === 401 ? 'APIキーが無効です' : res.status === 503 ? 'VOICEVOXが起動していません' : `サーバーエラー (${res.status})`;
- if (currentIdx === 0 && typeof showToast === 'function') showToast('VOICEVOX: ' + msg);
- return false;
+ for (let attempt = 0; attempt < 4; attempt++) {
+ if (attempt > 0) await new Promise(r => setTimeout(r, 2000 * attempt));
+ const res = await fetch(TTS_API, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
+ body: JSON.stringify({ text: preprocessText(text), speaker: ttsSpeaker }),
+ });
+ if (res.status === 503) {
+ const body = await res.json().catch(() => ({}));
+ if (body.error === 'TTS_BUSY' && attempt < 3) continue; // 自動リトライ
+ const msg = body.error === 'TTS_BUSY' ? 'サーバーが混雑しています' : 'VOICEVOXが起動していません';
+ if (currentIdx === 0 && typeof showToast === 'function') showToast('VOICEVOX: ' + msg);
+ return false;
+ }
+ if (!res.ok) {
+ const msg = res.status === 401 ? 'APIキーが無効です' : `サーバーエラー (${res.status})`;
+ if (currentIdx === 0 && typeof showToast === 'function') showToast('VOICEVOX: ' + msg);
+ return false;
+ }
+ const buf = await ctx.decodeAudioData(await res.arrayBuffer());
+ if (!isPlaying) return true;
+ return new Promise(resolve => {
+ currentSource = ctx.createBufferSource();
+ currentSource.buffer = buf;
+ currentSource.playbackRate.value = speechRate;
+ currentSource.connect(ctx.destination);
+ currentSource.onended = () => { currentSource = null; resolve(true); };
+ currentSource.start(0);
+ updateVoiceBadge(true);
+ });
}
-
- const buf = await ctx.decodeAudioData(await res.arrayBuffer());
- if (!isPlaying) return true;
-
- return new Promise(resolve => {
- currentSource = ctx.createBufferSource();
- currentSource.buffer = buf;
- currentSource.playbackRate.value = speechRate;
- currentSource.connect(ctx.destination);
- currentSource.onended = () => { currentSource = null; resolve(true); };
- currentSource.start(0);
- updateVoiceBadge(true);
- });
+ return false;
}
function speakBrowser(text, onEnd) {