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

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

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

أسهل شيء يمكنك القيام به هو ببساطة أن تطلب من المستخدم ملفًا مسجلًا مسبقًا. يمكنك إجراء ذلك من خلال إنشاء عنصر إدخال بسيط للملف وإضافة فلتر 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 وتطبيق الفلاتر عليها

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

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

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

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

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

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

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

وأحد العُقد التي يمكنك ربطها هو AudioWorkletNode. تمنحك هذه العقدة إمكانية منخفضة المستوى لمعالجة الصوت المخصص. تتم معالجة الصوت الفعلي من خلال طريقة معاودة الاتصال بـ 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، وحفظه في الخوادم أو على الجهاز

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

أسهل طريقة لحفظ البيانات من الميكروفون هي استخدام 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.

يكره المستخدمون أن يُطلب منهم الوصول إلى أجهزة قوية على أجهزتهم، ويحظرون الطلب في كثير من الأحيان، أو سيتجاهلونه إذا لم يفهموا السياق الذي تم إنشاء الطلب بشأنه. من أفضل الممارسات ألا تطلب الوصول إلى الميكروفون إلا عند الحاجة لأول مرة. بمجرد أن يمنح المستخدم إذن الوصول، لن يُطلب منه مرة أخرى. ومع ذلك، إذا رفض الوصول، لا يمكنك طلب الإذن من المستخدم مرة أخرى.

استخدام واجهة برمجة التطبيقات للأذونات للتحقّق مما إذا كان لديك إذن الوصول

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

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

للاستعلام عما إذا كان بإمكانك الوصول إلى ميكروفون المستخدم، يمكنك إدخال {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 () {};
});

إضافة ملاحظات