hookehuyr

fix(AudioPlayer): 修复音频加载逻辑并添加组件ID支持

- 修改音频加载逻辑,等待canplaythrough事件确保流畅播放
- 为AudioPlayer组件添加id prop用于唯一标识
- 重构音频控制方法,简化暂停逻辑
- 清理mock数据,保留一个带音频的测试用例
1 <!-- 1 <!--
2 * @Date: 2025-04-07 12:35:35 2 * @Date: 2025-04-07 12:35:35
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-05-30 15:26:15 4 + * @LastEditTime: 2025-05-30 18:03:29
5 * @FilePath: /mlaj/src/components/ui/AudioPlayer.vue 5 * @FilePath: /mlaj/src/components/ui/AudioPlayer.vue
6 * @Description: 音频播放器组件,支持播放控制、进度条调节、音量控制、播放列表等功能 6 * @Description: 音频播放器组件,支持播放控制、进度条调节、音量控制、播放列表等功能
7 --> 7 -->
...@@ -129,6 +129,7 @@ import { wxInfo } from '@/utils/tools' ...@@ -129,6 +129,7 @@ import { wxInfo } from '@/utils/tools'
129 129
130 // 组件属性定义 130 // 组件属性定义
131 const props = defineProps({ 131 const props = defineProps({
132 + id: { type: String }, // 组件ID
132 songs: { type: Array, required: true }, // 音频列表数据 133 songs: { type: Array, required: true }, // 音频列表数据
133 initialIndex: { type: Number, default: 0 } // 初始播放索引 134 initialIndex: { type: Number, default: 0 } // 初始播放索引
134 }) 135 })
...@@ -189,9 +190,17 @@ const loadAudio = async () => { ...@@ -189,9 +190,17 @@ const loadAudio = async () => {
189 audio.value.addEventListener('timeupdate', updateProgress) 190 audio.value.addEventListener('timeupdate', updateProgress)
190 audio.value.addEventListener('ended', handleEnded) 191 audio.value.addEventListener('ended', handleEnded)
191 192
192 - // 加载音频 193 + // 加载音频并等待可以流畅播放
193 - await audio.value.load() 194 + await audio.value?.load()
194 - return true 195 + return new Promise((resolve) => {
196 + audio.value.addEventListener('canplaythrough', () => {
197 + resolve(true)
198 + }, { once: true })
199 + audio.value.addEventListener('error', () => {
200 + console.error('音频加载失败')
201 + resolve(false)
202 + }, { once: true })
203 + })
195 } catch (error) { 204 } catch (error) {
196 console.error('加载音频失败:', error) 205 console.error('加载音频失败:', error)
197 audio.value = null 206 audio.value = null
...@@ -247,22 +256,6 @@ const togglePlay = async () => { ...@@ -247,22 +256,6 @@ const togglePlay = async () => {
247 } 256 }
248 } 257 }
249 258
250 -// 暴露给父组件的方法
251 -defineExpose({
252 - togglePlay,
253 - pause: () => {
254 - // 只有在播放状态且音频实例存在时才执行暂停
255 - if (isPlaying.value && audio.value) {
256 - audio.value.pause();
257 - isPlaying.value = false;
258 - emit('pause', audio.value);
259 - } else {
260 - // 如果音频实例不存在或已经暂停,只更新状态
261 - isPlaying.value = false;
262 - }
263 - },
264 - isPlaying: () => isPlaying.value
265 -})
266 259
267 // 进度更新 260 // 进度更新
268 const updateProgress = () => { 261 const updateProgress = () => {
...@@ -431,6 +424,7 @@ const selectSong = async (index) => { ...@@ -431,6 +424,7 @@ const selectSong = async (index) => {
431 424
432 // 确保音频实例加载成功后再播放 425 // 确保音频实例加载成功后再播放
433 if (loadSuccess && audio.value) { 426 if (loadSuccess && audio.value) {
427 + console.warn('正在播放选择的歌曲', audio.value);
434 try { 428 try {
435 await audio.value.play() 429 await audio.value.play()
436 isPlaying.value = true 430 isPlaying.value = true
...@@ -468,6 +462,25 @@ watch([isPlaying, currentIndex], () => { ...@@ -468,6 +462,25 @@ watch([isPlaying, currentIndex], () => {
468 } 462 }
469 } 463 }
470 }) 464 })
465 +
466 +
467 +// 暴露给父组件的方法
468 +defineExpose({
469 + togglePlay,
470 + pause: () => {
471 + // 只有在播放状态且音频实例存在时才执行暂停
472 + if (isPlaying.value && audio.value) {
473 + audio.value.pause();
474 + isPlaying.value = false;
475 + emit('pause', audio.value);
476 + } else {
477 + // 如果音频实例不存在或已经暂停,只更新状态
478 + isPlaying.value = false;
479 + }
480 + },
481 + isPlaying: () => isPlaying.value,
482 + id: props.id,
483 +})
471 </script> 484 </script>
472 485
473 <style scoped> 486 <style scoped>
......
1 <!-- 1 <!--
2 * @Date: 2025-05-29 15:34:17 2 * @Date: 2025-05-29 15:34:17
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-05-30 15:54:33 4 + * @LastEditTime: 2025-05-30 18:02:35
5 * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue 5 * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue
6 * @Description: 文件描述 6 * @Description: 文件描述
7 --> 7 -->
...@@ -113,6 +113,7 @@ ...@@ -113,6 +113,7 @@
113 v-if="post.audio.length" 113 v-if="post.audio.length"
114 :songs="post.audio" 114 :songs="post.audio"
115 class="post-audio" 115 class="post-audio"
116 + :id="post.id"
116 :ref="el => { 117 :ref="el => {
117 if(el) { 118 if(el) {
118 // 确保不重复添加 119 // 确保不重复添加
...@@ -196,8 +197,8 @@ const startPlay = (post) => { ...@@ -196,8 +197,8 @@ const startPlay = (post) => {
196 * @param {Object} post - 包含视频的帖子对象 197 * @param {Object} post - 包含视频的帖子对象
197 */ 198 */
198 const handleVideoPlay = (player, post) => { 199 const handleVideoPlay = (player, post) => {
199 - // 停止其他视频播放 200 + console.warn(0);
200 - stopOtherVideos(player, post); 201 + stopAllAudio();
201 }; 202 };
202 203
203 /** 204 /**
...@@ -231,9 +232,6 @@ const stopOtherVideos = (currentPlayer, currentPost) => { ...@@ -231,9 +232,6 @@ const stopOtherVideos = (currentPlayer, currentPost) => {
231 post.isPlaying = false; 232 post.isPlaying = false;
232 } 233 }
233 }); 234 });
234 -
235 - // 同时暂停所有音频播放器
236 - stopAllAudio();
237 }; 235 };
238 236
239 /** 237 /**
...@@ -244,117 +242,99 @@ const stopOtherVideos = (currentPlayer, currentPost) => { ...@@ -244,117 +242,99 @@ const stopOtherVideos = (currentPlayer, currentPost) => {
244 const handleAudioPlay = (player, post) => { 242 const handleAudioPlay = (player, post) => {
245 // 停止其他音频播放 243 // 停止其他音频播放
246 stopOtherAudio(player, post); 244 stopOtherAudio(player, post);
245 +};
247 246
248 - // 同时暂停所有视频 247 +const stopOtherAudio = (currentPlayer, currentPost) => {
249 - if (videoPlayers.value) { 248 + // 确保audioPlayers.value是一个数组
250 - videoPlayers.value.forEach(videoPlayer => { 249 + if (audioPlayers.value) {
251 - if (videoPlayer.pause) { 250 + // 暂停其他音频播放器
252 - videoPlayer.pause(); 251 + audioPlayers.value.forEach(player => {
252 + if (player.id!== currentPost.id && player.pause) {
253 + player.pause();
253 } 254 }
254 }); 255 });
255 } 256 }
256 - 257 + // 更新其他帖子的播放状态
257 - // 更新所有视频的播放状态为false 258 + mockPosts.value.forEach(post => {
258 - mockPosts.value.forEach(p => { 259 + if (post.id!== currentPost.id) {
259 - p.isPlaying = false; 260 + post.isPlaying = false;
261 + }
260 }); 262 });
261 -}; 263 +}
262 264
263 -/** 265 +const stopAllAudio = () => {
264 - * 停止除当前播放器外的所有其他音频
265 - * @param {Object} currentPlayer - 当前播放的音频播放器实例
266 - * @param {Object} currentPost - 当前播放的帖子对象
267 - */
268 -const stopOtherAudio = (currentPlayer, currentPost) => {
269 // 确保audioPlayers.value是一个数组 266 // 确保audioPlayers.value是一个数组
270 if (!audioPlayers.value) return; 267 if (!audioPlayers.value) return;
271 - 268 + audioPlayers.value?.forEach(player => {
272 - // 暂停其他音频播放器 269 + console.warn(player);
273 - audioPlayers.value.forEach(player => {
274 - // 只处理不是当前播放器的实例
275 - if (player !== currentPlayer) {
276 // 使用组件暴露的pause方法 270 // 使用组件暴露的pause方法
277 if (typeof player.pause === 'function') { 271 if (typeof player.pause === 'function') {
278 player.pause(); 272 player.pause();
279 } 273 }
280 - // 如果没有pause方法,尝试使用togglePlay方法
281 - else if (typeof player.togglePlay === 'function' && player.isPlaying && player.isPlaying()) {
282 - player.togglePlay();
283 - }
284 - // 最后尝试直接操作DOM
285 - else if (player.$el && player.$el.querySelector('audio')) {
286 - const audioElement = player.$el.querySelector('audio');
287 - if (audioElement) {
288 - audioElement.pause();
289 - }
290 - }
291 - }
292 }); 274 });
293 -}; 275 + // 更新所有帖子的播放状态
276 + mockPosts.value.forEach(post => {
277 + post.isPlaying = false;
278 + });
279 +}
294 280
295 /** 281 /**
296 - * 停止所有频播放 282 + * 停止所有频播放
297 */ 283 */
298 -const stopAllAudio = () => { 284 +const stopAllVideos = () => {
299 - // 确保audioPlayers.value是一个数组 285 + // 确保videoPlayers.value是一个数组
300 - if (!audioPlayers.value) return; 286 + if (!videoPlayers.value) return;
301 287
302 - audioPlayers.value.forEach(player => { 288 + videoPlayers.value?.forEach(player => {
303 // 使用组件暴露的pause方法 289 // 使用组件暴露的pause方法
304 if (typeof player.pause === 'function') { 290 if (typeof player.pause === 'function') {
305 player.pause(); 291 player.pause();
306 } 292 }
307 - // 如果没有pause方法,尝试使用togglePlay方法 293 + });
308 - else if (typeof player.togglePlay === 'function' && player.isPlaying && player.isPlaying()) { 294 +
309 - player.togglePlay(); 295 + // 更新所有帖子的播放状态
310 - } 296 + mockPosts.value.forEach(post => {
311 - // 最后尝试直接操作DOM 297 + post.isPlaying = false;
312 - else if (player.$el && player.$el.querySelector('audio')) {
313 - const audioElement = player.$el.querySelector('audio');
314 - if (audioElement) {
315 - audioElement.pause();
316 - }
317 - }
318 }); 298 });
319 }; 299 };
320 300
321 // Mock数据 301 // Mock数据
322 const mockPosts = ref([ 302 const mockPosts = ref([
323 - { 303 + // {
324 - id: 1, 304 + // id: 1,
325 - user: { 305 + // user: {
326 - name: '图片预览', 306 + // name: '图片预览',
327 - avatar: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg', 307 + // avatar: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
328 - time: '2小时前' 308 + // time: '2小时前'
329 - }, 309 + // },
330 - content: '今天完成了React基础课程的学习,收获满满!', 310 + // content: '今天完成了React基础课程的学习,收获满满!',
331 - images: [ 311 + // images: [
332 - 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg', 312 + // 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
333 - 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg', 313 + // 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
334 - 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg', 314 + // 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
335 - 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg', 315 + // 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
336 - ], 316 + // ],
337 - video: '', 317 + // video: '',
338 - videoCover: '', 318 + // videoCover: '',
339 - isPlaying: false, 319 + // isPlaying: false,
340 - audio: [], 320 + // audio: [],
341 - likes: 12 321 + // likes: 12
342 - }, 322 + // },
343 - { 323 + // {
344 - id: 2, 324 + // id: 2,
345 - user: { 325 + // user: {
346 - name: '小林', 326 + // name: '小林',
347 - avatar: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg', 327 + // avatar: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
348 - time: '2小时前' 328 + // time: '2小时前'
349 - }, 329 + // },
350 - content: '今天完成了React基础课程的学习,收获满满!', 330 + // content: '今天完成了React基础课程的学习,收获满满!',
351 - images: [], 331 + // images: [],
352 - video: 'https://cdn.ipadbiz.cn/space/lk3DmvLO02dUC2zPiFwiClDe3nKL.mp4', 332 + // video: 'https://cdn.ipadbiz.cn/space/lk3DmvLO02dUC2zPiFwiClDe3nKL.mp4',
353 - videoCover: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg', 333 + // videoCover: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
354 - isPlaying: false, 334 + // isPlaying: false,
355 - audio: [], 335 + // audio: [],
356 - likes: 12 336 + // likes: 12
357 - }, 337 + // },
358 { 338 {
359 id: 3, 339 id: 3,
360 user: { 340 user: {
...@@ -369,7 +349,13 @@ const mockPosts = ref([ ...@@ -369,7 +349,13 @@ const mockPosts = ref([
369 isPlaying: false, 349 isPlaying: false,
370 audio: [ 350 audio: [
371 { 351 {
372 - title: '学习心得分享', 352 + title: '学习心得分享1',
353 + artist: '小林',
354 + url: 'https://cdn.ipadbiz.cn/space/816560/双手合十迎客来_Fs2W-5mnQSFL8S5CDsHho-_xcvaY.mp3',
355 + cover: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg'
356 + },
357 + {
358 + title: '学习心得分享2',
373 artist: '小林', 359 artist: '小林',
374 url: 'https://cdn.ipadbiz.cn/space/816560/双手合十迎客来_Fs2W-5mnQSFL8S5CDsHho-_xcvaY.mp3', 360 url: 'https://cdn.ipadbiz.cn/space/816560/双手合十迎客来_Fs2W-5mnQSFL8S5CDsHho-_xcvaY.mp3',
375 cover: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg' 361 cover: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg'
......