audioManager.js 5.9 KB

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