MarkTime: 2024-05-24 10:41:26
LogTime: 2024-11-10 14:55:53
首先複習
setTimeout():
語法: let timeId = setTimeout(func|code, [delay_millisecond])
說明: 延時器. 延遲delay_millisecond後, 執行引數1
setInterval():
語法: 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,預設是16
sampleRate: 16000, // 取樣率,支援 11025、16000、22050、24000、44100、48000, 錄音一般用16000
numChannels: 2, // 聲道,支援 1 或 2, 預設是1
}),
pRecorder: new Recorder({ // 2號錄音物件 - 用來在1號啟動的期間, 把定量的音訊傳輸轉義
sampleBits: 16, // 取樣位數,支援 8 或 16,預設是16
sampleRate: 16000, // 取樣率,支援 11025、16000、22050、24000、44100、48000, 錄音一般用16000
numChannels: 2, // 聲道,支援 1 或 2, 預設是1
}),
isPeriodicTimerRun: false, // 是否啟動週期呼叫識別
}
},
methods: {
/**
* 錄音週期轉義文字
*/
toPhoTranscri(){
this.toStopPhoTranscri(); // 停止所有錄音物件
this.isPeriodicTimerRun = true;
this.pRecorder.start();
let cTime = '12345679';
let periodicTimerFunc = (cTime) => {
debugger
console.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的控制變數
debugger
this.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,預設是16
sampleRate: 16000, // 取樣率,支援 11025、16000、22050、24000、44100、48000, 錄音一般用16000
numChannels: 2, // 聲道,支援 1 或 2, 預設是1
}),
pRecorder: new Recorder({ // 2號錄音物件 - 用來在1號啟動的期間, 把定量的音訊傳輸轉義
sampleBits: 16, // 取樣位數,支援 8 或 16,預設是16
sampleRate: 16000, // 取樣率,支援 11025、16000、22050、24000、44100、48000, 錄音一般用16000
numChannels: 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 在延遲時間結束後才會執行被引用的函式.