html5錄音支持pc和Android、ios部分瀏覽器,微信也是支持的,JavaScript getUserMedia

之前在前人基礎上重複造了一個網頁錄音的輪子,順帶把github倉庫使用研究了一下,扔到了github上。javascript

優點在於結構簡單,可插拔式的錄音格式支持,幾乎能夠支持任意格式(前提有相應的編碼器);默認提供實時音量反饋、有一個波形顯示擴展支持。錄音結果很是容易當即播放錄音或者上傳錄音到服務器(提供參考源碼)。css

2018-05-16首發,2019-04-21更新html

GitHub地址:github.com/xiangyuecn/…html5

在線測試demo傳送門:xiangyuecn.github.io/Recorder/java

效果圖

自述

之前準備作一個網頁版聊天界面,表情啊、圖片啊、上傳文件啊都應該要有,視頻就算了,語音仍是要的。android

當下環境html5的錄音功能支持狀況大爲良好(IOS上偏落後點) ios

2019-04瀏覽器支持狀況,棒棒噠

如是,就有了這個輪子。git

如下內容copy自READMEgithub

Recorder用於html5錄音

在線測試,支持大部分已實現getUserMedia的移動端、PC端瀏覽器,包括騰訊Android X5內核(QQ、微信)。點此查看瀏覽器支持狀況。web

錄音默認輸出mp3格式,另外可選wav格式(此格式錄音文件超大);有限支持ogg(beta)、webm(beta)、amr(beta)格式;支持任意格式擴展(前提有相應編碼器)。

mp3默認16kbps的比特率,2kb每秒的錄音大小,音質還能夠(若是使用8kbps可達到1kb每秒,不過音質太渣)。

mp3使用lamejs編碼,壓縮後的recorder.mp3.min.js文件150kb左右(開啓gzip後54kb)。若是對錄音文件大小沒有特別要求,能夠僅僅使用錄音核心+wav編碼器,源碼不足300行,壓縮後的recorder.wav.min.js不足4kb。

瀏覽器Audio Media兼容性mp3最好,wav還行,其餘要麼不支持播放,要麼不支持編碼。

特別注:IOS(11.X、12.X)上只有Safari支持getUserMedia,其餘瀏覽器均不支持,參考下面的已知問題。

快速使用

【1】加載框架

在須要錄音功能的頁面引入壓縮好的recorder.***.min.js文件便可 (注意:須要在https等安全環境下才能進行錄音

<script src="recorder.mp3.min.js"></script>
複製代碼

或者直接使用源碼(src內的爲源碼、dist內的爲壓縮後的),能夠引用src目錄中的recorder-core.js+相應類型的實現文件,好比要mp3錄音:

<script src="src/recorder-core.js"></script> <!--必須引入的錄音核心-->
<script src="src/engine/mp3.js"></script> <!--相應格式支持文件-->
<script src="src/engine/mp3-engine.js"></script> <!--若是此格式有額外的編碼引擎的話,也要加上-->
複製代碼

【2】調用錄音

而後使用,假設當即運行,只錄3秒

var rec=Recorder();//使用默認配置,mp3格式

rec.open(function(){//打開麥克風受權得到相關資源
    rec.start();//開始錄音
    
    setTimeout(function(){
        rec.stop(function(blob,duration){//到達指定條件中止錄音
            console.log(URL.createObjectURL(blob),"時長:"+duration+"ms");
            rec.close();//釋放錄音資源
            //已經拿到blob文件對象想幹嗎就幹嗎:當即播放、上傳
            
            /*當即播放例子*/
            var audio=document.createElement("audio");
            audio.controls=true;
            document.body.appendChild(audio);
            //簡單的一嗶
            audio.src=URL.createObjectURL(blob);
            audio.play();
            
        },function(msg){
            console.log("錄音失敗:"+msg);
        });
    },3000);
},function(msg,isUserNotAllow){//用戶拒絕未受權或不支持
    console.log((isUserNotAllow?"UserNotAllow,":"")+"沒法錄音:"+msg);
});
複製代碼

【附】錄音上傳示例

var TestApi="/test_request";//用來在控制檯network中能看到請求數據,測試的請求結果可有可無
var rec=Recorder();rec.open(function(){rec.start();setTimeout(function(){rec.stop(function(blob,duration){
//-----↓↓↓如下才是主要代碼↓↓↓-------

//本例子假設使用jQuery封裝的請求方式,實際使用中自行調整爲本身的請求方式
//錄音結束時拿到了blob文件對象,能夠用FileReader讀取出內容,或者用FormData上傳
var api=TestApi;

/***方式一:將blob文件轉成base64純文本編碼,使用普通application/x-www-form-urlencoded表單上傳***/
var reader=new FileReader();
reader.onloadend=function(){
    $.ajax({
        url:api //上傳接口地址
        ,type:"POST"
        ,data:{
            mime:blob.type //告訴後端,這個錄音是什麼格式的,可能先後端都固定的mp3能夠不用寫
            ,upfile_b64:(/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result)||[])[1] //錄音文件內容,後端進行base64解碼成二進制
            //...其餘表單參數
        }
        ,success:function(v){
            console.log("上傳成功",v);
        }
        ,error:function(s){
            console.error("上傳失敗",s);
        }
    });
};
reader.readAsDataURL(blob);

/***方式二:使用FormData用multipart/form-data表單上傳文件***/
var form=new FormData();
form.append("upfile",blob,"recorder.mp3"); //和普通form表單並沒有二致,後端接收到upfile參數的文件,文件名爲recorder.mp3
//...其餘表單參數
$.ajax({
    url:api //上傳接口地址
    ,type:"POST"
    ,contentType:false //讓xhr自動處理Content-Type header,multipart/form-data須要生成隨機的boundary
    ,processData:false //不要處理data,讓xhr自動處理
    ,data:form
    ,success:function(v){
        console.log("上傳成功",v);
    }
    ,error:function(s){
        console.error("上傳失敗",s);
    }
});

//-----↑↑↑以上纔是主要代碼↑↑↑-------
},function(msg){console.log("錄音失敗:"+msg);});},3000);},function(msg){console.log("沒法錄音:"+msg);});
複製代碼

