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

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

להתחיל פשוט ובהדרגה

הדרך הקלה ביותר לעשות היא לבקש מהמשתמש קובץ מוקלט מראש. לשם כך, אפשר ליצור רכיב פשוט של קלט קובץ ולהוסיף מסנן 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, לצרף אותו ל-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. כדאי לקרוא לפונקציה הזו כדי להזין פרמטרים וקלט של פיד ולאחזר פלטים.

למידע נוסף, ראו Enter Audio Worklet.

<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, ואז לשמור אותו בשרתים או באופן מקומי.

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

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

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

משוב