Ghi video từ người dùng

Cân thảm

Nhiều trình duyệt hiện có thể truy cập vào video và âm thanh đầu vào của người dùng. Tuy nhiên, tuỳ thuộc vào trình duyệt, đó có thể là trải nghiệm động và cùng dòng đầy đủ hoặc có thể được uỷ quyền cho một ứng dụng khác trên thiết bị của người dùng.

Bắt đầu đơn giản và tăng dần

Điều dễ làm nhất là yêu cầu người dùng cung cấp tệp được ghi sẵn. Bạn có thể thực hiện việc này bằng cách tạo một phần tử đầu vào tệp đơn giản và thêm bộ lọc accept để cho biết chúng ta chỉ có thể chấp nhận các tệp video và thuộc tính capture cho biết chúng ta muốn lấy tệp trực tiếp từ máy ảnh.

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

Phương thức này hoạt động trên tất cả các nền tảng. Trên máy tính, hệ thống sẽ nhắc người dùng tải một tệp lên từ hệ thống tệp (bỏ qua thuộc tính capture). Trong Safari trên iOS, ứng dụng máy ảnh sẽ mở ra, cho phép bạn quay video rồi gửi lại trang web; trên Android, tính năng này sẽ cho người dùng lựa chọn ứng dụng để quay video trước khi gửi lại trang web.

Nhiều thiết bị di động có nhiều máy ảnh. Nếu có lựa chọn ưu tiên, bạn có thể đặt thuộc tính capture thành user nếu bạn muốn camera hướng vào người dùng hoặc environment nếu bạn muốn camera hướng ra ngoài.

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

Lưu ý rằng đây chỉ là một gợi ý - nếu trình duyệt không hỗ trợ tuỳ chọn này hoặc loại camera bạn yêu cầu không có sẵn, trình duyệt có thể chọn một camera khác.

Sau khi người dùng hoàn tất quá trình ghi và quay lại trang web, bạn cần phải giữ lại dữ liệu tệp. Bạn có thể truy cập nhanh bằng cách đính kèm sự kiện onchange vào phần tử đầu vào, sau đó đọc thuộc tính files của đối tượng sự kiện.

<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>

Sau khi truy cập được vào tệp, bạn có thể làm mọi việc mình muốn đối với tệp đó. Ví dụ: bạn có thể:

  • Đính kèm trực tiếp phần tử này vào phần tử <video> để bạn có thể phát phần tử đó
  • Tải ứng dụng xuống thiết bị của người dùng
  • Tải bản ghi lên máy chủ bằng cách đính kèm vào XMLHttpRequest
  • Vẽ các khung vào một canvas và áp dụng các bộ lọc cho khung đó

Mặc dù phương thức phần tử đầu vào để truy cập vào dữ liệu video khá phổ biến, nhưng đây là phương thức ít hấp dẫn nhất. Chúng tôi thực sự muốn truy cập vào máy ảnh và cung cấp trải nghiệm thú vị ngay trên trang.

Truy cập vào máy ảnh theo cách tương tác

Các trình duyệt hiện đại có thể có một đường truyền trực tiếp đến máy ảnh, cho phép chúng tôi xây dựng trải nghiệm được tích hợp đầy đủ với trang web và người dùng sẽ không bao giờ rời khỏi trình duyệt.

Lấy quyền truy cập vào máy ảnh

Chúng tôi có thể truy cập trực tiếp vào camera bằng cách sử dụng API trong thông số kỹ thuật WebRTC có tên là getUserMedia(). getUserMedia() sẽ nhắc người dùng truy cập vào các micrô và camera đã kết nối.

Nếu thành công, API sẽ trả về một Stream chứa dữ liệu từ camera hoặc micrô, sau đó chúng ta có thể đính kèm phần tử đó vào phần tử <video>, đính kèm vào luồng WebRTC hoặc lưu bằng API MediaRecorder.

Để lấy dữ liệu từ máy ảnh, chúng ta chỉ cần đặt video: true trong đối tượng ràng buộc được truyền vào API getUserMedia()

<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>

Nếu muốn chọn một camera cụ thể, trước tiên, bạn có thể liệt kê các camera có sẵn.

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

Sau đó, bạn có thể chuyển mã thiết bị mà bạn muốn sử dụng khi gọi getUserMedia.

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

Tuy nhiên, điều này không hữu ích. Tất cả những gì chúng ta có thể làm là lấy dữ liệu video và phát lại.

Truy cập dữ liệu thô từ máy ảnh

Để truy cập vào dữ liệu video thô từ máy ảnh, bạn có thể vẽ từng khung hình vào <canvas> và chỉnh sửa trực tiếp các pixel.

Đối với canvas 2D, bạn có thể sử dụng phương thức drawImage của bối cảnh để vẽ khung hiện tại của phần tử <video> vào canvas.

context.drawImage(myVideoElement, 0, 0);

Với canvas WebGL, bạn có thể sử dụng phần tử <video> làm nguồn cho họa tiết.

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

Xin lưu ý rằng trong cả hai trường hợp, khung hình này sẽ sử dụng khung hình hiện tại của video đang phát. Để xử lý nhiều khung hình, mỗi lần bạn cần vẽ lại video vào canvas.

Bạn có thể tìm hiểu thêm về điều này trong bài viết của chúng tôi về cách áp dụng hiệu ứng theo thời gian thực cho hình ảnh và video.

Lưu dữ liệu từ máy ảnh

Cách dễ nhất để lưu dữ liệu từ máy ảnh là sử dụng API MediaRecorder.

API MediaRecorder sẽ lấy luồng do getUserMedia tạo, sau đó lưu dần dữ liệu từ luồng vào điểm đến ưu tiên của bạn.

<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>

Trong trường hợp này, chúng ta sẽ lưu dữ liệu trực tiếp vào một mảng mà sau này có thể gửi vào một Blob, sau đó có thể dùng để lưu vào Máy chủ web hoặc lưu trực tiếp trong bộ nhớ trên thiết bị của người dùng.

Yêu cầu quyền sử dụng máy ảnh một cách có trách nhiệm

Nếu trước đây người dùng chưa cấp cho trang web của bạn quyền truy cập vào máy ảnh, thì ngay khi bạn gọi getUserMedia, trình duyệt sẽ nhắc người dùng cấp quyền truy cập vào máy ảnh cho trang web của bạn.

Người dùng không thích khi bị nhắc cấp quyền truy cập vào các thiết bị mạnh mẽ trên máy của họ, nên họ thường xuyên chặn yêu cầu hoặc sẽ bỏ qua nếu không hiểu ngữ cảnh của lời nhắc được tạo. Phương pháp hay nhất là chỉ yêu cầu quyền truy cập vào máy ảnh khi cần thiết. Sau khi người dùng cấp quyền truy cập, họ sẽ không bị yêu cầu cấp lại. Tuy nhiên, nếu từ chối quyền truy cập thì bạn không thể yêu cầu người dùng cấp quyền lần nữa.

Dùng API quyền để kiểm tra xem bạn đã có quyền truy cập hay chưa

API getUserMedia không cho biết liệu bạn đã có quyền truy cập vào camera hay chưa. Như vậy, bạn sẽ gặp phải một vấn đề. Đó là để cung cấp một giao diện người dùng đẹp mắt nhằm yêu cầu người dùng cấp cho bạn quyền truy cập vào máy ảnh, bạn phải yêu cầu quyền truy cập vào máy ảnh.

Bạn có thể giải quyết vấn đề này trong một số trình duyệt bằng cách sử dụng Permissions API. API navigator.permission cho phép bạn truy vấn trạng thái của khả năng truy cập vào các API cụ thể mà không cần phải nhắc lại.

Để truy vấn xem bạn có quyền truy cập vào máy ảnh của người dùng hay không, bạn có thể chuyển {name: 'camera'} vào phương thức truy vấn. Thao tác này sẽ trả về:

  • granted — người dùng đã từng cấp cho bạn quyền truy cập vào máy ảnh;
  • prompt – người dùng chưa cấp quyền truy cập cho bạn và sẽ được nhắc khi bạn gọi getUserMedia;
  • denied – hệ thống hoặc người dùng đã chặn quyền truy cập vào camera một cách rõ ràng và bạn sẽ không thể truy cập vào camera.

Giờ đây, bạn có thể kiểm tra nhanh để xem liệu bạn có cần thay đổi giao diện người dùng cho phù hợp với hành động mà người dùng cần thực hiện hay không.

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 () {};
});

Ý kiến phản hồi