【附】問題排查

  • 打開Demo頁面試試看,是否是也有一樣的問題。
  • 檢查是否是在https之類的安全環境下調用的。
  • 檢查是否是IOS系統,確認caniuseIOS對getUserMedia的支持狀況。
  • 檢查上面第1步是否把框架加載到位,在Demo頁面有應該加載哪些js的提示。
  • 提交Issue,熱心網友幫你解答。

方法文檔

【構造】rec=Recorder(set)

構造函數,拿到Recorder的實例,而後能夠進行請求獲取麥克風權限和錄音。

set參數爲配置對象,默認配置值以下:

set={
    type:"mp3" //輸出類型:mp3,wav等,使用一個類型前須要先引入對應的編碼引擎
    ,bitRate:16 //比特率 wav(位):1六、8,MP3(單位kbps):8kbps時文件大小1k/s,16kbps 2k/s,錄音文件很小
    
    ,sampleRate:16000 //採樣率,wav格式文件大小=sampleRate*時間;mp3此項對低比特率文件大小有影響,高比特率幾乎無影響。
                //wav任意值,mp3取值範圍:48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000
    
    ,bufferSize:4096 //AudioContext緩衝大小。會影響onProcess調用速度,相對於AudioContext.sampleRate=48000時,4096接近12幀/s,調節此參數可生成比較流暢的回調動畫。
                //取值256, 512, 1024, 2048, 4096, 8192, or 16384
                //注意,取值不能太低,2048開始不一樣瀏覽器可能回調速率跟不上形成音質問題(低端瀏覽器→說的就是騰訊X5)
    
    ,onProcess:NOOP //接收到錄音數據時的回調函數:fn(this.buffer,powerLevel,bufferDuration,bufferSampleRate) 
                //buffer=[緩衝PCM數據,...],powerLevel:當前緩衝的音量級別0-100,bufferDuration:已緩衝時長,bufferSampleRate:緩衝使用的採樣率
                //若是須要繪製波形之類功能,須要實現此方法便可,使用以計算好的powerLevel能夠實現音量大小的直觀展現,使用buffer能夠達到更高級效果
}
複製代碼

注意:set內是數字的明確傳數字,不要傳字符串之類的致使不可預測的異常,其餘有配置的地方也是同樣(感謝214282049@qq.com19-01-10發的反饋郵件)。

【方法】rec.open(success,fail)

請求打開錄音資源,若是瀏覽器不支持錄音或用戶拒絕麥克風權限將會調用fail,打開後須要調用close

注意:此方法是異步的;通常使用時打開,用完當即關閉;可重複調用,可用來測試是否能錄音。

另外:由於此方法會調起用戶受權請求,若是僅僅想知道瀏覽器是否支持錄音(好比:若是瀏覽器不支持就走另一套錄音方案),應使用Recorder.Support()方法。

success=fn();

