audioManager4444.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. export default {
  2. // 音频播放器实例
  3. player: null,
  4. // 当前运行环境检测
  5. isApp: null,
  6. // 初始化方法
  7. init() {
  8. // 检测运行环境
  9. const systemInfo = uni.getSystemInfoSync();
  10. this.isApp = systemInfo.platform === 'ios' || systemInfo.platform === 'android';
  11. // 初始化播放器
  12. if (!this.player) {
  13. this.player = uni.createInnerAudioContext();
  14. this.player.obeyMuteSwitch = false; // iOS静音模式下也可播放
  15. this.player.onError((err) => {
  16. console.error('音频播放器错误:', err);
  17. uni.showToast({
  18. title: '播放失败,请稍后再试',
  19. icon: 'none',
  20. duration: 2000
  21. });
  22. });
  23. }
  24. },
  25. // 从单词数据中提取所有音频信息(完整实现)
  26. extractAudioInfo(wordData) {
  27. if (!wordData || typeof wordData !== 'object') {
  28. console.error('无效的单词数据');
  29. return {
  30. main: null,
  31. syllables: [],
  32. frequencies: []
  33. };
  34. }
  35. const audioInfo = {
  36. main: null,
  37. syllables: [],
  38. frequencies: []
  39. };
  40. // 处理主音频
  41. if (wordData.yinpin && typeof wordData.yinpin === 'string') {
  42. audioInfo.main = {
  43. id: `word_${wordData.id}_main`,
  44. url: this.normalizeAudioUrl(wordData.yinpin),
  45. type: 'main',
  46. text: wordData.name || '未知单词',
  47. lastCached: 0 // 最后缓存时间戳
  48. };
  49. }
  50. // 处理音节音频
  51. if (Array.isArray(wordData.yinjie)) {
  52. audioInfo.syllables = wordData.yinjie
  53. .filter(item => item && typeof item === 'object')
  54. .map((item, index) => ({
  55. id: `word_${wordData.id}_syllable_${index}`,
  56. url: this.normalizeAudioUrl(item.yinpin),
  57. type: 'syllable',
  58. text: item.cigen || `音节${index + 1}`,
  59. index,
  60. lastCached: 0
  61. }))
  62. .filter(item => item.url);
  63. }
  64. // 处理频度音频
  65. if (Array.isArray(wordData.pindu)) {
  66. audioInfo.frequencies = wordData.pindu
  67. .filter(item => item && typeof item === 'object')
  68. .map((item, index) => ({
  69. id: `word_${wordData.id}_frequency_${index}`,
  70. url: this.normalizeAudioUrl(item.yinpin),
  71. type: 'frequency',
  72. text: item.cigen || `频度${index + 1}`,
  73. index,
  74. lastCached: 0
  75. }))
  76. .filter(item => item.url);
  77. }
  78. return audioInfo;
  79. },
  80. // 标准化音频URL(完整实现)
  81. normalizeAudioUrl(url) {
  82. if (!url || typeof url !== 'string') return null;
  83. // 已经是完整URL
  84. if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('blob:')) {
  85. return url;
  86. }
  87. // 处理相对路径(根据实际项目配置)
  88. const baseUrl = 'https://your-audio-server.com/'; // 替换为你的音频服务器地址
  89. return baseUrl + url.replace(/^\//, '');
  90. },
  91. // 缓存单词的所有音频(完整实现)
  92. async cacheWordAudios(wordData) {
  93. try {
  94. if (!wordData || !wordData.id) {
  95. throw new Error('无效的单词数据');
  96. }
  97. // 提取音频信息
  98. const audioInfo = this.extractAudioInfo(wordData);
  99. const allAudios = [];
  100. if (audioInfo.main) allAudios.push(audioInfo.main);
  101. if (audioInfo.syllables.length) allAudios.push(...audioInfo.syllables);
  102. if (audioInfo.frequencies.length) allAudios.push(...audioInfo.frequencies);
  103. // 真实下载音频文件(仅App环境)
  104. if (this.isApp) {
  105. await this.downloadAudioFiles(allAudios);
  106. }
  107. // 保存音频信息到缓存
  108. this.saveAudioInfoToStorage(wordData.id, audioInfo);
  109. return true;
  110. } catch (error) {
  111. console.error('缓存单词音频失败:', error);
  112. return false;
  113. }
  114. },
  115. // 下载音频文件(完整实现)
  116. async downloadAudioFiles(audioList) {
  117. if (!Array.isArray(audioList)) return;
  118. const downloadTasks = audioList.map(audio => {
  119. return new Promise((resolve, reject) => {
  120. // 检查是否已有缓存
  121. const cacheInfo = this.getAudioCacheInfo(audio.id);
  122. if (cacheInfo && cacheInfo.cached) {
  123. audio.lastCached = cacheInfo.timestamp;
  124. return resolve();
  125. }
  126. // 开始下载
  127. uni.downloadFile({
  128. url: audio.url,
  129. success: (res) => {
  130. if (res.statusCode === 200) {
  131. // 保存到缓存
  132. uni.setStorageSync(`audio_file_${audio.id}`, res.tempFilePath);
  133. uni.setStorageSync(`audio_cache_time_${audio.id}`, Date.now());
  134. audio.lastCached = Date.now();
  135. console.log(`音频缓存成功: ${audio.id}`);
  136. resolve();
  137. } else {
  138. reject(new Error(`下载失败,状态码: ${res.statusCode}`));
  139. }
  140. },
  141. fail: (err) => {
  142. reject(new Error(`下载失败: ${err.errMsg}`));
  143. }
  144. });
  145. }).catch(err => {
  146. console.warn(`音频下载失败 ${audio.id}:`, err);
  147. // 单个失败不影响其他
  148. });
  149. });
  150. await Promise.all(downloadTasks);
  151. },
  152. // 获取音频缓存信息(完整实现)
  153. getAudioCacheInfo(audioId) {
  154. if (!audioId) return null;
  155. const cachedPath = uni.getStorageSync(`audio_file_${audioId}`);
  156. if (!cachedPath) return null;
  157. return {
  158. cached: true,
  159. path: cachedPath,
  160. timestamp: uni.getStorageSync(`audio_cache_time_${audioId}`) || 0
  161. };
  162. },
  163. // 保存音频信息到Storage(完整实现)
  164. saveAudioInfoToStorage(wordId, audioInfo) {
  165. if (!wordId || !audioInfo) return;
  166. const cacheKey = `word_audio_${wordId}`;
  167. const cacheData = {
  168. ...audioInfo,
  169. timestamp: Date.now(),
  170. wordId: wordId
  171. };
  172. try {
  173. uni.setStorageSync(cacheKey, cacheData);
  174. } catch (e) {
  175. console.error('存储音频信息失败:', e);
  176. }
  177. },
  178. // 从Storage获取音频信息(完整实现)
  179. getAudioInfoFromStorage(wordId) {
  180. if (!wordId) return null;
  181. const cacheKey = `word_audio_${wordId}`;
  182. try {
  183. return uni.getStorageSync(cacheKey) || null;
  184. } catch (e) {
  185. console.error('读取音频信息失败:', e);
  186. return null;
  187. }
  188. },
  189. // 播放音频(完整实现)
  190. playAudio(audioInfo) {
  191. if (!audioInfo || !audioInfo.url) {
  192. console.error('无效的音频信息');
  193. return null;
  194. }
  195. // 确保播放器初始化
  196. this.init();
  197. // 停止当前播放
  198. this.player.stop();
  199. // 在App环境下优先使用缓存
  200. if (this.isApp) {
  201. const cacheInfo = this.getAudioCacheInfo(audioInfo.id);
  202. if (cacheInfo && cacheInfo.cached) {
  203. console.log(`使用缓存播放: ${audioInfo.id}`);
  204. this.player.src = cacheInfo.path;
  205. this.player.play();
  206. return this.player;
  207. }
  208. }
  209. // 无缓存或非App环境,使用网络播放
  210. console.log(`使用网络播放: ${audioInfo.id}`);
  211. this.player.src = audioInfo.url;
  212. this.player.play();
  213. // App环境下后台尝试缓存
  214. if (this.isApp) {
  215. this.downloadAudioFiles([audioInfo]).then(() => {
  216. console.log(`音频后台缓存成功: ${audioInfo.id}`);
  217. }).catch(err => {
  218. console.warn(`音频后台缓存失败: ${audioInfo.id}`, err);
  219. });
  220. }
  221. return this.player;
  222. },
  223. // // 清理指定单词的缓存(完整实现)
  224. // clearWordCache(wordId) {
  225. // if (!wordId) return;
  226. // // 清理音频信息
  227. // uni.removeStorageSync(`word_audio_${wordId}`);
  228. // // 清理所有相关音频文件
  229. // const prefix = `word_${wordId}_`;
  230. // const keys = uni.getStorageInfoSync().keys;
  231. // keys.forEach(key => {
  232. // if (key.startsWith('audio_file_') && key.includes(prefix)) {
  233. // uni.removeStorageSync(key);
  234. // }
  235. // if (key.startsWith('audio_cache_time_') && key.includes(prefix)) {
  236. // uni.removeStorageSync(key);
  237. // }
  238. // });
  239. // },
  240. // 清理所有缓存(完整实现)
  241. clearAllCache() {
  242. const keys = uni.getStorageInfoSync().keys;
  243. // 清理所有单词音频信息
  244. keys.filter(key => key.startsWith('word_audio_'))
  245. .forEach(key => uni.removeStorageSync(key));
  246. // 清理所有音频文件
  247. keys.filter(key => key.startsWith('audio_file_'))
  248. .forEach(key => uni.removeStorageSync(key));
  249. },
  250. // // 获取缓存大小(完整实现)
  251. // getCacheSize() {
  252. // const keys = uni.getStorageInfoSync().keys;
  253. // let size = 0;
  254. // keys.filter(key => key.startsWith('audio_file_'))
  255. // .forEach(key => {
  256. // try {
  257. // const filePath = uni.getStorageSync(key);
  258. // if (filePath && typeof filePath === 'string') {
  259. // // 注意:uni-app中无法直接获取文件大小,这里返回文件数量
  260. // size += 1;
  261. // }
  262. // } catch (e) {
  263. // console.error(`获取缓存大小失败: ${key}`, e);
  264. // }
  265. // });
  266. // return {
  267. // fileCount: size,
  268. // // 实际项目中可以添加更精确的大小计算
  269. // estimatedSize: size * 1024 * 100 // 假设每个文件约100KB
  270. // };
  271. // }
  272. };