hookehuyr

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

- 修改音频加载逻辑,等待canplaythrough事件确保流畅播放
- 为AudioPlayer组件添加id prop用于唯一标识
- 重构音频控制方法,简化暂停逻辑
- 清理mock数据,保留一个带音频的测试用例
<!--
* @Date: 2025-04-07 12:35:35
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-05-30 15:26:15
* @LastEditTime: 2025-05-30 18:03:29
* @FilePath: /mlaj/src/components/ui/AudioPlayer.vue
* @Description: 音频播放器组件,支持播放控制、进度条调节、音量控制、播放列表等功能
-->
......@@ -129,6 +129,7 @@ import { wxInfo } from '@/utils/tools'
// 组件属性定义
const props = defineProps({
id: { type: String }, // 组件ID
songs: { type: Array, required: true }, // 音频列表数据
initialIndex: { type: Number, default: 0 } // 初始播放索引
})
......@@ -189,9 +190,17 @@ const loadAudio = async () => {
audio.value.addEventListener('timeupdate', updateProgress)
audio.value.addEventListener('ended', handleEnded)
// 加载音频
await audio.value.load()
return true
// 加载音频并等待可以流畅播放
await audio.value?.load()
return new Promise((resolve) => {
audio.value.addEventListener('canplaythrough', () => {
resolve(true)
}, { once: true })
audio.value.addEventListener('error', () => {
console.error('音频加载失败')
resolve(false)
}, { once: true })
})
} catch (error) {
console.error('加载音频失败:', error)
audio.value = null
......@@ -247,22 +256,6 @@ const togglePlay = async () => {
}
}
// 暴露给父组件的方法
defineExpose({
togglePlay,
pause: () => {
// 只有在播放状态且音频实例存在时才执行暂停
if (isPlaying.value && audio.value) {
audio.value.pause();
isPlaying.value = false;
emit('pause', audio.value);
} else {
// 如果音频实例不存在或已经暂停,只更新状态
isPlaying.value = false;
}
},
isPlaying: () => isPlaying.value
})
// 进度更新
const updateProgress = () => {
......@@ -431,6 +424,7 @@ const selectSong = async (index) => {
// 确保音频实例加载成功后再播放
if (loadSuccess && audio.value) {
console.warn('正在播放选择的歌曲', audio.value);
try {
await audio.value.play()
isPlaying.value = true
......@@ -468,6 +462,25 @@ watch([isPlaying, currentIndex], () => {
}
}
})
// 暴露给父组件的方法
defineExpose({
togglePlay,
pause: () => {
// 只有在播放状态且音频实例存在时才执行暂停
if (isPlaying.value && audio.value) {
audio.value.pause();
isPlaying.value = false;
emit('pause', audio.value);
} else {
// 如果音频实例不存在或已经暂停,只更新状态
isPlaying.value = false;
}
},
isPlaying: () => isPlaying.value,
id: props.id,
})
</script>
<style scoped>
......
<!--
* @Date: 2025-05-29 15:34:17
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-05-30 15:54:33
* @LastEditTime: 2025-05-30 18:02:35
* @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue
* @Description: 文件描述
-->
......@@ -113,6 +113,7 @@
v-if="post.audio.length"
:songs="post.audio"
class="post-audio"
:id="post.id"
:ref="el => {
if(el) {
// 确保不重复添加
......@@ -196,8 +197,8 @@ const startPlay = (post) => {
* @param {Object} post - 包含视频的帖子对象
*/
const handleVideoPlay = (player, post) => {
// 停止其他视频播放
stopOtherVideos(player, post);
console.warn(0);
stopAllAudio();
};
/**
......@@ -231,9 +232,6 @@ const stopOtherVideos = (currentPlayer, currentPost) => {
post.isPlaying = false;
}
});
// 同时暂停所有音频播放器
stopAllAudio();
};
/**
......@@ -244,117 +242,99 @@ const stopOtherVideos = (currentPlayer, currentPost) => {
const handleAudioPlay = (player, post) => {
// 停止其他音频播放
stopOtherAudio(player, post);
};
// 同时暂停所有视频
if (videoPlayers.value) {
videoPlayers.value.forEach(videoPlayer => {
if (videoPlayer.pause) {
videoPlayer.pause();
const stopOtherAudio = (currentPlayer, currentPost) => {
// 确保audioPlayers.value是一个数组
if (audioPlayers.value) {
// 暂停其他音频播放器
audioPlayers.value.forEach(player => {
if (player.id!== currentPost.id && player.pause) {
player.pause();
}
});
}
// 更新所有视频的播放状态为false
mockPosts.value.forEach(p => {
p.isPlaying = false;
// 更新其他帖子的播放状态
mockPosts.value.forEach(post => {
if (post.id!== currentPost.id) {
post.isPlaying = false;
}
});
};
}
/**
* 停止除当前播放器外的所有其他音频
* @param {Object} currentPlayer - 当前播放的音频播放器实例
* @param {Object} currentPost - 当前播放的帖子对象
*/
const stopOtherAudio = (currentPlayer, currentPost) => {
const stopAllAudio = () => {
// 确保audioPlayers.value是一个数组
if (!audioPlayers.value) return;
// 暂停其他音频播放器
audioPlayers.value.forEach(player => {
// 只处理不是当前播放器的实例
if (player !== currentPlayer) {
audioPlayers.value?.forEach(player => {
console.warn(player);
// 使用组件暴露的pause方法
if (typeof player.pause === 'function') {
player.pause();
}
// 如果没有pause方法,尝试使用togglePlay方法
else if (typeof player.togglePlay === 'function' && player.isPlaying && player.isPlaying()) {
player.togglePlay();
}
// 最后尝试直接操作DOM
else if (player.$el && player.$el.querySelector('audio')) {
const audioElement = player.$el.querySelector('audio');
if (audioElement) {
audioElement.pause();
}
}
}
});
};
// 更新所有帖子的播放状态
mockPosts.value.forEach(post => {
post.isPlaying = false;
});
}
/**
* 停止所有频播放
* 停止所有频播放
*/
const stopAllAudio = () => {
// 确保audioPlayers.value是一个数组
if (!audioPlayers.value) return;
const stopAllVideos = () => {
// 确保videoPlayers.value是一个数组
if (!videoPlayers.value) return;
audioPlayers.value.forEach(player => {
videoPlayers.value?.forEach(player => {
// 使用组件暴露的pause方法
if (typeof player.pause === 'function') {
player.pause();
}
// 如果没有pause方法,尝试使用togglePlay方法
else if (typeof player.togglePlay === 'function' && player.isPlaying && player.isPlaying()) {
player.togglePlay();
}
// 最后尝试直接操作DOM
else if (player.$el && player.$el.querySelector('audio')) {
const audioElement = player.$el.querySelector('audio');
if (audioElement) {
audioElement.pause();
}
}
});
// 更新所有帖子的播放状态
mockPosts.value.forEach(post => {
post.isPlaying = false;
});
};
// Mock数据
const mockPosts = ref([
{
id: 1,
user: {
name: '图片预览',
avatar: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
time: '2小时前'
},
content: '今天完成了React基础课程的学习,收获满满!',
images: [
'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
],
video: '',
videoCover: '',
isPlaying: false,
audio: [],
likes: 12
},
{
id: 2,
user: {
name: '小林',
avatar: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
time: '2小时前'
},
content: '今天完成了React基础课程的学习,收获满满!',
images: [],
video: 'https://cdn.ipadbiz.cn/space/lk3DmvLO02dUC2zPiFwiClDe3nKL.mp4',
videoCover: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
isPlaying: false,
audio: [],
likes: 12
},
// {
// id: 1,
// user: {
// name: '图片预览',
// avatar: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
// time: '2小时前'
// },
// content: '今天完成了React基础课程的学习,收获满满!',
// images: [
// 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
// 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
// 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
// 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
// ],
// video: '',
// videoCover: '',
// isPlaying: false,
// audio: [],
// likes: 12
// },
// {
// id: 2,
// user: {
// name: '小林',
// avatar: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
// time: '2小时前'
// },
// content: '今天完成了React基础课程的学习,收获满满!',
// images: [],
// video: 'https://cdn.ipadbiz.cn/space/lk3DmvLO02dUC2zPiFwiClDe3nKL.mp4',
// videoCover: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
// isPlaying: false,
// audio: [],
// likes: 12
// },
{
id: 3,
user: {
......@@ -369,7 +349,13 @@ const mockPosts = ref([
isPlaying: false,
audio: [
{
title: '学习心得分享',
title: '学习心得分享1',
artist: '小林',
url: 'https://cdn.ipadbiz.cn/space/816560/双手合十迎客来_Fs2W-5mnQSFL8S5CDsHho-_xcvaY.mp3',
cover: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg'
},
{
title: '学习心得分享2',
artist: '小林',
url: 'https://cdn.ipadbiz.cn/space/816560/双手合十迎客来_Fs2W-5mnQSFL8S5CDsHho-_xcvaY.mp3',
cover: 'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg'
......