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

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 không nhận thấy điều này, nhưng sự phát triển của HTML5 đã làm tăng đáng kể quyền 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ẽ đến mức đáng kinh ngạc, hiển thị 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 ứ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 về quá trình phát triển của API này, thì cách chúng tôi tạo ra 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 sự điên rồ! Nhóm làm việc về chính sách API thiết bị (DAP) được giao nhiệm vụ hợp nhất và chuẩn hoá nhiều đề 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 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. Phương thứ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 ghi âm cũng tương tự như sau:

<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. Hãy xem video này để biết cách thực hiện.
  • 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à vào cùng ngày), WhatWG đã quyết định loại bỏ thẻ <device> để chuyển sang một thẻ mới mẻ khác, 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 thở sâu. 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 chụp phù hợp đã tăng lên nhờ nỗ lực lớn hơn trong việc sử dụng WebRTC (Giao tiếp theo thời gian thực trên web). Quy cách đó do Nhóm làm việc về WebRTC của W3C giám sát. Google, Opera, Mozilla và một số trình duyệt khác đã triển khai tính năng này.

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, người dùng chỉ cần một cuộc gọi là có thể cấp quyền truy cập vào máy ảnh, chứ 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

Để sử 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 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>

Được rồi. Vậy điều gì đang xảy ra? Tính năng quay video là một ví dụ hoàn hảo về việc các API HTML5 mới hoạt động cùng nhau. Phương thức này hoạt động cùng với các đối tác HTML5 khác của chúng tôi, <audio><video>. Lưu ý rằng chúng ta không thiết lập 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 của tệp phương tiện cho video, 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ấ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 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

Các 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;
}