جارٍ تسجيل فيديو من المستخدم

Mat Scales

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

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

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

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

تعمل هذه الطريقة على جميع المنصات. على أجهزة الكمبيوتر المكتبي، سيُطلب من المستخدم تحميل ملف من نظام الملفات (مع تجاهل السمة capture). في Safari على نظام iOS، سيفتح تطبيق الكاميرا، مما يسمح لك بتسجيل الفيديو ثم إرساله مرة أخرى إلى صفحة الويب؛ على Android، سيمنح المستخدم خيار التطبيق الذي سيستخدمه لتسجيل الفيديو قبل إرساله مرة أخرى إلى صفحة الويب.

تحتوي العديد من الأجهزة الجوّالة على أكثر من كاميرا واحدة. إذا كان لديك تفضيل، يمكنك ضبط السمة capture على user إذا كنت تريد الكاميرا التي تواجه المستخدم، أو environment إذا كنت تريد الكاميرا التي تواجه الخارج.

<input type="file" accept="video/*" capture="user" />
<input type="file" accept="video/*" capture="environment" />

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

بعد أن ينتهي المستخدم من التسجيل ويعود إلى الموقع الإلكتروني، عليك الحصول على بيانات الملف بطريقة ما. يمكنك الوصول بسرعة من خلال إرفاق حدث onchange بعنصر الإدخال ثم قراءة السمة files لكائن الحدث.

<input type="file" accept="video/*" capture="camera" id="recorder" />
<video id="player" controls></video>
<script>
  var recorder = document.getElementById('recorder');
  var player = document.getElementById('player');

  recorder.addEventListener('change', function (e) {
    var file = e.target.files[0];
    // Do something with the video file.
    player.src = URL.createObjectURL(file);
  });
</script>

بعد الوصول إلى الملف، يمكنك إجراء أي تعديل عليه. على سبيل المثال، يمكنك:

  • أرفِقها مباشرةً بعنصر <video> لتتمكّن من تشغيلها
  • تنزيلها على جهاز المستخدم
  • تحميله إلى خادم من خلال إرفاقه بـ XMLHttpRequest
  • رسم الإطارات في لوحة وتطبيق الفلاتر عليها

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

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

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

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

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

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

للحصول على البيانات من الكاميرا، نضبط video: true في عنصر القيود الذي يتم تمريره إلى واجهة برمجة التطبيقات getUserMedia()

<video id="player" controls></video>
<script>
  var player = document.getElementById('player');

  var handleSuccess = function (stream) {
    player.srcObject = stream;
  };

  navigator.mediaDevices
    .getUserMedia({audio: true, video: true})
    .then(handleSuccess);
</script>

إذا أردت اختيار كاميرا معيّنة، يمكنك أولاً تعداد الكاميرات المتاحة.

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

يمكنك بعد ذلك تمرير deviceId الذي تريد استخدامه عند طلب getUserMedia.

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

هذا ليس مفيدًا في حد ذاته. كل ما يمكننا فعله هو أخذ بيانات الفيديو وتشغيلها.

الوصول إلى البيانات الأولية من الكاميرا

للوصول إلى بيانات الفيديو الأولية من الكاميرا، يمكنك رسم كل إطار في <canvas> ومعالجة وحدات البكسل مباشرةً.

بالنسبة إلى لوحة الرسم الثنائية الأبعاد، يمكنك استخدام طريقة drawImage للسياق من أجل رسم الإطار الحالي لعنصر <video> في لوحة الرسم.

context.drawImage(myVideoElement, 0, 0);

باستخدام لوحة WebGL، يمكنك استخدام عنصر <video> كمصدر للزخرفة.

gl.texImage2D(
  gl.TEXTURE_2D,
  0,
  gl.RGBA,
  gl.RGBA,
  gl.UNSIGNED_BYTE,
  myVideoElement,
);

يُرجى العِلم أنّه في كلتا الحالتَين، سيتم استخدام الإطار الحالي من فيديو قيد التشغيل. لمعالجة إطارات متعددة، عليك إعادة رسم الفيديو على لوحة العرض في كل مرة.

يمكنك الاطّلاع على مزيد من المعلومات حول هذا الموضوع في مقالتنا حول تطبيق التأثيرات في الوقت الفعلي على الصور والفيديو.

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

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

ستأخذ واجهة برمجة التطبيقات MediaRecorder مصدر البيانات الذي تم إنشاؤه بواسطة getUserMedia ثم تحفظ البيانات تدريجيًا من مصدر البيانات إلى وجهتك المفضلة.

<a id="download">Download</a>
<button id="stop">Stop</button>
<script>
  let shouldStop = false;
  let stopped = false;
  const downloadLink = document.getElementById('download');
  const stopButton = document.getElementById('stop');

  stopButton.addEventListener('click', function() {
    shouldStop = true;
  })

  var handleSuccess = function(stream) {
    const options = {mimeType: 'video/webm'};
    const recordedChunks = [];
    const mediaRecorder = new MediaRecorder(stream, options);

    mediaRecorder.addEventListener('dataavailable', function(e) {
      if (e.data.size > 0) {
        recordedChunks.push(e.data);
      }

      if(shouldStop === true && stopped === false) {
        mediaRecorder.stop();
        stopped = true;
      }
    });

    mediaRecorder.addEventListener('stop', function() {
      downloadLink.href = URL.createObjectURL(new Blob(recordedChunks));
      downloadLink.download = 'acetest.webm';
    });

    mediaRecorder.start();
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: true })
      .then(handleSuccess);
</script>

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

طلب الإذن لاستخدام الكاميرا بشكل مسؤول

إذا لم يسبق للمستخدم منح موقعك الإلكتروني الإذن بالوصول إلى الكاميرا، سيطلب المتصفّح من المستخدم منح موقعك الإلكتروني الإذن بالوصول إلى الكاميرا فور استدعاء getUserMedia.

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

استخدِم واجهة برمجة التطبيقات Permissions API للتحقّق مما إذا كان لديك إذن بالوصول إلى الميزة

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

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

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

  • ‫granted: سبق أن منحك المستخدم إذن الوصول إلى الكاميرا
  • ‫prompt: لم يمنحك المستخدم الإذن بالوصول وسيُطلب منه ذلك عند طلب getUserMedia
  • ‫denied: حظر النظام أو المستخدم الوصول إلى الكاميرا بشكل صريح، ولن تتمكّن من الوصول إليها.

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

navigator.permissions.query({name: 'camera'}).then(function (result) {
  if (result.state == 'granted') {
  } else if (result.state == 'prompt') {
  } else if (result.state == 'denied') {
  }
  result.onchange = function () {};
});

الملاحظات