fix(AudioPlayer): 修复音频加载逻辑并添加组件ID支持
- 修改音频加载逻辑,等待canplaythrough事件确保流畅播放 - 为AudioPlayer组件添加id prop用于唯一标识 - 重构音频控制方法,简化暂停逻辑 - 清理mock数据,保留一个带音频的测试用例
Showing
2 changed files
with
111 additions
and
112 deletions
| 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' | ... | ... |
-
Please register or login to post a comment