fail=fn(errMsg,isUserNotAllow); 若是是用戶主動拒絕的錄音權限,除了有錯誤消息外,isUserNotAllow=true,方便程序中作不一樣的提示,提高用戶主動受權機率

【方法】rec.close(success)

關閉釋放錄音資源,釋放完成後會調用success()回調

【方法】rec.start()

開始錄音,需先調用open;若是不支持、錯誤,不會有任何提示,由於stop時天然能獲得錯誤。

【方法】rec.stop(success,fail)

結束錄音並返回錄音數據blob對象,拿到blob對象就能夠隨心所欲了,不限於當即播放、上傳

success(blob,duration)blob:錄音數據audio/mp3|wav...格式,duration:錄音時長,單位毫秒

fail(errMsg):錄音出錯回調

提示:stop時會進行音頻編碼,音頻編碼可能會很慢,10幾秒錄音花費2秒左右算是正常,編碼並未使用Worker方案(文件多),內部採起的是分段編碼+setTimeout來處理,界面卡頓不明顯。

【方法】rec.pause()

暫停錄音。

【方法】rec.resume()

恢復繼續錄音。

【方法】rec.mock(pcmData,pcmSampleRate)

模擬一段錄音數據,後面能夠調用stop進行編碼。需提供pcm數據int[],和pcm數據的採樣率。

可用於將一個音頻解碼出來的pcm數據方便的轉換成另一個格式:

var amrBlob=...;//amr音頻blob對象
var amrSampleRate=8000;//amr音頻採樣率

//解碼amr獲得pcm數據
var reader=new FileReader();
reader.onload=function(){
    Recorder.AMR.decode(new Uint8Array(reader.result),function(pcm){
        transformOgg(pcm);
    });
};
reader.readAsArrayBuffer(amrBlob);

//將pcm轉成ogg
function transformOgg(pcmData){
    Recorder({type:"ogg",bitRate:64,sampleRate:32000})
        .mock(pcmData,amrSampleRate)
        .stop(function(blob,duration){
            //咱們就獲得了新採樣率和比特率的ogg文件
            console.log(blob,duration);
        });
};
複製代碼

【靜態方法】Recorder.Support()

判斷瀏覽器是否支持錄音,隨時能夠調用。注意:僅僅是檢測瀏覽器支持狀況,不會判斷和調起用戶受權(rec.open()會判斷用戶受權),不會判斷是否支持特定格式錄音。

【靜態方法】Recorder.IsOpen()

因爲Recorder持有的錄音資源是全局惟一的,可經過此方法檢測是否有Recorder已調用過open打開了錄音功能。

壓縮合並一個本身須要的js文件

可參考/src/package-build.js中如何合併的一個文件,好比mp3是由recorder-core.js,engine/mp3.js,engine/mp3-engine.js組成的。

除了recorder-core.js其餘引擎文件都是可選的,能夠把所有編碼格式合到一塊兒也,也能夠只合並幾種,而後就能夠支持相應格式的錄音了。

能夠修改/src/package-build.js後,在src目錄內執行壓縮:

cnpm install
npm start
複製代碼

關於現有編碼器

若是你有其餘格式的編碼器而且想貢獻出來,能夠提交新增格式文件的pull(文件放到/src/engine中),咱們升級它。

wav

wav格式編碼器時參考網上資料寫的,會發現代碼和別人家的差很少。源碼2kb大小。

mp3

採用的是lamejs(LGPL License)這個庫的代碼,https://github.com/zhuker/lamejs/blob/bfb7f6c6d7877e0fe1ad9e72697a871676119a0e/lame.all.js這個版本的文件代碼;已對lamejs源碼進行了部分改動,用於修復發現的問題。LGPL協議涉及到的文件:mp3-engine.js;這些文件也採用LGPL受權,不適用MIT協議。源碼518kb大小,壓縮後150kb左右,開啓gzip後50來k。

beta-ogg

採用的是ogg-vorbis-encoder-js(MIT License),https://github.com/higuma/ogg-vorbis-encoder-js/blob/7a872423f416e330e925f5266d2eb66cff63c1b6/lib/OggVorbisEncoder.js這個版本的文件代碼。此編碼器源碼2.2M,超級大,壓縮後1.6M,開啓gzip後327K左右。對錄音的壓縮率比lamejs高出一倍, 但Vorbis in Ogg好像Safari不支持(真的假的)。

beta-webm

