player.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. "use strict";
  2. const common_vendor = require("../../common/vendor.js");
  3. const utils_api = require("../../utils/api.js");
  4. const _sfc_main = {
  5. data() {
  6. return {
  7. audiobookId: null,
  8. chapterId: null,
  9. bookInfo: {
  10. id: null,
  11. title: "",
  12. author: "",
  13. image: ""
  14. },
  15. chapterInfo: {
  16. id: null,
  17. title: "",
  18. audioUrl: "",
  19. duration: 0
  20. },
  21. isPlaying: false,
  22. currentTime: "00:00",
  23. currentSeconds: 0,
  24. totalTime: "00:00",
  25. totalSeconds: 0,
  26. progressPercent: 0,
  27. audioContext: null,
  28. playTimer: null,
  29. progressSaveTimer: null,
  30. userInfo: null,
  31. isDragging: false
  32. };
  33. },
  34. onLoad(options) {
  35. if (options.audiobookId) {
  36. this.audiobookId = parseInt(options.audiobookId);
  37. } else if (options.bookId) {
  38. this.audiobookId = parseInt(options.bookId);
  39. }
  40. if (options.title) {
  41. this.bookInfo.title = decodeURIComponent(options.title);
  42. }
  43. if (options.image) {
  44. this.bookInfo.image = decodeURIComponent(options.image);
  45. }
  46. if (options.author) {
  47. this.bookInfo.author = decodeURIComponent(options.author);
  48. }
  49. if (options.chapterId) {
  50. this.chapterId = parseInt(options.chapterId);
  51. }
  52. if (options.audioUrl) {
  53. this.chapterInfo.audioUrl = decodeURIComponent(options.audioUrl);
  54. }
  55. this.loadUserInfo();
  56. if (this.chapterId) {
  57. this.initPlayer();
  58. }
  59. },
  60. onUnload() {
  61. this.saveProgress();
  62. if (this.audioContext) {
  63. this.audioContext.destroy();
  64. }
  65. if (this.playTimer) {
  66. clearInterval(this.playTimer);
  67. }
  68. if (this.progressSaveTimer) {
  69. clearInterval(this.progressSaveTimer);
  70. }
  71. },
  72. methods: {
  73. loadUserInfo() {
  74. try {
  75. const userInfo = common_vendor.index.getStorageSync("userInfo");
  76. const isLogin = common_vendor.index.getStorageSync("isLogin");
  77. if (userInfo && userInfo.id && isLogin) {
  78. this.userInfo = userInfo;
  79. }
  80. } catch (e) {
  81. common_vendor.index.__f__("error", "at pages/player/player.vue:146", "获取用户信息失败:", e);
  82. }
  83. },
  84. async initPlayer() {
  85. try {
  86. if (this.chapterId) {
  87. const res = await utils_api.getChapterDetail(this.chapterId);
  88. if (res && res.code === 200 && res.data) {
  89. this.chapterInfo = {
  90. id: res.data.id,
  91. title: res.data.title || "",
  92. audioUrl: res.data.audioUrl || this.chapterInfo.audioUrl,
  93. duration: res.data.duration || 0
  94. };
  95. this.totalSeconds = this.chapterInfo.duration;
  96. this.totalTime = this.formatTime(this.totalSeconds);
  97. }
  98. }
  99. if (this.userInfo && this.userInfo.id && this.chapterId) {
  100. const progressRes = await utils_api.getListeningProgress(this.userInfo.id, this.chapterId);
  101. if (progressRes && progressRes.code === 200 && progressRes.data) {
  102. const progress = progressRes.data;
  103. this.currentSeconds = progress.currentPosition || 0;
  104. this.currentTime = this.formatTime(this.currentSeconds);
  105. this.progressPercent = this.totalSeconds > 0 ? this.currentSeconds / this.totalSeconds * 100 : 0;
  106. }
  107. }
  108. this.audioContext = common_vendor.index.createInnerAudioContext();
  109. this.audioContext.src = this.chapterInfo.audioUrl;
  110. this.audioContext.startTime = this.currentSeconds;
  111. this.audioContext.onPlay(() => {
  112. this.isPlaying = true;
  113. this.startProgressTimer();
  114. });
  115. this.audioContext.onPause(() => {
  116. this.isPlaying = false;
  117. this.stopProgressTimer();
  118. });
  119. this.audioContext.onEnded(() => {
  120. this.isPlaying = false;
  121. this.stopProgressTimer();
  122. });
  123. this.audioContext.onTimeUpdate(() => {
  124. if (!this.isDragging) {
  125. this.currentSeconds = Math.floor(this.audioContext.currentTime);
  126. this.currentTime = this.formatTime(this.currentSeconds);
  127. if (this.totalSeconds > 0) {
  128. this.progressPercent = this.currentSeconds / this.totalSeconds * 100;
  129. }
  130. }
  131. });
  132. if (this.audiobookId && this.chapterId) {
  133. try {
  134. await utils_api.playChapter(this.audiobookId, this.chapterId);
  135. } catch (e) {
  136. common_vendor.index.__f__("error", "at pages/player/player.vue:215", "记录播放失败:", e);
  137. }
  138. }
  139. if (this.userInfo && this.userInfo.id && this.audiobookId && this.chapterId) {
  140. try {
  141. await utils_api.recordListeningHistory(this.userInfo.id, this.audiobookId, this.chapterId);
  142. } catch (e) {
  143. common_vendor.index.__f__("error", "at pages/player/player.vue:224", "记录听书历史失败:", e);
  144. }
  145. }
  146. this.progressSaveTimer = setInterval(() => {
  147. this.saveProgress();
  148. }, 3e4);
  149. } catch (e) {
  150. common_vendor.index.__f__("error", "at pages/player/player.vue:234", "初始化播放器失败:", e);
  151. common_vendor.index.showToast({
  152. title: "加载失败,请重试",
  153. icon: "none"
  154. });
  155. }
  156. },
  157. goBack() {
  158. this.saveProgress();
  159. common_vendor.index.navigateBack();
  160. },
  161. handleShare() {
  162. common_vendor.index.showToast({
  163. title: "分享",
  164. icon: "none"
  165. });
  166. },
  167. handleAddToShelf() {
  168. common_vendor.index.showToast({
  169. title: "已加入书架",
  170. icon: "success"
  171. });
  172. },
  173. togglePlay() {
  174. if (!this.audioContext) {
  175. common_vendor.index.showToast({
  176. title: "播放器未初始化",
  177. icon: "none"
  178. });
  179. return;
  180. }
  181. if (this.isPlaying) {
  182. this.audioContext.pause();
  183. } else {
  184. this.audioContext.play();
  185. }
  186. },
  187. startProgressTimer() {
  188. },
  189. stopProgressTimer() {
  190. },
  191. playPrevious() {
  192. common_vendor.index.showToast({
  193. title: "上一章功能待实现",
  194. icon: "none"
  195. });
  196. },
  197. playNext() {
  198. common_vendor.index.showToast({
  199. title: "下一章功能待实现",
  200. icon: "none"
  201. });
  202. },
  203. rewind15s() {
  204. if (this.audioContext) {
  205. const newTime = Math.max(0, this.currentSeconds - 15);
  206. this.audioContext.seek(newTime);
  207. this.currentSeconds = newTime;
  208. this.currentTime = this.formatTime(newTime);
  209. if (this.totalSeconds > 0) {
  210. this.progressPercent = newTime / this.totalSeconds * 100;
  211. }
  212. }
  213. },
  214. forward15s() {
  215. if (this.audioContext) {
  216. const newTime = Math.min(this.totalSeconds, this.currentSeconds + 15);
  217. this.audioContext.seek(newTime);
  218. this.currentSeconds = newTime;
  219. this.currentTime = this.formatTime(newTime);
  220. if (this.totalSeconds > 0) {
  221. this.progressPercent = newTime / this.totalSeconds * 100;
  222. }
  223. }
  224. },
  225. handleProgressTouchStart(e) {
  226. this.isDragging = true;
  227. },
  228. handleProgressTouchMove(e) {
  229. },
  230. async handleProgressTouchEnd(e) {
  231. this.isDragging = false;
  232. if (this.audioContext && this.totalSeconds > 0) {
  233. const newTime = Math.floor(this.progressPercent / 100 * this.totalSeconds);
  234. this.audioContext.seek(newTime);
  235. this.currentSeconds = newTime;
  236. this.currentTime = this.formatTime(newTime);
  237. await this.saveProgress();
  238. }
  239. },
  240. async saveProgress() {
  241. if (this.userInfo && this.userInfo.id && this.audiobookId && this.chapterId) {
  242. try {
  243. await utils_api.saveListeningProgress(
  244. this.userInfo.id,
  245. this.audiobookId,
  246. this.chapterId,
  247. this.currentSeconds,
  248. this.totalSeconds
  249. );
  250. } catch (e) {
  251. common_vendor.index.__f__("error", "at pages/player/player.vue:344", "保存进度失败:", e);
  252. }
  253. }
  254. },
  255. formatTime(seconds) {
  256. if (!seconds || seconds < 0) {
  257. return "00:00";
  258. }
  259. const hours = Math.floor(seconds / 3600);
  260. const mins = Math.floor(seconds % 3600 / 60);
  261. const secs = seconds % 60;
  262. if (hours > 0) {
  263. return `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
  264. } else {
  265. return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
  266. }
  267. }
  268. }
  269. };
  270. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  271. return common_vendor.e({
  272. a: common_vendor.o((...args) => $options.goBack && $options.goBack(...args)),
  273. b: common_vendor.o((...args) => $options.handleShare && $options.handleShare(...args)),
  274. c: $data.bookInfo.image,
  275. d: common_vendor.t($data.bookInfo.title),
  276. e: $data.chapterInfo.title
  277. }, $data.chapterInfo.title ? {
  278. f: common_vendor.t($data.chapterInfo.title)
  279. } : {}, {
  280. g: common_vendor.t($data.bookInfo.author),
  281. h: common_vendor.o((...args) => $options.handleAddToShelf && $options.handleAddToShelf(...args)),
  282. i: $data.progressPercent + "%",
  283. j: !$data.isDragging
  284. }, !$data.isDragging ? {
  285. k: $data.progressPercent + "%"
  286. } : {}, {
  287. l: common_vendor.o((...args) => $options.handleProgressTouchStart && $options.handleProgressTouchStart(...args)),
  288. m: common_vendor.o((...args) => $options.handleProgressTouchMove && $options.handleProgressTouchMove(...args)),
  289. n: common_vendor.o((...args) => $options.handleProgressTouchEnd && $options.handleProgressTouchEnd(...args)),
  290. o: common_vendor.t($data.currentTime),
  291. p: common_vendor.t($data.totalTime),
  292. q: common_vendor.o((...args) => $options.rewind15s && $options.rewind15s(...args)),
  293. r: common_vendor.o((...args) => $options.playPrevious && $options.playPrevious(...args)),
  294. s: common_vendor.t($data.isPlaying ? "⏸" : "▶"),
  295. t: common_vendor.o((...args) => $options.togglePlay && $options.togglePlay(...args)),
  296. v: common_vendor.o((...args) => $options.playNext && $options.playNext(...args)),
  297. w: common_vendor.o((...args) => $options.forward15s && $options.forward15s(...args))
  298. });
  299. }
  300. const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-0391012f"]]);
  301. wx.createPage(MiniProgramPage);
  302. //# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/player/player.js.map