fix: TTS pre-warm — prioritize user requests, fix cache key mismatch
- Split preWarmBusy from ttsBusy so user requests are never blocked by pre-warm - /tts endpoint waits up to 6s for pre-warm synthesis then proceeds - Pre-warm skips articles when user is actively using TTS - Fix text format to match Brief exactly (remove substring(60), fix source fallback) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fa7da98511
commit
df0e1b66ad
23
server.js
23
server.js
|
|
@ -1123,6 +1123,7 @@ ${excerpt}
|
||||||
const ttsCache = new Map(); // key: "speaker:text" → Buffer
|
const ttsCache = new Map(); // key: "speaker:text" → Buffer
|
||||||
const TTS_CACHE_MAX = 60; // 約60文を最大キャッシュ(メモリ節約)
|
const TTS_CACHE_MAX = 60; // 約60文を最大キャッシュ(メモリ節約)
|
||||||
let ttsBusy = false; // VOICEVOX は同時1リクエストのみ対応(排他ロック)
|
let ttsBusy = false; // VOICEVOX は同時1リクエストのみ対応(排他ロック)
|
||||||
|
let preWarmBusy = false; // プリウォームが合成中(ユーザーリクエストを優先するために分離)
|
||||||
|
|
||||||
// 合成ヘルパー(/tts と /tts/warmup から共用)
|
// 合成ヘルパー(/tts と /tts/warmup から共用)
|
||||||
async function ttsSynthesize(text, speaker) {
|
async function ttsSynthesize(text, speaker) {
|
||||||
|
|
@ -1160,6 +1161,13 @@ ${excerpt}
|
||||||
return res.send(cached);
|
return res.send(cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// プリウォーム中なら最大6秒待ってユーザーを優先
|
||||||
|
if (preWarmBusy) {
|
||||||
|
const deadline = Date.now() + 6000;
|
||||||
|
while (preWarmBusy && Date.now() < deadline) {
|
||||||
|
await new Promise(r => setTimeout(r, 200));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (ttsBusy) return res.status(503).json({ error: 'TTS_BUSY' });
|
if (ttsBusy) return res.status(503).json({ error: 'TTS_BUSY' });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -1246,12 +1254,11 @@ ${excerpt}
|
||||||
const articles = (data.articles || []).slice(0, 5);
|
const articles = (data.articles || []).slice(0, 5);
|
||||||
if (articles.length === 0) return;
|
if (articles.length === 0) return;
|
||||||
|
|
||||||
// ブラウザと同じロジックでテキスト生成
|
// ブラウザと同じロジックでテキスト生成(Brief の speechQueue と完全一致させる)
|
||||||
const texts = [];
|
const texts = [];
|
||||||
articles.forEach((a, i) => {
|
articles.forEach((a, i) => {
|
||||||
const prefix = i === 0 ? '最初のニュースです。' : '続いて。';
|
const prefix = i === 0 ? '最初のニュースです。' : '続いて。';
|
||||||
const body = (a.title || '').substring(0, 60);
|
texts.push(`${prefix}${a.source || ''}より。${a.title || ''}`);
|
||||||
texts.push(`${prefix}${a.source || '不明なソース'}より。${body}`);
|
|
||||||
});
|
});
|
||||||
texts.push('本日のブリーフィングは以上です。');
|
texts.push('本日のブリーフィングは以上です。');
|
||||||
|
|
||||||
|
|
@ -1262,8 +1269,12 @@ ${excerpt}
|
||||||
console.log(`[TTS pre-warm] skip (cached): ${text.substring(0, 25)}`);
|
console.log(`[TTS pre-warm] skip (cached): ${text.substring(0, 25)}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
while (ttsBusy) await new Promise(r => setTimeout(r, 500));
|
// ユーザーリクエスト中はスキップ(ユーザー優先)
|
||||||
ttsBusy = true;
|
if (ttsBusy) {
|
||||||
|
console.log(`[TTS pre-warm] skip (user active): ${text.substring(0, 25)}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
preWarmBusy = true;
|
||||||
try {
|
try {
|
||||||
const buf = await ttsSynthesize(text, speaker);
|
const buf = await ttsSynthesize(text, speaker);
|
||||||
if (ttsCache.size >= TTS_CACHE_MAX) ttsCache.delete(ttsCache.keys().next().value);
|
if (ttsCache.size >= TTS_CACHE_MAX) ttsCache.delete(ttsCache.keys().next().value);
|
||||||
|
|
@ -1272,7 +1283,7 @@ ${excerpt}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[TTS pre-warm] synth failed: ${e.message}`);
|
console.error(`[TTS pre-warm] synth failed: ${e.message}`);
|
||||||
} finally {
|
} finally {
|
||||||
ttsBusy = false;
|
preWarmBusy = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('[TTS pre-warm] done');
|
console.log('[TTS pre-warm] done');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue