天天看點

[轉]錄制使用者的音頻

許多浏覽器現在都能通路使用者的視訊和音頻輸入。 不過,根據浏覽器的不同,這一功能可能展現為一種全動态的内置體驗,也可能通過授權給使用者裝置上的其他應用來實作。

從簡單做起,循序漸進

最簡易的做法是直接要求使用者提供預先錄制的檔案。 其實作步驟是:建立一個簡單的檔案輸入元素,然後添加一個表示我們隻能接受音頻檔案的

accept

過濾器,在理想的情況下,我們可以直接從麥克風擷取這些檔案。

<input type="file" accept="audio/*" capture="microphone">           

此方法在所有平台上都有效。在桌面平台上,它會提示使用者通過檔案系統上傳檔案(忽略

capture="microphone"

)。 在

iOS

上的

Safari

中,它會打開麥克風應用以便您錄制音頻,然後将其傳回網頁;在

Android

上,它允許使用者選擇使用哪一個應用來錄制音頻,錄制完畢後将其傳回網頁。

使用者完成錄制并傳回網站後,您需要以某種方式掌握檔案資料。 為

input

元素附加一個

onchange

事件,然後讀取事件對象的

files

屬性,便可快速獲得檔案通路權。

<input type="file" accept="audio/*" capture="microphone" id="recorder">
<audio id="player" controls></audio>
<script>
  var recorder = document.getElementById('recorder');
  var player = document.getElementById('player')'

  recorder.addEventListener('change', function(e) {
    var file = e.target.files[0]; 
    // Do something with the audio file.
    player.src =  URL.createObjectURL(file);
  });
</script>           

獲得對檔案的通路權後,便可随意對其執行任何操作。例如,可以執行以下操作:

. 将其直接附加到一個

<audio>

元素,這樣便能播放檔案

. 将其下載下傳至使用者的裝置

. 通過将其附加到一個

XMLHttpRequest

,上傳至伺服器

. 通過

Web Audio API

傳遞檔案并對其應用過濾器

盡管使用

input

元素方法獲得對音頻資料通路權的情況普遍存在,卻是最沒有吸引力的方案。 因為我們真正需要的是獲得對麥克風的通路權,直接在頁面内提供良好的體驗。

以互動方式通路麥克風

現代浏覽器可直連麥克風,我們可以借此打造與網頁完全內建的體驗,讓使用者永遠都不需要離開浏覽器。

獲得對麥克風的通路權

我們可以利用

WebRTC

規範中名為

getUserMedia()

API

直接通路麥克風。

getUserMedia()

将提示使用者授予對其相連麥克風和攝像頭的通路權。

如果授權成功,該

API

将傳回一個

Stream

,其中包含來自攝像頭或麥克風的資料,然後我們可以将資料附加到一個

<audio>

元素、将其附加到一個網絡音頻

AudioContext

或使用

MediaRecorder API

對其進行儲存。

要從麥克風擷取資料,我們隻需在傳遞給

getUserMedia() API

的限制對象中設定

audio: true

