Ghi âm và quay video ở định dạng HTML5

Giới thiệu

Tính năng ghi âm/ghi hình đã từng là mục tiêu "cao cả" của hoạt động phát triển web trong một thời gian dài. Trong nhiều năm, chúng ta phải dựa vào các trình bổ trợ trình duyệt (Flash hoặc Silverlight) để hoàn tất công việc. Nhanh lên!

HTML5 góp sức. Có thể bạn không nhận thấy, nhưng sự phát triển của HTML5 đã mang đến sự gia tăng đáng kể về quyền truy cập vào phần cứng thiết bị. Vị trí địa lý (GPS), Orientation API (gia tốc kế), WebGL (GPU) và Web Audio API (phần cứng âm thanh) là những ví dụ hoàn hảo. Các tính năng này cực kỳ mạnh mẽ, cung cấp các API JavaScript cấp cao nằm trên các chức năng phần cứng cơ bản của hệ thống.

Hướng dẫn này giới thiệu một API mới, GetUserMedia, cho phép các ứng dụng web truy cập vào camera và micrô của người dùng.

Đường dẫn đến getUserMedia()

Nếu bạn chưa biết về lịch sử của API này, thì cách chúng ta đạt được API getUserMedia() là một câu chuyện thú vị.

Một số biến thể của "Media Capture APIs" đã phát triển trong vài năm qua. Nhiều người nhận ra nhu cầu truy cập vào các thiết bị gốc trên web, nhưng điều đó khiến mọi người và mẹ của họ phải cùng nhau đưa ra một quy cách mới. Mọi thứ trở nên lộn xộn đến mức W3C cuối cùng đã quyết định thành lập một nhóm làm việc. Mục đích duy nhất của họ là gì? Hãy tìm hiểu xem điều gì đang xảy ra! Nhóm công tác về Chính sách API thiết bị (DAP) được giao nhiệm vụ hợp nhất và chuẩn hoá vô số đề xuất.

Tôi sẽ cố gắng tóm tắt những gì đã xảy ra vào năm 2011…

Vòng 1: HTML Media Capture

HTML Media Capture là lần đầu tiên DAP cố gắng chuẩn hoá tính năng chụp ảnh và quay video trên web. Thao tác này hoạt động bằng cách nạp chồng <input type="file"> và thêm các giá trị mới cho tham số accept.

Nếu bạn muốn cho phép người dùng chụp ảnh nhanh bằng webcam, thì bạn có thể làm việc đó bằng capture=camera:

<input type="file" accept="image/*;capture=camera">

Cách quay video hoặc ghi âm tương tự nhau:

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

Nghe cũng hay đúng không? Tôi đặc biệt thích việc nó sử dụng lại một tệp đầu vào. Về mặt ngữ nghĩa, điều này rất hợp lý. Điểm hạn chế của "API" cụ thể này là khả năng tạo hiệu ứng theo thời gian thực (ví dụ: kết xuất dữ liệu webcam trực tiếp thành một <canvas> và áp dụng bộ lọc WebGL). Tính năng Chụp ảnh/quay video bằng HTML chỉ cho phép bạn ghi lại một tệp nội dung nghe nhìn hoặc chụp nhanh tại một thời điểm.

Hỗ trợ:

  • Trình duyệt Android 3.0 – một trong những lần triển khai đầu tiên. Hãy xem video này để biết cách hoạt động của tính năng này.
  • Chrome dành cho Android (0.16)
  • Firefox Mobile 10.0
  • Safari và Chrome trên iOS 6 (hỗ trợ một phần)

Vòng 2: phần tử thiết bị

Nhiều người cho rằng HTML Media Capture quá hạn chế, vì vậy, một quy cách mới đã xuất hiện để hỗ trợ mọi loại thiết bị (trong tương lai). Không có gì ngạc nhiên khi thiết kế này yêu cầu một phần tử mới, đó là phần tử <device>. Phần tử này trở thành tiền thân của getUserMedia().

