關鍵詞: STATUS_ACCESS_VIOLATION AudioContext AudioWorkletNode audioWorklet addModule resume suspended createScriptProcessor
搞崩Chrome測試頁:測試頁地址
事件起因
我前些年GitHub開源的前端H5錄音庫:https://github.com/xiangyuecn/Recorder,提供了 mp3 wav ogg webm amr 格式支持,擁有豐富的音頻可視化、變速變調處理、音頻流播放、ASR語音識別等配套功能;搭配上強大的實時處理支持,可用於各種網頁應用;最近打算嘗試使用新瀏覽器特性升級一下,跟隨上時代的發展。
不記得Chrome從哪個版本開始,對AudioContext的createScriptProcessor方法調用時會在控制台中打印方法過時提醒:[Deprecation] The ScriptProcessorNode is deprecated. Use AudioWorkletNode instead.。

ScriptProcessor雖然被標記為過時了,但當前所有現代瀏覽器包括早期點的瀏覽器均得到了很好的支持,還沒有在哪個瀏覽器上被正式的移除,Chrome的過這個時提醒就導致了經常有人問是不是要改用AudioWorklet了,目前的結論是還沒有這個必要,因為單就獲得錄音數據這個場景而言,PC端兩者並在性能上並沒有區別,反而ScriptProcessor在移動端更具性能優勢,這當然是后話了。
其實很早就想去升級提供AudioWorklet的支持,簡單的過了一遍文檔,預計的需要增加的代碼量也不會很大(最后實際壓縮后增加了2KB 這里查看這100來行的代碼),不過拖到了前段時間才把代碼給寫了。

現象復現
在剛開始編寫好測試的過程中,發現只要交互操作足夠快,Chrome (版本:97)瀏覽器經常莫名其妙的崩潰(從來沒有見過的現象),老版本Chrome80也會崩潰,錯誤代碼:STATUS_ACCESS_VIOLATION,更老的66、70反而沒有出現崩潰(得益於之前寫的《自己制作Chrome便攜版實現多版本共存》很方便測試),FireFox也不會崩潰。
經過反復測試,定位到問題:suspended狀態下的AudioContext,在audioWorklet.addModule+構造AudioWorkletNode未完成時,同時進行resume調用,在恢復到running狀態那一刻,瀏覽器崩潰了。其實根源還是在頁面還沒有用戶交互過,就創建了AudioContext,這時的狀態大概率是suspended(目的是瀏覽器禁止繞過沒有用戶操作自動聲音播放)。
這些測試代碼我已整合成了一個測試文件,點此進行測試,Chrome里面非常容易復現。


填坑處理
知道問題后,那解決就很簡單了:
- 方式一:等到有用戶操作后再進行
AudioContext的創建,此時能保證它一定是running狀態; - 方式二:直接調用
AudioContext的resume方法,等到running后就沒有崩潰的問題了。
由於我這個是開源庫,沒法決定開發者是否會等用戶操作,所以方式二根據普遍適用性。
調用AudioContext.resume后,它返回的是一個Promise,在finally塊(不管reject,小概率中的小概率崩潰可以忽略)中進行AudioWorklet的初始化,就是把原有的初始化代碼套一層即可。

另外AudioContext.audioWorklet.addModule在本地file://協議下,Chrome竟然不支持加載Blob Url(FireFox沒有這個問題),同樣是Worker,WebWorker就沒有這個毛病;最后改用data url來兼容Chrome:data:text/javascript;base64,......
最終結果
坑也填了,該測試的也測試了,滿懷欣喜的發布了采用AudioWorklet錄音的新版本。發布后,在手機上把玩把玩,咦,怎么好像短了幾秒錄音。。。
PC端、手機端反復的定時測試,最后發布聲明:由於audioWorklet內部1秒375次回調,在移動端可能會有性能問題導致回調丟失錄音變短,PC端無影響,暫不建議開啟audioWorklet。
所以,又更新了一個版本,簡單恢復了一下,庫里面默認ScriptProcessor依舊是主力,可通過設置Recorder.ConnectEnableWorklet=true強制開啟使用AudioWorklet。
面向未來,如果以后哪個瀏覽器正式移除了ScriptProcessor,Recorder將會自動啟用AudioWorklet,所以現在寫的代碼對將來會有很大意義,雖然目前有點雞肋(感覺對AudioWorklet支持還是寫早了,有點白寫了的感覺),但意義還是很大的。
完整AudioWorklet實現代碼,請移步閱讀:recorder-core.js 第L157-L281行。
【End】
