1.样式代码和HTML
html
<view class="audio-player" v-for="(item2, index) in item.fieldValueUrl" :key="index">
<!-- 圆形播放按钮 -->
<uv-icon :name="(audioNum === index) ? 'pause-circle' : 'play-circle'" size="20" color="primary"
@click="audioNum === index ? closeAudio(index) : openAudio(item2, index)"
:label="`${index + 1}.`"></uv-icon>
<!-- 当前播放时间 -->
<text class="current-time">{{ audioList[index].currentTimeStr }}</text>
<!-- 进度条容器 -->
<view class="progress-container" @click="clickSeek" @touchmove.stop.prevent>
<!-- 底层轨道 -->
<view class="progress-bg"></view>
<!-- 已播放轨道 -->
<view class="progress-active" :style="{ width: audioList[index].progressPercent + '%' }"></view>
<!-- 圆形滑块 -->
<view class="progress-dot" :style="{ left: audioList[index].progressPercent + '%' }"></view>
</view>
<!-- 总时长 -->
<text class="total-time">{{ audioList[index].totalTimeStr }}</text>
</view>
css
.audio-player {
display: flex;
align-items: center;
gap: 20rpx;
width: 100%;
padding: 40rpx 20rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
background: #ffffff;
/* 时间文字 */
.current-time,
.total-time {
font-size: 30rpx;
color: #222;
flex-shrink: 0;
}
/* 进度条外层容器 */
.progress-container {
flex: 1;
height: 8rpx;
position: relative;
margin: 0 10rpx;
}
/* 底层轨道 */
.progress-bg {
width: 100%;
height: 100%;
background: #bee5ff;
border-radius: 999rpx;
}
/* 已播放轨道 */
.progress-active {
position: absolute;
left: 0;
top: 0;
height: 100%;
background: #1C90FF;
border-radius: 999rpx;
}
/* 圆形滑块 */
.progress-dot {
position: absolute;
top: 50%;
width: 30rpx;
height: 30rpx;
border-radius: 50%;
background: #1C90FF;
transform: translate(-50%, -50%);
}
}
2.逻辑代码
javascript
//在从后端获取音频数据时,循环创建空数组和创建临时实列,获取总时长
list.forEach((item, index) => {
audioList.value.push({
'currentTime': 0,
'totalTime': 0,
currentTimeStr: '00:00',
totalTimeStr: '00:00',
"progressPercent": 0,
audioUrl: item
})
})
// 循环每条音频,异步预取总时长
for (let i = 0; i < audioList.value.length; i++) {
const item = audioList.value[i]
const dur = await getAudioDuration(item.audioUrl)
// 赋值总时长,并格式化显示文字
item.totalTime = dur
item.totalTimeStr = formatTime(dur)
}
// 秒格式化 mm:ss
const formatTime = (sec) => {
const min = Math.floor(sec / 60)
const s = Math.floor(sec % 60)
return `${min.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
}
// 音频播放start
// 获取音频总时长(静默加载,无声音)
const getAudioDuration = (url) => {
return new Promise((resolve) => {
// 创建临时实例,只用来读时长
const tempAudio = uni.createInnerAudioContext()
tempAudio.muted = true // 静音,不会出声
tempAudio.src = url
// 加载成功,返回时长并销毁
tempAudio.onCanplay(() => {
const dur = tempAudio.duration || 0
tempAudio.destroy()
resolve(dur)
})
// 加载失败返回0
tempAudio.onError(() => {
tempAudio.destroy()
resolve(0)
})
})
}
let innerAudioContext = null // 音频实例
// 创建音频实例(统一管理)--点击播放时创建
const createAudio = (index) => {
// 销毁之前的实例,避免冲突
if (innerAudioContext) {
try {
innerAudioContext.stop()
innerAudioContext.destroy()
} catch (e) {}
}
innerAudioContext = uni.createInnerAudioContext()
audioNum.value = index
innerAudioContext.onCanplay(() => {
audioList.value[index].totalTime = innerAudioContext.duration || 0
})
// 实时监听播放进度,每秒自动更新 currentTime
innerAudioContext.onTimeUpdate(() => {
audioList.value[index].currentTime = innerAudioContext.currentTime
})
audioList.value[index].progressPercent = computed(() => {
if (audioList.value[index].totalTime === 0) return 0
return (audioList.value[index].currentTime / audioList.value[index].totalTime) * 100
})
audioList.value[index].currentTimeStr = computed(() => formatTime(audioList.value[index].currentTime))
audioList.value[index].totalTimeStr = computed(() => formatTime(audioList.value[index].totalTime))
}
// 播放音频
const openAudio = (url, index) => {
// 停止上一个
if (audioNum.value !== '') {
closeAudio(audioNum.value)
}
createAudio(index) // 每次播放都创建新实例,稳定不报错
innerAudioContext.autoplay = true
innerAudioContext.src = url // 音频地址
// 播放成功
innerAudioContext.onPlay(() => {
audioNum.value = index
})
// 播放完毕
innerAudioContext.onEnded(() => {
audioNum.value = '' // 图标复原
})
// 报错处理
innerAudioContext.onError((err) => {
console.log('播放报错', err)
audioNum.value = ''
})
}
// 关闭音频
const closeAudio = (index) => {
audioNum.value = ''
if (innerAudioContext) {
try {
innerAudioContext.stop()
innerAudioContext.destroy()
innerAudioContext = null
} catch (e) {}
}
}
// 音频播放end
3.结果展示