Opera là một trong những trình duyệt đầu tiên tạo ra các cách triển khai ban đầu tính năng quay video dựa trên phần tử <device>. Ngay sau đó (chính xác là cùng ngày), WhatWG quyết định loại bỏ thẻ <device> để chuyển sang một thẻ khác đang nổi lên, lần này là một API JavaScript có tên là navigator.getUserMedia(). Một tuần sau, Opera phát hành các bản dựng mới có hỗ trợ quy cách getUserMedia() mới. Cuối năm đó, Microsoft cũng tham gia bằng cách phát hành Lab for IE9 (Phòng thí nghiệm cho IE9) hỗ trợ quy cách mới.

<device> sẽ có dạng như sau:

<device type="media" onchange="update(this.data)"></device>
<video autoplay></video>
<script>
  function update(stream) {
    document.querySelector('video').src = stream.url;
  }
</script>

Hỗ trợ:

Rất tiếc, chưa có trình duyệt phát hành nào có <device>. Tôi đoán là sẽ có ít API hơn để lo lắng :) Tuy nhiên, <device> có 2 điểm nổi bật: 1) có tính ngữ nghĩa và 2) dễ dàng mở rộng để hỗ trợ nhiều thiết bị hơn chứ không chỉ thiết bị âm thanh/video.

Hít một hơi thật sâu. Những thứ này thay đổi rất nhanh!

Vòng 3: WebRTC

Phần tử <device> cuối cùng cũng biến mất.

Tốc độ tìm thấy một API chụp phù hợp đã tăng lên nhờ nỗ lực lớn hơn của WebRTC (Giao tiếp theo thời gian thực trên web). Quy cách đó do Nhóm công tác WebRTC của W3C giám sát. Google, Opera, Mozilla và một số trình duyệt khác đã triển khai API này.

getUserMedia() có liên quan đến WebRTC vì đây là cổng vào của bộ API đó. API này cung cấp phương tiện để truy cập vào luồng camera/micrô cục bộ của người dùng.

Hỗ trợ:

getUserMedia() được hỗ trợ từ Chrome 21, Opera 18 và Firefox 17.

Bắt đầu

Với navigator.mediaDevices.getUserMedia(), cuối cùng chúng ta có thể khai thác đầu vào của webcam và micrô mà không cần trình bổ trợ. Giờ đây, bạn có thể truy cập vào camera bằng một cuộc gọi, chứ không cần cài đặt. Tính năng này được tích hợp trực tiếp vào trình duyệt. Bạn đã thấy hứng thú chưa?

Phát hiện đối tượng

Tính năng phát hiện là một bước kiểm tra đơn giản về sự tồn tại của navigator.mediaDevices.getUserMedia:

if (navigator.mediaDevices?.getUserMedia) {
  // Good to go!
} else {
  alert("navigator.mediaDevices.getUserMedia() is not supported");
}

Có quyền truy cập vào thiết bị đầu vào

Để sử dụng webcam hoặc micrô, chúng tôi cần yêu cầu bạn cấp quyền. Tham số đầu tiên cho navigator.mediaDevices.getUserMedia() là một đối tượng chỉ định thông tin chi tiết và yêu cầu cho từng loại nội dung nghe nhìn mà bạn muốn truy cập. Ví dụ: nếu bạn muốn truy cập vào webcam, thì tham số đầu tiên phải là {video: true}. Để sử dụng cả micrô và camera, hãy truyền {video: true, audio: true}:

<video autoplay></video>

<script>
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: true })
    .then((localMediaStream) => {
      const video = document.querySelector("video");
      video.srcObject = localMediaStream;
    })
    .catch((error) => {
      console.log("Rejected!", error);
    });
</script>

