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

Giới thiệu

Tính năng quay video/âm thanh đã là công cụ "thần thánh" trong quá trình 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 thành công việc. Thôi nào!

HTML5 sẽ giúp bạn giải quyết vấn đề này. Có thể bạn chưa rõ, nhưng sự phát triển của HTML5 đã mang lại sự đột phá trong việc truy cập vào phần cứng thiết bị. Vị trí địa lý (GPS), API hướng (cảm biến gia tốc), WebGL (GPU) và API âm thanh trên web (phần cứng âm thanh) là những ví dụ hoàn hảo. Các tính năng này mạnh mẽ một cách kỳ lạ, hiển thị các API JavaScript cấp cao, vượt trội hơn các tính 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 ứng dụng web truy cập vào máy ảnh và micrô của người dùng.

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

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

Trong vài năm qua, một số biến thể của "Media Capture API" đã phát triển. Nhiều người nhận thấy cần 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 cuối cùng W3C đã 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 hiểu về sự điên rồ này! Nhóm công tác về Chính sách API thiết bị (DAP) có nhiệm vụ hợp nhất + chuẩn hoá hàng loạt đề xuất.

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

Vòng 1: Quay video bằng HTML

HTML Media Capture là nỗ lực đầu tiên của DAP nhằm chuẩn hoá tính năng quay video trên web. Công 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 tự chụp ảnh chân dung bằng webcam, bạn có thể thực hiện việc này bằng capture=camera:

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

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

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

Khá ổn phải không? Tôi đặc biệt thích việc nó sử dụng lại dữ liệu đầu vào của tệp. Về ngữ nghĩa, điều này rất hợp lý. Điểm yếu của "API" cụ thể này là không thể 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 vào <canvas> và áp dụng bộ lọc WebGL). HTML Media Capture chỉ cho phép bạn ghi lại tệp nội dung nghe nhìn hoặc chụp ảnh nhanh tại một thời điểm.

Hỗ trợ:

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

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

Nhiều người cho rằng tính nă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 để 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, phần tử <device>, 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 phương thức 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> để thay thế một thẻ mới, 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ợ cho thông số kỹ thuật getUserMedia() đã cập nhật. Cuối năm đó, Microsoft đã tham gia 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.

<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 nào được phát hành bao gồm <device>. Tôi đoán là bạn sẽ bớt phải lo lắng về một API :) Tuy nhiên, <device> có hai điểm mạnh: 1.) có ngữ nghĩa và 2.) dễ dàng mở rộng để hỗ trợ nhiều thiết bị hơn là chỉ âm thanh/video.

Hãy hít một hơi nào. Mọi thứ diễn ra rất nhanh!

Vòng 3: WebRTC

Cuối cùng, phần tử <device> cũng đi theo con đường của Dodo.

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

getUserMedia() có liên quan đến WebRTC vì đây là cổng vào nhóm API đó. API này cung cấp phương tiện để truy cập vào 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ể khai thác đầu vào webcam và micrô mà không cần trình bổ trợ. Giờ đây, bạn có thể truy cập camera mà không cần phải cài đặt. Trình bổ trợ được tích hợp trực tiếp vào trình duyệt. Bạn có hào hứng chưa?

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

Phát hiện tính năng là một bước 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");
}

Truy cập vào thiết bị đầu vào

Để dùng webcam hoặc micrô, chúng tôi cần yêu cầu cấp quyền. Tham số đầu tiên của navigator.mediaDevices.getUserMedia() là một đối tượng chỉ định thông tin chi tiết và yêu cầu đối với 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 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>

Được rồi. Vậy điều gì đang xảy ra? Ghi nội dung đa phương tiện là một ví dụ hoàn hảo về các API HTML5 mới hoạt động cùng nhau. API này hoạt động cùng với các bạn bè HTML5 khác của chúng tôi, <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 URL cho video một tệp nội dung nghe nhìn, chúng ta sẽ đặt srcObject thành đối tượng LocalMediaStream đại diện cho webcam.

Tôi cũng đang yêu cầu <video> chuyển đến autoplay, nếu không, nó sẽ bị treo trên khung đầu tiên. Việc thêm controls cũng hoạt động như mong đợi.

Đặt các quy tắc 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 cho getUserMedia() để chỉ định thêm các yêu cầu (hoặc quy tắc ràng buộc) đối với luồng nội dung đa phương tiện được trả về. Ví dụ: thay vì chỉ cho biết bạn muốn quyền truy cập cơ bản vào video (ví dụ: {video: true}), bạn có thể yêu cầu luồng phải ở định dạ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 quy tắc ràng buộc.

Chọn nguồn nội dung đa phương tiệ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. Lời hứa được trả về đượ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à máy ảnh cuối cùng 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ị 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 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 những người dùng không được hỗ trợ navigator.mediaDevices.getUserMedia(), bạn có thể sử dụng một 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;
}