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

Giới thiệu

Thu thập âm thanh/video đã trở thành "Chìa khoá thần thánh" trong hoạt động phát triển web trong một thời gian dài. Trong nhiều năm, chúng tôi đã phải dựa vào các trình bổ trợ cho trình duyệt (flash hoặc Silverlight) để hoàn thành công việc. Đừng lo lắng!

HTML5 giải cứu. Điều này có thể không rõ ràng, nhưng sự phát triển của HTML5 đã mang lại khả năng truy cập phần cứng thiết bị tăng vọt. Các định dạng 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ẽ, cho thấy các API JavaScript cấp cao ở trên các tính năng phần cứng cơ bản của hệ thống.

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

Đường đến getUserMedia()

Nếu bạn chưa biết về lịch sử của API này, thì cách chúng tôi truy cập API getUserMedia() quả là một câu chuyện thú vị.

Một số biến thể của "Media Capture API" (API Thu thập nội dung nghe nhìn) đã được phát triển trong vài năm qua. Nhiều người nhận ra sự cần thiết phải có khả năng 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 đưa ra một thông số kỹ thuật 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ì? Tìm lý do là điều điên rồ! 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á số lượng lớn các đề xuất.

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

Vòng 1: Chụp nội dung nghe nhìn HTML

HTML Media Capture là giải pháp đầu tiên của DAP tiêu chuẩn hóa việc chụp nội dung nghe nhìn trên web. Phương thức này hoạt động bằng cách nạp chồng <input type="file"> và thêm giá trị mới cho tham số accept.

Nếu muốn cho phép người dùng tự chụp ảnh nhanh màn hình bằng webcam, bạn có thể thực hiện điều này với capture=camera:

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

Việc ghi video hoặc ghi âm cũng tương tự như vậy:

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

Hơi đẹp, đúng không? Tôi đặc biệt thích việc API này sử dụng lại thông tin đầu vào là tệp. Về mặt ngữ nghĩa, điều này rất hợp lý. Điểm "API" cụ thể này là khả năng thực hiện hiệu ứng theo thời gian thực (ví dụ: kết xuất dữ liệu webcam trực tiếp cho <canvas> và áp dụng bộ lọc WebGL). HTML Media Capture chỉ cho phép bạn ghi tệp phương tiện hoặc chụp nhanh kịp thời.

Hỗ trợ:

  • Trình duyệt Android 3.0 – một trong những triển khai đầu tiên. Tham khảo video này để tìm hiểu ví dụ thực tế.
  • Chrome dành cho Android (0.16)
  • Firefox Mobile 10.0
  • iOS6 Safari và Chrome (hỗ trợ một phần)

Vòng 2: thành phần thiết bị

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

Opera là một trong những trình duyệt đầu tiên tạo các bước triển khai ban đầu 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> và thay bằng một sản phẩm khác, lần này là API JavaScript có tên là navigator.getUserMedia(). Một tuần sau, Opera ra mắt các bản dựng mới bao gồm hỗ trợ cho thông số kỹ thuật getUserMedia() đã cập nhật. Cuối năm đó, Microsoft tham gia nhóm bằng cách phát hành Phòng thí nghiệm cho IE9 hỗ trợ thông số kỹ thuật mới này.

<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, không có trình duyệt nào được phát hành bao gồm <device>. Tôi đoán ít hơn một API để phải lo lắng về điều này :) Mặc dù vậy, <device> thực sự có hai điều tuyệt vời: 1.) đó là ngữ nghĩa và 2.) nó có thể dễ dàng mở rộng để hỗ trợ nhiều hơn là chỉ các thiết bị âm thanh/video.

Hãy hít thở sâu. Nội dung này có tốc độ rất nhanh!

Vòng 3: WebRTC

Cuối cùng, phần tử <device> đã giống với Dodo.

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

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

Hỗ trợ:

getUserMedia() được hỗ trợ kể 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ể nhấn vào webcam và đầu vào micrô mà không cần trình bổ trợ. Giờ đây, bạn có thể truy cập vào camera chỉ bằng một cuộc gọi chứ không phải cài đặt. Nó được đưa trực tiếp vào trình duyệt. Bạn thấy hào hứng chưa?

Phát hiện tính năng

Việc phát hiện tính năng chỉ là cách kiểm tra đơn giản để xác định 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");
}

Nhận 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 quyền. Tham số đầu tiên đối với 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, tham số đầu tiên phải là {video: true}. Để sử dụng cả micrô và máy ảnh, 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>

OK. Đ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. Nó hoạt động cùng với những người bạn HTML5 khác của chúng tôi, <audio><video>. Xin lưu ý rằng chúng tôi 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 URL một tệp nội dung nghe nhìn, chúng ta sẽ đặt srcObject cho đối tượng LocalMediaStream đại diện cho webcam.

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

Đặt các điều kiện ràng buộc đối với 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 đối với getUserMedia() để chỉ định các yêu cầu (hoặc quy tắc ràng buộc) khác đố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 luồng phải có độ phân giải 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, vui lòng xem API giới hạn.

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ô, máy ảnh, tai nghe, v.v. Promise được trả về được giải quyết bằng một mảng các đối tượng MediaDeviceInfo mô tả thiết bị.

Trong ví dụ này, micrô và máy ảnh cuối cùng tìm thấy được chọn làm nguồn phát trực tuyến 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

Các trình duyệt sẽ hiển thị hộp thoại cấp quyền khi gọi navigator.mediaDevices.getUserMedia(), qua đó cho phép người dùng cấp hoặc từ chối cấp quyền truy cập vào máy ảnh/micrô. Ví dụ: sau đây là hộp thoại cấp 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 người dùng không hỗ trợ navigator.mediaDevices.getUserMedia(), một lựa chọn là dự phòng vào 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ì lý do nào đó:

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