vue+element 实现音乐播放(已修正element slider自动滑动的bug)
这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入欢迎使用Mar
最近项目里需要用到音乐播放器,但是h5自带audio标签样式太丑,于是便自己实现一个。
关于使用vue+element实现audio的文章有很多,这里不作赘述,直接上效果图和第一版源代码。
初始版本
<template>
<div>
<audio ref="audio" id="audio" src="../../assets/audio/audio.mp3"
@pause="onPause"
@play="onPlay"
@timeupdate="onTimeupdate"
@loadedmetadata="onLoadedmetadata">
您的浏览器不支持audio标签
</audio>
<div class="custom-audio clearfix">
<div class="audio-item play" @click="play" v-if="!audio.playing">
<img src="../../assets/audio/play.png" width="20"/>
</div>
<div class="audio-item play" @click="pause" v-if="audio.playing">
<img src="../../assets/audio/pause.png" width="20"/>
</div>
<div class="audio-item mute" @click="mute" v-if="!audio.muted">
<img src="../../assets/audio/cancleMute.png" width="20"/>
</div>
<div class="audio-item mute" @click="cancelMute" v-if="audio.muted">
<img src="../../assets/audio/mute.png" width="20"/>
</div>
<div class="audio-item play-time">{{ audio.currentTime | formatSecond}}</div>
<div class="audio-item progress">
<el-slider @change="progressChange" v-model="audio.currentTime" :format-tooltip="realFormatSecond"
:max="audio.maxTime"></el-slider>
</div>
<div class="audio-item total-time">{{ audio.maxTime | formatSecond}}</div>
</div>
</div>
</template>
<script>
function realFormatSecond(second) {
var secondType = typeof second;
if (secondType === 'number' || secondType === 'string') {
second = parseInt(second);
var hours = Math.floor(second / 3600);
second = second - hours * 3600;
var mimute = Math.floor(second / 60);
second = second - mimute * 60;
return ('0' + mimute).slice(-2) + ':' + ('0' + second).slice(-2)
} else {
return '00:00'
}
}
export default {
name: "CustomAudio",
data() {
return {
value: 20,
audio: {
// 播放状态
playing: false,
// 静音状态
muted: false,
// 音频当前播放时长
currentTime: 0,
// 音量
volume: 1,
// 音频最大播放时长
maxTime: 0
},
cacheCurrent: 0,
cacheVoice: 1,
}
},
methods: {
// 音频相关方法
// 播放音频
play() {
this.$refs.audio.play()
},
// 暂停音频
pause() {
this.$refs.audio.pause()
},
// 当音频播放
onPlay() {
this.audio.playing = true
},
// 当音频暂停
onPause() {
this.audio.playing = false
},
// 静音
mute() {
this.audio.volume = 0;
this.audio.muted = true;
this.$refs.audio.muted = true;
},
// 取消静音
cancelMute() {
this.audio.muted = false;
this.$refs.audio.muted = false;
},
// 拖动进度滚动条
progressChange() {
console.log('拖动滚动条触发', this.cacheCurrent);
this.$refs.audio.currentTime = this.cacheCurrent;
this.audio.currentTime = this.cacheCurrent
},
// 当timeupdate事件大概每秒一次,用来更新音频流的当前播放时间
onTimeupdate(res) {
this.audio.currentTime = res.target.currentTime
},
// 获取音频长度
onLoadedmetadata(res) {
this.audio.maxTime = parseInt(res.target.duration)
},
realFormatSecond(second) {
console.log('自动播放触发', second);
this.cacheCurrent = second;
return realFormatSecond(second);
},
},
filters: {
// 将整数转化成时分秒
formatSecond(second = 0) {
return realFormatSecond(second)
}
}
}
</script>
<style scoped lang="scss">
.custom-audio {
line-height: 68px;
border: 1px solid #6779ff;
padding: 0 15px;
text-align: center;
.audio-item {
float: left;
img {
vertical-align: middle;
}
&.play {
width: 50px;
cursor: pointer;
}
&.mute {
width: 20px;
margin-left: 8px;
cursor: pointer;
line-height: 30px;
margin-top: 19px;
}
&.play-time {
width: 80px;
}
&.progress {
width: calc(100% - 238px);
padding-top: 16px;
}
&.total-time {
width: 80px;
}
}
}
</style>
复制粘贴就完事了。
附赠几张用到的图片。
到这里大体结构就已经出来,播放暂停静音功能可以正常使用。
一个小bug
不过拖动进度条会出现一个bug,博主这里用的element的版本是2.12.0,若是后面版本的element的slider滑块组件解决了这个bug,则可以跳过直达后面修改音量。
这个bug是什么呢?
在使用中可以很轻松的触发,当拖动进度停住鼠标延迟大概一秒左右松开,进度会回到原来的位置,而快速拖动和点击进度条则不会出现这个问题,我在第一版代码里打印了日志,截图如下:
拖动滚动条延迟一小会松开,自动播放绑定的值又会变化一次,导致最后一次变化的值覆盖了原来的值,即使已经在format-tooltip事件使用缓存的变量(有文章说这样可以获得正确的值,其实并没有用),使用官方的input方法也是一样。
那既然是倒数第二个值正确,那用数组把变化的值存起来,取倒数第二个值呢?
也不行,因为快速拖动和点击的话是最后一个值正确,这里就非常的蛋疼了,我就翻各种介绍实现audio的文章、帖子,大多数没有提到这个bug,直到我看到有篇文章写,在拖动的时候暂停然后松开再播放,这样就规避的这个bug,文章地址:https://www.cnblogs.com/522040-m/p/9837932.html,
就在我也以为只能这样的时候,不经意间在监听鼠标抬起事件的时候,打印了我缓存的值,意外惊喜出现了:
在鼠标抬起的时候居然打印到了最后的值,虽然后面又触发了change,但是只要鼠标抬起的时候手动触发一下change方法,就可以完美解决,代码如下:
// html修改
<el-slider @change="progressChange" @mouseup.native="mouseupChangeTime" v-model="audio.currentTime" :format-tooltip="realFormatSecond"
:max="audio.maxTime"></el-slider>
// 方法修改
// 鼠标抬起改变当前时间点
mouseupChangeTime() {
this.progressChange(this.cacheCurrent)
},
// 拖动进度滚动条
progressChange(value) {
this.$refs.audio.currentTime = value >= 0 ? value : this.cacheCurrent;
this.audio.currentTime = value >= 0 ? value : this.cacheCurrent
},
由于change方法会默认传一个对象,所以用value>=0来判断一下是否从使用传入的值,这样无论是怎样拖动或是点击都可以正常使用。
加个音量调节器
音量调节器也是用slider实现的,外面套一层弹出层,代码:
// html 部分 使用el-tooltip把原来的两张声音图片包住
<el-tooltip class="item" effect="light" placement="top">
<div slot="content">
<el-slider vertical height="120px" :step="0.01" :max="1" v-model="audio.volume" @input="voiceChange()"
:format-tooltip="handelVoice"></el-slider>
</div>
<div class="audio-item mute" @click="mute" v-if="!audio.muted">
<img src="../../assets/audio/cancleMute.png" width="20"/></div>
<div class="audio-item mute" @click="cancelMute" v-if="audio.muted">
<img src="../../assets/audio/mute.png" width="20"/></div>
</el-tooltip>
// js方法部分
// 拖动音量滚动条
voiceChange() {
this.$refs.audio.volume = this.audio.volume;
},
// 处理音量显示
handelVoice() {
return parseInt(this.audio.volume.toFixed(2) * 100);
}
这里的slider没有自动变化的情况,正常使用即可。
优化一下:
- 当声音调整为0的时候,置为静音
- 静音的时候调节声音改为取消静音
- 点击取消静音的时候回到原来的音量
// 拖动音量滚动条
voiceChange() {
this.cancelMute(false);
if (this.audio.volume === 0) {
this.mute(false)
}
this.$refs.audio.volume = this.audio.volume;
},
// 静音
mute(event) {
// 正常点击静音和取消静音的时候把当前音量存下来,拖动触发的静音方法只需要改变状态
event && (this.cacheVoice = this.audio.volume);
this.audio.volume = 0;
this.audio.muted = true;
this.$refs.audio.muted = true;
},
// 取消静音
cancelMute(event) {
event && (this.audio.volume = this.cacheVoice);
this.audio.muted = false;
this.$refs.audio.muted = false;
},
到这里基本功能已经实现,如需更多功能,自行添加一下即可,难点主要在slider的滑动那里,但是解决方案也异常简单,只是不太容易想到。
如果文章帮助到了您,还请帮忙点个赞!(o)/
最终版本
<template>
<div>
<audio ref="audio" id="audio" src="../../assets/audio/audio.mp3"
@pause="onPause"
@play="onPlay"
@timeupdate="onTimeupdate"
@loadedmetadata="onLoadedmetadata">
您的浏览器不支持audio标签
</audio>
<div class="custom-audio clearfix">
<div class="audio-item play" @click="play" v-if="!audio.playing">
<img src="../../assets/audio/play.png" width="20"/></div>
<div class="audio-item play" @click="pause" v-if="audio.playing">
<img src="../../assets/audio/pause.png" width="20"/></div>
<el-tooltip class="item" effect="light" placement="top">
<div slot="content">
<el-slider vertical height="120px" :step="0.01" :max="1" v-model="audio.volume" @input="voiceChange()"
:format-tooltip="handelVoice"></el-slider>
</div>
<div class="audio-item mute" @click="mute" v-if="!audio.muted">
<img src="../../assets/audio/cancleMute.png" width="20"/></div>
<div class="audio-item mute" @click="cancelMute" v-if="audio.muted">
<img src="../../assets/audio/mute.png" width="20"/></div>
</el-tooltip>
<div class="audio-item play-time">{{ audio.currentTime | formatSecond}}</div>
<div class="audio-item progress">
<el-slider @change="progressChange($event)" @mouseup.native="mouseupChangeTime"
v-model="audio.currentTime" :format-tooltip="realFormatSecond" :max="audio.maxTime"></el-slider>
</div>
<div class="audio-item total-time">{{ audio.maxTime | formatSecond}}</div>
</div>
</div>
</template>
<script>
function realFormatSecond(second) {
var secondType = typeof second;
if (secondType === 'number' || secondType === 'string') {
second = parseInt(second);
var hours = Math.floor(second / 3600);
second = second - hours * 3600;
var mimute = Math.floor(second / 60);
second = second - mimute * 60;
return ('0' + mimute).slice(-2) + ':' + ('0' + second).slice(-2)
} else {
return '00:00'
}
}
export default {
name: "CustomAudio",
data() {
return {
value: 20,
audio: {
// 播放状态
playing: false,
// 静音状态
muted: false,
// 音频当前播放时长
currentTime: 0,
// 音量
volume: 1,
// 音频最大播放时长
maxTime: 0
},
cacheCurrent: 0,
cacheVoice: 1,
}
},
methods: {
// 音频相关方法
// 播放音频
play() {
this.$refs.audio.play()
},
// 暂停音频
pause() {
this.$refs.audio.pause()
},
// 当音频播放
onPlay() {
this.audio.playing = true
},
// 当音频暂停
onPause() {
this.audio.playing = false
},
// 拖动音量滚动条
voiceChange() {
this.cancelMute(false);
if (this.audio.volume === 0) {
this.mute(false)
}
this.$refs.audio.volume = this.audio.volume;
},
// 静音
mute(event) {
event && (this.cacheVoice = this.audio.volume);
this.audio.volume = 0;
this.audio.muted = true;
this.$refs.audio.muted = true;
},
// 取消静音
cancelMute(event) {
event && (this.audio.volume = this.cacheVoice);
this.audio.muted = false;
this.$refs.audio.muted = false;
},
// 鼠标抬起改变当前时间点
mouseupChangeTime() {
this.progressChange(this.cacheCurrent)
},
// 拖动进度滚动条
progressChange(value) {
this.$refs.audio.currentTime = value >= 0 ? value : this.cacheCurrent;
this.audio.currentTime = value >= 0 ? value : this.cacheCurrent
},
// 当timeupdate事件大概每秒一次,用来更新音频流的当前播放时间
onTimeupdate(res) {
this.audio.currentTime = res.target.currentTime
},
// 获取音频长度
onLoadedmetadata(res) {
this.audio.maxTime = parseInt(res.target.duration)
},
realFormatSecond(second) {
this.cacheCurrent = second;
return realFormatSecond(second);
},
// 处理音量显示
handelVoice() {
return parseInt(this.audio.volume.toFixed(2) * 100);
}
},
filters: {
// 将整数转化成时分秒
formatSecond(second = 0) {
return realFormatSecond(second)
}
}
}
</script>
<style scoped lang="scss">
.custom-audio {
line-height: 68px;
border: 1px solid #6779ff;
padding: 0 15px;
text-align: center;
.audio-item {
float: left;
img {
vertical-align: middle;
}
&.play {
width: 50px;
cursor: pointer;
}
&.mute {
width: 20px;
margin-left: 8px;
cursor: pointer;
line-height: 30px;
margin-top: 19px;
}
&.play-time {
width: 80px;
}
&.progress {
width: calc(100% - 238px);
padding-top: 31px;
>>> .el-slider__runway {
margin: 0;
}
}
&.total-time {
width: 80px;
}
}
}
</style>
更多推荐
所有评论(0)