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 linh độ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
Việc dễ dàng nhất bạn có thể làm là yêu cầu người dùng cung cấp tệp được ghi sẵn. Hãy 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 một bộ lọc accept
cho biết chúng ta chỉ có thể chấp nhận các tệp âm thanh và một thuộc tính capture
cho biết chúng ta muốn nhận trực tiếp từ micrô.
<input type="file" accept="audio/*" 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, thao tác này 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, thao tác này sẽ mở ứng dụng micrô, cho phép bạn ghi âm rồi gửi lại cho trang web; trên Android, thao tác này sẽ cho phép người dùng chọn ứng dụng để ghi âm trước khi gửi lại cho trang web.
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 nắm bắt 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 một 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="audio/*" capture id="recorder" />
<audio id="player" controls></audio>
<script>
const recorder = document.getElementById('recorder');
const player = document.getElementById('player');
recorder.addEventListener('change', function (e) {
const file = e.target.files[0];
const url = URL.createObjectURL(file);
// Do something with the audio file.
player.src = url;
});
</script>
</audio>
Sau khi có quyền truy cập vào tệp, bạn có thể làm bất cứ điều gì mình muốn với tệp đó. Ví dụ: bạn có thể:
- Đính kèm trực tiếp vào phần tử
<audio>
để bạn 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 tệp đó vào
XMLHttpRequest
- Truyền dữ liệu đó qua API Âm thanh trên web và áp dụng bộ lọc cho dữ liệu đó
Mặc dù phương thức phần tử đầu vào để truy cập dữ liệu âm thanh rất 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 micrô và mang đến trải nghiệm tốt ngay trên trang.
Truy cập vào micrô 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 micrô, cho phép chúng tôi xây dựng các 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.
Có quyền truy cập vào micrô
Chúng ta có thể truy cập trực tiếp vào Micrô bằng cách sử dụng một API trong quy cách 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à máy ảnh đã kết nối.
Nếu thành công, API sẽ trả về một Stream
chứa dữ liệu từ máy ảnh hoặc micrô. Sau đó, chúng ta có thể đính kèm dữ liệu đó vào một phần tử <audio>
, đính kèm vào luồng WebRTC, đính kèm vào AudioContext
Âm thanh trên web hoặc lưu dữ liệu đó bằng API MediaRecorder
.
Để lấy dữ liệu từ micrô, chúng ta chỉ cần đặt audio: true
trong đối tượng ràng buộc được truyền đến API getUserMedia()
.
<audio id="player" controls></audio>
<script>
const player = document.getElementById('player');
const handleSuccess = function (stream) {
if (window.URL) {
player.srcObject = stream;
} else {
player.src = stream;
}
};
navigator.mediaDevices
.getUserMedia({audio: true, video: false})
.then(handleSuccess);
</script>
Nếu muốn chọn một micrô cụ thể, trước tiên, bạn có thể liệt kê các micrô có sẵn.
navigator.mediaDevices.enumerateDevices().then((devices) => {
devices = devices.filter((d) => d.kind === 'audioinput');
});
Sau đó, bạn có thể truyền deviceId
mà bạn muốn sử dụng khi gọi getUserMedia
.
navigator.mediaDevices.getUserMedia({
audio: {
deviceId: devices[0].deviceId,
},
});
Chỉ riêng thông tin nà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 âm thanh và phát lại.
Truy cập dữ liệu thô từ micrô
Để truy cập vào dữ liệu thô từ micrô, chúng ta phải lấy luồng do getUserMedia()
tạo, sau đó sử dụng API Âm thanh trên web để xử lý dữ liệu. API Âm thanh trên web là một API đơn giản lấy nguồn đầu vào và kết nối các nguồn đó với các nút có thể xử lý dữ liệu âm thanh (điều chỉnh Gain, v.v.) và cuối cùng là với loa để người dùng có thể nghe thấy.
Một trong các nút mà bạn có thể kết nối là AudioWorkletNode
. Nút này cung cấp cho bạn khả năng xử lý âm thanh tuỳ chỉnh ở cấp thấp. Quá trình xử lý âm thanh thực tế diễn ra trong phương thức gọi lại process()
trong AudioWorkletProcessor
.
Gọi hàm này để cung cấp dữ liệu đầu vào và tham số cũng như tìm nạp dữ liệu đầu ra.
Hãy xem phần Enter Audio Worklet (Sử dụng công cụ Audio Worklet) để tìm hiểu thêm.
<script>
const handleSuccess = async function(stream) {
const context = new AudioContext();
const source = context.createMediaStreamSource(stream);
await context.audioWorklet.addModule("processor.js");
const worklet = new AudioWorkletNode(context, "worklet-processor");
source.connect(worklet);
worklet.connect(context.destination);
};
navigator.mediaDevices.getUserMedia({ audio: true, video: false })
.then(handleSuccess);
</script>
// processor.js
class WorkletProcessor extends AudioWorkletProcessor {
process(inputs, outputs, parameters) {
// Do something with the data, e.g. convert it to WAV
console.log(inputs);
return true;
}
}
registerProcessor("worklet-processor", WorkletProcessor);
Dữ liệu được lưu giữ trong vùng đệm là dữ liệu thô từ micrô và bạn có một số lựa chọn về những việc có thể làm với dữ liệu đó:
- Tải trực tiếp lên máy chủ
- Lưu trữ cục bộ
- Chuyển đổi tệp đó sang định dạng tệp chuyên dụng, chẳng hạn như WAV, sau đó lưu vào máy chủ hoặc trên máy
Lưu dữ liệu từ micrô
Cách dễ nhất để lưu dữ liệu từ micrô 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 trên luồng vào đích đến mà bạn muốn.
<a id="download">Download</a>
<button id="stop">Stop</button>
<script>
const downloadLink = document.getElementById('download');
const stopButton = document.getElementById('stop');
const handleSuccess = function(stream) {
const options = {mimeType: 'audio/webm'};
const recordedChunks = [];
const mediaRecorder = new MediaRecorder(stream, options);
mediaRecorder.addEventListener('dataavailable', function(e) {
if (e.data.size > 0) recordedChunks.push(e.data);
});
mediaRecorder.addEventListener('stop', function() {
downloadLink.href = URL.createObjectURL(new Blob(recordedChunks));
downloadLink.download = 'acetest.wav';
});
stopButton.addEventListener('click', function() {
mediaRecorder.stop();
});
mediaRecorder.start();
};
navigator.mediaDevices.getUserMedia({ audio: true, video: false })
.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 chúng ta có thể chuyển thành Blob
. Sau đó, chúng ta có thể dùng Blob
này để lưu dữ liệu vào Máy chủ web hoặc trực tiếp vào bộ nhớ trên thiết bị của người dùng.
Yêu cầu quyền sử dụng micrô 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 micrô, 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 micrô.
Người dùng không thích được nhắc cấp quyền truy cập vào các thiết bị mạnh trên máy của họ và họ thường 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à chỉ yêu cầu quyền truy cập vào micrô khi cần thiết lần đầu tiên. 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ể yêu cầu người dùng cấp quyền lại.
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 cung cấp cho bạn thông tin về việc bạn đã có quyền truy cập vào micrô hay chưa. Điều này gây ra vấn đề cho bạn, để cung cấp một giao diện người dùng đẹp mắt giúp người dùng cấp cho bạn quyền truy cập vào micrô, bạn phải yêu cầu quyền truy cập vào micrô.
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 phải nhắc lại.
Để truy vấn xem bạn có quyền truy cập vào micrô của người dùng hay không, bạn có thể truyền {name: 'microphone'}
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 micrô trước đó;prompt
– người dùng chưa cấp cho bạn quyền truy cập 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 micrô một cách rõ ràng và bạn sẽ không thể truy cập vào micrô đó.
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 hành động mà người dùng cần thực hiện hay không.
navigator.permissions.query({name: 'microphone'}).then(function (result) {
if (result.state == 'granted') {
} else if (result.state == 'prompt') {
} else if (result.state == 'denied') {
}
result.onchange = function () {};
});