微信JSSDK與錄音相關的坑
最近一直在做微信JSSDK與錄音相關的功能開發, 遇到了各種奇尺大坑, 時不時冷不丁地被坑一道, 讓我時常想嘶吼: "微信JSSDK就是個大臘雞!!!!!!!!!!"
現在工作得到階段性成果, 有時間休息總結下, 故來整理一下這段時間碰到的bug, 希望做個前車之鑒, 勸大家謹慎入坑.
checkJsApi
功能: 判斷當前客戶端版本是否支持指定JS接口
轉載: http://www.fwqtg.net/%E5%BE%AE%E4%BF%A1jssdk%E4%B8%8E%E5%BD%95%E9%9F%B3%E7%9B%B8%E5%85%B3%E7%9A%84%E5%9D%91.html
我遇到的一個不大不小的坑是: onVoiceRecordEnd和onVoicePlayEnd總是返回false, 即使這兩個接口是被支持的!
還有要注意的是, 這個API只是檢查當前客戶端版本是否支持該API, 與該API是否開啟並沒有卵關系! 所以當你check某個API發現是true, 但是又怎么調用都不起作用的時候, 記得檢查下wx.config中的jsApiList參數.
startRecord, stopRecord
微信JSSDK的API都有一個大問題, 就是如果調用時間間隔過短, 就非常可能產生無效調用. 無效調用的意思是, 雖然你調用了某個API, 但是相當於沒調用, 它不會觸發success, fail或complete中的任何一個callback!
打比方說, 你做一個"按下錄音, 抬手停止錄音"的功能, 如果用戶點擊了一下錄音按鈕, 相當於快速地startRecord然后stopRecord, 那么stopRecord是極有可能是無效的, 不會執行任何callback.
然而問題還不止這個, 微信JSSDK的調用是異步的. 舉例來說就是, 你調用startRecord的時間, 和startRecord的success的callback被執行的時間可能間隔了若干毫秒甚至秒. 這意味着, 用戶點擊按鈕可能會造成: 雖然是先調用startRecord再調用stopRecord, 但是可能stopRecord先於startRecord調用成功!
我做了各種嘗試后意識到, 微信JSSDK太脆弱, 頻繁操作就會被玩兒壞. 所以我最終的結論是: ==一定要在微信JSSDK外面包一層, 控制API的調用頻率.==
以錄音為例, (我曾經的解決方案見這里, 但是后來發現頻繁地調用stopRecord還是會有問題) 我現在的解決方案是: 當用戶按下錄音時調用startRecord, 然后一秒之內抬手都會提示用戶"錄音太短", 然后在第一秒結束時再調用stopRecord, 這樣可以確保兩個API之間的調用間隔至少一秒, 不會崩掉.
最靠譜的方法還是用狀態機來做, 我目前的狀態機如下圖. 圖中沒有包含uploadVoice, uploadVoice期間應該不允許用戶操作按鈕.

