归根结底 语音功能就是录音,上传,播放,知道了这个就可以开始啦
1.录音
1)可以使用navigator.mediaDevices.getUserMedia
startRecording() {
navigator.mediaDevices.getUserMedia({ audio: true })
.then((stream) => {
this.audioRecorder = new MediaRecorder(stream);
this.audioRecorder.ondataavailable = (e) => {
if (e.data.size > 0) {
this.audioRecorder.onstop = () => {
const audioBlob = new Blob([e.data], { type: 'audio/wav' });
var file = new File([audioBlob], '语音', {
type: 'application/json',
lastModified: Date.now()
});
const audioUrl = URL.createObjectURL(audioBlob);
console.log(audioUrl)
this.messages.push({ id: Date.now(), audioUrl });
};
// this.audioRecorder.stop();
}
};
this.audioRecorder.start();
})
.catch((error) => {
console.error('Error accessing microphone:', error);
});
},
2)可以使用插件npm i js-audio-recorder
插件的使用可以看另一篇文章
2.上传
const formData = new FormData();
const blob = this.recorder.getWAVBlob(); // 获取wav格式音频数据
// 此处获取到blob对象后需要设置fileName满足当前项目上传需求,其它项目可直接传把blob作为file塞入formData
const newbolb = new Blob([blob], { type: "audio/wav" });
const fileOfBlob = new File([newbolb], new Date().getTime() + ".wav");
formData.append("file", fileOfBlob);
const url = window.URL.createObjectURL(fileOfBlob);
this.src = url;
uni.showLoading({
title: "上传中",
});
console.log(11111111111111);
uni.uploadFile({
url: `${indexConfig.baseUrl}/common/uploadFile`,
filePath: this.src,
name: "file",
formData: {},
header: {
Authorization: uni.getStorageSync("accessToken"),
},
success: (res) => {
uni.hideLoading();
if (res && res.data) {
const req = JSON.parse(res.data);
if (req.code == 200) {
let img = indexConfig.assetsPath + req.data;
this.$emit("ok", 4, img, this.durationSeconds);
this.cancelRecord();
this.isShowAudio = true;
}
}
},
fail: () => {
uni.hideLoading();
uni.$u.toast("发送失败,请重新发送");
this.cancelRecord();
this.isShowAudio = true;
},
});
3.播放
1)用audio
<audio controls :src="message.audioUrl"></audio>
2)用uni的api uni.createInnerAudioContext()
const innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.play();
innerAudioContext.pause();
innerAudioContext.onEnded(() => {
console.log("音频播放结束");
});
4.完整代码
组件
<template>
<div>
<u-modal
:showConfirmButton="false"
style="width: 100%; height: 100%"
:show="show"
:title-style="{ color: 'red' }"
>
<!-- 音频录制 -->
<div class="audio-record" v-show="recordStatus">
<!-- <img
class="cancel-img"
width="20"
src="@/assets/image/close.png"
@click="cancelRecord()"
/> -->
<u-icon
class="cancel-img"
@click="cancelRecord()"
name="close"
color="rgb(184 187 193)"
size="26"
></u-icon>
<p class="duration-seconds-style">
<span>已录制 {{ durationSeconds }}″</span>
<!-- <span v-show="countDownRecord">{{ countDownSecond }}″ 后停止录制</span> -->
</p>
<div class="icon">
<div class="voice-animation" ref="voiceAnimation">
<p v-for="item in 7" :key="item"></p>
</div>
</div>
<div class="record-status" v-show="!startRecord" @click="startAudioRecord">
<p>开始录制</p>
</div>
<div class="btn" v-show="startRecord">
<div class="cancel" @click="cancelRecord">
<p>取消</p>
</div>
<div class="send" @click="sendRecord">
<p>发送</p>
</div>
</div>
</div>
<!-- 语音条 -->
<!-- <div class="audio-detail-msg" v-show="isShowAudio">
<div
class="audio-style"
:class="{ 'add-animation': isPlay }"
:style="{ width: handleAudioStyleWidth() }"
@click="playAudio()"
>
<div class="small"></div>
<div class="middle"></div>
<div class="large"></div>
</div>
<div class="duration-seconds">{{ showDurationSeconds }}″</div>
</div> -->
</u-modal>
</div>
</template>
<script>
import Recorder from "js-audio-recorder";
import indexConfig from "@/config/index.config";
export default {
props: {
show: {
type: Boolean,
default: false,
},
},
data() {
return {
// show: false,
recorder: null,
showDurationSeconds: 0, // 语音时长
durationSeconds: 0, // 语音时长
isPlay: false, // 当前语音是否正在播放
playAudioTimer: null, // 语音播放定时器
recordStatus: true, // 音频录制显示状态
isDurationSeconds: false, // 开始录制时间状态
countDownRecord: false, // 倒计时录制状态
countDownSecond: 10, // 倒计时录制时间
startRecord: false, // 开始录制按钮状态
isShowAudio: false, // 语音条是否显示
};
},
created() {
this.recorder = new Recorder();
},
watch: {
durationSeconds(newVal) {
if (newVal === 2) {
this.isDurationSeconds = false;
clearInterval(this.confTime);
this.countDownRecord = true;
this.confTime = setInterval(() => {
this.countDownSecond--;
this.durationSeconds++;
}, 1000);
}
},
// countDownSecond(newVal) {
// if (newVal === 0) this.sendRecord();
// },
},
methods: {
// 取消语音录制
cancelRecord() {
this.showDurationSeconds = this.durationSeconds;
// this.recordStatus = false;
this.startRecord = false;
this.isDurationSeconds = false;
this.countDownRecord = false;
this.$refs.voiceAnimation.classList.remove("start");
clearInterval(this.confTime);
this.confTime = null;
this.durationSeconds = 0;
this.countDownSecond = 10;
this.$emit("cancel");
// this.show = false;
},
// 开始录制语音消息
startAudioRecord() {
this.startRecord = true;
this.isDurationSeconds = true;
this.$refs.voiceAnimation.classList.add("start");
this.recorder = new Recorder();
Recorder.getPermission().then(
() => {
console.log("开始录音");
this.recorder.start(); // 开始录音
},
(error) => {
this.$message({
message: "请先允许该网页使用麦克风",
type: "info",
});
console.log(`${error.name} : ${error.message}`);
}
);
this.confTime = setInterval(() => {
this.durationSeconds++;
}, 1000);
},
// 完成录制并发送
sendRecord() {
if (!this.durationSeconds) {
this.cancelRecord();
uni.$u.toast("录制时间太短!");
return;
}
this.recorder.pause(); // 暂停录音
console.log("上传录音"); // 上传录音
const formData = new FormData();
const blob = this.recorder.getWAVBlob(); // 获取wav格式音频数据
// 此处获取到blob对象后需要设置fileName满足当前项目上传需求,其它项目可直接传把blob作为file塞入formData
const newbolb = new Blob([blob], { type: "audio/wav" });
const fileOfBlob = new File([newbolb], new Date().getTime() + ".wav");
formData.append("file", fileOfBlob);
const url = window.URL.createObjectURL(fileOfBlob);
this.src = url;
console.log(newbolb, fileOfBlob, this.src, "fileOfBlob");
uni.showLoading({
title: "上传中",
});
console.log(11111111111111);
uni.uploadFile({
url: `${indexConfig.baseUrl}/common/uploadFile`,
filePath: this.src,
name: "file",
formData: {},
header: {
Authorization: uni.getStorageSync("accessToken"),
},
success: (res) => {
uni.hideLoading();
if (res && res.data) {
const req = JSON.parse(res.data);
if (req.code == 200) {
let img = indexConfig.assetsPath + req.data;
console.log(222222222, this.durationSeconds);
this.$emit("ok", 4, img, this.durationSeconds);
this.cancelRecord();
this.isShowAudio = true;
}
}
},
fail: () => {
uni.hideLoading();
this.show = false;
uni.$u.toast("发送失败,请重新发送");
this.cancelRecord();
this.isShowAudio = true;
},
});
},
// 设置语音条宽度样式
handleAudioStyleWidth() {
if (this.showDurationSeconds === 1) {
return "38px";
} else if (this.showDurationSeconds > 1 && this.showDurationSeconds < 20) {
return `${38 + (this.showDurationSeconds / 10) * 36}px`;
} else if (this.showDurationSeconds >= 20) {
return `${106.39 + (this.showDurationSeconds / 10) * 18.935}px`;
}
},
// 播放语音
playAudio() {
// 若当前为播放状态则关闭
if (this.isPlay) return (this.isPlay = false);
this.isPlay = true;
// 计时停止播放语音
this.playAudioTimer = setTimeout(() => {
this.isPlay = false;
}, parseInt(this.showDurationSeconds * 1000));
},
},
};
</script>
<style lang="scss">
p {
margin: 0;
}
// 音频录制
.audio-record {
// position: absolute;
// top: 100%;
// left: 50%;
// transform: translate(-50%, -50%);
width: 150px;
height: 145px;
background: rgba(0, 0, 0, 0.8);
padding: 20px;
// border-radius: 15px;
color: #ccc;
font-size: 15px;
text-align: center;
.cancel-img {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
}
.duration-seconds-style {
height: 20px;
}
.icon {
display: flex;
justify-content: center;
align-items: flex-end;
margin: 18px 0 30px;
}
.voice-animation {
margin-left: 15px;
p {
height: 3px;
margin-top: 4px;
background: #666;
}
p:nth-of-type(1) {
width: 28px;
}
p:nth-of-type(2) {
width: 24px;
}
p:nth-of-type(3) {
width: 20px;
}
p:nth-of-type(4) {
width: 16px;
}
p:nth-of-type(5) {
width: 12px;
}
p:nth-of-type(6) {
width: 8px;
}
p:nth-of-type(7) {
width: 5px;
}
&.start {
p:nth-of-type(1) {
animation: backgroundInfinite7 1.5s ease-in-out infinite;
}
p:nth-of-type(2) {
animation: backgroundInfinite6 1.5s ease-in-out infinite;
}
p:nth-of-type(3) {
animation: backgroundInfinite5 1.5s ease-in-out infinite;
}
p:nth-of-type(4) {
animation: backgroundInfinite4 1.5s ease-in-out infinite;
}
p:nth-of-type(5) {
animation: backgroundInfinite3 1.5s ease-in-out infinite;
}
p:nth-of-type(6) {
animation: backgroundInfinite2 1.5s ease-in-out infinite;
}
p:nth-of-type(7) {
background: #f5f5f5;
}
}
}
.record-status {
width: 100px;
height: 28px;
margin: 0 auto;
line-height: 28px;
color: #ffffff;
border-radius: 2px;
&:hover {
color: #04113d;
background: #e1e1e1;
}
p {
cursor: pointer;
}
}
.btn {
display: flex;
justify-content: space-between;
align-items: center;
div {
width: 60px;
height: 30px;
color: #888;
cursor: pointer;
line-height: 30px;
border-radius: 4px;
}
.cancel:hover {
color: #cbcbcb;
// background: #830808;
}
.send:hover {
color: #cbcbcb;
// background: #4a8b2a;
}
}
}
// 语音条
.audio-detail-msg {
display: flex;
align-items: center;
.audio-style {
display: flex;
align-items: center;
height: 32px;
margin-right: 8px;
padding: 0 10px;
border-radius: 4px;
background: #f5f5f5;
.small {
border: 4px solid #4c4c4c;
border-top-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
}
.middle {
width: 16px;
height: 16px;
margin-left: -11px;
opacity: 1;
}
.large {
width: 24px;
height: 24px;
margin-left: -19px;
opacity: 1;
}
& > div {
border: 2px solid #4c4c4c;
border-top-color: transparent;
border-left-color: transparent;
border-bottom-color: transparent;
border-radius: 50%;
box-sizing: border-box;
}
&.add-animation {
.middle {
animation: show2 1.2s ease-in-out infinite;
}
.large {
animation: show3 1.2s ease-in-out infinite;
}
}
}
// 语音播放动画
@keyframes show2 {
0% {
opacity: 0;
}
10% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes show3 {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
60% {
opacity: 0;
}
100% {
opacity: 0;
}
}
// 语音录制动画
@keyframes backgroundInfinite2 {
0% {
background: #666;
}
20% {
background: #f5f5f5;
}
95% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite3 {
0% {
background: #666;
}
30% {
background: #f5f5f5;
}
85% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite4 {
0% {
background: #666;
}
55% {
background: #f5f5f5;
}
75% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite5 {
0% {
background: #666;
}
45% {
background: #666;
}
60% {
background: #f5f5f5;
}
75% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite6 {
0% {
background: #666;
}
65% {
background: #666;
}
85% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
@keyframes backgroundInfinite7 {
0% {
background: #666;
}
75% {
background: #666;
}
95% {
background: #f5f5f5;
}
100% {
background: #666;
}
}
}
/deep/.u-modal__content {
padding: 0 !important;
}
/deep/.u-modal {
width: 100% !important;
}
/deep/.u-line {
border: 0 !important;
}
</style>
5.效果展示