مقدمة إلى Screen Capture API - مسح رموز QR في المستعرض

مقدمة


في هذه المقالة ، خمننا أننا سنتحدث عن API Screen Capture. وُلدت واجهة برمجة التطبيقات هذه في عام 2014 ، ومن الصعب أن نسميها جديدة ، لكن دعم المتصفح لا يزال ضعيفًا للغاية. ومع ذلك ، يمكن استخدامه للمشاريع الشخصية أو حيث يكون هذا الدعم غير مهم.


بعض الروابط لتبدأ بها:



في حالة تعطل الرابط مع العرض التوضيحي (أو إذا كنت كسولًا جدًا لا يمكنك الذهاب إلى هناك) - فهذه هي الطريقة التي يبدو بها العرض التوضيحي النهائي:



لنبدأ.


حافز


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


تتضمن بعض المنتجات ، مثل 1Password ، حلاً مثيراً للاهتمام لهذا الموقف. إذا كنت بحاجة إلى إعداد حساب من رمز QR ، فإنهم يفتحون نافذة شفافة يمكنك سحبها فوق الصورة باستخدام الرمز ، ويتم التعرف عليه تلقائيًا. إليك ما يبدو عليه:



سيكون مثالياً إذا استطعنا تنفيذ شيء مماثل لتطبيقنا. ولكن ربما لن ينجح ذلك في المتصفح ...


يجتمع - getDisplayMedia


حسنا ، تقريبا. هنا getDisplayMedia API Screen Capture مع طريقة getDisplayMedia الوحيدة. getDisplayMedia getUserMedia ، فقط لشاشة الجهاز ، بدلاً من الكاميرا الخاصة به. لسوء الحظ ، دعم المستعرض ، كما هو مذكور أعلاه ، بعيد عن الانتشار إلى الكاميرا. وفقًا لـ MDN ، يمكن استخدامه في Firefox و Chrome و Edge (على الرغم من وجوده في المكان الخطأ - مباشرة في navigator وليس في navigator.mediaDevices ) + Edge Mobile و ... Opera for Android.


مجموعة رائعة من متصفحات الجوال بجوار Big Two المتوقعة.


API نفسها بسيطة للغاية. إنه يعمل بنفس طريقة getUserMedia ، ولكنه يسمح لك بالتقاط دفق فيديو من أحد أسطح العرض المحددة:


  • من الشاشة (الشاشة بأكملها) ،
  • من نافذة أو جميع نوافذ تطبيق معين ،
  • من مستعرض أو بالأحرى من مستند معين. في Chrome ، هذا المستند هو علامة تبويب منفصلة ، لكن في FF لا يوجد مثل هذا الخيار.

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


نحن نجمع


لذلك ، اكتشفنا القدرات التي يوفرها لنا API. ما التالي؟


ثم نحتاج إلى تجاوز دفق الفيديو هذا إلى صور يمكننا العمل عليها. للقيام بذلك ، نستخدم عناصر <video> و <canvas> وبعض JS.


تبدو الصورة عن قرب من العملية كما يلي:


  • دفق مباشر إلى <video> ؛
  • بتردد معين ، ارسم محتويات <video> في <canvas> ؛
  • جمع كائن ImageData من <canvas> باستخدام أسلوب سياق getImageData 2D.

قد يبدو هذا الإجراء برمته غريبًا بعض الشيء نظرًا لوجود مثل هذا الخط الطويل ، ولكن هذه الطريقة شائعة جدًا وتم استخدامها لالتقاط البيانات من كاميرات الويب في getUserMedia .


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


 async function run() { const video = document.createElement('video'); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); const displayMediaOptions = { video: { cursor: "never" }, audio: false } video.srcObject = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions); const videoTrack = video.srcObject.getVideoTracks()[0]; const { height, width } = videoTrack.getSettings(); context.drawImage(video, 0, 0, width, height); return context.getImageData(0, 0, width, height); } await run(); 