WinPhone上無法錄音
通過WinPhone訪問微信官方測試頁面http://demo.open.weixin.qq.com/jssdk嘗試調用錄音接口, 然而並沒有卵用!!!! (WinPhone用戶再次受到一萬點傷害)
好, 接下來只討論iPhone和Android用戶…
iPhone中的假錄音狀態
機型: iPhone
症狀: 微信顯示"錄音中", 但是其實沒有在錄音! 此時, startRecord會失敗, 錯誤信息startRecord:fail;stopRecord不調用任何callback, 仍然顯示錄音中. 那就這么掛了? 不…
觸發方式:
- 錄音中按Home返回桌面
- 錄音中從屏幕底部上划打開iPhone的設置菜單, 再關閉菜單.
解法: 此時你需要先調用stopRecord, 雖然不會觸發任何callback, 但微信內部一定設置了某個flag, 然后就可以正常調用startRecord了.
呵呵… 一口鮮血噴向屏幕.
你無法知道是否進入了假錄音狀態, 所以這個問題暫時無解!
不startRecord, 直接stopRecord有什么效果?
我的測試結果是, 三個callback都不會被觸發!
我覺得這應該算是一個bug, 如果沒有在錄音但是調用了stopRecord, 那應該失敗並且errMsg為NotRecording.
錄音中按Home返回桌面
iPhone: 錄音中斷, 回到微信進入假錄音狀態.
Android: 錄音繼續, 回到微信可以正常stopRecord.
錄音中打開設置菜單
iPhone: 從屏幕底部上滑可以打開設置菜單, 關閉菜單后進入假錄音狀態.
Android: 從屏幕上部下滑可以打開設置菜單, 關閉菜單后可以正常停止錄音.
錄音中關閉WebApp
iPhone: 錄音結束, 回到WebApp可以正常開啟錄音.
Android: 錄音繼續, 回到WebApp調用startRecord失敗提示recording.
錄音中刷新WebApp
iPhone: 微信不再顯示"錄音中", 但是實際上此時在錄音… startRecord失敗, 錯誤信息startRecord:fail;stopRecord會成功.
Android: 我沒有刷新按鈕… (Android用戶起立鼓掌)
iPhone上錄音后播放audio聲音變得特別小
這是一個微信JSSDK炒雞惡心的BUG… 只在iPhone出現: 在你使用的是外放(就是聲音是從手機下方的小音箱里面放出來的)的前提下, 錄音之后, 播放audio/video聲音會轉而從聽筒(就是不插耳機打電話是耳朵對着的位置)播放出來. 如果你不知道的話, 會以為錄音之后播放audio/video聲音變得特別小.
但是, 如果你用耳機的話則不會受到影響, 因為聲音會始終從耳機里播放出來. (難道你要我告訴用戶, "請帶上耳機使用本產品"嘛?!)
后來我們發現playVoice一次之后, audio/video的聲音就會, 神奇地, 又從外放里播放出來了… 無語凝噎.jpg
所以, 有一個丑, 但是管用的workaround… 就是, 每次stopRecord成功后之后立即調用playVoice和stopVoice… 這樣至少聽筒模式的bug解了.
但是還有個問題是, 如果你播放的是用戶剛剛錄的音, 有可能播放錄音時會發出"噗"的一聲… 這是錄音時錄進去的雜音. 想要解決這個問題你就必須要上傳一個空白的語音到微信, 然后在WebApp啟動的時候通過serverId下載這個語音, 然后每次stopRecord的時候播放這個空白語音…
然后還有個問題就是, 你上傳的這個語音必須是臨時素材, 這樣下載下來才會是localId, 可以用playVoice播放; 如果你上傳語音作為用舊素材的話, 下載下來是二進制, 不能用playVoice播放…
然后還有個問題就是, 臨時素材只會被微信保留三天… 三天后過期… 所以你必須每三天上傳一段新的空白語音, 更新serverId…
抱頭痛哭.jpg
Android上無法實現長按錄音的功能?
經過測試發現, (很多?)Android上無法實現長按錄音, 為什么?
因為我發現, 只要調用了startRecord就會觸發按鈕的touchcancel事件. 於是, 之后的touch相關事件就不會再被監聽了. (本人猜測, 這和下面的uploadVoice導致UI卡住是一個問題, 在Android上微信JSSDK的執行會卡住UI導致觸發了按鍵的touchcancel事件)
這意味着即使你手還按在按鍵上, 瀏覽器已經認為你松手了; 等你真的松手的時候, 瀏覽器並沒有在監聽touchend事件了…
所以目前, 雖然是一套代碼運行在iPhone和Android上, 但是iPhone上就是長按錄音, 但是Android上需要用戶點擊開始(響應touchstart), 點擊結束(響應touchend)…
事實上, 兩次點擊錄音比長按錄音更容易出bug, 因為第一次和第二次點擊之間用戶可以隨便亂操作, 很容易出現各種問題; 但是長按期間, 用戶亂操作的可能性會低得多.
(突然想到, 可不可以在Android系統上, 當調用startRecord后觸發touchcancel時, 手動觸發一下按鍵的touchstart事件, 這樣讓按鈕能夠監聽touchend? 之后做做實驗)
初次以及未知時間間隔之后, 提示用戶"是否開啟錄音"
新用戶進入WebApp后第一次調用startRecord的時候, 微信會彈出一個對話框, 詢問用戶"是否開啟錄音".
這對於"長按錄音"操作來說, 非常影響體驗, 因為用戶按到一半需要松手去點對話框.
如果用戶一不小心點了"否", 那你可以去哭了, 接下的錄音API調用會一直失敗.
如果用戶點了"是", 接下來, 至少一段時間內, 用戶可以安心地錄音了, 不會彈出對話框.
但是, 惡心的是, 在不確定的(至少目前我還沒找到規律)時間之后(比如4天之后)用戶再次打開WebApp時微信可能會重新詢問"是否開啟錄音"…
有一個不完美的解決方法是: 在剛剛打開WebApp的時候就嘗試錄音一下. 如果微信彈出對話框, 用戶可以在這個時候點選是/否, 而不至於影響真正錄音時的體驗.
吐槽onVoiceRecordEnd和onVoicePlayEnd
先說onVoiceRecordEnd: 微信錄音的最長時間是1分鍾, 超過這個時間錄音會自動停止. onVoiceRecordEnd的作用就是注冊一個callback, 當錄音超時的時候執行.
我想吐槽的是這個API的設計, 我覺得更合理的方式是應該在startRecord的時候就注冊進去, 因為這個超時邏輯和stopRecord邏輯有很大程度上是重疊的, 比如錯誤處理, 重置錄音按鈕樣式等, 而這些邏輯在startRecord的時候就是已知的了.
就是說這個API應該設計成這樣:
wx.startRecord({ success: // ... fail: //... complete: //... onVoiceRecordEnd: //... })
但是現在的onVoiceRecordEnd卻是一個獨立的API:
wx.onVoiceRecordEnd({ // 錄音時間超過一分鍾沒有停止的時候會執行 complete 回調 complete: function (res) { var localId = res.localId; } });
微信是覺得我的WebApp里面只會有一種onVoiceRecordEnd邏輯么? 事實上同一個WebApp的不同頁面很可能有不同的onVoiceRecordEnd邏輯.
所以我現在只能在startRecord的技術上由封裝了一層, 當startRecord成功的時候將當前的onVoiceRecordEnd邏輯注入到wx.onVoiceRecordEnd中.
onVoicePlayEnd的作用是注冊一個callback, 當用戶錄音播放結束時執行. 它和onVoiceRecordEnd非常類似, 也是一個被處理出來的API. 它應該在playVoice的時候就被注冊.
uploadVoice
錄音結束后, 你是無法直接訪問錄音數據的, 需要先調用uploadVoice將數據上傳到微信服務器, 然后讓你的后台再從微信服務器下載數據… 這個設計直接導致了處理錄音數據時間的增加.
微信這么做, 應該是希望保護用戶隱私, 但是如果能提供一個新的授權方式(目前只支持兩種授權方式, snsapi_base和snsapi_userinfo), 在用戶允許錄音並且可以直接上傳語音到對方服務器, 那就太好了.
Android上, 上傳/下載錄音時UI會卡住
uploadVoice提供了一個選項isShowProgressTips, 默認為1, 表示顯示進度提示. 吐槽1: 但是並沒有說不顯示進度應該設置幾, 雖然0是常用的falsy. 文檔不細致, 還要人去試. 吐槽2: 這變量名起的… 我覺得語法有問題,showProgressTip就行了.
這都不重要, 惡心的是: 在Android上, 即使設置了isShowProgressTips為0, 即不顯示進度提示, UI依然會被卡住不動, 待上傳完之后UI才能繼續正常運行. 這個問題在iPhone上就沒有.
所以, 比較好的解決方法是, 如果是iPhone則根據需要設置isShowProgressTips, 但Android上最好設置isShowProgressTips為1.
playVoice, stopVoice
這對接口有着和startRecord和stopRecord一樣的問題–調用不能過於頻繁否則會玩兒壞微信, 需要在微信的接口上再封裝一層以控制調用頻率.
假設你錄制了多個語音, 在播放其中一個的時候, 播放另一個會產生不確定的結果. 有的時候第二個錄音正常播放, 但是有的時候第二個播放不出來. 當你連續播放三個乃至更多的音頻的時候問題更多.
還有一個問題就是onVoicePlayEnd在這種情況下也是不穩定的, 連續播放N個音頻時, onVoicePlayEnd是不一定觸發的…
即使你每次播放錄音B之前調用stopVoice停掉正在播放的錄音A, 這行為也是不可靠的… 有時候能停下來, 有時候會直接播放錄音B的后半截, 並且隨后的播放錄音行為會亂掉.
這些惡心的bug意味着你必須要限制UI上的操作頻率和順序(比方說當播放一個錄音的時候, 其他播放錄音按鈕是被禁用的), 也就無法創造一個操作流暢(比如隨便點擊某個錄音按鈕就可以停掉正在播放的錄音而播放新的錄音)的用戶體驗了.
stopVoice的小bug
這個bug跟其他bug比起來可以忽略, 但是仍然蠻影響用戶體驗, 就是: 在錄音播放的最后幾秒鍾調用stopVoice一次, 會顯示ok說明停止錄音成功, 但是聲音仍然在播放, 繼續調用stopVoice會失敗, 錯誤信息為not playing.
(這位處女座請把你手里的板磚放下, 謝謝.)
結語
總之, 微信JSSDK還只是提供了基本功能, 但是健壯性遠不足以支撐豐富交互的WebApp. 這種基於微信JSSDK開發的WebApp會有諸多限制, 遠無法達到NativeApp的效果.
但是, 基於微信的WebApp有着易訪問, 易傳播的巨大優勢, 可以作為NativeApp開發之前的一個試點項目. 最終為了確保良好的用戶體驗, NativeApp還是必不可少的.
