Pengantar Screen Capture API - Memindai kode QR di browser

Pendahuluan


Di artikel ini, kami menduga bahwa kami akan berbicara tentang Screen Capture API. API ini lahir pada tahun 2014, dan sulit untuk menyebutnya baru, tetapi dukungan browser masih sangat lemah. Namun demikian, dapat digunakan untuk proyek pribadi atau di mana dukungan ini tidak begitu penting.


Beberapa tautan untuk membantu Anda memulai:



Jika tautan dengan demo jatuh (atau jika Anda terlalu malas untuk pergi ke sana) - ini adalah bagaimana demo yang sudah selesai terlihat:



Mari kita mulai.


Motivasi


Baru-baru ini saya muncul dengan ide aplikasi web yang menggunakan kode QR dalam pekerjaannya. Dan meskipun mereka biasanya nyaman untuk ditransmisikan, misalnya, tautan panjang di dunia nyata tempat Anda dapat mengarahkan telepon kepada mereka, di desktop itu sedikit lebih rumit. Jika kode QR ada di layar perangkat yang sama dengan yang Anda butuhkan untuk membacanya, Anda perlu mengacaukan layanan untuk pengakuan atau mengenalinya dari telepon dan mentransfer data kembali ke PC. Nyaman


Beberapa produk, seperti 1Password , menyertakan solusi menarik untuk situasi ini. Jika Anda perlu mengatur akun dari kode QR, mereka membuka jendela transparan yang dapat Anda seret gambar dengan kode, dan itu dikenali secara otomatis. Begini tampilannya:



Akan ideal jika kita dapat mengimplementasikan sesuatu yang serupa untuk aplikasi kita. Tapi mungkin itu tidak akan berhasil di browser ...


Bertemu - getDisplayMedia


Yah, hampir. Di sini, Screen Capture API dengan satu-satunya metode getDisplayMedia akan getDisplayMedia . getDisplayMedia seperti getUserMedia , hanya untuk layar perangkat, bukan kameranya. Sayangnya, dukungan browser, seperti yang disebutkan di atas, jauh dari luasnya akses ke kamera. Menurut MDN, ini dapat digunakan di Firefox, Chrome, Edge (meskipun ada di tempat yang salah - tepat di navigator , dan bukan di navigator.mediaDevices ) + Edge Mobile dan ... Opera untuk Android.


Pilihan browser seluler yang cukup aneh di sebelah Big Two yang diharapkan.


API itu sendiri sangat sederhana. Ini berfungsi sama dengan getUserMedia , tetapi memungkinkan Anda untuk menangkap aliran video dari salah satu permukaan tampilan yang ditentukan:


  • dari monitor (seluruh layar),
  • dari jendela atau semua jendela aplikasi tertentu,
  • dari browser , atau lebih tepatnya dari dokumen tertentu. Di Chrome, dokumen ini adalah tab terpisah, tetapi di FF tidak ada opsi seperti itu.

API Browser, yang memungkinkan Anda melihat di luar browser ... Kedengarannya biasa dan biasanya menjadi pertanda beberapa masalah, tetapi dalam hal ini bisa sangat nyaman. Anda dapat mengambil gambar dari jendela lain dan, misalnya, mengenali dan menerjemahkan teks secara real time, seperti Google Translate Camera. Yah, dan mungkin ada banyak kegunaan yang lebih menarik.


Kami mengumpulkan


Jadi, kami menemukan kemampuan yang diberikan API kepada kami. Apa selanjutnya


Dan kemudian kita perlu menyalip aliran video ini menjadi gambar yang bisa kita kerjakan. Untuk melakukan ini, kami menggunakan elemen <video> , <canvas> dan beberapa JS lainnya.


Tampilan dekat dari proses ini terlihat seperti ini:


  • Aliran langsung ke <video> ;
  • Dengan frekuensi tertentu, gambarkan konten <video> di <canvas> ;
  • Kumpulkan objek ImageData dari <canvas> menggunakan metode konteks getImageData 2D.

Keseluruhan prosedur ini mungkin terdengar sedikit aneh karena pipa yang begitu panjang, tetapi metode ini cukup populer dan digunakan untuk mengambil data dari webcam di getUserMedia .


Menghilangkan semua yang tidak relevan, untuk memulai aliran dan mengeluarkan bingkai darinya, kita perlu tentang kode berikut:


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

Seperti yang disebutkan di atas: pertama-tama kita membuat elemen <video> dan <canvas> dan meminta kanvas untuk konteks 2D ( CanvasRenderingContext2D ).


Kemudian kami mendefinisikan batasan / kondisi aliran. Tidak seperti stream dari kamera, ada beberapa di antaranya. Kami mengatakan bahwa kami tidak ingin melihat kursor, dan bahwa kami tidak perlu audio. Meskipun pada saat penulisan ini, pengambilan audio masih belum didukung oleh siapa pun.


Setelah itu, kami menghubungkan aliran yang diterima dari tipe MediaStream ke elemen <video> . Perhatikan bahwa getDisplayMedia mengembalikan Janji.