這個編碼器時經過查閱MDN編寫的一個玩意,沒多大使用價值:錄幾秒就至少要幾秒來編碼。。。緣由是:未找到對已有pcm數據進行快速編碼的方法。數據導入到MediaRecorder,音頻有幾秒就要等幾秒,相似邊播放邊收聽形。(想接原始錄音Stream?我不可能給的!)輸出音頻雖然能夠經過比特率來控制文件大小,但音頻文件中的比特率並不是設定比特率,採樣率因爲是咱們本身採樣的,到這個編碼器隨他怎麼搞。只有比較新的瀏覽器支持(需實現瀏覽器MediaRecorder),壓縮率和mp3差很少。源碼2kb大小。

beta-amr

採用的是benz-amr-recorder(MIT License)優化後的amr.js(Unknown License),https://github.com/BenzLeung/benz-amr-recorder/blob/462c6b91a67f7d9f42d0579fb5906fad9edb2c9d/src/amrnb.js這個版本的文件代碼,已對此代碼進行過調整更方便使用。支持編碼和解碼操做。因爲最高只有12.8kbps的碼率,音質和同比配置的mp三、ogg差一個檔次。因爲支持解碼操做,理論上全部支持Audio的瀏覽器均可以播放(須要本身寫代碼實現)。源碼1M多,蠻大,壓縮後445K,開啓gzip後136K。優勢:錄音文件小。

Recorder.amr2wav(amrBlob,True,False)

已實現的一個把amr轉成wav格式來播放的方法,True=fn(wavBlob,duration)。要使用此方法須要帶上上面的wav格式編碼器。仿照此方法可輕鬆轉成別的格式,參考mock方法介紹那節。

其餘音頻格式支持辦法

//好比增長aac格式支持 (可參考/src/engine/mp3.js實現)

//新增一個aac.js,編寫如下格式代碼便可實現這個類型
Recorder.prototype.aac=function(pcmData,successCall,failCall){
    //經過aac編碼器把pcm數據轉成aac格式數據,經過this.set拿到傳入的配置數據
    ... pcmData->aacData
    
    //返回數據
    successCall(new Blob(aacData,{type:"audio/aac"}));
}

//調用
Recorder({type:"aac"})
複製代碼

擴展

src/extensions目錄內爲擴展支持庫,這些擴展庫默認都沒有合併到生成代碼中,需單獨引用(distsrc中的)才能使用。

WaveView擴展

waveview.js,4kb大小源碼,錄音時動態顯示波形,具體樣子參考演示地址頁面。此擴展參考MCVoiceWave庫編寫的,具體代碼在https://github.com/HaloMartin/MCVoiceWave/blob/f6dc28975fbe0f7fc6cc4dbc2e61b0aa5574e9bc/MCVoiceWave/MCVoiceWaveView.m中。

此擴展是在錄音時onProcess回調中使用;bufferSize會影響繪製幀率,越小越流暢(但越消耗cpu),默認配置的大概12幀/s。基礎使用方法:

var wave;
var rec=Recorder({
    onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate){
        wave.input(buffers[buffers.length-1],powerLevel,bufferSampleRate);//輸入音頻數據,更新顯示波形
    }
});
rec.open(function(){
    wave=Recorder.WaveView({elem:".elem"}); //建立wave對象,寫這裏面瀏覽器妥妥的
    
    rec.start();
});
複製代碼

【構造】wave=Recorder.WaveView(set)

構造函數,set參數爲配置對象,默認配置值以下:

set={
    elem:"css selector" //自動顯示到dom,並以此dom大小爲顯示大小
        //或者配置顯示大小,手動把waveviewObj.elem顯示到別的地方
    ,width:0 //顯示寬度
    ,height:0 //顯示高度
    
    //以上配置二選一
    
    scale:2 //縮放係數,由於正整數,使用2(3? no!)倍寬高進行繪製,避免移動端繪製模糊
    ,speed:8 //移動速度係數,越大越快
    
    ,lineWidth:2 //線條基礎粗細
            
    //漸變色配置:[位置,css顏色,...] 位置: 取值0.0-1.0之間
    ,linear1:[0,"rgba(150,97,236,1)",1,"rgba(54,197,252,1)"] //線條漸變色1,從左到右
    ,linear2:[0,"rgba(209,130,253,0.6)",1,"rgba(54,197,252,0.6)"] //線條漸變色2,從左到右
    ,linearBg:[0,"rgba(255,255,255,0.2)",1,"rgba(54,197,252,0.2)"] //背景漸變色,從上到下
}
複製代碼

【方法】wave.input(pcmData,powerLevel,sampleRate)

輸入音頻數據,更新波形顯示,這個方法調用的越快,波形越流暢。pcmData爲當前的錄音數據片斷,其餘參數和onProcess回調相同。

