تسجيل الصوت من المستخدم

يمكن الآن للعديد من المتصفّحات الوصول إلى إدخال الفيديو والصوت من العميل. ومع ذلك، استنادًا إلى المتصفّح، قد تكون تجربة كاملة ديناميكية ومضمّنة ، أو قد يتم تفويضها إلى تطبيق آخر على جهاز المستخدم.

البدء بخطوات بسيطة وبشكل تدريجي

إنّ أسهل طريقة لحلّ هذه المشكلة هي أن تطلب من المستخدم تقديم ملف مسجّل مسبقًا. يمكنك إجراء ذلك من خلال إنشاء عنصر إدخالملف بسيط وإضافة فلتر accept يشير إلى أنّه يمكننا قبول ملفات صوتية فقط، وإضافة سمة capture تشير إلى أنّنا نريد الحصول على الصوت مباشرةً من الميكروفون.

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

تعمل هذه الطريقة على جميع المنصات. على الكمبيوتر المكتبي، سيُطلب من المستخدم upload a file from the file system (ignoring the capture attribute). في 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 وتطبيق الفلاتر عليها

على الرغم من أنّ استخدام طريقة عنصر الإدخال للوصول إلى البيانات الصوتية هو شائع، إلا أنّه الخيار الأقل جاذبية. نريد الوصول إلى الميكروفون وتقديم تجربة رائعة مباشرةً في الصفحة.

الوصول إلى الميكروفون بشكل تفاعلي

يمكن للمتصفّحات الحديثة الوصول مباشرةً إلى الميكروفون، ما يتيح لنا إنشاء تجربتَين مدمجتَين بالكامل مع صفحة الويب ولن يضطر المستخدم أبدًا إلى مغادرة المتصفّح.

الحصول على إذن الوصول إلى الميكروفون

يمكننا الوصول إلى الميكروفون مباشرةً باستخدام واجهة برمجة تطبيقات في مواصفة WebRTC المُسماة getUserMedia(). سيطلب تطبيق getUserMedia() من المستخدم منح الإذن بالوصول إلى كاميراته وميكروفوناته المتصلة.

في حال نجاح العملية، ستعرض واجهة برمجة التطبيقات Stream سيحتوي على البيانات من الكاميرا أو الميكروفون، ويمكننا بعد ذلك إرفاقه بعنصر <audio> أو إرفاقه ببث WebRTC أو إرفاقه بعنصر AudioContext في Web Audio أو حفظه باستخدام واجهة برمجة التطبيقات MediaRecorder.

للحصول على بيانات من الميكروفون، ما عليك سوى ضبط audio: true في قيد العنصر الذي يتم تمريره إلى واجهة برمجة التطبيقات 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 هي واجهة برمجة تطبيقات بسيطة تأخذ مصادر الإدخال وتربط تلك المصادر بالعقد التي يمكنها معالجة بيانات الصوت (ضبط الكسب وما إلى ذلك) وأخيرًا بمكبّر صوت حتى يتمكّن المستخدم من سماعه.

من بين العقد التي يمكنك ربطها، هناك AudioWorkletNode. تمنحك هذه العقدة إمكانية منخفضة المستوى لمعالجة الصوت المخصّصة. تحدث معالجة المقطع الصوتي الفعلية في طريقة طلب معاودة الاتصال process() في AudioWorkletProcessor. استخدِم هذه الدالة لإدخال المدخلات والمَعلمات واسترداد النتائج.

اطّلِع على استخدام 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، ثم احفظه على الخوادم أو على جهازك

حفظ البيانات من الميكروفون

إنّ أسهل طريقة لحفظ البيانات من الميكروفون هي استخدام واجهة برمجة التطبيقات MediaRecorder.

ستستخدِم واجهة برمجة التطبيقات 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 للتحقّق مما إذا كان لديك إذن الوصول

لا تُعلمك واجهة برمجة التطبيقات getUserMedia API ما إذا كان لديك إذن بالوصول إلى الميكروفون. يتسبب ذلك في مشكلة، إذ عليك طلب الوصول إلى الميكروفون لتوفير واجهة مستخدم رائعة لحث المستخدم على منحك إذن الوصول إلى الميكروفون.

يمكن حلّ هذه المشكلة في بعض المتصفّحات باستخدام واجهة برمجة التطبيقات Permission API. تتيح لك واجهة برمجة التطبيقات navigator.permission طلب معلومات عن حالة إمكانية الوصول إلى واجهات برمجة تطبيقات معيّنة بدون الحاجة إلى طلب الإذن مرة أخرى.

لمعرفة ما إذا كان بإمكانك الوصول إلى ميكروفون المستخدم، يمكنك إدخال ‎ {name: 'microphone'} في طريقة طلب البحث، وسيؤدي ذلك إلى عرض أيّ مما يلي:

  • granted - إذا منحك المستخدم الإذن بالوصول إلى الميكروفون في السابق
  • prompt - لم يمنحك المستخدم إذن الوصول، وسيتم سؤاله عند الاتصال بالرقم getUserMedia.
  • denied - حظر النظام أو المستخدم الوصول إلى الmegleophone صراحةً، ولن تتمكّن من الوصول إليه.

ويمكنك الآن التحقّق سريعًا لمعرفة ما إذا كنت بحاجة إلى تغيير واجهة المستخدِم لتناسب الإجراءات التي يحتاج المستخدِم إلى اتّخاذها.

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

ملاحظات