封装简单的音频组件(vue3语法)
参考方法
https://blog.csdn.net/sun12356499514452248/article/details/128518711
组件代码
<template>
<div class="ks-audio">
<div class="audio-wrapper">
<van-slider
class="audio-slider"
:activeColor="color"
v-model="sliderValue"
:max="totalDuration"
@dragStart="isSeeking = true"
@dragEnd="isSeeking = false"
@change="change"
/>
<div class="audio-number-area">
<div class="audio-number">{{ format(sliderValue) }}</div>
<div class="audio-number">{{ format(totalDuration) }}</div>
</div>
</div>
<div class="audio-control-wrapper">
<div class="audio-control" v-if="control" :style="{ backgroundColor: color }" @click="toggleRate(-15)">
<span class="iconfont icon-kuaitui"></span>
</div>
<div class="audio-control" v-if="control" :style="{ backgroundColor: color }" @click="prevAudio">
<span class="iconfont icon-shangyige"></span>
</div>
<div
class="audio-control big-sign"
:style="{ backgroundColor: color }"
:class="{ audioLoading: loading }"
@click="togglePlayState"
>
<span class="iconfont icon-jiazaizhong" v-show="loading"></span>
<span class="iconfont icon-bofang5" v-show="!loading && isPaused"></span>
<span class="iconfont icon-zanting1" v-show="!loading && !isPaused"></span>
</div>
<div class="audio-control" v-if="control" :style="{ backgroundColor: color }" @click="nextAudio">
<span class="iconfont icon-xiayige"></span>
</div>
<div class="audio-control" v-if="control" :style="{ backgroundColor: color }" @click="toggleRate(15)">
<span class="iconfont icon-kuaijin"></span>
</div>
</div>
</div>
</template>
<script>
import { onBeforeUnmount, reactive, toRefs, watch } from 'vue';
import { showToast } from 'vant';
export default {
name: 'KsAudio',
props: {
src: {
//音频链接
type: String,
default: '',
},
autoplay: {
//是否自动播放
type: Boolean,
default: false,
},
control: {
//是否显示控件
type: Boolean,
default: false,
},
continue: {
//播放完成后是否继续播放下一首,需定义@next事件
type: Boolean,
default: true,
},
color: {
//主色调
type: String,
default: '#3366cc',
},
},
setup(props, { emit }) {
const state = reactive({
audioElem: null,
sliderValue: 0, //slider当前进度
totalDuration: 0, //总时长(单位:s)
isError: false, //音频是否加载出错,常见于src出错时导致音频加载出错
loading: false, //是否处于读取状态
isPaused: true, //是否处于暂停状态
isSeeking: false, //是否处于拖动状态
});
initAudio();
//监听音频地址更改
watch(
() => props.src,
() => {
state.sliderValue = 0;
state.totalDuration = 0;
state.isError = false;
state.loading = true;
state.audioElem.src = props.src;
state.audioElem.play();
},
);
// 初始化音频
function initAudio() {
state.audioElem = new Audio(); //创建音频
state.audioElem.src = props.src;
let readyFunc = 'canplay';
let isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios手机终端
if (isIOS) {
//注:ios系统canplay方法不生效,使用durationchange方法
readyFunc = 'durationchange';
}
state.audioElem.addEventListener(readyFunc, () => {
state.isError = false;
state.totalDuration = state.audioElem.duration || 0;
state.audioElem.autoplay = props.autoplay;
});
//音频进度更新事件
state.audioElem.addEventListener('timeupdate', () => {
if (!state.isSeeking) {
state.sliderValue = state.audioElem.currentTime;
}
});
//音频播放事件
state.audioElem.addEventListener('play', () => {
state.isPaused = false;
state.loading = false;
});
//音频暂停事件
state.audioElem.addEventListener('pause', () => {
state.isPaused = true;
});
//音频结束事件
state.audioElem.addEventListener('ended', () => {
if (props.continue) {
nextAudio();
} else {
state.isPaused = true;
state.sliderValue = 0;
}
});
//音频完成更改进度事件
state.audioElem.addEventListener('seeked', () => {
state.isSeeking = false;
});
//播放出错事件
state.audioElem.addEventListener('error', () => {
state.isError = true;
state.isPaused = true;
showToast('此音频暂不支持播放');
});
}
//上一曲
function prevAudio() {
emit('prev');
}
//下一曲
function nextAudio() {
emit('next');
}
//格式化时长
function format(num) {
return (
'0'.repeat(2 - String(Math.floor(num / 60)).length) +
Math.floor(num / 60) +
':' +
'0'.repeat(2 - String(Math.floor(num % 60)).length) +
Math.floor(num % 60)
);
}
//播放/暂停操作
function togglePlayState() {
if (!state.isError) {
if (state.audioElem.paused) {
state.audioElem.play();
state.loading = true;
} else {
state.audioElem.pause();
}
} else {
showToast('此音频暂不支持播放');
}
}
//完成拖动事件
function change() {
state.audioElem.currentTime = state.sliderValue;
}
//快进快退
function toggleRate(rate) {
if (state.isError) {
return;
}
let finalRate = state.sliderValue + rate;
state.audioElem.currentTime = finalRate;
}
onBeforeUnmount(() => {
//页面销毁前,暂停当前audio实例的播放,防止退出页面后音频播放仍然发出声音
state.audioElem.pause();
state.audioElem = null;
});
return {
prevAudio,
nextAudio,
togglePlayState,
change,
toggleRate,
format,
...toRefs(state),
};
},
};
</script>
<style scoped lang="less">
.ks-audio {
.audio-wrapper {
:deep(.van-slider) {
background: #ccc;
height: 4px;
border-radius: 8px;
.van-slider__button {
width: 16px;
height: 16px;
}
}
.audio-number-area {
display: flex;
justify-content: space-between;
margin-top: 4px;
.audio-number {
font-size: 10px;
line-height: 12px;
color: #999;
}
}
}
.audio-control-wrapper {
margin-top: 10px;
display: flex;
justify-content: center;
align-items: center;
.audio-control {
border-radius: 50%;
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
margin-right: 24px;
.iconfont {
font-size: 12px;
}
&:last-child {
margin-right: 0;
}
}
.big-sign {
width: 40px;
height: 40px;
.iconfont {
font-size: 18px;
}
}
.audioLoading {
animation: loading 2s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
}
}
@keyframes loading {
to {
transform: rotate(360deg);
}
}
</style>
调用代码
<ks-audio
ref="ksAudioRef"
:src="nowAudio.aurl"
control
:continue="true"
@prev="toggleAudio('prev')"
@next="toggleAudio('next')"
></ks-audio>
//上下曲
function toggleAudio(type) {
let pointIndex = null;
state.audioList.forEach((item, index) => {
if (item.aid == state.nowAudio.aid) {
if (type == 'prev') {
if (index == 0) {
pointIndex = state.audioList.length - 1;
} else {
pointIndex = index - 1;
}
} else if (type == 'next') {
if (index == state.audioList.length - 1) {
pointIndex = 0;
} else {
pointIndex = index + 1;
}
}
}
});
selectAudio(state.audioList[pointIndex]);
}