update:优化语音播报流程
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user