הקלטת אודיו מהמשתמש

בדפדפנים רבים יש עכשיו אפשרות לגשת לקלט וידאו ואודיו מהמשתמש. עם זאת, בהתאם לדפדפן, יכול להיות שמדובר בחוויה דינמית מלאה בתוך הדף, או שהיא יכולה להיות מועברת לאפליקציה אחרת במכשיר של המשתמש.

מתחילים בפשטות ובאופן הדרגתי

הדרך הקלה ביותר היא פשוט לבקש מהמשתמש קובץ שהוקלט מראש. כדי לעשות זאת, יוצרים רכיב פשוט של קלט קובץ ומוסיפים מסנן accept שמציין שאנחנו יכולים לקבל רק קובצי אודיו, ומאפיין capture שמציין שאנחנו רוצים לקבל אותו ישירות מהמיקרופון.

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

השיטה הזו פועלת בכל הפלטפורמות. במחשב, המשתמש יתבקש להעלות קובץ ממערכת הקבצים (בלי להתחשב במאפיין capture). ב-Safari ב-iOS, הבקשה תפתח את אפליקציית המיקרופון, שתאפשר לכם להקליט אודיו ולשלוח אותו בחזרה לדף האינטרנט. ב-Android, הבקשה תאפשר למשתמש לבחור באפליקציה שבה הוא רוצה להקליט את האודיו לפני שהוא יישלח בחזרה לדף האינטרנט.

אחרי שהמשתמש מסיים את הצילום וחוזרים לאתר, צריך לקבל את נתוני הקובץ בדרך כלשהי. כדי לקבל גישה מהירה, אפשר לצרף אירוע onchange לרכיב הקלט ואז לקרוא את המאפיין files של אובייקט האירוע.

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

אחרי שתקבלו גישה לקובץ, תוכלו לעשות איתו מה שתרצו. לדוגמה, תוכלו:

  • לצרף אותו ישירות לרכיב <audio> כדי שתוכלו להפעיל אותו
  • מורידים אותה למכשיר של המשתמש
  • מעלים אותו לשרת על ידי צירוף שלו ל-XMLHttpRequest
  • מעבירים אותו דרך Web Audio API ומחילים עליו מסננים

השימוש בשיטה של רכיב הקלט כדי לקבל גישה לנתוני אודיו נפוץ מאוד, אבל זוהי האפשרות הכי פחות אטרקטיבית. אנחנו רוצים מאוד לקבל גישה למיקרופון ולספק חוויה נעימה ישירות בדף.

גישה למיקרופון באופן אינטראקטיבי

בדפדפנים מודרניים יכול להיות חיבור ישיר למיקרופון, שמאפשר לנו ליצור חוויות שמשתלבות באופן מלא בדף האינטרנט, והמשתמש אף פעם לא יוצא מהדפדפן.

קבלת גישה למיקרופון

אנחנו יכולים לגשת ישירות למיקרופון באמצעות ממשק API במפרט של WebRTC שנקרא getUserMedia(). getUserMedia() יבקש מהמשתמש גישה למיקרופונים ולמצלמות המחוברים.

אם הפעולה תתבצע בהצלחה, ה-API יחזיר Stream שיכיל את הנתונים מהמצלמה או מהמיקרופון, ואז נוכל לצרף אותו לאלמנט <audio>, לצרף אותו לסטרימינג של WebRTC, לצרף אותו ל-Web Audio AudioContext או לשמור אותו באמצעות ה-API של MediaRecorder.

כדי לקבל נתונים מהמיקרופון, אנחנו פשוט מגדירים את audio: true באובייקט האילוצים שמוענק ל-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>

אם רוצים לבחור מיקרופון מסוים, קודם צריך למנות את המיקרופונים הזמינים.

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

לאחר מכן תוכלו להעביר את deviceId שבו אתם רוצים להשתמש כשאתם קוראים ל-getUserMedia.

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

לבד, המידע הזה לא כל כך שימושי. כל מה שאנחנו יכולים לעשות הוא לקחת את נתוני האודיו ולהפעיל אותם.

גישה לנתונים הגולמיים מהמיקרופון

