MarkTime: 2024-05-24 10:41:26
LogTime: 2024-11-10 14:55:53
首先复习
setTimeout():
语法: let timeId = setTimeout(func|code, [delay_millisecond])
说明: 延时器. 延迟delay_millisecond后, 执行参数1setInterval():
语法: let timeId = setInterval(func|code, [delay_millisecond])
说明: 周期性执行器. 每隔delay_millisecond执行一次参数1
说明
问题
利用标识来判断是否执行下一次的 setTimeout函数,
原本写代码的时候想着的是:
第一次的periodicTimerFunc() 执行后,
如果1号还在录音,
则等5s后开始下一次的 periodicTimerFunc() 执行
但直接就执行了,
并没有等待
其他补充
-
版本相关:
- vue: 2.6.14
- js-audio-recorder: 1.0.7
-
背景
-
需求: 一边进行录音, 一边对录音的内容转义为中文输出到交互面板上
-
说明:
-
以下代码 是当时写的 语音交互的第一版( js-audio-recorder1.0.7 ), 文档: https://recorder-api.zhuyuntao.cn/Recorder/event.html不支持 边转边录其实(没办法获取到中间数据), 所以先自己取巧了一下, 大体逻辑就是 启动完1号录音对象 再定时启动2号录音对象进行转录.存在明显 Bug (2号每次启动时, 会对录制正在进行中的1号造成干扰, 1号这个时候录制不到音频.=> 1号 完成录制导出的录音文件: 听感: 每隔几秒就很有一段人体感受很不舒适的段静默 )
-
查阅了下, 找到比较新的大大也还在维护的 recorder-core1.3.24040900, 支持了在启动 recorder 录音对象之后的实时回调函数 onProcess(). 那么之前的Bug, 最后采取的 优化思路:在onProcess里实现边录边转,并在最后导出文件的时候 把 段音频数组 合并即可.源码、说明文档: https://github.com/xiangyuecn/Recorder
-
-
源码
<script>/* 省略了很多, 只是留下基础结构为说明*/import Recorder from 'js-audio-recorder';export default {data(){return {recorder: new Recorder({ // 1号录音对象sampleBits: 16, // 采样位数,支持 8 或 16,默认是16sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000, 录音一般用16000numChannels: 2, // 声道,支持 1 或 2, 默认是1}),pRecorder: new Recorder({ // 2号录音对象 - 用来在1号启动的期间, 把定量的音频传输转义sampleBits: 16, // 采样位数,支持 8 或 16,默认是16sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000, 录音一般用16000numChannels: 2, // 声道,支持 1 或 2, 默认是1}),isPeriodicTimerRun: false, // 是否启动周期调用识别}}, methods: {/*** 录音周期转义文字*/toPhoTranscri(){this.toStopPhoTranscri(); // 停止所有录音对象this.isPeriodicTimerRun = true;this.pRecorder.start();let cTime = '12345679';let periodicTimerFunc = (cTime) => {debuggerconsole.log(cTime);let wavResource = this.pRecorder.getWAV();let mp3Resource = this.convertToMp3('pRecorder', wavResource);this.pRecorder.stop();this.pRecorder.start(); // TODO 重新启动会有延迟let currentTime = new Date().formatStr('yyyy-MM-dd HH:mm:ss');let formData = this.convertBlobToMultipartFile(mp3Resource, `录音${currentTime}`);// blahblahblah... 省略一堆把 wav转为mp3文件之后, 调用接口处理识别为中文后返回, 并后续渲染的逻辑if (this.isPeriodicTimerRun){ // isPeriodicTimerRun 是如果用户点击了"停止录音"的按钮之后会=false的控制变量debuggerthis.periodicTimer = setTimeout(periodicTimerFunc(currentTime), 5000);}}this.periodicTimer = setTimeout(periodicTimerFunc(cTime), 5000);},/*** 停止所有录音对象行为、一些初始化行为*/toStopPhoTranscri(){ /* ... */ },/*** 将wav文件转换为mp3格式*/convertToMp3(){ /* ... */ },/*** 将blob文件转换为form表单对象*/convertBlobToMultipartFile(){ /* ... */ },}}
</script>
解决
嘿嘿我是笨蛋, 用来直接做参数传递肯定就直接一下子执行了啊, 会把执行的结果当作传参再延迟 5s 后执行.
所以需要改变一下写法,
让它在5s后再执行 periodicTimerFunc().
使用箭头函数, 去创建一个新的函数, 只是函数内部执行的是 periodicTimerFunc(),
这样子当定时器在 5s 后被调用的时候才会去执行函数内部的内容.
即 setTimeout(periodicTimerFunc(cTime), 5000)
=> setTimeout(() => periodicTimerFunc(cTime), 5000)
<script>import Recorder from 'js-audio-recorder';export default {data(){return {recorder: new Recorder({ // 1号录音对象sampleBits: 16, // 采样位数,支持 8 或 16,默认是16sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000, 录音一般用16000numChannels: 2, // 声道,支持 1 或 2, 默认是1}),pRecorder: new Recorder({ // 2号录音对象 - 用来在1号启动的期间, 把定量的音频传输转义sampleBits: 16, // 采样位数,支持 8 或 16,默认是16sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000, 录音一般用16000numChannels: 2, // 声道,支持 1 或 2, 默认是1}),isPeriodicTimerRun: false, // 是否启动周期调用识别}}, methods: {/*** 录音周期转义文字*/toPhoTranscri(){this.toStopPhoTranscri();this.isPeriodicTimerRun = true;this.pRecorder.start();let cTime = '';let periodicTimerFunc = (cTime) => {let wavResource = this.pRecorder.getWAV();let mp3Resource = this.convertToMp3('pRecorder', wavResource);this.pRecorder.stop();this.pRecorder.start();let currentTime = new Date().formatStr('yyyy-MM-dd HH:mm:ss');let formData = this.convertBlobToMultipartFile(mp3Resource, `录音${currentTime}`);// blahblahblah... 省略一堆把 wav转为mp3文件之后, 调用接口处理识别为中文后返回, 并后续渲染的逻辑if (this.isPeriodicTimerRun){this.periodicTimer = setTimeout(() => periodicTimerFunc(currentTime), 5000); // (ง •_•)ง 就是这里用箭头函数改一下}}this.periodicTimer = setTimeout(periodicTimerFunc(cTime), 5000); // 这里的确需要 periodicTimerFunc() 立刻执行, 即第一次启动入口},/*** 停止所有录音对象行为、一些初始化行为*/toStopPhoTranscri(){ /* ... */ },/*** 将wav文件转换为mp3格式*/convertToMp3(){ /* ... */ },/*** 将blob文件转换为form表单对象*/convertBlobToMultipartFile(){ /* ... */ },}}
</script>
↓ 2024-05-24 10:41:26 至 2024-05-24 10:41:37 中间的时间过长是因为打了debugger断点被我人为中断了
总结
直接传递函数调用会导致函数立即执行, 并将结果传递给 setTimeout , 而不是传递函数本身, 所以应该传递的是返回函数调用的箭头函数, 即传递一个函数引用. 这样子 setTimeout 在延迟时间结束后才会执行被引用的函数.