錄製使用者影片

Mat Scales

許多瀏覽器現在都能存取使用者的影片和音訊輸入內容。不過,視瀏覽器而定,這可能是完整的動態和內嵌體驗,也可能委派給使用者裝置上的其他應用程式。

從簡單開始,逐步

最簡單的做法是直接要求使用者提供預錄檔案。方法是建立簡單的檔案輸入元素,並新增 accept 篩選器,表示我們只能接受影片檔案,以及 capture 屬性,表示我們想直接從相機取得影片。

<input type="file" accept="video/*" capture />

這種方法適用於所有平台。在電腦上,系統會提示使用者從檔案系統上傳檔案 (忽略 capture 屬性)。在 iOS 的 Safari 中,系統會開啟相機應用程式,讓您錄製影片,然後傳回網頁;在 Android 上,系統會讓使用者選擇要使用哪個應用程式錄製影片,然後傳回網頁。

許多行動裝置都有多個相機。如果您有偏好設定,可以將 capture 屬性設為 user (如果想要面向使用者的相機),或 environment (如果想要面向外側的相機)。

<input type="file" accept="video/*" capture="user" />
<input type="file" accept="video/*" capture="environment" />

請注意,這只是提示。如果瀏覽器不支援該選項,或您要求的相機類型無法使用,瀏覽器可能會選擇其他相機。

使用者完成錄製並返回網站後,您需要以某種方式取得檔案資料。如要快速存取,請將 onchange 事件附加至輸入元素,然後讀取事件物件的 files 屬性。

<input type="file" accept="video/*" capture="camera" id="recorder" />
<video id="player" controls></video>
<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 video file.
    player.src = URL.createObjectURL(file);
  });
</script>

取得檔案存取權後,你就能對檔案執行任何操作。舉例來說,您可以執行下列操作:

  • 直接附加到 <video> 元素,即可播放
  • 下載到使用者裝置
  • 附加至 XMLHttpRequest,上傳到伺服器
  • 將影格繪製到畫布上,並套用濾鏡

雖然使用輸入元素方法取得影片資料很常見,但這是最不吸引人的選項。我們希望取得相機存取權,直接在網頁中提供良好的體驗。

以互動方式存取相機

新式瀏覽器可直接連線至相機,讓我們打造與網頁完全整合的體驗,使用者完全不必離開瀏覽器。

取得相機存取權

我們可以使用 WebRTC 規格中名為 getUserMedia() 的 API,直接存取相機。getUserMedia() 會提示使用者授予權限,允許存取已連結的麥克風和攝影機。

如果成功,API 會傳回 Stream,其中包含來自攝影機或麥克風的資料,然後我們可以將其附加到 <video> 元素、附加到 WebRTC 串流,或使用 MediaRecorder API 儲存。

如要從攝影機取得資料,我們只需在傳遞至 getUserMedia() API 的限制物件中設定 video: true

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

  var handleSuccess = function (stream) {
    player.srcObject = stream;
  };

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

如要選擇特定攝影機,可以先列出可用的攝影機。

navigator.mediaDevices.enumerateDevices().then((devices) => {
  devices = devices.filter((d) => d.kind === 'videoinput');
});

然後,您可以在呼叫 getUserMedia 時傳遞要使用的 deviceId。

navigator.mediaDevices.getUserMedia({
  audio: true,
  video: {
    deviceId: devices[0].deviceId,
  },
});

單獨使用這個函式並不是很有用。我們只能擷取影片資料並播放。

存取相機的原始資料

如要存取相機的原始影片資料,您可以將每個影格繪製到 <canvas> 中,並直接操控像素。

如果是 2D 畫布,您可以使用結構定義的 drawImage 方法,將 <video> 元素的目前影格繪製到畫布中。

context.drawImage(myVideoElement, 0, 0);

使用 WebGL 畫布時,您可以使用 <video> 元素做為紋理來源。

gl.texImage2D(
  gl.TEXTURE_2D,
  0,
  gl.RGBA,
  gl.RGBA,
  gl.UNSIGNED_BYTE,
  myVideoElement,
);

請注意,無論是哪種情況,系統都會使用播放中影片的「目前」影格。如要處理多個影格,您必須每次都將影片重新繪製到畫布上。

如要進一步瞭解這項功能,請參閱這篇文章,瞭解如何對圖片和影片套用即時效果。

儲存相機資料

如要儲存相機資料,最簡單的方法是使用 MediaRecorder API。

MediaRecorder API 會擷取 getUserMedia 建立的串流,然後逐步將串流中的資料儲存至偏好的目的地。

<a id="download">Download</a>
<button id="stop">Stop</button>
<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'};
    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.webm';
    });

    mediaRecorder.start();
  };

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

在本例中,我們會將資料直接儲存至陣列,之後再轉換為 Blob,以便儲存至網路伺服器,或直接儲存至使用者裝置的儲存空間。

以負責任的態度要求相機使用權限

如果使用者先前未授予網站攝影機存取權,則在您呼叫 getUserMedia 時,瀏覽器會提示使用者授予網站攝影機權限。

使用者通常不喜歡系統提示存取裝置上的強大裝置,因此經常會封鎖要求,或在不瞭解提示建立情境的情況下忽略要求。最佳做法是只在首次需要時,才要求存取相機。使用者授予存取權後,系統就不會再次詢問。不過,如果使用者拒絕存取,您就無法再次要求使用者授予權限。

使用權限 API 檢查是否已有存取權

getUserMedia API 不會提供您是否已具備相機存取權的資訊。這會帶來一個問題:為了提供良好的使用者介面,讓使用者授予相機存取權,您必須要求相機存取權。

在某些瀏覽器中,這個問題可以透過 Permission API 解決。navigator.permission API 可讓您查詢存取特定 API 的能力狀態,而不必再次提示。

如要查詢您是否可以存取使用者的相機,可以將 {name: 'camera'} 傳入查詢方法,系統會傳回下列其中一項:

  • granted:使用者先前已授予相機存取權。
  • prompt:使用者尚未授予存取權,系統會在您呼叫 getUserMedia 時提示使用者;
  • denied:系統或使用者已明確封鎖相機存取權,您將無法取得存取權。

現在,您可以快速檢查是否需要修改使用者介面,以配合使用者需要採取的動作。

navigator.permissions.query({name: 'camera'}).then(function (result) {
  if (result.state == 'granted') {
  } else if (result.state == 'prompt') {
  } else if (result.state == 'denied') {
  }
  result.onchange = function () {};
});

意見回饋