Web Audio API 頁(yè)面錄音功能實(shí)現(xiàn)

近期公司需要一個(gè)頁(yè)面錄音的小功能,因此調(diào)研了Web Audio API,因?yàn)橐恢币矝](méi)怎么做過(guò)js開發(fā)撕彤,期間踩坑無(wú)數(shù)鱼鸠,在此做一記錄,希望能幫到后面有需要的人羹铅。

1.Web Audio介紹

Web Audio API 官方文檔不但提供了在Web上控制音頻的一個(gè)非常有效通用的系統(tǒng)蚀狰,而且提供了大量音頻相關(guān)的基礎(chǔ)知識(shí),對(duì)入門音頻編程有極大的幫助,允許開發(fā)者機(jī)型自選音頻源,對(duì)音頻添加特效职员,使音頻可視化麻蹋,添加空間效果等功能的開發(fā)。

一個(gè)簡(jiǎn)單而典型的Web audio流程如下:

  • 1.創(chuàng)建音頻上下文
  • 2.在音頻上下文理創(chuàng)建源 例如 振蕩器, 流
  • 3.創(chuàng)建效果節(jié)點(diǎn)焊切,例如混響哥蔚、雙二階濾波器、平移蛛蒙、壓縮
  • 4.為音頻選擇一個(gè)目的地,例如你的系統(tǒng)揚(yáng)聲器
  • 5.連接源到效果器渤愁,對(duì)目的地進(jìn)行效果輸出

詳細(xì)信息請(qǐng)參考官方網(wǎng)站,網(wǎng)站地址:

https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Audio_API

2.JS this指針說(shuō)明

開發(fā)過(guò)程中關(guān)于JavaScript this踩了無(wú)數(shù)坑牵祟。在函數(shù)調(diào)用過(guò)程中,this綁定的對(duì)象各種出錯(cuò)抖格,因此本節(jié)中特意給出JavaScript this 的官方說(shuō)明.
官方參考文檔:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this

3.錄音源碼Demo

鑒于在開發(fā)過(guò)程中未找到合適的參考代碼诺苹,把源碼公布如下:

/*!
 * Record Javascript Library
 */

'use strict';

/**
* AudioRecorder類.
* @constructor
*/
function AudioRecorder(){
    //麥克風(fēng)
    this.mDevice = null;
    //從麥克風(fēng)獲取的音頻流
    this.mMediaStream = null;
    this.mAudioContext = null;
    this.mAudioFromMicrophone = null;
    this.mMediaRecorder = null;
    this.mStatus = "stop";
    this.mChunks = [];
    //回調(diào)函數(shù)
    this.onStopCallBack = null;
    
}

