From 910530748de00ade2fdb1740b5c396fe6a9ad235 Mon Sep 17 00:00:00 2001 From: kron Date: Tue, 30 Dec 2025 18:10:31 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=86=E8=8A=82=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/audioManager.js | 156 ++++++++++++++++++++++++++----- src/components/BowTarget.vue | 64 +++++++------ src/components/Container.vue | 23 +++-- src/components/PlayerScore.vue | 24 +++-- src/components/PointSwitcher.vue | 70 ++++++++++++++ src/pages/index.vue | 1 + src/pages/melee-match.vue | 4 +- 7 files changed, 270 insertions(+), 72 deletions(-) create mode 100644 src/components/PointSwitcher.vue diff --git a/src/audioManager.js b/src/audioManager.js index f8608a5..0b01a5f 100644 --- a/src/audioManager.js +++ b/src/audioManager.js @@ -128,9 +128,42 @@ class AudioManager { this.failedLoadKeys = new Set(); // 加载代数,用于 reloadAll 时作废旧的加载循环 this.loadGeneration = 0; + // 本地路径缓存 Map: { url: localPath } + this.localFileCache = uni.getStorageSync("audio_local_files") || {}; + // 启动时自动清理过期的缓存文件(URL 已不在 audioFils 中的文件) + this.cleanObsoleteCache(); this.initAudios(); } + // 清理不再使用的缓存文件 + cleanObsoleteCache() { + const activeUrls = new Set(Object.values(audioFils)); + const cachedUrls = Object.keys(this.localFileCache); + let hasChanges = false; + + for (const url of cachedUrls) { + if (!activeUrls.has(url)) { + debugLog(`发现废弃音频缓存,正在清理: ${url}`); + const path = this.localFileCache[url]; + // 移除物理文件 + uni.removeSavedFile({ + filePath: path, + complete: () => { + // 忽略移除结果,直接移除记录 + }, + }); + // 移除记录 + delete this.localFileCache[url]; + hasChanges = true; + } + } + + if (hasChanges) { + uni.setStorageSync("audio_local_files", this.localFileCache); + debugLog("废弃缓存清理完成"); + } + } + // 初始化音频(两阶段:首轮串行加载全部,次轮仅串行加载失败项一次) initAudios() { if (this.isLoading) { @@ -244,7 +277,7 @@ class AudioManager { }); } - // 创建单个音频实例(统一在失败时记录,而非立即重试) + // 创建单个音频实例(支持本地缓存) createAudio(key, callback) { this.currentLoadingIndex++; const src = audioFils[key]; @@ -287,7 +320,7 @@ class AudioManager { clearTimeout(loadTimeout); this.readyMap.set(key, true); this.failedLoadKeys.delete(key); - debugLog(`音频 ${key} 已加载完成`, this.getLoadProgress()); + // debugLog(`音频 ${key} 已加载完成`, this.getLoadProgress()); uni.$emit("audioLoaded", key); const loadedAudioKeys = uni.getStorageSync("loadedAudioKeys") || {}; loadedAudioKeys[key] = true; @@ -298,11 +331,20 @@ class AudioManager { audio.onError((res) => { clearTimeout(loadTimeout); debugLog(`音频 ${key} 加载失败:`, res.errMsg); + // 如果是本地文件加载失败,可能是文件损坏,清除缓存以便下次重新下载 + if (realSrc !== src && this.localFileCache[src] === realSrc) { + debugLog(`本地缓存失效,移除记录: ${key}`); + delete this.localFileCache[src]; + uni.setStorageSync("audio_local_files", this.localFileCache); + // 移除文件 + uni.removeSavedFile({ filePath: realSrc }); + } this.recordLoadFailure(key); this.audioMap.delete(key); audio.destroy(); if (this.readyMap.get(key)) { - this.readyMap.set(key, false); + // 这里不要去除,不然检查进度的时候由于没有重新加载而进度卡住,等播放失败的时候会重新加载 + // this.readyMap.set(key, false); } else { if (callback) callback(); } @@ -326,23 +368,72 @@ class AudioManager { this.audioMap.set(key, audio); }; - // 统一先下载到本地再加载 - uni.downloadFile({ - url: src, - timeout: 20000, - success: (res) => { - // 成功必须有临时文件路径 - if (res.tempFilePath) { - setupAudio(res.tempFilePath); - } else { - this.recordLoadFailure(key); - if (callback) callback(); - } - }, - fail: () => { - this.recordLoadFailure(key); - if (callback) callback(); - }, + // 检查是否有可用的本地缓存 + this.checkLocalFile(src).then((localPath) => { + if (localPath) { + debugLog(`命中本地缓存: ${key}`); + setupAudio(localPath); + } else { + console.log("download"); + // 下载并尝试保存 + uni.downloadFile({ + url: src, + timeout: 20000, + success: (res) => { + if (res.tempFilePath) { + // 尝试保存文件到本地存储(持久化) + uni.saveFile({ + tempFilePath: res.tempFilePath, + success: (saveRes) => { + const savedPath = saveRes.savedFilePath; + this.localFileCache[src] = savedPath; + uni.setStorageSync("audio_local_files", this.localFileCache); + debugLog(`音频已缓存到本地: ${key}`); + setupAudio(savedPath); + }, + fail: (err) => { + debugLog( + `保存音频失败(可能空间不足),使用临时文件: ${key}`, + err + ); + setupAudio(res.tempFilePath); + }, + }); + } else { + this.recordLoadFailure(key); + if (callback) callback(); + } + }, + fail: () => { + this.recordLoadFailure(key); + if (callback) callback(); + }, + }); + } + }); + } + + // 检查本地文件是否有效 + checkLocalFile(url) { + return new Promise((resolve) => { + const path = this.localFileCache[url]; + if (!path) { + resolve(null); + return; + } + // 检查文件是否存在 + uni.getFileSystemManager().getFileInfo({ + filePath: path, + success: () => { + resolve(path); + }, + fail: () => { + // 文件不存在,清理记录 + delete this.localFileCache[url]; + uni.setStorageSync("audio_local_files", this.localFileCache); + resolve(null); + }, + }); }); } @@ -550,9 +641,28 @@ class AudioManager { return Number((loaded / total).toFixed(2)); } - + // 清理本地音频缓存文件 + clearCache() { + debugLog("开始清理本地音频缓存..."); + const cache = uni.getStorageSync("audio_local_files") || {}; + const paths = Object.values(cache); + for (const path of paths) { + uni.removeSavedFile({ + filePath: path, + complete: (res) => { + // 无论成功失败都继续 + }, + }); + } + uni.removeStorageSync("audio_local_files"); + this.localFileCache = {}; + debugLog("本地音频缓存清理完成"); + } + // 手动重置并重新加载所有音频(用于卡住时恢复) reloadAll() { + debugLog("执行 reloadAll: 重置所有状态并重新加载"); + // 1. 停止所有播放 this.stopAll(); @@ -573,6 +683,10 @@ class AudioManager { this.sequenceIndex = 0; this.isSequenceRunning = false; + // 清理一下可能损坏的缓存(可选,如果用户因为缓存坏了卡住,这一步很有用) + // 这里选择不自动全清,而是依赖 onError 里的单点清除。如果需要彻底重置,可取消注释: + // this.clearCache(); + // 4. 强制重置加载锁 this.isLoading = false; this.loadingPromise = null; diff --git a/src/components/BowTarget.vue b/src/components/BowTarget.vue index 431fc90..8d92d1e 100644 --- a/src/components/BowTarget.vue +++ b/src/components/BowTarget.vue @@ -1,6 +1,8 @@