Akhirnya, dari data yang diterima tentang aliran, kita ingat resolusi video untuk menggambarnya dengan benar di kanvas, menggambar bingkai dan menarik keluar objek ImageData dari ImageData .


Untuk penggunaan penuh, Anda kemungkinan besar ingin memproses frame dalam satu lingkaran daripada satu kali. Misalnya, saat Anda menunggu ketika gambar yang diinginkan muncul di bingkai. Dan di sini beberapa kata perlu diucapkan.


Ketika datang ke "menangani sesuatu di DOM dalam loop konstan," hal pertama yang terlintas dalam pikiran kemungkinan besar adalah requestAnimationFrame . Namun, dalam kasus kami, menggunakannya tidak akan berhasil. Masalahnya adalah bahwa ketika tab berhenti menjadi aktif - browser berhenti memproses loop rAF. Dalam kasus kami, saat ini kami ingin memproses gambar.


Dalam hal ini, alih-alih rAF, kami akan menggunakan setInterval lama yang baik. Tetapi hal-hal yang tidak begitu mulus dengannya. Dalam tab tidak aktif, interval antara operasi panggilan balik setidaknya 1 detik . Namun demikian, ini cukup bagi kami.


Akhirnya, ketika kita sampai ke frame, kita dapat memprosesnya sesuka kita. Untuk keperluan demo ini, kami akan menggunakan perpustakaan jsQR . Ini sangat sederhana: input menerima ImageData , lebar dan tinggi gambar. Jika gambar yang diterima memiliki kode QR, Anda akan mendapatkan objek JS dengan data yang dikenali kembali.
Mari kita melengkapi contoh kita sebelumnya dengan hanya beberapa baris kode:


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

Selesai!


NPM


Saya berpikir bahwa kode utama di belakang contoh ini dapat dikemas ke dalam pustaka npm dan menghemat waktu dalam penggunaan awal untuk digunakan nanti. Pustaka sangat sederhana, pada tahap ini ia hanya menerima panggilan balik ke mana ImageData akan dikirim, dan satu parameter tambahan adalah frekuensi pengiriman data. Semua pemrosesan Anda perlu membawa sendiri. Saya akan memikirkan apakah masuk akal untuk memperluas fungsinya.


Perpustakaan disebut stream-display : NPM | Github .


Penggunaannya dikurangi menjadi tiga baris kode dan panggilan balik:


 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 

Demo dapat dilihat di sini . Ada juga versi CodePen untuk eksperimen cepat. Kedua contoh menggunakan paket NPM di atas.


Sedikit tentang pengujian


Mengemas kode ini ke perpustakaan, saya harus memikirkan cara mengujinya. Saya benar-benar tidak ingin menyeret 50MB Chrome tanpa kepala untuk menjalankan beberapa tes kecil di dalamnya. Dan meskipun gagasan menulis bertopik untuk semua komponen tampak terlalu menyakitkan, pada akhirnya saya melakukannya.
Sebagai pelari uji, tape dipilih. Inilah yang akhirnya harus saya simulasikan:


  • objek document dan elemen DOM. Untuk ini, saya mengambil jsdom ;
  • beberapa metode jsdom yang kurang implementasi: HTMLMediaElement#play , HTMLCanvasElement#getContext dan navigator.mediaDevices#getDisplayMedia ;
  • waktu. Untuk melakukan ini, saya menggunakan useFakeTimers perpustakaan useFakeTimers , yang di bawah tenda memanggil lolex . Ini mengatur penggantiannya ke setInterval , requestAnimationFrame dan banyak fungsi lain yang bekerja dengan waktu, dan juga memungkinkan Anda untuk mengontrol aliran waktu palsu ini. Tapi hati-hati: Jsdom menggunakan berlalunya waktu di satu tempat proses inisialisasi, dan jika Anda menyalakan sinon pertama, semuanya akan membeku.

Saya juga menggunakan sinon untuk semua fungsi bertopik yang perlu dipantau. Sisanya diimplementasikan oleh fungsi JS kosong.


Tentu saja, Anda bebas memilih alat yang sudah Anda kenal. Tapi, saya harap daftar ini memungkinkan Anda untuk mempersiapkannya terlebih dahulu, karena sekarang Anda tahu apa yang harus Anda tangani.


Hasil akhirnya dapat dilihat di repositori perpustakaan. Itu tidak terlihat terlalu cantik, tetapi berhasil.


Kesimpulan


Solusinya ternyata tidak seanggun jendela transparan yang disebutkan di awal artikel, tetapi mungkin web akan datang ke suatu hari nanti. Orang hanya bisa berharap bahwa ketika browser belajar melihat melalui windows mereka - kemampuan ini akan dikontrol ketat oleh kami. Sementara itu, ingatlah bahwa ketika Anda meraba-raba layar di Chrome - itu dapat diuraikan, direkam, dll. Jadi jangan mencari-cari lebih dari yang diperlukan!


Saya harap seseorang setelah artikel ini mempelajari trik baru untuk diri mereka sendiri. Jika Anda memiliki ide untuk hal apa lagi ini dapat digunakan, tulis di komentar. Dan sampai ketemu lagi.

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


All Articles