export const audioFils = { 激光已校准: "https://static.shelingxingqiu.com/attachment/2025-10-29/ddupaur1vdkyhzaqdc.mp3", 胜利: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcuo9yjp0kt5msvmvd.mp3", 失败: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcuo9yht2sdwhuqygy.mp3", 请射箭测试距离: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcuvj8avzqyw4hpq7t.mp3", 距离合格: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutwrda0amn5kqr4j.mp3", 距离不足: "https://static.shelingxingqiu.com/attachment/2025-11-12/de6hr2faw28t0ianh0.mp3", 轮到你了: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutzdrn4lxcpv8aqr.mp3", 第一轮: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutyyr9a7m1vz2w13.mp3", 第二轮: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutyyr9ldnfexjxtw.mp3", 第三轮: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutyyr97m4ipxaze4.mp3", 第四轮: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutyyr9x5addohlzf.mp3", 第五轮: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutyyr9d7lw2gebpv.mp3", 决金箭轮: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutwrd9zs4oi2kujv.mp3", 请蓝方射箭: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutzdrxcbe5ll46as.mp3", 请红方射箭: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutzdrl3re3dhlfjd.mp3", 中场休息: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutwrd9zdk1xyolst.mp3", 比赛结束: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutya59b6pu0ur4um.mp3", 比赛开始: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcuu5z3a3lumkutske.mp3", 请开始射击: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutzdrl5u0iromqhf.mp3", 射击无效: "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutya55ufiiw8oo55.mp3", 未上靶: "https://static.shelingxingqiu.com/attachment/2025-11-12/de6n45o3tsm1v4unam.mp3", "1环": "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin1aq7gxjih5l.mp3", "2环": "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin64tdgx2s4at.mp3", "3环": "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxinlmf87vt8z65.mp3", "4环": "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxinniv97sx0q9u.mp3", "5环": "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin7j01kknpb7k.mp3", "6环": "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin4syy1015rtq.mp3", "7环": "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin3iz3dvmjdai.mp3", "8环": "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxinnjd42lhpfiw.mp3", "9环": "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxin69nj1xh7yfz.mp3", "10环": "https://static.shelingxingqiu.com/attachment/2025-09-17/dcutxinnvsx0tt7ksa.mp3", 向上调整: "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellf5pfvu3l8dhr.mp3", 向右上调整: "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellf45v88pirarr.mp3", 向右调整: "https://static.shelingxingqiu.com/attachment/2025-11-12/de6elleqnhrenggxsb.mp3", 向右下调整: "https://static.shelingxingqiu.com/attachment/2025-11-12/de6elleo6q16qctf6a.mp3", 向下调整: "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellek2mu2cri2n9.mp3", 向左下调整: "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellf25yu1pt2k5r.mp3", 向左调整: "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellen3zoalxcb06.mp3", 向左上调整: "https://static.shelingxingqiu.com/attachment/2025-11-12/de6ellf37a2iw6w4pu.mp3", 最后30秒: "https://static.shelingxingqiu.com/attachment/2025-11-13/de7kzzllq0futwynso.mp3", }; // 版本控制日志函数 function debugLog(...args) { // 获取当前环境信息 const accountInfo = uni.getAccountInfoSync(); const envVersion = accountInfo.miniProgram.envVersion; // 只在体验版打印日志,正式版(release)和开发版(develop)不打印 if (envVersion === "trial") { console.log(...args); } } class AudioManager { constructor() { this.audioMap = new Map(); this.currentPlayingKey = null; this.retryCount = new Map(); this.maxRetries = 3; // 显式授权播放标记,防止 iOS 在设置 src 后误播 this.allowPlayMap = new Map(); // 串行加载相关属性 this.audioKeys = []; this.currentLoadingIndex = 0; this.isLoading = false; this.loadingPromise = null; // 连续播放队列相关属性 this.sequenceQueue = []; this.sequenceIndex = 0; this.isSequenceRunning = false; // 静音开关 this.isMuted = false; // 网络状态相关 this.networkOnline = true; this.pendingPlayKey = null; try { uni.onNetworkStatusChange(({ isConnected }) => { this.networkOnline = !!isConnected; if (this.networkOnline) { this.onNetworkRestored(); } else { this.onNetworkLost(); } }); } catch (_) {} this.initAudios(); } // 初始化音频 initAudios() { if (this.isLoading) { debugLog("音频正在加载中,跳过重复初始化"); return this.loadingPromise; } debugLog("开始串行加载音频..."); this.isLoading = true; this.audioKeys = Object.keys(audioFils); this.currentLoadingIndex = 0; this.loadingPromise = new Promise((resolve) => { this.loadNextAudio(resolve); }); return this.loadingPromise; } // 串行加载下一个音频 loadNextAudio(onComplete) { if (this.currentLoadingIndex >= this.audioKeys.length) { debugLog("所有音频加载完成"); this.isLoading = false; if (onComplete) onComplete(); return; } const key = this.audioKeys[this.currentLoadingIndex]; debugLog( `开始加载音频 ${this.currentLoadingIndex + 1}/${ this.audioKeys.length }: ${key}` ); this.createAudio(key, () => { this.currentLoadingIndex++; setTimeout(() => { this.loadNextAudio(onComplete); }, 100); }); } // 创建单个音频实例 createAudio(key, callback) { const src = audioFils[key]; const audio = uni.createInnerAudioContext(); audio.autoplay = false; audio.src = src; // 初始化音量(支持的平台用 volume,H5 也可用 muted 作为兜底) try { if (typeof audio.volume === "number") { audio.volume = this.isMuted ? 0 : 1; } else if (typeof audio.muted !== "undefined") { audio.muted = this.isMuted; } } catch (_) {} // 初始化为不允许播放,只有显式 play() 才允许 this.allowPlayMap.set(key, false); // 防止 iOS 误播:非授权播放立刻停止 audio.onPlay(() => { if (!this.allowPlayMap.get(key)) { try { audio.stop(); } catch (_) {} } }); // 设置加载超时 const loadTimeout = setTimeout(() => { debugLog(`音频 ${key} 加载超时`); audio.destroy(); if (callback) callback(); }, 10000); // 监听加载状态 audio.onCanplay(() => { // 预加载阶段:仅在未授权情况下暂停,避免用户刚点击播放被打断 if (!this.allowPlayMap.get(key)) { try { audio.pause(); } catch (_) {} } clearTimeout(loadTimeout); debugLog(`音频 ${key} 已加载完成`); uni.$emit("audioLoaded", key); const loadedAudioKeys = uni.getStorageSync("loadedAudioKeys") || {}; loadedAudioKeys[key] = true; uni.setStorageSync("loadedAudioKeys", loadedAudioKeys); this.retryCount.set(key, 0); if (callback) callback(); }); audio.onError((res) => { clearTimeout(loadTimeout); debugLog(`音频 ${key} 加载失败:`, res.errMsg); this.allowPlayMap.set(key, false); this.handleAudioError(key); if (callback) callback(); }); // 监听播放结束事件 audio.onEnded(() => { if (this.currentPlayingKey === key) { this.currentPlayingKey = null; } this.allowPlayMap.set(key, false); // 触发连续播放队列的衔接 this.onAudioEnded(key); }); // 监听播放停止事件 audio.onStop(() => { if (this.currentPlayingKey === key) { this.currentPlayingKey = null; } this.allowPlayMap.set(key, false); }); this.audioMap.set(key, audio); if (!this.retryCount.has(key)) { this.retryCount.set(key, 0); } } // 处理音频加载错误 handleAudioError(key) { // 网络不可用时不重试,等重连后统一重建 if (!this.networkOnline) { debugLog(`网络不可用,暂不重试音频: ${key}`); return; } const currentRetries = this.retryCount.get(key) || 0; if (currentRetries < this.maxRetries) { this.retryCount.set(key, currentRetries + 1); debugLog(`音频 ${key} 开始第 ${currentRetries + 1} 次重试...`); setTimeout(() => { this.retryLoadAudio(key); }, 1000); } else { console.error( `音频 ${key} 重试 ${this.maxRetries} 次后仍然失败,停止重试` ); const failedAudio = this.audioMap.get(key); if (failedAudio) { failedAudio.destroy(); this.audioMap.delete(key); } } } // 重新加载音频 retryLoadAudio(key) { if (!this.networkOnline) { debugLog(`网络不可用,稍后再重载音频: ${key}`); return; } const oldAudio = this.audioMap.get(key); if (oldAudio) { oldAudio.destroy(); } this.createAudio(key); } // 播放指定音频或音频数组(数组则按顺序连续播放) play(input, interrupt = true) { // 统一规范化为队列 let queue = []; if (Array.isArray(input)) { queue = input.filter((k) => !!audioFils[k]); } else if (typeof input === "string") { queue = !!audioFils[input] ? [input] : []; } else { debugLog("play 参数类型无效,仅支持字符串或字符串数组"); return; } if (queue.length === 0) { debugLog("连续播放队列为空或无效"); return; } if (interrupt) { // 立即打断并启动新的播放序列 this.stopAll(); this.isSequenceRunning = false; this.sequenceQueue = []; this.sequenceIndex = 0; this.sequenceQueue = queue; this.sequenceIndex = 0; this.isSequenceRunning = true; this._playSingle(queue[0], false); return; } // 不打断当前播放:把新的队列加入到序列中,等待当前播放结束后衔接 if (this.currentPlayingKey) { if (this.isSequenceRunning) { // 已有序列在跑:直接追加 this.sequenceQueue = this.sequenceQueue.concat(queue); } else { // 没有序列但当前有正在播放的:以当前为序列的起点 this.isSequenceRunning = true; this.sequenceQueue = [this.currentPlayingKey].concat(queue); this.sequenceIndex = 0; // 不触发 _playSingle,等待当前音频自然结束后由 onAudioEnded 接管 } } else { // 当前没有播放:直接启动新的序列 this.sequenceQueue = queue; this.sequenceIndex = 0; this.isSequenceRunning = true; this._playSingle(queue[0], false); } } // 内部方法:播放单个 key _playSingle(key, forceStopAll = false) { if (!this.networkOnline) { const audio = this.audioMap.get(key); const isReady = this.readyMap && this.readyMap.get(key); // 离线但已就绪:允许直接播放;未就绪则记录等待重连 if (!audio || !isReady) { this.pendingPlayKey = key; debugLog(`网络不可用,记录播放: ${key}`); return; } } if (forceStopAll) { this.stopAll(); } else if (this.currentPlayingKey && this.currentPlayingKey !== key) { this.stop(this.currentPlayingKey); } const audio = this.audioMap.get(key); if (audio) { // 播放前确保遵循当前静音状态 try { if (typeof audio.volume === "number") { audio.volume = this.isMuted ? 0 : 1; } else if (typeof audio.muted !== "undefined") { audio.muted = this.isMuted; } } catch (_) {} // 同一音频:避免 stop() 触发 onStop 清除授权,使用 pause()+seek(0) try { audio.pause(); } catch (_) {} try { if (typeof audio.seek === "function") { audio.seek(0); } else { audio.startTime = 0; } } catch (_) { audio.startTime = 0; } // 显式授权播放并立即播放 this.allowPlayMap.set(key, true); audio.play(); this.currentPlayingKey = key; } else { debugLog(`音频 ${key} 不存在,尝试重新加载...`); this.reloadAudio(key); } } // 连续播放:在某个音频结束后,若处于队列播放状态则继续下一个 onAudioEnded(key) { if (!this.isSequenceRunning) return; const currentKey = this.sequenceQueue[this.sequenceIndex]; if (currentKey !== key) return; const nextIndex = this.sequenceIndex + 1; if (nextIndex < this.sequenceQueue.length) { this.sequenceIndex = nextIndex; const nextKey = this.sequenceQueue[nextIndex]; this._playSingle(nextKey, false); } else { // 队列播放完成 this.isSequenceRunning = false; this.sequenceQueue = []; this.sequenceIndex = 0; } } // 停止指定音频 stop(key) { const audio = this.audioMap.get(key); if (audio) { audio.stop(); this.allowPlayMap.set(key, false); if (this.currentPlayingKey === key) { this.currentPlayingKey = null; } } } // 停止所有音频 stopAll() { for (const [k, audio] of this.audioMap.entries()) { try { audio.stop(); } catch (_) {} this.allowPlayMap.set(k, false); } this.currentPlayingKey = null; } // 销毁所有音频实例并清理状态 destroyAll() { for (const [k, audio] of this.audioMap.entries()) { try { audio.destroy(); } catch (_) {} this.allowPlayMap.delete(k); this.retryCount.delete(k); this.audioMap.delete(k); } this.audioKeys = []; this.currentLoadingIndex = 0; this.isLoading = false; this.loadingPromise = null; this.currentPlayingKey = null; } // 断网回调:停止当前播放,避免误状态 onNetworkLost() { try { this.stopAll(); } catch (_) {} } // 重连回调:重建全部音频后,重放上一次的播放 onNetworkRestored() { // 网络恢复后,仅继续加载未就绪或缺失的音频,已就绪的不动,保证离线可播的缓存不被重置 const keys = Object.keys(audioFils); const needLoad = keys.filter( (k) => !this.readyMap.get(k) || !this.audioMap.has(k) ); if (needLoad.length > 0) { this.loadKeysSequentially(needLoad, () => { if (this.pendingPlayKey) { const pending = this.pendingPlayKey; this.pendingPlayKey = null; setTimeout(() => { this.play(pending); }, 20); } }); } else { // 没有需要加载的音频,直接处理待播放 if (this.pendingPlayKey) { const pending = this.pendingPlayKey; this.pendingPlayKey = null; setTimeout(() => { this.play(pending); }, 20); } } } // 重连后统一重建音频实例 reinitializeOnReconnect() { this.destroyAll(); this.initAudios(); } // 按自定义列表串行加载音频(避免并发过多) loadKeysSequentially(keys, onComplete) { let idx = 0; const next = () => { if (idx >= keys.length) { if (onComplete) onComplete(); return; } const k = keys[idx++]; // 已存在但未就绪:重载;不存在:创建 if (this.audioMap.has(k)) { this.retryLoadAudio(k); } else { this.createAudio(k, () => { setTimeout(next, 100); }); return; // createAudio 内部会触发 next } setTimeout(next, 100); }; next(); } // 手动重新加载指定音频 reloadAudio(key) { if (audioFils[key]) { debugLog(`手动重新加载音频: ${key}`); this.retryCount.set(key, 0); this.retryLoadAudio(key); } } // 设置静音开关:true 静音,false 取消静音 setMuted(muted) { this.isMuted = !!muted; for (const audio of this.audioMap.values()) { try { if (typeof audio.volume === "number") { audio.volume = this.isMuted ? 0 : 1; } else if (typeof audio.muted !== "undefined") { audio.muted = this.isMuted; } } catch (_) {} } debugLog(`静音状态已设置为: ${this.isMuted}`); } } // 导出单例 export default new AudioManager();