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