Nhiều trình duyệt hiện có khả năng truy cập vào dữ liệu đầu vào video và âm thanh của người dùng. Tuy nhiên, tuỳ thuộc vào trình duyệt, đây có thể là trải nghiệm động và nội tuyến đầ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à dần dần
Cách dễ nhất là yêu cầu người dùng cung cấp một tệp đã ghi sẵn. Hãy thực hiện việc này bằng cách tạo một phần tử nhập 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 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, thuộc tính này sẽ nhắc người dùng tải tệp lên từ hệ thống tệp (bỏ qua thuộc tính capture). Trong Safari trên iOS, thao tác này sẽ mở ứng dụng máy ảnh, cho phép bạn quay video rồi gửi lại video đó cho trang web; trên Android, thao tác này sẽ cho phép người dùng chọn ứng dụng để quay video trước khi gửi lại video đó cho trang web.
Nhiều thiết bị di động có nhiều camera. 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 muốn camera hướng về phía người dùng hoặc environment nếu muốn camera hướng ra ngoài.
<input type="file" accept="video/*" capture="user" />
<input type="file" accept="video/*" capture="environment" />
Xin lưu ý rằng đây chỉ là 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, thì trình duyệt có thể chọn một camera khác.
Sau khi người dùng ghi xong và quay lại trang web, bạn cần lấy dữ liệu tệp bằng cách nào đó. 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 có quyền truy cập vào tệp, bạn có thể làm bất cứ điều gì bạn muốn với tệp đó. Ví dụ: bạn có thể:
- Đính kèm trực tiếp vào phần tử
<video>để có thể phát video - Tải xuống thiết bị của người dùng
- Tải tệp lên máy chủ bằng cách đính kèm vào
XMLHttpRequest - Vẽ khung hình vào canvas và áp dụng bộ lọc cho khung hình đó
Mặc dù việc sử dụng phương thức phần tử đầu vào để truy cập vào dữ liệu video là phổ biến, nhưng đây là lựa chọn ít hấp dẫn nhất. Chúng tôi thực sự muốn có quyền truy cập vào máy ảnh và mang lại trải nghiệm thú vị ngay trên trang.
Truy cập vào camera theo cách tương tác
Các trình duyệt hiện đại có thể có đường truyền trực tiếp đến camera, cho phép chúng tôi xây dựng trải nghiệm được tích hợp hoàn toàn với trang web và người dùng sẽ không bao giờ rời khỏi trình duyệt.
Yêu cầu quyền truy cập vào camera
Chúng ta có thể truy cập trực tiếp vào camera bằng cách sử dụng một API trong thông số kỹ thuật WebRTC có tên là getUserMedia(). getUserMedia() sẽ nhắc người dùng cấp quyền truy cập vào 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 dữ liệu đó vào một phần tử <video>, đính kèm vào luồng WebRTC hoặc lưu dữ liệu đó bằng API MediaRecorder.
Để lấy dữ liệu từ camera, chúng ta chỉ cần đặt video: true trong đối tượng ràng buộc được truyền đến 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 hiện có.
navigator.mediaDevices.enumerateDevices().then((devices) => {
devices = devices.filter((d) => d.kind === 'videoinput');
});
Sau đó, bạn có thể truyền deviceId mà bạn muốn sử dụng khi gọi getUserMedia.
navigator.mediaDevices.getUserMedia({
audio: true,
video: {
deviceId: devices[0].deviceId,
},
});
Nếu chỉ có vậy thì không hữu ích lắm. 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 vào dữ liệu thô từ camera
Để 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à thao tác trực tiếp với các pixel.
Đối với canvas 2D, bạn có thể sử dụng phương thức drawImage của ngữ cảnh để vẽ khung hình 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 kết cấu.
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, thao tác 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, bạn cần vẽ lại video vào canvas mỗi lần.
Bạn có thể tìm hiểu thêm về vấn đề 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 đích đến bạn muố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ể chuyển thành Blob. Sau đó, chúng ta có thể dùng mảng này để lưu vào Máy chủ web hoặc lưu trực tiếp vào bộ nhớ trên thiết bị của người dùng.
Yêu cầu cấp quyền sử dụng camera 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 cho trang web của bạn quyền truy cập vào máy ảnh.
Người dùng không thích việc được 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ọ và họ thường sẽ chặn yêu cầu hoặc bỏ qua yêu cầu nếu không hiểu bối cảnh tạo lời nhắc. Tốt nhất là bạn chỉ nên 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 được hỏi lại. Tuy nhiên, nếu họ từ chối quyền truy cập, bạn sẽ không thể truy cập lại để yêu cầu người dùng cấp quyền.
Sử 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 bạn biết liệu bạn đã có quyền truy cập vào máy ảnh hay chưa. Điều này đặt ra cho bạn một vấn đề: để cung cấp một giao diện người dùng đẹp mắt nhằm thu hút người dùng cấp cho bạn quyền truy cập vào camera, bạn phải yêu cầu quyền truy cập vào camera.
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 Permission 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 nhắc lại.
Để truy vấn xem bạn có quyền truy cập vào camera của người dùng hay không, bạn có thể truyền {name: 'camera'} vào phương thức truy vấn và phương thức này sẽ trả về:
granted— người dùng đã cấp cho bạn quyền truy cập vào camera;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ọigetUserMedia;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ể nhanh chóng kiểm tra xem có cần thay đổi giao diện người dùng để phù hợp với các thao tác 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 () {};
});