START
- 最近遇到需求,需要对音频进行处理,展示,但是还是遇到了很多坑,写个文章记录一下,以后避免踩坑。
- 本着尽可能描述详细的初衷去编写本文,内容略长,请配合目录食用。
- 仅以此文,纪念一下自己找BUG的一天时间 ღ( ´・ᴗ・` )
目录
1.业务逻辑梳理
-
说一下应用场景,由于最近做的是微信服务号的二次开发,所有数据,基本上都是从微信官方哪里获取的(数据包含,文字,图片,音频,视频,图文。这里单方面讨论音频)
-
微信官方提供的接口请求音频数据,是根据凭证access_token以及音频的media_id去请求数据的。
-
微信官方返回的数据是,二进制数据流
效果 如下图:
-
-
和后端朋友讨论了一下,这里处理这个数据有两种方案,简单的总结如下:
- 后端返回音频URL:后端去请求微信的接口,拿到二进制数据流,保存在我司自己的服务器上,再生成一个URL返回给(前端),前端根据URL去播放
- 后端返回二进制音频数据流:前端直接请求微信接口,拿到二进制数据流,前端处理二进制数据流(拿到数据后,下载播放都是可以的)。
2.项目环境
- vue 2
- element-ui
- sass
3.URL音频处理
-
由于公司代码不方便展示,所以写个demo做展示。
效果如下图:
-
话不多说,上代码 (原生的 controls 太丑了,所以我写了一个虚拟的)
template
<template> <div class="audio_page"> <h2>前端audio(音频)处理</h2> <div class="voice_show_item"> <div class="voice_play"> <img v-if="!isPlay" :src="pauseImgUrl" alt="暂停中" @click=" onPlayOrPause( 'https://res.wx.qq.com/voice/getvoice?mediaid=MzI3ODQwMjQwMV8xMDAwMDAxMzE=' ) " /> <img v-if="isPlay" :src="playImgUrl" alt="播放中" @click=" onPlayOrPause( 'https://res.wx.qq.com/voice/getvoice?mediaid=MzI3ODQwMjQwMV8xMDAwMDAxMzE=' ) " /> </div> <div class="voice_info"> <p>{{ submitContent.name }}</p> <!-- <div class="voice_progress"></div> --> <el-progress :percentage=" currentTime && duration ? (currentTime / duration) * 100 : 0 " status="success" :stroke-width="2" :show-text="false" ></el-progress> <div class="voice_time"> <span class="current_time">{{ currentTime | formatSecond }}</span> <span class="duration">{{ duration | formatSecond }}</span> </div> </div> <audio :src="voiceUrl" ref="audio" @timeupdate="onTimeUpedate" @loadedmetadata="onLoadedmetadata" ></audio> </div> </div> </template>
script
<script> // 格式化音频时间 XXX秒 => 00:00:00 const realFormatSecond = 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" + hours).slice(-2) + ":" + ("0" + mimute).slice(-2) + ":" + ("0" + second).slice(-2) ); } else { return "00:00:00"; } }; export default { name: "AudioPage", data() { return { submitContent: { name: "lazy_tomato", }, isPlay: false, playImgUrl: require("../assets/wx_image/play.gif"), pauseImgUrl: require("../assets/wx_image/pause.png"), voiceUrl: "", currentTime: 0, duration: 0, }; }, filters: { // 将整数转换成 时分秒 formatSecond(second = 0) { return realFormatSecond(second); }, }, methods: { // 已选音频播放 onPlayOrPause(url) { this.$refs["audio"].src = url; if (this.isPlay) { this.$refs["audio"].pause(); this.isPlay = false; } else { this.$refs["audio"].play(); this.isPlay = true; } }, // 当前播放时间 onTimeUpedate(e) { this.currentTime = e.target.currentTime; }, // 最大播放时长 onLoadedmetadata(e) { this.duration = e.target.duration; }, }, }; </script>
style
<style lang="scss" scoped > .voice_show_item { display: inline-block; width: 350px; height: 85px; border-radius: 5px; border: 1px solid #e0e0e0; .voice_play { float: left; width: 72px; height: 85px; line-height: 85px; img { margin: 25px 20px; width: 32px; height: 32px; cursor: pointer; } } .voice_info { float: left; width: 262px; p { margin: 18px 0; width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .voice_progress { width: 100%; height: 2px; background-color: #ebebeb; } .voice_time { width: 100%; .currentTime { float: left; } .duration { float: right; } } } } </style>
3.1 audio标签
方案一: 可以直接在html中写一个 audio标签 动态赋值src 既可
由于原生的音乐播放很丑,所以模拟做了一个假的播放效果 ,代码如下:
// 已选音频播放
onPlayOrPause(url) {
this.$refs["audio"].src = url;
if (this.isPlay) {
this.$refs["audio"].pause();
this.isPlay = false;
} else {
this.$refs["audio"].play();
this.isPlay = true;
}
},
方案二 :可以在播放按钮添加 点击事件 new Aduio() 也是可以的
代码如下
let audio = new Audio();
audio.src = url;
audio.pause() //暂停
audio.play() //播放
3.2 音频最大时长获取
html
<audio
:src="voiceUrl"
ref="audio"
@timeupdate="onTimeUpedate"
@loadedmetadata="onLoadedmetadata"
></audio>
js
// 最大播放时长
onLoadedmetadata(e) {
this.duration = e.target.duration;
},
3.3 音频当前播放时间
html
<audio
:src="voiceUrl"
ref="audio"
@timeupdate="onTimeUpedate"
@loadedmetadata="onLoadedmetadata"
></audio>
js
// 当前播放时间
onTimeUpedate(e) {
this.currentTime = e.target.currentTime;
},
3.4 播放进度
- 使用了element-ui 中的进度条,配合 播放时间/ 音频总时间 => 动态显示播放进度,实现音乐播放进度条
-
这里要判断 播放时间和音频总时间 是否存在,不然 进度条组件会报错。(/ω\),percentage数值区间0-100,而
1/0 => Infinity
<el-progress
:percentage="
currentTime && duration ? (currentTime / duration) * 100 : 0
"
status="success"
:stroke-width="2"
:show-text="false"
></el-progress>
3.5 播放时间格式化
const realFormatSecond = 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" + hours).slice(-2) +
":" +
("0" + mimute).slice(-2) +
":" +
("0" + second).slice(-2)
);
} else {
return "00:00:00";
}
};
//加个过滤器
filters: {
// 将整数转换成 时分秒
formatSecond(second = 0) {
return realFormatSecond(second);
},
},
4.二进制数据流音频处理
4.1整体代码
template
<template>
<div>
<h1>获取音频二进制数据流</h1>
<el-button @click="up">开始获取音频数据</el-button>
<audio :src="voiceUrl" controls></audio>
</div>
</template>
script
<script>
export default {
data() {
return {
voiceUrl: "",
};
},
methods: {
up() {
this.$axios.defaults.baseURL = "/api";
this.$axios({
url: "/custom/weChat/doTest",
method: "post",
data: { media_id: "w5M3TLtyN7W4_4E8bL4F_kscTXc9CeV7HfxmZutPpZ4" },
responseType: "blob",
})
.then((res) => {
const blob = res.data;
// 1.获取数据流并下载
// const reader = new FileReader();
// reader.readAsDataURL(blob);
// console.log(reader);
// reader.onload = (e) => {
// const a = document.createElement("a");
// a.download = `文件名称.mp3`;
// a.href = e.target.result;
// document.body.appendChild(a);
// a.click();
// document.body.removeChild(a);
// };
// 2.获取数据流处理成url,直接复制给audio的src去使用
var filereader = new FileReader();
filereader.readAsArrayBuffer(blob);
filereader.onload = (e) => {
console.log(filereader.result);
var blob = new Blob([filereader.result]);
this.voiceUrl = URL.createObjectURL(blob);
};
})
.catch((err) => {
console.log(err);
});
},
},
};
</script>
4.2 请求需要添加配置
this.$axios({
url: "/custom/weChat/doTest",
method: "post",
data: { media_id: "w5M3TLtyN7W4_4E8bL4F_kscTXc9CeV7HfxmZutPpZ4" },
// 设置响应类型为 blob ,不设置,拿到的数据格式会有问题
responseType: "blob",
})
4.3 下载 二进制数据流的音频
// 声明一个变量 blob 接收数据流
const blob = res.data;
// 1.获取数据流并下载
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = (e) => {
const a = document.createElement("a");
//下面设置下载的文件名成
a.download = `文件名称.mp3`;
a.href = e.target.result;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
readAsDataURL
方法会读取指定的 [Blob
]或 [File
] 对象。读取操作完成的时候,[readyState
] 会变成已完成DONE
,并触发loadend
事件,同时 [result
]属性将包含一个data:
URL格式的字符串(base64编码)以表示所读取文件的内容。
4.4 数据转换成可访问的url
// 声明一个变量 blob 接收数据流
const blob = res.data;
// 2.获取数据流处理成url,直接复制给audio的src去使用
var filereader = new FileReader();
filereader.readAsArrayBuffer(blob);
filereader.onload = (e) => {
console.log(filereader.result);
var blob = new Blob([filereader.result]);
this.voiceUrl = URL.createObjectURL(blob);
// this.voiceUrl 就是转换好的url,直接赋值给音频的src即可
};
5. 音频相关的问题
-
数据修改了,视图不更新的问题
-
循环创建音频,实现音频独立,互斥
END
暂时就写那么多啦,后期再补充。ღ( ´・ᴗ・` )比心
网友评论