update:优化性能问题
This commit is contained in:
+121
-22
@@ -112,6 +112,8 @@ export const audioFils = {
|
||||
const AUDIO_LOAD_TIMEOUT_MS = 5000;
|
||||
const AUDIO_WARM_CONCURRENCY = 3;
|
||||
const AUDIO_WARM_RETRIES = 1;
|
||||
const AUDIO_INSTANCE_LIMIT = 24;
|
||||
const AUDIO_QUEUE_COMPACT_THRESHOLD = 12;
|
||||
const AUDIO_WARM_PRIORITY_KEYS = [
|
||||
"点击按钮",
|
||||
"设备连接已断开",
|
||||
@@ -162,6 +164,8 @@ class AudioManager {
|
||||
this.loadingKeyPromises = new Map();
|
||||
this.pendingPlayKeys = new Set();
|
||||
this.cacheWritePromises = new Map();
|
||||
this.audioLastUsedAt = new Map();
|
||||
this.warmPriorityPromise = null;
|
||||
this.warmAllPromise = null;
|
||||
|
||||
// 连续播放队列相关属性
|
||||
@@ -253,14 +257,74 @@ class AudioManager {
|
||||
}
|
||||
|
||||
getWarmupKeys() {
|
||||
return Array.from(
|
||||
new Set([...AUDIO_WARM_PRIORITY_KEYS, ...Object.keys(audioFils)])
|
||||
);
|
||||
return Array.from(new Set(AUDIO_WARM_PRIORITY_KEYS));
|
||||
}
|
||||
|
||||
getAllAudioKeys() {
|
||||
return Object.keys(audioFils);
|
||||
}
|
||||
|
||||
touchAudioKey(key) {
|
||||
this.audioLastUsedAt.set(key, Date.now());
|
||||
}
|
||||
|
||||
destroyAudioInstance(key) {
|
||||
const audio = this.audioMap.get(key);
|
||||
if (!audio) return false;
|
||||
|
||||
this.clearPlayWatchdog(key);
|
||||
this.audioMap.delete(key);
|
||||
this.readyMap.delete(key);
|
||||
this.allowPlayMap.delete(key);
|
||||
this.audioLastUsedAt.delete(key);
|
||||
try {
|
||||
audio.destroy();
|
||||
} catch (_) {}
|
||||
// destroy 可能同步触发 onStop,最后再清一次对应状态。
|
||||
this.allowPlayMap.delete(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
pruneAudioInstances(preserveKey) {
|
||||
while (this.audioMap.size >= AUDIO_INSTANCE_LIMIT) {
|
||||
const candidate = Array.from(this.audioMap.keys())
|
||||
.filter(
|
||||
(key) =>
|
||||
key !== preserveKey &&
|
||||
key !== this.currentPlayingKey &&
|
||||
!this.loadingKeyPromises.has(key)
|
||||
)
|
||||
.sort(
|
||||
(left, right) =>
|
||||
(this.audioLastUsedAt.get(left) || 0) -
|
||||
(this.audioLastUsedAt.get(right) || 0)
|
||||
)[0];
|
||||
if (!candidate || !this.destroyAudioInstance(candidate)) return;
|
||||
}
|
||||
}
|
||||
|
||||
compactSequenceQueue(force = false) {
|
||||
if (!this.isSequenceRunning || this.sequenceIndex <= 0) return;
|
||||
if (!force && this.sequenceIndex < AUDIO_QUEUE_COMPACT_THRESHOLD) return;
|
||||
this.sequenceQueue = this.sequenceQueue.slice(this.sequenceIndex);
|
||||
this.sequenceIndex = 0;
|
||||
}
|
||||
|
||||
getRuntimeStats() {
|
||||
return {
|
||||
audioInstanceCount: this.audioMap.size,
|
||||
loadingAudioCount: this.loadingKeyPromises.size,
|
||||
pendingPlayCount: this.pendingPlayKeys.size,
|
||||
sequenceQueueLength: this.isSequenceRunning
|
||||
? Math.max(this.sequenceQueue.length - this.sequenceIndex, 0)
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
|
||||
ensureAudio(key) {
|
||||
if (!audioFils[key]) return Promise.resolve(false);
|
||||
if (this.readyMap.get(key) && this.audioMap.has(key)) {
|
||||
this.touchAudioKey(key);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
@@ -312,23 +376,39 @@ class AudioManager {
|
||||
return this.warmKeys(["点击按钮"], { concurrency: 1, retries: 1 });
|
||||
}
|
||||
|
||||
warmAll() {
|
||||
if (this.warmAllPromise) return this.warmAllPromise;
|
||||
warmCommon() {
|
||||
if (this.warmPriorityPromise) return this.warmPriorityPromise;
|
||||
|
||||
debugLog("开始后台分级预热全部语音");
|
||||
debugLog("开始后台预热常用语音");
|
||||
this.isLoading = true;
|
||||
this.warmAllPromise = this.warmKeys(this.getWarmupKeys())
|
||||
this.warmPriorityPromise = this.warmKeys(this.getWarmupKeys())
|
||||
.catch((error) => {
|
||||
debugLog("后台语音预热异常", error);
|
||||
debugLog("常用语音预热异常", error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
debugLog("后台语音预热结束", this.getLoadProgress());
|
||||
debugLog("常用语音预热结束", this.getRuntimeStats());
|
||||
});
|
||||
return this.warmPriorityPromise;
|
||||
}
|
||||
|
||||
warmAll() {
|
||||
if (this.warmAllPromise) return this.warmAllPromise;
|
||||
|
||||
debugLog("音频测试页开始遍历全部语音");
|
||||
this.isLoading = true;
|
||||
this.warmAllPromise = this.warmKeys(this.getAllAudioKeys())
|
||||
.catch((error) => {
|
||||
debugLog("全部语音遍历异常", error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false;
|
||||
debugLog("全部语音遍历结束", this.getRuntimeStats());
|
||||
});
|
||||
return this.warmAllPromise;
|
||||
}
|
||||
|
||||
// 保留旧入口兼容测试页或其他历史调用,实际行为改为非阻塞后台预热。
|
||||
// 音频测试页保留全量遍历;实例池会及时淘汰旧实例,避免同时常驻全部语音。
|
||||
initAudios() {
|
||||
return this.warmAll();
|
||||
}
|
||||
@@ -366,6 +446,7 @@ class AudioManager {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pruneAudioInstances(key);
|
||||
const audio = uni.createInnerAudioContext();
|
||||
audio.autoplay = false;
|
||||
try {
|
||||
@@ -377,6 +458,7 @@ class AudioManager {
|
||||
} catch (_) {}
|
||||
this.allowPlayMap.set(key, false);
|
||||
audio.onPlay(() => {
|
||||
if (this.audioMap.get(key) !== audio) return;
|
||||
if (!this.allowPlayMap.get(key)) {
|
||||
try {
|
||||
audio.stop();
|
||||
@@ -414,6 +496,7 @@ class AudioManager {
|
||||
}
|
||||
this.readyMap.set(key, false);
|
||||
this.audioMap.delete(key);
|
||||
this.audioLastUsedAt.delete(key);
|
||||
try {
|
||||
audio.destroy();
|
||||
} catch (_) {}
|
||||
@@ -446,6 +529,7 @@ class AudioManager {
|
||||
if (!isCurrentGeneration()) {
|
||||
clearLoadTimeout();
|
||||
this.audioMap.delete(key);
|
||||
this.audioLastUsedAt.delete(key);
|
||||
try {
|
||||
audio.destroy();
|
||||
} catch (_) {}
|
||||
@@ -480,6 +564,7 @@ class AudioManager {
|
||||
});
|
||||
|
||||
audio.onEnded(() => {
|
||||
if (this.audioMap.get(key) !== audio) return;
|
||||
this.finishPlayback(key, {
|
||||
advanceSequence: true,
|
||||
emitEnded: true,
|
||||
@@ -487,10 +572,12 @@ class AudioManager {
|
||||
});
|
||||
|
||||
audio.onStop(() => {
|
||||
if (this.audioMap.get(key) !== audio) return;
|
||||
this.finishPlayback(key);
|
||||
});
|
||||
|
||||
this.audioMap.set(key, audio);
|
||||
this.touchAudioKey(key);
|
||||
audio.src = realSrc;
|
||||
};
|
||||
|
||||
@@ -700,14 +787,7 @@ class AudioManager {
|
||||
const loadingPromise = this.loadingKeyPromises.get(key);
|
||||
if (loadingPromise) return loadingPromise;
|
||||
|
||||
this.clearPlayWatchdog(key);
|
||||
const oldAudio = this.audioMap.get(key);
|
||||
if (oldAudio) {
|
||||
try {
|
||||
oldAudio.destroy();
|
||||
} catch (_) {}
|
||||
}
|
||||
this.audioMap.delete(key);
|
||||
this.destroyAudioInstance(key);
|
||||
this.readyMap.set(key, false);
|
||||
return this.ensureAudio(key);
|
||||
}
|
||||
@@ -754,7 +834,8 @@ class AudioManager {
|
||||
// 不打断当前播放:把新的队列加入到序列中,等待当前播放结束后衔接
|
||||
if (this.currentPlayingKey) {
|
||||
if (this.isSequenceRunning) {
|
||||
// 已有序列在跑:直接追加
|
||||
// 已有序列在跑:先移除已消费前缀,再追加待播语音。
|
||||
this.compactSequenceQueue(true);
|
||||
this.sequenceQueue = this.sequenceQueue.concat(queue);
|
||||
} else {
|
||||
// 没有序列但当前有正在播放的:以当前为序列的起点
|
||||
@@ -763,6 +844,17 @@ class AudioManager {
|
||||
this.sequenceIndex = 0;
|
||||
// 不触发 _playSingle,等待当前音频自然结束后由 onAudioEnded 接管
|
||||
}
|
||||
} else if (
|
||||
this.isSequenceRunning &&
|
||||
this.sequenceQueue[this.sequenceIndex]
|
||||
) {
|
||||
// 当前队首正在按需加载时只追加,不能覆盖已经排队的语音。
|
||||
this.compactSequenceQueue(true);
|
||||
this.sequenceQueue = this.sequenceQueue.concat(queue);
|
||||
const pendingKey = this.sequenceQueue[this.sequenceIndex];
|
||||
if (!this.pendingPlayKeys.has(pendingKey)) {
|
||||
this._playSingle(pendingKey, false);
|
||||
}
|
||||
} else {
|
||||
// 当前没有播放:直接启动新的序列
|
||||
this.sequenceQueue = queue;
|
||||
@@ -869,6 +961,7 @@ class AudioManager {
|
||||
this.currentPlayingKey = key;
|
||||
this.lastPlayKey = key;
|
||||
this.lastPlayAt = Date.now();
|
||||
this.touchAudioKey(key);
|
||||
this.startPlayWatchdog(key);
|
||||
} else {
|
||||
debugLog(`音频 ${key} 尚未就绪,按需加载后播放...`);
|
||||
@@ -885,7 +978,8 @@ class AudioManager {
|
||||
const nextIndex = this.sequenceIndex + 1;
|
||||
if (nextIndex < this.sequenceQueue.length) {
|
||||
this.sequenceIndex = nextIndex;
|
||||
const nextKey = this.sequenceQueue[nextIndex];
|
||||
this.compactSequenceQueue();
|
||||
const nextKey = this.sequenceQueue[this.sequenceIndex];
|
||||
this._playSingle(nextKey, false);
|
||||
} else {
|
||||
// 队列播放完成
|
||||
@@ -969,6 +1063,7 @@ class AudioManager {
|
||||
reloadAll() {
|
||||
debugLog("执行 reloadAll: 重置音频实例并后台恢复");
|
||||
const shouldWarmAll = this.warmAllPromise !== null;
|
||||
const shouldWarmPriority = this.warmPriorityPromise !== null;
|
||||
this.loadGeneration += 1;
|
||||
|
||||
// 1. 停止所有播放
|
||||
@@ -981,6 +1076,7 @@ class AudioManager {
|
||||
} catch (_) {}
|
||||
}
|
||||
this.audioMap.clear();
|
||||
this.audioLastUsedAt.clear();
|
||||
|
||||
// 3. 重置状态
|
||||
this.readyMap.clear();
|
||||
@@ -999,10 +1095,13 @@ class AudioManager {
|
||||
|
||||
// 4. 重置后台预热状态
|
||||
this.isLoading = false;
|
||||
this.warmPriorityPromise = null;
|
||||
this.warmAllPromise = null;
|
||||
|
||||
// 5. 之前已启动过全量预热则继续全量恢复,否则只恢复按钮音效。
|
||||
return shouldWarmAll ? this.warmAll() : this.warmButton();
|
||||
// 5. 按中断前使用范围恢复,产品页不再全量创建语音实例。
|
||||
if (shouldWarmAll) return this.warmAll();
|
||||
if (shouldWarmPriority) return this.warmCommon();
|
||||
return this.warmButton();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user