时隔差不多半个月, 现在才来写这编博客。由于某些原因,我一直没有写,请大家原谅。前段时间开发了一个小模块。模块的主要功能就是有一个录音的功能。也就是说,模仿微信发送语音的功能一样。不多说,直接来一段代码
//自定义长按事件,注意, directives是跟 data ,created() 同一级的,注意注意
directives: {
longpress: {
bind(el, binding, vnode) {
let pressTimer = null;
let maxPressTime = 60000; // 60秒 = 60000毫秒
let startCallback = binding.value.start || Vue.noop;
let endCallback = binding.value.end || Vue.noop;
function handlePress() {
startCallback();
pressTimer = setTimeout(() => {
endCallback();
clearTimeout(pressTimer);
pressTimer = null;
}, maxPressTime);
}
function handleRelease() {
if (pressTimer) {
clearTimeout(pressTimer);
pressTimer = null;
endCallback();
}
}
// 确保这些函数在unbind中可用
el._handlePress = handlePress;
el._handleRelease = handleRelease;
// 添加事件监听器
el.addEventListener('mousedown', handlePress);
el.addEventListener('touchstart', handlePress);
el.addEventListener('mouseup', handleRelease);
el.addEventListener('touchend', handleRelease);
el.addEventListener('mouseleave', handleRelease);
el.addEventListener('touchcancel', handleRelease);
},
unbind(el) {
// 移除事件监听器
el.removeEventListener('mousedown', el._handlePress);
el.removeEventListener('touchstart', el._handlePress);
el.removeEventListener('mouseup', el._handleRelease);
el.removeEventListener('touchend', el._handleRelease);
el.removeEventListener('mouseleave', el._handleRelease);
el.removeEventListener('touchcancel', el._handleRelease);
// 清理自定义属性
el._handlePress = null;
el._handleRelease = null;
}
}
},
data(){
return {
zheZhao:false,//录音时的遮罩层
isMai:false,//初始化,是否支持录音
luYinAttr: {
isAn:false,
s: 0,
timer:null,
audioArray:[],
startTime: null,//录音开始时间
endTime: null, //录音结束时间
mediaRecorder: null,
recording: false, //录音状态(用于录音记时)
timeLength: 0,//录音时长
recordedChunks: [],
timer: null,
longPressThreshold:800,
},
}
}
//初始化录音对象(记住,录音一定得在 https里面。当然,在本地开发 localhost 可以不需要,但上线一定得在https里面)
luYin() {
//这儿先做判断,判断当前设备或当前网络是否支持录音,如果不支持,那下面的也就不用多说了。
//console.log(navigator, 'navigator.mediaDevices')
if (!('mediaDevices' in navigator)) {
this.isMai = false;
return false;
}
navigator.mediaDevices.getUserMedia({
audio: true })
.then(this.luYinSuccess) //支持录音,继续执行下面的操作
.catch(this.luYinErr) //不支持录音,没下文
},
luYinSuccess(stream) {
const audioContext = new AudioContext();
//const sourceNode = audioContext.createMediaStreamSource(stream); //没用上,先注释
const mediaRecorder = new MediaRecorder(stream);
const recordedChunks = [];
this.luYinAttr.mediaRecorder = mediaRecorder
this.luYinAttr.recordedChunks = recordedChunks
this.isMai = true;
},
luYinErr(err) {
this.isMai = false;
//console.log('The following error occurred: ' + err);
},
//startRecording , stopRecording是开始计时和结束计时(由于录音的时间不能太长,这儿我,限制为1分分钟的时长。所以,我得有一个录音计时,超过1分钟,录音取消)
//开始计时
startRecording() {
console.log('开始计时')
this.luYinAttr.isAn = true;
this.zheZhao = true;
this.luYinAttr.s = 0;
this.luYinAttr.recording = true;
this.luYinAttr.startTime = Date.now();
this.luYinAttr.timer = setInterval(() => {
this.luYinAttr.s = this.luYinAttr.s + 1;
} , 500)
},
//结束录音计时
stopRecording() {
this.luYinAttr.recording = false;
const endTime = Date.now();
const startTime = this.luYinAttr.startTime
const recordDuration = (endTime - startTime) / 1000; // 计算录音时长(秒)
if (recordDuration >= 60) {
this.luYinAttr.timeLength = 60;
//结束录音
}
this.luYinAttr.timeLength = recordDuration;
/*const maxWidth = 300; // 设置图标的最大宽度
const indicatorWidth = Math.min(recordDuration * 20, maxWidth); // 根据录音时长计算图标宽度,假设每秒对应2个像素宽度
console.log(indicatorWidth, '时长')
console.log('结束计时')*/
},
startLongPress() {
//console.log('长按开始');
let that = this;
clearTimeout(this.luYinAttr.timer);
that.luYinAttr.timer = setTimeout(function () {
// 执行长按操作
//console.log('长按操作开始');
that.luYinAttr.mediaRecorder.start();
//console.log(that.luYinAttr.mediaRecorder.state , 'qqggg');
//startRecord.disabled = true;
//stopRecord.disabled = false;
that.startRecording();
}, that.luYinAttr.longPressThreshold);
// 在这里编写长按开始时的逻辑
},
endLongPress() {
//长按结束事件
let that = this;
//console.log('取消')
clearTimeout(that.luYinAttr.timer);
that.luYinAttr.mediaRecorder.stop();
that.stopRecording();
that.luYinAttr.mediaRecorder.ondataavailable = async function (e) {
console.log(e)
if (e.data.size > 0) {
const blobObj = e.data;
let nowTime = new Date().getTime();
let fileName = 'LY_' + nowTime + '.mp3';
const file = new File([blobObj], fileName, {
type: 'audio/mpeg' });
//that.uplodAudio(file) //上传录音
that.luYinAttr.isAn = false;
that.zheZhao = false;
clearInterval(that.luYinAttr.timer)
} else {
// no data to push
}
};
},
//试听录音(在苹果机上不支持)
listenAudio(obj, index) {
let myTimer = null;
this.play.listenStatus = this.play.listenStatus + 1;
if (this.play.listenStatus > 1) {
clearInterval(myTimer)
return false;
}
if (!this.playActive.isLongPressing) {
let that = this;
let itemAudio = [(that.luYinAttr.recordedChunks)[index]]
//console.log(itemAudio, 'itemAudio', obj)
let len = JSON.parse(JSON.stringify(obj.timeLength));
obj.timeLength = 0;
myTimer = setInterval(() => {
obj.timeLength = obj.timeLength + 1;
if (obj.timeLength == len) {
clearInterval(myTimer)
this.play.listenStatus = 0;
}
} , 1000)
const blob = new Blob(itemAudio, {
'type': 'audio/ogg; codecs=opus' });
//const blob = new Blob(that.luYinAttr.recordedChunks, { 'type': 'audio/ogg; codecs=opus' });
//console.log(blob, 'blob')
const audioURL = window.URL.createObjectURL(blob);
//console.log(audioURL, 'audioURL')
const audio = new Audio(audioURL);
//console.log(audio,'audio')
audio.play().catch(err => {
console.log('Failed to play sound:', err);
});
}
},
<template v-if="isMai == true">
<div class="" style="background:#ffffff; padding:10px 16px;">
<div class="list" style="text-align:center">
<template v-for="(audioArrayItem , audioArrayIndex) in luYinAttr.audioArray">
<el-tooltip placement="top" :manual="true" :value="audioArrayItem.tooltip">
<div slot="content">
<span @click="returnAudioArrayItem(audioArrayItem , audioArrayIndex)">移除</span>
</div>
<div style="margin-bottom:10px;" @click.prevent="listenAudio(audioArrayItem , audioArrayIndex)">
<div style="overflow:hidden">
<div style="float:left">
<p style="background: #3975C6; color: #ffffff; padding:0 8px; display:inline-block; border-radius:4px;">
<span class="icon iconfont playIcon" style="display:inline-block; height:20px; font-size:14px; text-align:center;"></span>
<span style="position:relative; top:-5px; margin:0 5px;">
<template v-for="(item , index) in audioArrayItem.timeLength">
<span>,,</span>
</template>
</span>
<span>{
{ audioArrayItem.timeLength }}'</span>
</p>
</div>
<div style="float:right"><van-button type="info" size="mini" @click.stop="returnAudioArrayItem(audioArrayItem , audioArrayIndex)">移除</van-button></div>
</div>
</div>
</el-tooltip>
</template>
</div>
<div class="byBtn" :class="luYinAttr.isAn == true ? 'myIsYesAn': 'myIsNoAn' " style="text-align: center; font-size:14px;" v-longpress="{ start: startLongPress, end: endLongPress }">
<p class="icon iconfont" style="color: #3975C6; font-size: 24px;"></p>
<p>任务详情 - 按住说话</p>
</div>
</div>
</template>