כדי לגשת לנתונים הגולמיים מהמיקרופון, אנחנו צריכים לקחת את הסטרימינג שנוצר על ידי getUserMedia() ואז להשתמש ב-Web Audio API כדי לעבד את הנתונים. Web Audio API הוא ממשק API פשוט שמקבל מקורות קלט ומחבר אותם לצמתים שיכולים לעבד את נתוני האודיו (לכוונן את ההגברה וכו') ובסופו של דבר לרמקולים כדי שהמשתמש יוכל לשמוע אותם.

אחד הצמתים שאפשר לחבר הוא AudioWorkletNode. הצומת הזה מספק יכולת ברמה נמוכה לעיבוד אודיו בהתאמה אישית. עיבוד האודיו בפועל מתבצע בשיטת ה-callback process() ב-AudioWorkletProcessor. קוראים לפונקציה הזו כדי להזין קלט ופרמטרים ולאחזר את הפלט.

מידע נוסף זמין במאמר הוספת רכיב 'הזנת אודיו'.

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

הנתונים שנשמרים במאגרים הם הנתונים הגולמיים מהמיקרופון, ויש לכם כמה אפשרויות לגבי מה שתוכלו לעשות עם הנתונים האלה:

  • להעלות אותו ישירות לשרת
  • לשמור אותם באופן מקומי
  • ממירים אותו לפורמט קובץ ייעודי, כמו WAV, ולאחר מכן שומרים אותו בשרתים או באופן מקומי.

שמירת הנתונים מהמיקרופון

הדרך הקלה ביותר לשמור את הנתונים מהמיקרופ היא להשתמש ב-MediaRecorder API.

ה-API של MediaRecorder יקבל את המקור שנוצר על ידי getUserMedia, ולאחר מכן ישמור בהדרגה את הנתונים שנמצאים במקור ביעד המועדף.

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

במקרה שלנו, אנחנו שומרים את הנתונים ישירות במערך שאפשר להפוך מאוחר יותר ל-Blob, שאפשר להשתמש בו כדי לשמור את הנתונים בשרת האינטרנט שלנו או ישירות באחסון במכשיר של המשתמש.

לבקש הרשאה לשימוש במיקרופון בצורה אחראית

אם המשתמש לא העניק לאתר גישה למיקרופון בעבר, ברגע שתפעילו את getUserMedia, הדפדפן יציג למשתמש בקשה להעניק לאתר הרשאה למיקרופון.

משתמשים לא אוהבים לקבל בקשות גישה למכשירים חזקים במחשב שלהם, ולרוב הם ימנעו את הבקשה או יתעלמו ממנה אם הם לא מבינים את ההקשר שבו היא נוצרה. מומלץ לבקש גישה למיקרופון רק בפעם הראשונה שנדרשת. אחרי שהמשתמש ייתן גישה, לא תתבקשו ממנו שוב. עם זאת, אם הוא ידחה את הגישה, לא תוכלו לבקש ממנו שוב.

איך משתמשים ב-Permissions API כדי לבדוק אם כבר יש לכם גישה

ממשק ה-API של getUserMedia לא מספק מידע על כך שכבר יש לכם גישה למיקרופון. הבעיה היא שצריך לבקש גישה למיקרופון כדי לספק ממשק משתמש נוח שיגרום למשתמש להעניק גישה למיקרופון.

אפשר לפתור את הבעיה הזו בדפדפנים מסוימים באמצעות Permission API. באמצעות API ‏navigator.permission אפשר לשלוח שאילתה לגבי המצב של היכולת לגשת לממשקי API ספציפיים, בלי צורך לבצע שוב את הבקשה.

כדי לבדוק אם יש לכם גישה למיקרופון של המשתמש, אתם יכולים להעביר את הערך {name: 'microphone'} לשיטת השאילתה, והיא תחזיר את אחת מהאפשרויות הבאות:

  • granted – המשתמש העניק לכם גישה למיקרופון בעבר.
  • prompt – המשתמש לא העניק לכם גישה, ותופיע בקשה כשתתקשרו אל getUserMedia.
  • denied – המערכת או המשתמש חסמו באופן מפורש את הגישה למיקרופון, ולא תוכלו לגשת אליו.

עכשיו תוכלו לבדוק במהירות אם צריך לשנות את ממשק המשתמש כדי להתאים אותו לפעולות שהמשתמשים צריכים לבצע.

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

משוב