<audio id="player" controls></audio>
<script>  
  var player = document.getElementById('player');

  var handleSuccess = function(stream) {
    if (window.URL) {
      player.src = window.URL.createObjectURL(stream);
    } else {
      player.src = stream;
    }
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .then(handleSuccess)
</script>           

這段代碼本身的用處并不大。我們所能做的就是擷取音頻資料并進行播放。

從麥克風擷取原始資料

要從麥克風擷取原始資料,我們需要擷取

getUserMedia()

建立的卡片資訊流,然後利用

Web Audio API

處理資料。

Web Audio API

是一個簡單的

API

,用于擷取輸入源并将這些輸入源連接配接到可以處理音頻資料(調節增益等)的節點,最終目的是連接配接到揚聲器以便使用者能夠聽到聲音。

可以連接配接的其中一個節點是

ScriptProcessorNode

。每次音頻緩沖區已滿,需要您進行處理時,該節點都會發出一個

onaudioprocess

事件。此時,您可以将資料儲存到自己的緩沖區内,留供以後使用。

<script>  
  var handleSuccess = function(stream) {
    var context = new AudioContext();
    var input = context.createMediaStreamSource(stream)
    var processor = context.createScriptProcessor(1024,1,1);

    source.connect(processor);
    processor.connect(context.destination);

    processor.onaudioprocess = function(e){
      // Do something with the data, i.e Convert this to WAV
      console.log(e.inputBuffer);
    };
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .then(handleSuccess)
</script>           

保留在緩沖區内的資料是來自麥克風的原始資料,在這些資料的處理上有以下這幾種選擇:

. 将其直接上傳至伺服器

. 将其存儲在本地

. 将其轉換為專用檔案格式(例如

WAV

),然後儲存至伺服器或本地

儲存來自麥克風的資料

要想儲存來自麥克風的資料,最簡便的方法是使用

MediaRecorder API

MediaRecorder API

将擷取

getUserMedia

建立的卡片資訊流,然後漸進式地将卡片資訊流中的資料儲存到首選目的地。

<a id="download">Download
<button id="stop">Stop
<script> 
  let shouldStop = false;
  let stopped = false;
  const downloadLink = document.getElementById('download');
  const stopButton = document.getElementById('stop');

  stopButton.addEventListener('click', function() {
    shouldStop = true;
  })

  var handleSuccess = function(stream) {  
    const options = {mimeType: 'video/webm;codecs=vp9'};
    const recordedChunks = [];
    const mediaRecorder = new MediaRecorder(stream, options);  

    mediaRecorder.addEventListener('dataavailable', function(e) {
      if (e.data.size > 0) {
        recordedChunks.push(e.data);
      }

      if(shouldStop === true && stopped === false) {
        mediaRecorder.stop();
        stopped = true;
      }
    });

    mediaRecorder.addEventListener('stop', function() {
      downloadLink.href = URL.createObjectURL(new Blob(recordedChunks));
      downloadLink.download = 'acetest.wav';
    });

    mediaRecorder.start();
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .then(handleSuccess);

</script>           

在我們這種情況下,我們要将資料直接儲存到一個數組中,然後在稍後轉換成

Blob

後再将其儲存到網絡伺服器,或直接儲存在使用者裝置的存儲内。

以負責任的方式請求麥克風使用權限

如果使用者之前未授予網站對麥克風的通路權,則調用

getUserMedia

時浏覽器會立即提示使用者授予網站對麥克風的通路權。

使用者讨厭在其機器上收到索要功能強大裝置通路權的提示,他們常常會屏蔽權限請求,而如果他們不了解提示的産生環境,也會将其忽略。最好的做法是在首次需要權限時隻請求通路麥克風。 一旦使用者授予了通路權,就不會再次收到提示,但如果他們拒絕授權,您就無法再次獲得通路權以向使用者請求權限。

在頁面加載時請求獲得對麥克風的通路權将導緻大多數使用者拒絕您通路麥克風

利用 Permission API 确認是否已獲得通路權

getUserMedia API

并不能讓您了解自己是否已獲得對麥克風的通路權。 這就帶來了一個問題:為了提供友善的 UI,讓使用者願意授予對麥克風的通路權,您就必須請求獲得對麥克風的通路權。

在某些浏覽器中,可以利用

Permission API

來解決這個問題。

navigator

.

permission API

讓您不必再次提示使用者便可查詢到通路特定

API

能力的狀态。

要想查詢是否有權通路使用者的麥克風,可以将 {

name

: '

microphone

'} 傳入

query

方法,後者将傳回:

granted

— 使用者之前已授予對麥克風的通路權;

.

prompt

— 使用者尚未授予通路權,調用

getUserMedia

時将會收到提示;

denied

— 系統或使用者已顯式屏蔽對麥克風的通路權,您将無法獲得對其的通路權。

現在您就可以進行快速檢查,以确認是否需要改動使用者界面來适應使用者需要執行的操作。

navigator.permissions.query({name:'microphone'}).then(function(result) {
  if (result.state == 'granted') {

  } else if (result.state == 'prompt') {

  } else if (result.state == 'denied') {

  }
  result.onchange = function() {

  };
});           

繼續閱讀