开发初衷:
项目需要,没在网上找到合适的,于是就手写了。功能点有:显示、播放上传的音频、更改上传音频的名字、设置音频上线的时间。
该组件的主要优点是:灵活性高,样式可以自己按照设计稿布局
使用规则:
- 引入audioItem组件
- 调用组件
先上图:
<template>
<div>
<audio-player ref="audioPlayer" :item="list[0]" @handleConfirm="getConfirm"></audio-player>
<audio-player ref="audioPlayer" :item="list[1]" @handleConfirm="getConfirm"></audio-player>
</div>
</template>
<script>
import AudioPlayer from './components/audioItem'
export default {
data() {
return {
list: [
{
index: 0,
title: '歌曲名称1',
url: 'http://image.kaolafm.net/mz/audios/202003/c4a9f630-95bd-44d7-9874-3d004d7c5f25.mp3',
input: '',
date: '',
curState: false
},
{
index: 1,
title: '歌曲名称2',
url: 'http://audio.leting.io/45b32eea707d93f18bd23bc04c987f6d.mp3',
input: '',
date: '',
curState: false
}
]
}
},
mounted() {
},
methods: {
getConfirm(obj) {
this.list.map(item => {
// 若非当前obj音频播放,则让列表中的其他对象的状态为false,即:暂停其它音频播放
if (item.index !== obj.index) {
item.curState = false
}
})
}
},
components: {
AudioPlayer
}
}
</script>
<style scoped>
</style>
组件代码:
// 组件audioItem.vue
<template>
<div class="wrapper">
<audio ref="audio">
<source :src="item.url" type="audio/mp3" />
</audio>
<div class="top">
<div class="control">
<i class="play" :style="backgroundImage('audioplay/'+ (item.curState ? 'play' : 'pause') +'.svg')" :class="{ 'active' : item.curState }" @click="playHandle"></i>
<div class="name" :title="name">{{ name }}</div>
</div>
<div class="time">
<div class="time-line">
<span class="left">{{ curTime }}</span>
<div class="bar" ref="progressBarBg" @mousedown="handledown()">
<div class="play-bar" ref="progressBar" :style="{ 'width' : width + '%'}"></div>
<div class="play-point" ref="progressDot" :style="{ 'left' : width + '%'}"></div>
<div class="play-bg"></div>
</div>
<span class="right">{{ duration }}</span>
</div>
</div>
</div>
<div class="bottom">
<el-input class="bot-input"
placeholder="请输入内容"
v-model="item.input"
clearable>
</el-input>
<div class="data-box">
<el-date-picker
v-model="item.date"
type="datetime"
placeholder="选择日期时间">
</el-date-picker>
</div>
</div>
</div>
</template>
<script>
import { backgroundImage } from '@/utils/index'
const getMmSsStr = (timestamp) => {
const m = Math.floor(timestamp / 60)
const s = Math.floor(timestamp % 60)
return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
}
export default {
name: 'AudioPlayer',
props: {
onEnd: {
type: Function,
default: () => {}
},
item: {
type: Object,
default: () => {
return {
url: '', // 音频地址
title: '', // 音频名称
input: '', // 输入框值
date: '', // date选择器
curState: false // 当前播放状态 未播放
}
}
}
},
data() {
return {
slider: null,
point: null,
per: 0,
curTime: '00:00',
name: this.item.title,
duration: '00:00',
mp3: this.item.url,
backgroundImage: backgroundImage
}
},
watch: {
item: {
handler(val) {
console.log(val.curState, 'watch------')
if (val.url !== '') {
this.mp3 = this.item.url
}
this.name = this.item.title
try {
if (!this.item.curState) {
if (this.audio) {
this.pause()
}
}
} catch (err) {
throw err
}
},
deep: true,
immediate: true
},
'$route': function() {
this.pause()
this.item.curState = false
},
'mp3': function() {
this.pause()
this.item.curState = false
}
},
computed: {
width() {
return this.per
}
},
mounted() {
this.init()
// this.dragEvent()
},
methods: {
init() {
const that = this
this.audio = this.$refs.audio
this.audio.src = this.item.url
this.audio.load()
// 暂停
this.audio.addEventListener('pause', () => {
// callback(this.audio.currentTime, this.audio.duration)
that.$nextTick(() => {
that.item.curState = false
})
})
// 结束
this.audio.addEventListener('ended', () => {
that.$nextTick(() => {
that.item.curState = false
})
})
// 更新
this.audio.addEventListener('timeupdate', () => {
if (isNaN(that.audio.currentTime) || isNaN(that.audio.duration)) {
return
} else {
that.$nextTick(() => {
that.curTime = getMmSsStr(that.audio.currentTime)
that.duration = getMmSsStr(that.audio.duration)
that.per = that.audio.currentTime / that.audio.duration * 100
console.log('timeUpDate...')
})
}
})
// 出错
this.audio.addEventListener('error', () => {
this.$message.error('音频加载出错...')
})
this.audio.addEventListener('canplaythrough', () => {
// this.audio.play()
})
},
play(mp3) {
if (this.audio.src === mp3) {
this.audio.play()
} else {
this.audio.src = mp3
this.audio.load()
}
},
pause() {
this.audio.pause()
},
handledown() {
// 只有音乐开始播放后才可以调节,已经播放过但暂停了的也可以
console.log(this.audio.currentTime)
if (!this.audio.paused || this.audio.currentTime !== 0) {
const pgsWidth = this.$refs.progressBarBg.offsetWidth
const rate = event.offsetX / pgsWidth
this.audio.currentTime = this.audio.duration * rate
this.updateProgress()
}
},
// 更新进度条与当前播放时间
updateProgress() {
const value = this.audio.currentTime / this.audio.duration
this.$refs.progressBar.style.width = value * 100 + '%'
this.$refs.progressDot.style.left = value * 100 + '%'
this.$nextTick(() => {
this.curTime = getMmSsStr(this.audio.currentTime)
this.duration = getMmSsStr(this.audio.duration)
this.per = this.audio.currentTime / this.audio.duration * 100
})
},
playHandle() {
const _this = this
console.log(_this.mp3, _this.item.curState, '0000000000')
// 针对付费和免费的播放碎片处理
_this.$emit('handleConfirm', _this.item)
if (_this.item.curState) {
console.log('走到暂停....')
_this.pause()
_this.$nextTick(() => {
_this.item.curState = false
})
} else {
console.log('走到播放....')
_this.play(this.mp3)
_this.$nextTick(() => {
_this.item.curState = true
})
}
}
}
}
</script>
<style lang="scss" scoped>
.progress-bar-bg {
width: 200px;
margin-left: 14px;
margin-right: 14px;
background-color: #d9d9d9;
position: relative;
height: 2px;
cursor: pointer;
}
.progress-bar {
background-color: #649fec;
width: 0;
height: 2px;
}
.progress-bar-bg span {
content: " ";
width: 10px;
height: 10px;
border-radius: 50%;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
background-color: #3e87e8;
position: absolute;
left: 0;
top: 50%;
margin-top: -5px;
margin-left: -5px;
cursor: pointer;
}
@mixin flexs($direction:row,$row: center,$column:center){
display: flex;
flex-direction: $direction;
justify-content: $row;
align-items: $column;
}
.wrapper {
width: 600px;
height: 120px;
padding: 0 15px;
box-sizing: border-box;
background-color: rgba($color: #000000, $alpha: 0.35);
border-radius: 4px;
font-size: 14px;
color: #ffffff;
margin: 10px 0;
.top{
width:100%;
height:40px;
display:flex;
justify-content: space-between !important;
align-items:center !important;
.control {
width: 250px;
height: 25px;
display:flex;
justify-content: space-between;
align-items:center;
&>i {
cursor: pointer;
display: inline-block;
background-size: contain;
background-repeat: no-repeat;
}
.play {
width: 25px;
height: 25px;
}
}
.time {
width:300px;
height: 25px;
@include flexs(column, space-around , flex-start);
// flex: 1;
margin-left: 20px;
.name {
height: 22px;
line-height: 22px;
font-size: 12px;
max-width: 240px;
text-overflow: ellipsis;//让超出的用...实现
white-space: nowrap;//禁止换行
overflow: hidden;//超出的隐藏
}
.time-line {
height: 40px;
line-height: 40px;
@include flexs(row, flex-start , center);
&>span {
font-size: 12px;
}
.bar {
width: 200px;
margin: 0 10px;
margin-top: -4px;
position: relative;
&>div {
position: absolute;
top: 0;
}
.play-bar {
width: 0px;
height: 0;
height: 4px;
left: 0;
top: 0;
background: #F84E4E;
z-index: 8;
}
.play-point {
cursor: pointer;
width: 10px;
height: 10px;
background: #fff;
top: -3px;
box-sizing: border-box;
box-shadow: 0 0 4px 0 #F84E4E;
border-radius: 50%;
z-index: 9;
left: 0;
}
.play-bg {
width: 100%;
height: 0;
height: 4px;
opacity: .9;
background-color: #ffffff;
z-index: 7;
left: 0;
}
}
}
}
}
.bottom{
width:100%;
margin-top:16px;
display:flex;
justify-content:space-between;
align-items:center;
.bot-input{
width:240px;
}
.date-box{
width:100%;
position:relative;
}
}
}
</style>