AudioRecorder.prototype={
    

    /**
    * 獲取錄音機(jī)對(duì)象設(shè)備
    * @method getAudioRecorderDevice
    * @for AudioRecorder
    * @returns {Promise} 返回一個(gè)promise對(duì)象
    */
    getAudioRecorderDevice: function(){
        //僅用來(lái)進(jìn)行錄音
        var constraints = { audio: true};
        // 老的瀏覽器可能根本沒(méi)有實(shí)現(xiàn) mediaDevices,所以我們可以先設(shè)置一個(gè)空的對(duì)象
        if(navigator.mediaDevices === undefined) {
            navigator.mediaDevices = {};
        }
        // 一些瀏覽器部分支持 mediaDevices雹拄。我們不能直接給對(duì)象設(shè)置 getUserMedia 
        // 因?yàn)檫@樣可能會(huì)覆蓋已有的屬性收奔。這里我們只會(huì)在沒(méi)有g(shù)etUserMedia屬性的時(shí)候添加它。
        if(navigator.mediaDevices.getUserMedia === undefined) {
            navigator.mediaDevices.getUserMedia = function(constraints) {

                // 首先滓玖,如果有g(shù)etUserMedia的話坪哄,就獲得它
                var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

                // 一些瀏覽器根本沒(méi)實(shí)現(xiàn)它 - 那么就返回一個(gè)error到promise的reject來(lái)保持一個(gè)統(tǒng)一的接口
                if(!getUserMedia) {
                    return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
                }

                // 否則,為老的navigator.getUserMedia方法包裹一個(gè)Promise
                this.mDevice = new Promise(function(resolve, reject) {
                    getUserMedia.call(navigator, constraints, resolve, reject);
                });
            }
        }
        else
        {
            this.mDevice = navigator.mediaDevices.getUserMedia(constraints);
        }
        
        if(this.mDevice != null)
        {
            this.mDevice.then((mediaStream) => { this.openDeviceSuccess.call(this,mediaStream) },this.openDeviceFailure);
        }
    },
    
    addOnStopCallback : function (onStop)
    {
        this.onStopCallBack = onStop;
    },
    
    
    openDeviceSuccess : function(mediaStream)
    {
        this.mMediaStream = mediaStream;
    },
    
    openDeviceFailure : (reason) =>
    {
        let errorMessage;
        switch(reason.name) {
            // 用戶拒絕
            case 'NotAllowedError':
            case 'PermissionDeniedError':
                errorMessage = '用戶已禁止網(wǎng)頁(yè)調(diào)用錄音設(shè)備';
                break;
                // 沒(méi)接入錄音設(shè)備
            case 'NotFoundError':
            case 'DevicesNotFoundError':
                errorMessage = '錄音設(shè)備未找到';
                break;
                // 其它錯(cuò)誤
            case 'NotSupportedError':
                errorMessage = '不支持錄音功能';
                break;
            default:
                errorMessage = '錄音調(diào)用錯(cuò)誤';
                window.console.log(error);
        }
        alert(errorMessage);
    },
    
    /**
    * 開始錄音
    * @method startRecord
    * @for AudioRecorder
    * @return {Boolean}
    */
    startRecord : function(){
        let retValue = false;
        if(this.mStatus == "stop")
        {
            this.mChunks = [];
            if(this.mMediaRecorder == null)
            {
                const AudioContext = window.AudioContext || window.webkitAudioContext;
                this.mAudioContext = new AudioContext();
                //創(chuàng)建音頻源
                this.mAudioFromMicrophone= this.mAudioContext.createMediaStreamSource(this.mMediaStream);
                //創(chuàng)建目的節(jié)點(diǎn)
                var destination = this.mAudioContext.createMediaStreamDestination();
                this.mMediaRecorder = new MediaRecorder(destination.stream);
                this.mAudioFromMicrophone.connect(destination);
                this.mMediaRecorder.ondataavailable = (audioData) => { this.onProcessData.call(this,audioData)};
                this.mMediaRecorder.onstop = (event) => { this.onStop.call(this,event)};
            }
            this.mMediaRecorder.start();
            this.mStatus = "record";
            retValue = true;
        }
        return retValue;
    },
    
    onProcessData : function(audioData)
    {
        this.mChunks.push(audioData.data);
    },
    
    onStop : function (event)
    {
        //var blob = new Blob(this.mChunks, { 'type' : 'audio/ogg; codecs=opus' });
        var blob = new Blob(this.mChunks, { 'type' : 'audio/mpeg' });
        var mp3URL = URL.createObjectURL(blob);
        if(this.onStopCallBack != null)
        {
            this.onStopCallBack(mp3URL);
        }
        
    },
        
    /**
    * 結(jié)束錄音
    * @method stopRecord
    * @for AudioRecorder
    */
    stopRecord: function(){
        if(this.mStatus == "record")
        {
            this.mMediaRecorder.requestData();
            this.mMediaRecorder.stop();
            this.mStatus = "stop";
        }
    }
}
 

4.結(jié)束語(yǔ)

這次小功能的調(diào)研势篡,發(fā)現(xiàn)Web Audio API的功能強(qiáng)大翩肌,可以完成各種音頻處理的工作,后續(xù)如果有時(shí)間可以寫一個(gè)頁(yè)面版的音頻應(yīng)用禁悠。

參考:

https://github.com/mdn/webaudio-examples
https://mdn.github.io/webaudio-examples/create-media-stream-destination/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末念祭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碍侦,更是在濱河造成了極大的恐慌粱坤,老刑警劉巖隶糕,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異站玄,居然都是意外死亡枚驻,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門蜒什,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)测秸,“玉大人,你說(shuō)我怎么就攤上這事灾常■耄” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵钞瀑,是天一觀的道長(zhǎng)沈撞。 經(jīng)常有香客問(wèn)我,道長(zhǎng)雕什,這世上最難降的妖魔是什么缠俺? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮贷岸,結(jié)果婚禮上壹士,老公的妹妹穿的比我還像新娘。我一直安慰自己偿警,他們只是感情好躏救,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著螟蒸,像睡著了一般盒使。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上七嫌,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天少办,我揣著相機(jī)與錄音,去河邊找鬼诵原。 笑死英妓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的皮假。 我是一名探鬼主播鞋拟,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼惹资!你這毒婦竟也來(lái)了贺纲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤褪测,失蹤者是張志新(化名)和其女友劉穎猴誊,沒(méi)想到半個(gè)月后潦刃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懈叹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年乖杠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澄成。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胧洒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出墨状,到底是詐尸還是另有隱情卫漫,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布肾砂,位于F島的核電站列赎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏镐确。R本人自食惡果不足惜包吝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望源葫。 院中可真熱鬧诗越,春花似錦、人聲如沸息堂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)储矩。三九已至,卻和暖如春褂乍,著一層夾襖步出監(jiān)牢的瞬間持隧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工逃片, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屡拨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓褥实,卻偏偏與公主長(zhǎng)得像呀狼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子损离,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容