audioManager2.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. // utils/audioManager.js
  2. export default {
  3. player: null,
  4. initPlayer() {
  5. if (!this.player) {
  6. this.player = uni.createInnerAudioContext()
  7. this.player.obeyMuteSwitch = false // iOS静音模式设置
  8. }
  9. return this.player
  10. },
  11. // 从单词数据中提取所有音频信息
  12. extractAudioInfo(wordData) {
  13. const audioInfo = {
  14. main: null,
  15. syllables: [],
  16. frequencies: []
  17. }
  18. // 主音频
  19. if (wordData.yinpin) {
  20. audioInfo.main = {
  21. id: `word_${wordData.id}_main`,
  22. url: wordData.yinpin,
  23. type: 'main',
  24. text: wordData.name
  25. }
  26. }
  27. // 音节音频
  28. if (wordData.yinjie && Array.isArray(wordData.yinjie)) {
  29. audioInfo.syllables = wordData.yinjie.map((item, index) => ({
  30. id: `word_${wordData.id}_syllable_${index}`,
  31. url: item.yinpin,
  32. type: 'syllable',
  33. text: item.cigen,
  34. index
  35. })).filter(item => item.url)
  36. }
  37. // 频度音频
  38. if (wordData.pindu && Array.isArray(wordData.pindu)) {
  39. audioInfo.frequencies = wordData.pindu.map((item, index) => ({
  40. id: `word_${wordData.id}_frequency_${index}`,
  41. url: item.yinpin,
  42. type: 'frequency',
  43. text: item.cigen,
  44. index
  45. })).filter(item => item.url)
  46. }
  47. return audioInfo
  48. },
  49. // 缓存单词的所有音频
  50. async cacheWordAudios(wordData) {
  51. try {
  52. // 1. 提取音频信息
  53. const { main, syllables, frequencies } = this.extractAudioInfo(wordData)
  54. // 2. 准备所有需要缓存的音频
  55. const allAudios = []
  56. if (main) allAudios.push(main)
  57. allAudios.push(...syllables)
  58. allAudios.push(...frequencies)
  59. // 3. 缓存所有音频
  60. await this.cacheAllAudios(allAudios)
  61. // 4. 保存音频信息到统一存储
  62. this.saveAudioInfoToStorage(wordData.id, {
  63. main,
  64. syllables,
  65. frequencies
  66. })
  67. return true
  68. } catch (error) {
  69. console.error('音频缓存失败:', error)
  70. return false
  71. }
  72. },
  73. // 保存音频信息到storage(结构化存储)
  74. saveAudioInfoToStorage(wordId, audioInfo) {
  75. const cacheKey = `word_audio_${wordId}`
  76. const cachedData = {
  77. timestamp: Date.now(),
  78. ...audioInfo
  79. }
  80. uni.setStorageSync(cacheKey, cachedData)
  81. },
  82. // 从storage获取音频信息
  83. getAudioInfoFromStorage(wordId) {
  84. const cacheKey = `word_audio_${wordId}`
  85. return uni.getStorageSync(cacheKey) || null
  86. },
  87. // 缓存所有音频
  88. async cacheAllAudios(audioList) {
  89. const cachePromises = audioList.map(audio => {
  90. return this.cacheSingleAudio(audio.id, audio.url).catch(e => {
  91. console.warn(`音频 ${audio.id} 预加载失败:`, e)
  92. return null
  93. })
  94. })
  95. await Promise.all(cachePromises)
  96. },
  97. // 缓存单个音频
  98. async cacheSingleAudio(id, url) {
  99. return new Promise((resolve, reject) => {
  100. uni.downloadFile({
  101. url,
  102. success: (res) => {
  103. if (res.statusCode === 200) {
  104. // 存储音频文件路径
  105. uni.setStorageSync(`audio_file_${id}`, res.tempFilePath)
  106. resolve()
  107. } else {
  108. reject(new Error(`下载失败,状态码: ${res.statusCode}`))
  109. }
  110. },
  111. fail: reject
  112. })
  113. })
  114. },
  115. // 播放音频
  116. playAudio(audioInfo) {
  117. if (!audioInfo || !audioInfo.url) return
  118. const player = this.initPlayer()
  119. player.stop()
  120. // 尝试从缓存获取
  121. const cachedPath = uni.getStorageSync(`audio_file_${audioInfo.id}`)
  122. if (cachedPath) {
  123. // 有缓存,直接播放
  124. player.src = cachedPath
  125. player.play()
  126. } else {
  127. // 无缓存,从网络播放并后台缓存
  128. console.warn(`音频 ${audioInfo.id} 未缓存,从网络加载`)
  129. player.src = audioInfo.url
  130. player.play()
  131. // 后台缓存
  132. this.cacheSingleAudio(audioInfo.id, audioInfo.url).catch(console.error)
  133. }
  134. player.onError((err) => {
  135. console.error('播放失败:', err)
  136. uni.showToast({ title: '播放失败', icon: 'none' })
  137. })
  138. return player
  139. },
  140. // 清理指定单词的缓存
  141. clearWordCache(wordId) {
  142. // 1. 清理音频信息
  143. uni.removeStorageSync(`word_audio_${wordId}`)
  144. // 2. 清理所有相关音频文件
  145. const prefix = `word_${wordId}_`
  146. const keys = uni.getStorageInfoSync().keys
  147. keys.filter(key => key.startsWith('audio_file_') && key.includes(prefix))
  148. .forEach(key => uni.removeStorageSync(key))
  149. },
  150. // 清理所有缓存
  151. clearAllCache() {
  152. // 1. 清理所有音频信息
  153. const keys = uni.getStorageInfoSync().keys
  154. keys.filter(key => key.startsWith('word_audio_'))
  155. .forEach(key => uni.removeStorageSync(key))
  156. // 2. 清理所有音频文件
  157. keys.filter(key => key.startsWith('audio_file_'))
  158. .forEach(key => uni.removeStorageSync(key))
  159. }
  160. }