Ghi âm từ người dùng

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à một 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 tệp âm thanh và thuộc tính capture cho biết chúng ta muốn lấy tệp 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, ứng dụng này sẽ mở ứng dụng micrô, cho phép bạn ghi âm 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 để sử dụng tính năng ghi âm trước khi gửi lại 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 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="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 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ử <audio> để 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 sao lên máy chủ bằng cách đính kèm vào XMLHttpRequest
  • Truyền nội dung qua API Web âm thanh và áp dụng bộ lọc cho nội dung đó

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

Truy cập micrô 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 tới micrô, 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 sử dụng micrô

Chúng ta có thể truy cập trực tiếp vào Micrô bằng cách sử dụng API trong quy cách 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ừ máy ảnh hoặc micrô, sau đó chúng tôi có thể đính kèm nó vào một phần tử <audio>, đính kèm vào luồng WebRTC, đính kèm vào một Web Audio AudioContext hoặc lưu bằng API MediaRecorder.

Để nhận 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,
  },
});

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 âm thanh và phát lại.

Truy cập dữ liệu thô từ micrô

Để truy cập dữ liệu thô từ micrô, chúng ta phải lấy luồng do getUserMedia() tạo, sau đó sử dụng Web Audio API để xử lý dữ liệu. Web Audio API là một API đơn giản có chức năng lấy các 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 Độ lợi ích, v.v.) và cuối cùng là cho loa để người dùng có thể nghe thấy.

Một trong những nút mà bạn có thể kết nối là AudioWorkletNode. Nút này cung cấp cho bạn chức năng ở cấp thấp để xử lý âm thanh tuỳ chỉnh. 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 để cấp dữ liệu các đầu vào và tham số cũng như đầu ra tìm nạp.

Hãy xem bài viết Nhập Worklet âm thanh để 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ố cách để xử lý dữ liệu đó:

  • Tải thẳng lên máy chủ
  • Lưu trữ cục bộ
  • Chuyển đổi sang một định dạng tệp chuyên dụng, chẳng hạn như WAV rồi lưu vào máy chủ của bạn hoặc

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 có trên luồng vào đích đến ưu tiên của bạ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 có thể biến thành Blob, sau đó có thể dùng để 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 quyền truy cập vào micrô cho trang web của bạn.

Người dùng không muốn nhận lời 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ọ. Họ sẽ 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 micrô khi cần thiết lần đầu. Sau khi người dùng cấp quyền truy cập, họ sẽ không nhận được yêu cầu nữa. 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 lại quyề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 bạn biết liệu bạn đã có quyền truy cập vào micrô hay chưa. Như vậy, bạn sẽ gặp phải một vấn đề. Để 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 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 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 micrô của người dùng hay không, bạn có thể chuyển {name: 'microphone'} vào phương thức truy vấn và phương thức này sẽ trả về một trong hai cách sau:

  • granted — người dùng đã từng cấp cho bạn quyền truy cập vào micrô;
  • 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 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 liệu mình 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: 'microphone'}).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