كما ذكر أعلاه: أولاً نقوم بإنشاء العناصر <video> و <canvas> ونطلب من اللوحة القماشية سياقًا ثنائي الأبعاد ( CanvasRenderingContext2D ).


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


بعد ذلك ، نقوم بربط الدفق المستلم من النوع MediaStream بالعنصر <video> . لاحظ أن getDisplayMedia تُرجع getDisplayMedia .


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


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


عندما يتعلق الأمر بـ "التعامل مع شيء ما في DOM في حلقة ثابتة" ، فإن أول ما يتبادر إلى الذهن هو على الأرجح requestAnimationFrame . ومع ذلك ، في حالتنا ، واستخدامه لن ينجح. الشيء هو أنه عندما تتوقف علامة التبويب عن أن تكون نشطة - توقف المتصفحات معالجة حلقة rAF. في حالتنا ، سنود في الوقت الحالي معالجة الصور.


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


أخيرًا ، عندما نصل إلى الإطارات ، يمكننا معالجتها كما يحلو لنا. لأغراض هذا العرض التوضيحي ، سوف نستخدم مكتبة jsQR . الأمر بسيط للغاية: يقبل الإدخال ImageData ، عرض الصورة وارتفاعها. إذا كانت الصورة المستلمة تحتوي على رمز QR ، فستحصل على كائن JS مع استعادة البيانات المعترف بها.
لنكمل مثالنا السابق ببضع سطور من التعليمات البرمجية:


 const imageData = await run(); const code = jsQR(imageData.data, streamWidth, streamHeight); 

القيام به!


الآلية الوقائية الوطنية


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


تسمى المكتبة stream-display : NPM | جيثب .


يتم تقليل استخدامه إلى ثلاثة سطور من التعليمات البرمجية واستدعاء:


 const callback = imageData => {...} // do whatever with those images const capture = new StreamDisplay(callback); // specify where the ImageData will go await capture.startCapture(); // when ready capture.stopCapture(); // when done 

يمكن رؤية التجريبي هنا . هناك أيضًا إصدار CodePen لإجراء تجارب سريعة. يستخدم كلا المثالين حزمة NPM أعلاه.


قليلا عن الاختبار


بتعبئة هذا الرمز في المكتبة ، كان علي أن أفكر في كيفية اختباره. لا أريد مطلقًا سحب 50 ميغابايت من Chrome مقطوعة الرأس لإجراء بعض الاختبارات الصغيرة فيه. وعلى الرغم من أن فكرة كتابة بذرة لجميع المكونات تبدو مؤلمة للغاية ، إلا أنني فعلت ذلك في النهاية.
باعتباره عداء اختبار ، تم اختيار tape . إليكم ما أتيحت لي أخيرًا:


  • كائن document وعناصر DOM. لهذا ، أخذت jsdom .
  • بعض أساليب jsdom التي تفتقر إلى التنفيذ: HTMLMediaElement#play و HTMLCanvasElement#getContext و navigator.mediaDevices#getDisplayMedia ؛
  • الوقت. للقيام بذلك ، استخدمت useFakeTimers مكتبة useFakeTimers ، والتي تحت غطاء محرك السيارة تدعو lolex . يقوم بتعيين setInterval إلى setInterval ، requestAnimationFrame والعديد من الوظائف الأخرى التي تعمل مع الوقت ، ويسمح لك أيضًا بالتحكم في تدفق هذا الوقت المزيف. لكن كن حذرًا: تستخدم jsdom مرور الوقت في مكان واحد من عملية التهيئة ، وإذا قمت بتشغيل sinon أولاً ، فسيتم تجميد كل شيء.

كما أنني استخدمت sinon لجميع بذرات الوظيفة التي يجب مراقبتها. تم تنفيذ الباقي بواسطة وظائف JS فارغة.


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


يمكن رؤية النتيجة النهائية في مستودع المكتبة. لا تبدو جميلة للغاية ، ولكنها تعمل.


استنتاج


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


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

Source: https://habr.com/ru/post/ar460825/


All Articles