update:优化语音播报流程

This commit is contained in:
2026-06-15 15:35:04 +08:00
parent 69ed5bda1b
commit 045827cb33
2 changed files with 187 additions and 17 deletions

View File

@@ -131,6 +131,10 @@ class AudioManager {
this.lastPlayKey = null; this.lastPlayKey = null;
this.lastPlayAt = 0; this.lastPlayAt = 0;
this.isInterrupted = false; this.isInterrupted = false;
this.interruptedAt = 0;
this.interruptionFallbackMs = 5000;
this.playWatchdogMs = 8000;
this.playWatchdogTimers = new Map();
// 静音开关 // 静音开关
this.isMuted = false; this.isMuted = false;
@@ -157,6 +161,7 @@ class AudioManager {
const begin = () => { const begin = () => {
if (this.isInterrupted) return; if (this.isInterrupted) return;
this.isInterrupted = true; this.isInterrupted = true;
this.interruptedAt = Date.now();
this.stopAll(); this.stopAll();
this.isSequenceRunning = false; this.isSequenceRunning = false;
this.sequenceQueue = []; this.sequenceQueue = [];
@@ -168,6 +173,7 @@ class AudioManager {
const end = () => { const end = () => {
if (!this.isInterrupted) return; if (!this.isInterrupted) return;
this.isInterrupted = false; this.isInterrupted = false;
this.interruptedAt = 0;
uni.$emit(AUDIO_INTERRUPTION_END_EVENT); uni.$emit(AUDIO_INTERRUPTION_END_EVENT);
void this.reloadAll(); void this.reloadAll();
}; };
@@ -350,9 +356,14 @@ class AudioManager {
const loadTimeout = setTimeout(() => { const loadTimeout = setTimeout(() => {
debugLog(`音频 ${key} 加载超时`); debugLog(`音频 ${key} 加载超时`);
this.recordLoadFailure(key); this.recordLoadFailure(key);
this.audioMap.delete(key);
try { try {
audio.destroy(); audio.destroy();
} catch (_) {} } catch (_) {}
this.finishPlayback(key, {
advanceSequence: true,
emitEnded: true,
});
if (callback) callback(); if (callback) callback();
}, 10000); }, 10000);
@@ -386,7 +397,13 @@ class AudioManager {
} }
this.recordLoadFailure(key); this.recordLoadFailure(key);
this.audioMap.delete(key); this.audioMap.delete(key);
try {
audio.destroy(); audio.destroy();
} catch (_) {}
this.finishPlayback(key, {
advanceSequence: true,
emitEnded: true,
});
if (this.readyMap.get(key)) { if (this.readyMap.get(key)) {
// 这里不要去除,不然检查进度的时候由于没有重新加载而进度卡住,等播放失败的时候会重新加载 // 这里不要去除,不然检查进度的时候由于没有重新加载而进度卡住,等播放失败的时候会重新加载
// this.readyMap.set(key, false); // this.readyMap.set(key, false);
@@ -396,19 +413,14 @@ class AudioManager {
}); });
audio.onEnded(() => { audio.onEnded(() => {
if (this.currentPlayingKey === key) { this.finishPlayback(key, {
this.currentPlayingKey = null; advanceSequence: true,
} emitEnded: true,
this.allowPlayMap.set(key, false); });
this.onAudioEnded(key);
uni.$emit('audioEnded', key);
}); });
audio.onStop(() => { audio.onStop(() => {
if (this.currentPlayingKey === key) { this.finishPlayback(key);
this.currentPlayingKey = null;
}
this.allowPlayMap.set(key, false);
}); });
this.audioMap.set(key, audio); this.audioMap.set(key, audio);
@@ -446,11 +458,19 @@ class AudioManager {
}); });
} else { } else {
this.recordLoadFailure(key); this.recordLoadFailure(key);
this.finishPlayback(key, {
advanceSequence: true,
emitEnded: true,
});
if (callback) callback(); if (callback) callback();
} }
}, },
fail: () => { fail: () => {
this.recordLoadFailure(key); this.recordLoadFailure(key);
this.finishPlayback(key, {
advanceSequence: true,
emitEnded: true,
});
if (callback) callback(); if (callback) callback();
}, },
}); });
@@ -487,15 +507,137 @@ class AudioManager {
this.failedLoadKeys.add(key); this.failedLoadKeys.add(key);
} }
clearPlayWatchdog(key) {
const timer = this.playWatchdogTimers.get(key);
if (timer) {
clearTimeout(timer);
this.playWatchdogTimers.delete(key);
}
}
clearAllPlayWatchdogs() {
for (const timer of this.playWatchdogTimers.values()) {
clearTimeout(timer);
}
this.playWatchdogTimers.clear();
}
startPlayWatchdog(key) {
this.clearPlayWatchdog(key);
const timer = setTimeout(() => {
if (this.currentPlayingKey !== key) return;
debugLog(`音频 ${key} 播放超时,跳过当前音频并继续队列`);
this.finishPlayback(key, {
advanceSequence: true,
emitEnded: true,
force: true,
});
this.reloadAudioKey(key);
}, this.playWatchdogMs);
this.playWatchdogTimers.set(key, timer);
}
finishPlayback(key, { advanceSequence = false, emitEnded = false, force = false } = {}) {
const wasCurrent = this.currentPlayingKey === key;
const isSequenceCurrent =
this.isSequenceRunning && this.sequenceQueue[this.sequenceIndex] === key;
this.clearPlayWatchdog(key);
this.allowPlayMap.set(key, false);
if (!force && !wasCurrent && !isSequenceCurrent) return false;
if (wasCurrent) {
this.currentPlayingKey = null;
}
if (advanceSequence && isSequenceCurrent) {
this.onAudioEnded(key);
}
if (emitEnded) {
uni.$emit("audioEnded", key);
}
return true;
}
recoverFromInterruptionIfStale(force = false) {
if (!this.isInterrupted) return false;
const interruptedFor = Date.now() - (this.interruptedAt || Date.now());
if (!force && interruptedFor < this.interruptionFallbackMs) return false;
debugLog("音频中断状态超时,执行兜底恢复");
this.isInterrupted = false;
this.interruptedAt = 0;
uni.$emit(AUDIO_INTERRUPTION_END_EVENT);
void this.reloadAll();
return true;
}
recoverIfStale(expectedKey) {
if (this.recoverFromInterruptionIfStale(true)) return;
const key =
expectedKey || this.currentPlayingKey || this.sequenceQueue[this.sequenceIndex];
if (!key) {
if (this.isSequenceRunning) {
this.sequenceQueue = [];
this.sequenceIndex = 0;
this.isSequenceRunning = false;
}
return;
}
const isStaleCurrent =
this.currentPlayingKey === key ||
(this.isSequenceRunning && this.sequenceQueue[this.sequenceIndex] === key);
if (!isStaleCurrent) return;
debugLog(`音频 ${key} 等待超时,执行轻量恢复`);
const audio = this.audioMap.get(key);
if (audio) {
try {
audio.stop();
} catch (_) {}
}
this.finishPlayback(key, {
advanceSequence: true,
emitEnded: true,
force: true,
});
this.reloadAudioKey(key);
}
reloadAudioKey(key) {
const audio = this.audioMap.get(key);
if (audio) {
try {
audio.destroy();
} catch (_) {}
this.audioMap.delete(key);
}
this.readyMap.set(key, false);
this.retryLoadAudio(key);
}
// 重新加载音频 // 重新加载音频
retryLoadAudio(key) { retryLoadAudio(key) {
this.clearPlayWatchdog(key);
const oldAudio = this.audioMap.get(key); const oldAudio = this.audioMap.get(key);
if (oldAudio) oldAudio.destroy(); if (oldAudio) {
try {
oldAudio.destroy();
} catch (_) {}
}
this.createAudio(key); this.createAudio(key);
} }
// 播放指定音频或音频数组(数组则按顺序连续播放) // 播放指定音频或音频数组(数组则按顺序连续播放)
play(input, interrupt = true) { play(input, interrupt = true) {
if (this.isInterrupted) {
this.recoverFromInterruptionIfStale();
}
if (this.isInterrupted) { if (this.isInterrupted) {
debugLog("音频处理中断状态,忽略播放请求"); debugLog("音频处理中断状态,忽略播放请求");
return; return;
@@ -553,6 +695,9 @@ class AudioManager {
// 内部方法:播放单个 key // 内部方法:播放单个 key
_playSingle(key, forceStopAll = false) { _playSingle(key, forceStopAll = false) {
if (this.isInterrupted) {
this.recoverFromInterruptionIfStale();
}
if (this.isInterrupted) { if (this.isInterrupted) {
debugLog(`音频处理中断状态,跳过播放: ${key}`); debugLog(`音频处理中断状态,跳过播放: ${key}`);
return; return;
@@ -561,6 +706,11 @@ class AudioManager {
const now = Date.now(); const now = Date.now();
if (this.lastPlayKey === key && now - this.lastPlayAt < 250) { if (this.lastPlayKey === key && now - this.lastPlayAt < 250) {
debugLog(`忽略快速重复播放: ${key}`); debugLog(`忽略快速重复播放: ${key}`);
this.finishPlayback(key, {
advanceSequence: true,
emitEnded: true,
force: true,
});
return; return;
} }
@@ -603,21 +753,34 @@ class AudioManager {
try { try {
audio.play(); audio.play();
} catch (err) { } catch (err) {
this.allowPlayMap.set(key, false); this.finishPlayback(key, {
advanceSequence: true,
emitEnded: true,
force: true,
});
debugLog(`音频 ${key} 播放调用失败`, err?.errMsg || err); debugLog(`音频 ${key} 播放调用失败`, err?.errMsg || err);
return; return;
} }
this.currentPlayingKey = key; this.currentPlayingKey = key;
this.lastPlayKey = key; this.lastPlayKey = key;
this.lastPlayAt = Date.now(); this.lastPlayAt = Date.now();
this.startPlayWatchdog(key);
} else { } else {
debugLog(`音频 ${key} 不存在,尝试重新加载...`); debugLog(`音频 ${key} 不存在,尝试重新加载...`);
this.retryLoadAudio(key); this.retryLoadAudio(key);
const handler = (loadedKey) => { let loadWaitTimer = null;
if (loadedKey === key) { const cleanup = () => {
try { try {
uni.$off("audioLoaded", handler); uni.$off("audioLoaded", handler);
} catch (_) {} } catch (_) {}
if (loadWaitTimer) {
clearTimeout(loadWaitTimer);
loadWaitTimer = null;
}
};
const handler = (loadedKey) => {
if (loadedKey === key) {
cleanup();
// 再次校验是否存在且就绪 // 再次校验是否存在且就绪
const a = this.audioMap.get(key); const a = this.audioMap.get(key);
if (a && this.readyMap.get(key)) { if (a && this.readyMap.get(key)) {
@@ -628,6 +791,7 @@ class AudioManager {
try { try {
uni.$on("audioLoaded", handler); uni.$on("audioLoaded", handler);
} catch (_) {} } catch (_) {}
loadWaitTimer = setTimeout(cleanup, 12000);
} }
} }
@@ -653,6 +817,7 @@ class AudioManager {
// 停止指定音频 // 停止指定音频
stop(key) { stop(key) {
const audio = this.audioMap.get(key); const audio = this.audioMap.get(key);
this.clearPlayWatchdog(key);
if (audio) { if (audio) {
audio.stop(); audio.stop();
this.allowPlayMap.set(key, false); this.allowPlayMap.set(key, false);
@@ -664,6 +829,7 @@ class AudioManager {
// 停止所有音频 // 停止所有音频
stopAll() { stopAll() {
this.clearAllPlayWatchdogs();
for (const [k, audio] of this.audioMap.entries()) { for (const [k, audio] of this.audioMap.entries()) {
try { try {
audio.stop(); audio.stop();
@@ -737,6 +903,7 @@ class AudioManager {
this.readyMap.clear(); this.readyMap.clear();
this.failedLoadKeys.clear(); this.failedLoadKeys.clear();
this.allowPlayMap.clear(); this.allowPlayMap.clear();
this.clearAllPlayWatchdogs();
this.currentPlayingKey = null; this.currentPlayingKey = null;
this.sequenceQueue = []; this.sequenceQueue = [];
this.sequenceIndex = 0; this.sequenceIndex = 0;

View File

@@ -432,7 +432,10 @@ function playAudioKeys(keys, { interrupt = false, timeout } = {}) {
resolve(); resolve();
}, },
}; };
const timer = setTimeout(waiter.done, waitTime); const timer = setTimeout(() => {
audioManager.recoverIfStale(expectedKey);
waiter.done();
}, waitTime);
audioWaiters.add(waiter); audioWaiters.add(waiter);
audioManager.play(audioKeys, interrupt); audioManager.play(audioKeys, interrupt);
}); });