兼容性

對於支持錄音的瀏覽器可以正常錄音並返回錄音數據;對於不支持的瀏覽器,引入js和執行相關方法都不會產生異常,而且進入相關的fail回調。通常在open的時候就能檢測到是否支持或者被用戶拒絕,可在用戶開始錄音以前提示瀏覽器不支持錄音或受權。

Android Hybrid App中錄音示例

在Android Hybrid App中使用本庫來錄音,須要在App源碼中實現如下兩步分:

  1. AndroidManifest.xml聲明須要用到的兩個權限
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
複製代碼
  1. WebChromeClient中實現onPermissionRequest網頁受權請求
@Override
public void onPermissionRequest(PermissionRequest request) {
    ...此處應包裹一層系統權限請求
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        request.grant(request.getResources());
    }
}
複製代碼

注:若是應用的騰訊X5內核,除了上面兩個權限外,還必須提供android.permission.CAMERA權限。另外沒法重寫此onPermissionRequest方法,他會本身彈框詢問,若是被拒絕了就永遠拒絕了,參考已知問題部分。

若是不出意外,App內顯示的網頁就能正常錄音了。

附帶測試項目

.assets/android_test目錄中提供了Android測試源碼(若是不想本身打包能夠用打包好的apk來測試,位於.assets/android_test/app-debug-xxx.apk)。提供了系統自帶WebView、和騰訊X5內核兩個測試界面。

關於微信JsSDK

微信內瀏覽器他家的JsSDK也支持錄音,涉及笨重難調的公衆號開發(光sdk初始化就能阻礙不少新奇想法的產生,signature限制太多),只能知足最基本的使用(大部分狀況足夠了)。若是JsSDK錄完音能返回音頻數據,這個SDK將好用10000倍,若是能實時返回音頻數據,將好用100000倍。關鍵是他們家是拒絕給這種簡單好用的功能的,必須繞一個大圈:錄好音了->上傳到微信服務器->自家服務器請求微信服務器多進行媒體下載->保存錄音(微信小程序之前也是二逼路子,如今稍微好點能實時拿到錄音mp3數據),若是能升級:錄好音了拿到音頻數據->上傳保存錄音,目測對最終結果是沒有區別的,還簡單很多,對微信自家也算是很是經濟實用。[2018]因爲微信IOS上不支持原生JS錄音,Android上又支持,爲了兼容而去兼容的事情我是拒絕的(並且是僅僅爲了兼容IOS上面的微信),其實也算不上去兼容,由於微信JsSDK中的接口徹底算是另一種東西,接入的話對整個錄音流程都會產生徹底不同的變化,還不如沒有進入錄音流程以前就進行分支判斷處理。

最後:若是是在微信上用的多,應優先直接接入他家的JsSDK(沒有公衆號開個訂閱號又不要錢),基本上能夠忽略兼容性問題,就是麻煩點。

已知問題

2018-09-19 caniuse 註明IOS 11.X - 12.X 上 只有Safari支持調用getUserMedia,其餘App下WKWebView(UIWebView?)(相關資料)均不支持。經用戶測試驗證IOS 12上chrome、UC都沒法錄音,部分IOS 12 Safari能夠獲取到而且能正常錄音,但部分不行,緣由未知,參考ios 12 支不支持錄音了。在IOS上不支持錄音的環境下應該採用其餘解決方案,參考案例演示關於微信JsSDK部分。

2019-02-28 issues#14 若是getUserMedia返回的MediaStreamTrack.readyState == "ended""ended" which indicates that the input is not giving any more data and will never provide new data. ,致使沒法錄音。若是產生這種狀況,目前在rec.open方法調用時會正確檢測到,並執行fail回調。形成issues#14 ended緣由是App源碼中AndroidManifest.xml中沒有聲明android.permission.MODIFY_AUDIO_SETTINGS權限,致使騰訊X5不能正常錄音。

2019-03-09 在Android上QQ、微信裏,請求受權使用麥克風的提示,通過長時間觀察發現,他們的表現很隨機、很奇特。可能每次在調用getUserMedia時候都會彈選擇,也可能選擇一次就不會再彈提示,也可能重啓App後又會彈。若是用戶拒絕了,可能次日又會彈,或者永遠都不彈了,要麼重置(裝)App。使用騰訊X5內核的App測試也是同樣奇特表現,拒絕權限後可能必需要重置(裝)。這個問題貌似跟X5內核自動升級的版本有關。

若是這個庫有幫助到您,請 Star 一下。GitHub:github.com/xiangyuecn/…

相關文章
相關標籤/搜索