audio.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // pages/audio/audio.js
  2. const chapterApi = require('../../api/chapter');
  3. const historyApi = require('../../api/history');
  4. const userUtil = require('../../utils/user');
  5. Page({
  6. data: {
  7. contentId: null,
  8. audioId: null,
  9. audio: null,
  10. chapters: [],
  11. currentIndex: 0,
  12. playing: false,
  13. currentTime: 0,
  14. duration: 0,
  15. speed: 1.0, // 播放速度
  16. loading: true,
  17. audioContext: null
  18. },
  19. onLoad(options) {
  20. const contentId = parseInt(options.contentId);
  21. const audioId = parseInt(options.audioId);
  22. if (!contentId || !audioId) {
  23. wx.showToast({
  24. title: '参数错误',
  25. icon: 'none'
  26. });
  27. setTimeout(() => {
  28. wx.navigateBack();
  29. }, 1500);
  30. return;
  31. }
  32. this.setData({ contentId, audioId });
  33. this.loadChapters();
  34. this.loadAudio();
  35. this.initAudio();
  36. },
  37. onUnload() {
  38. // 页面卸载时停止播放
  39. if (this.data.audioContext) {
  40. this.data.audioContext.stop();
  41. }
  42. },
  43. // 初始化音频上下文
  44. initAudio() {
  45. const audioContext = wx.createInnerAudioContext();
  46. audioContext.onPlay(() => {
  47. this.setData({ playing: true });
  48. });
  49. audioContext.onPause(() => {
  50. this.setData({ playing: false });
  51. });
  52. audioContext.onTimeUpdate(() => {
  53. this.setData({
  54. currentTime: audioContext.currentTime,
  55. duration: audioContext.duration
  56. });
  57. });
  58. audioContext.onEnded(() => {
  59. this.setData({ playing: false });
  60. // 自动播放下一章
  61. this.nextChapter();
  62. });
  63. audioContext.onError((err) => {
  64. wx.showToast({
  65. title: '播放出错',
  66. icon: 'none'
  67. });
  68. console.error('音频播放错误:', err);
  69. });
  70. this.setData({ audioContext });
  71. },
  72. // 加载章节列表
  73. async loadChapters() {
  74. try {
  75. const chapters = await chapterApi.getAudioChapterList(this.data.contentId);
  76. const currentIndex = chapters.findIndex(c => c.audioId === this.data.audioId);
  77. this.setData({
  78. chapters,
  79. currentIndex: currentIndex >= 0 ? currentIndex : 0
  80. });
  81. } catch (error) {
  82. console.error('加载章节列表失败:', error);
  83. }
  84. },
  85. // 加载音频信息
  86. async loadAudio() {
  87. this.setData({ loading: true });
  88. try {
  89. const audio = await chapterApi.getAudioChapter(this.data.audioId);
  90. this.setData({ audio });
  91. // 设置音频源
  92. if (this.data.audioContext && audio.audioUrl) {
  93. this.data.audioContext.src = audio.audioUrl;
  94. this.data.audioContext.playbackRate = this.data.speed;
  95. }
  96. wx.setNavigationBarTitle({
  97. title: audio.chapterTitle || '听书'
  98. });
  99. } catch (error) {
  100. wx.showToast({
  101. title: error || '加载失败',
  102. icon: 'none'
  103. });
  104. } finally {
  105. this.setData({ loading: false });
  106. }
  107. },
  108. // 播放/暂停
  109. togglePlay() {
  110. if (!this.data.audioContext) return;
  111. if (this.data.playing) {
  112. this.data.audioContext.pause();
  113. } else {
  114. this.data.audioContext.play();
  115. }
  116. },
  117. // 上一章
  118. prevChapter() {
  119. if (this.data.currentIndex > 0) {
  120. const prevChapter = this.data.chapters[this.data.currentIndex - 1];
  121. this.setData({
  122. audioId: prevChapter.audioId,
  123. currentIndex: this.data.currentIndex - 1,
  124. playing: false,
  125. currentTime: 0
  126. });
  127. if (this.data.audioContext) {
  128. this.data.audioContext.stop();
  129. }
  130. this.loadAudio();
  131. } else {
  132. wx.showToast({
  133. title: '已经是第一章了',
  134. icon: 'none'
  135. });
  136. }
  137. },
  138. // 下一章
  139. nextChapter() {
  140. if (this.data.currentIndex < this.data.chapters.length - 1) {
  141. const nextChapter = this.data.chapters[this.data.currentIndex + 1];
  142. this.setData({
  143. audioId: nextChapter.audioId,
  144. currentIndex: this.data.currentIndex + 1,
  145. playing: false,
  146. currentTime: 0
  147. });
  148. if (this.data.audioContext) {
  149. this.data.audioContext.stop();
  150. }
  151. this.loadAudio();
  152. } else {
  153. wx.showToast({
  154. title: '已经是最后一章了',
  155. icon: 'none'
  156. });
  157. }
  158. },
  159. // 调整播放进度
  160. onSeek(e) {
  161. const time = e.detail.value;
  162. if (this.data.audioContext) {
  163. this.data.audioContext.seek(time);
  164. this.setData({ currentTime: time });
  165. }
  166. },
  167. // 调整播放速度
  168. changeSpeed(e) {
  169. const speed = parseFloat(e.detail.value);
  170. this.setData({ speed });
  171. if (this.data.audioContext) {
  172. this.data.audioContext.playbackRate = speed;
  173. }
  174. },
  175. // 显示章节列表
  176. showChapterList() {
  177. const itemList = this.data.chapters.map((item, index) =>
  178. `${index + 1}. ${item.chapterTitle}`
  179. );
  180. wx.showActionSheet({
  181. itemList,
  182. success: (res) => {
  183. const chapter = this.data.chapters[res.tapIndex];
  184. this.setData({
  185. audioId: chapter.audioId,
  186. currentIndex: res.tapIndex,
  187. playing: false,
  188. currentTime: 0
  189. });
  190. if (this.data.audioContext) {
  191. this.data.audioContext.stop();
  192. }
  193. this.loadAudio();
  194. }
  195. });
  196. },
  197. // 格式化时间
  198. formatTime(seconds) {
  199. if (!seconds || isNaN(seconds)) return '00:00';
  200. const mins = Math.floor(seconds / 60);
  201. const secs = Math.floor(seconds % 60);
  202. return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
  203. }
  204. });