Được rồi. Vậy điều gì đang xảy ra ở đây? Tính năng ghi lại nội dung nghe nhìn là một ví dụ hoàn hảo về các API HTML5 mới hoạt động cùng nhau. Thẻ này hoạt động cùng với các thẻ HTML5 khác là <audio><video>. Xin lưu ý rằng chúng ta không đặt thuộc tính src hoặc đưa các phần tử <source> vào phần tử <video>. Thay vì cung cấp cho video một URL đến tệp đa phương tiện, chúng ta sẽ đặt srcObject thành đối tượng LocalMediaStream đại diện cho webcam.

Tôi cũng yêu cầu <video> autoplay, nếu không thì nó sẽ bị treo ở khung hình đầu tiên. Thao tác thêm controls cũng hoạt động như bạn mong đợi.

Thiết lập các điều kiện hạn chế về nội dung nghe nhìn (độ phân giải, chiều cao, chiều rộng)

Bạn cũng có thể dùng tham số đầu tiên cho getUserMedia() để chỉ định thêm các yêu cầu (hoặc điều kiện hạn chế) đối với luồng nội dung nghe nhìn được trả về. Ví dụ: thay vì chỉ cho biết bạn muốn có quyền truy cập cơ bản vào video (ví dụ: {video: true}), bạn có thể yêu cầu thêm rằng luồng phát phải có chất lượng HD:

const hdConstraints = {
  video: { width: { exact:  1280} , height: { exact: 720 } },
};

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);
const vgaConstraints = {
  video: { width: { exact:  640} , height: { exact: 360 } },
};

const stream = await navigator.mediaDevices.getUserMedia(hdConstraints);

Để biết thêm các cấu hình, hãy xem API ràng buộc.

Chọn nguồn nội dung nghe nhìn

Phương thức enumerateDevices() của giao diện MediaDevices yêu cầu danh sách các thiết bị đầu vào và đầu ra đa phương tiện hiện có, chẳng hạn như micrô, camera, tai nghe, v.v. Promise được trả về sẽ được phân giải bằng một mảng các đối tượng MediaDeviceInfo mô tả các thiết bị.

Trong ví dụ này, micrô và camera gần đây nhất được tìm thấy sẽ được chọn làm nguồn luồng nội dung nghe nhìn:

if (!navigator.mediaDevices?.enumerateDevices) {
  console.log("enumerateDevices() not supported.");
} else {
  // List cameras and microphones.
  navigator.mediaDevices
    .enumerateDevices()
    .then((devices) => {
      let audioSource = null;
      let videoSource = null;

      devices.forEach((device) => {
        if (device.kind === "audioinput") {
          audioSource = device.deviceId;
        } else if (device.kind === "videoinput") {
          videoSource = device.deviceId;
        }
      });
      sourceSelected(audioSource, videoSource);
    })
    .catch((err) => {
      console.error(`${err.name}: ${err.message}`);
    });
}

async function sourceSelected(audioSource, videoSource) {
  const constraints = {
    audio: { deviceId: audioSource },
    video: { deviceId: videoSource },
  };
  const stream = await navigator.mediaDevices.getUserMedia(constraints);
}

Hãy xem bản minh hoạ tuyệt vời của Sam Dutton về cách cho phép người dùng chọn nguồn nội dung nghe nhìn.

Bảo mật

Trình duyệt sẽ hiển thị một hộp thoại cấp quyền khi gọi navigator.mediaDevices.getUserMedia(), cho phép người dùng cấp hoặc từ chối quyền truy cập vào camera/micrô của họ. Ví dụ: sau đây là hộp thoại quyền của Chrome:

Hộp thoại cấp quyền trong Chrome
Hộp thoại cấp quyền trong Chrome

Cung cấp phương án dự phòng

Đối với những người dùng không được hỗ trợ navigator.mediaDevices.getUserMedia(), một lựa chọn là quay lại tệp video hiện có nếu API không được hỗ trợ và/hoặc lệnh gọi không thành công vì một lý do nào đó:

if (!navigator.mediaDevices?.getUserMedia) {
  video.src = "fallbackvideo.webm";
} else {
  const stream = await navigator.mediaDevices.getUserMedia({ video: true });
  video.srcObject = stream;
}