Berbagi layar tab browser di HTML5?

Dalam beberapa tahun terakhir, saya telah membantu beberapa perusahaan mendapatkan fungsi seperti berbagi layar hanya dengan menggunakan teknologi browser. Berdasarkan pengalaman saya, menerapkan VNC hanya dalam teknologi platform web (yaitu tanpa plugin) adalah masalah yang sulit. Ada banyak hal yang perlu dipertimbangkan dan banyak tantangan yang harus diatasi. Menyampaikan posisi pointer mouse, meneruskan penekanan tombol, dan mencapai pengecatan ulang warna 24-bit penuh pada 60 fps hanyalah beberapa masalahnya.

Mengambil gambar konten tab

Jika kami menghilangkan kerumitan berbagi layar tradisional dan berfokus pada berbagi konten tab browser, masalahnya sangat menyederhanakan pada a.) menangkap tab yang terlihat dalam kondisinya saat ini, dan b.) mengirimkan "bingkai" tersebut melalui kabel. Pada dasarnya, kita memerlukan cara untuk mengambil snapshot DOM dan membagikannya.

Berbagi dapat dilakukan dengan mudah. Websocket sangat mampu mengirim data dalam format yang berbeda (string, JSON, biner). Bagian snapshot adalah masalah yang jauh lebih sulit. Project seperti html2canvas telah menangani HTML screenshot dengan mengimplementasikan kembali mesin rendering browser...di JavaScript! Contoh lainnya adalah Google Masukan, meskipun tidak bersifat open source. Jenis proyek ini sangat keren, tetapi juga sangat lambat. Anda akan beruntung mendapatkan throughput 1 fps, jauh lebih sedikit daripada 60 fps.

Artikel ini membahas beberapa solusi bukti konsep favorit saya untuk tab "berbagi layar".

Metode 1: Pengamat Mutasi + WebSocket

Salah satu pendekatan untuk mencerminkan tab ditunjukkan oleh +Rafael Weinstein awal tahun ini. Tekniknya menggunakan Mutation Observers dan WebSocket.

Pada dasarnya, tab yang digunakan presenter untuk memantau perubahan halaman dan mengirimkan diff kepada penonton menggunakan websocket. Saat pengguna men-scroll atau berinteraksi dengan halaman, observer mengambil perubahan tersebut dan melaporkannya kembali ke pelihat menggunakan library ringkasan mutasi Rafael. Hal ini membuat semuanya tetap berperforma baik. Seluruh halaman tidak dikirim untuk setiap frame.

Seperti yang ditunjukkan Rafael dalam video, ini hanyalah bukti konsep. Namun, menurut saya ini cara yang mudah untuk menggabungkan fitur platform yang lebih baru seperti Mutation Observers dengan yang lebih lama seperti Websockets.

Metode 2: Blob dari HTMLDocument + Binary WebSocket

Ini adalah metode yang baru saja saya sadari. Ini mirip dengan pendekatan Mutation Observers, tetapi bukannya mengirim perbedaan ringkasan, clone Blob dari seluruh HTMLDocument dan mengirimkannya melalui websocket biner. Berikut penyiapan berdasarkan penyiapan:

  1. Tulis ulang semua URL di halaman menjadi absolut. Hal ini mencegah aset gambar statis dan CSS berisi link rusak.
  2. Clone elemen dokumen halaman: document.documentElement.cloneNode(true);
  3. Membuat clone hanya dapat dibaca, tidak dapat dipilih, dan mencegah scroll menggunakan CSS pointer-events: 'none';user-select:'none';overflow:hidden;
  4. Ambil posisi scroll halaman saat ini dan tambahkan sebagai atribut data-* pada duplikat.
  5. Buat new Blob() dari .outerHTML duplikat.

Kodenya terlihat seperti ini (saya telah menyederhanakan dari sumber lengkapnya):

function screenshotPage() {
    // 1. Rewrite current doc's imgs, css, and script URLs to be absolute before
    // we duplicate. This ensures no broken links when viewing the duplicate.
    urlsToAbsolute(document.images);
    urlsToAbsolute(document.querySelectorAll("link[rel='stylesheet']"));
    urlsToAbsolute(document.scripts);

    // 2. Duplicate entire document tree.
    var screenshot = document.documentElement.cloneNode(true);

    // 3. Screenshot should be readyonly, no scrolling, and no selections.
    screenshot.style.pointerEvents = 'none';
    screenshot.style.overflow = 'hidden';
    screenshot.style.userSelect = 'none'; // Note: need vendor prefixes

    // 4. … read on …

    // 5. Create a new .html file from the cloned content.
    var blob = new Blob([screenshot.outerHTML], {type: 'text/html'});

    // Open a popup to new file by creating a blob URL.
    window.open(window.URL.createObjectURL(blob));
}

urlsToAbsolute() berisi ekspresi reguler sederhana untuk menulis ulang URL relatif/tanpa skema menjadi URL absolut. Hal ini diperlukan agar gambar, CSS, font, dan skrip tidak rusak saat dilihat dalam konteks URL blob (misalnya dari asal yang berbeda).

Satu penyesuaian terakhir yang saya buat adalah menambahkan dukungan scroll. Saat presenter men-scroll halaman, penonton seharusnya mengikuti. Untuk melakukannya, saya menyimpan posisi scrollX dan scrollY saat ini sebagai atribut data-* pada HTMLDocument duplikat. Sebelum Blob akhir dibuat, sedikit JS dimasukkan yang diaktifkan saat pemuatan halaman:

// 4. Preserve current x,y scroll position of this page. See addOnPageLoad().
screenshot.dataset.scrollX = window.scrollX;
screenshot.dataset.scrollY = window.scrollY;

// 4.5. When screenshot loads (e.g. in blob URL), scroll it to the same location
// of this page. Do this by appending a window.onDOMContentLoaded listener
// which pulls out the screenshot (dupe's) saved scrollX/Y state on the DOM.
var script = document.createElement('script');
script.textContent = '(' + addOnPageLoad_.toString() + ')();'; // self calling.
screenshot.querySelector('body').appendChild(script);

// NOTE: Not to be invoked directly. When the screenshot loads, scroll it
// to the same x,y location of original page.
function addOnPageLoad() {
    window.addEventListener('DOMContentLoaded', function(e) {
    var scrollX = document.documentElement.dataset.scrollX || 0;
    var scrollY = document.documentElement.dataset.scrollY || 0;
    window.scrollTo(scrollX, scrollY);
    });

Memalsukan scrolling memberi kesan bahwa kami telah mengambil screenshot sebagian dari halaman asli, padahal kami telah menduplikasi semuanya dan hanya mengubah posisinya. #clever

Demo

Namun, untuk berbagi tab, kita perlu terus merekam tab tersebut dan mengirimkannya kepada penonton. Untuk itu, saya telah menulis server websocket Node kecil, aplikasi, dan bookmarklet yang menunjukkan alurnya. Jika Anda tidak tertarik dengan kode, berikut video singkat tentang cara kerjanya:

Peningkatan Mendatang

Satu pengoptimalan adalah tidak menduplikasi seluruh dokumen di setiap frame. Itu sia-sia dan contoh Mutation Observer dapat melakukannya dengan baik. Peningkatan lainnya adalah menangani gambar latar CSS relatif di urlsToAbsolute(). Hal itu tidak dipertimbangkan oleh skrip saat ini.

Metode 3: Chrome Extension API + Binary WebSocket

Di Google I/O 2012, saya mendemonstrasikan pendekatan lain untuk berbagi layar konten tab browser. Namun, yang ini adalah cheat. Diperlukan API Ekstensi Chrome: bukan keajaiban HTML5 murni.

Sumber untuk sumber ini juga ada di GitHub, tetapi intinya adalah:

  1. Ambil tab saat ini dalam format .png dataURL. Ekstensi Chrome memiliki API untuk chrome.tabs.captureVisibleTab() tersebut.
  2. Konversi dataURL menjadi Blob. Lihat helper convertDataURIToBlob().
  3. Kirim setiap Blob (frame) ke penonton menggunakan websocket biner dengan menyetel socket.responseType='blob'.

Contoh

Berikut adalah kode untuk mengambil screenshot tab saat ini sebagai png dan mengirim frame melalui websocket:

var IMG_MIMETYPE = 'images/jpeg'; // Update to image/webp when crbug.com/112957 is fixed.
var IMG_QUALITY = 80; // [0-100]
var SEND_INTERVAL = 250; // ms

var ws = new WebSocket('ws://…', 'dumby-protocol');
ws.binaryType = 'blob';

function captureAndSendTab() {
    var opts = {format: IMG_MIMETYPE, quality: IMG_QUALITY};
    chrome.tabs.captureVisibleTab(null, opts, function(dataUrl) {
    // captureVisibleTab returns a dataURL. Decode it -> convert to blob -> send.
    ws.send(convertDataURIToBlob(dataUrl, IMG_MIMETYPE));
    });
}

var intervalId = setInterval(function() {
    if (ws.bufferedAmount == 0) {
    captureAndSendTab();
    }
}, SEND_INTERVAL);

Peningkatan Mendatang

Frekuensi gambar sangat bagus untuk yang satu ini, tetapi bisa jadi lebih baik lagi. Salah satu peningkatannya adalah menghapus overhead konversi dataURL menjadi Blob. Sayangnya, chrome.tabs.captureVisibleTab() hanya memberi kita dataURL. Jika Blob atau Typed Array ditampilkan, kami dapat mengirimkannya langsung melalui websocket, bukan melakukan konversi ke Blob sendiri. Bintangi crbug.com/32498 untuk mewujudkannya.

Metode 4: WebRTC - masa depan yang sesungguhnya

Terakhir, namun tidak kalah pentingnya,

Masa depan berbagi layar di browser akan tercapai oleh WebRTC. Pada 14 Agustus 2012, tim mengusulkan WebRTC Tab Content Capture API untuk berbagi konten tab:

Sampai orang ini siap, kita tinggal dengan metode 1-3.

Kesimpulan

Jadi berbagi tab browser dimungkinkan dengan teknologi web saat ini!

Tapi...pernyataan itu harus direspons dengan cepat. Meskipun rapi, teknik-teknik dalam artikel ini kurang lengkap untuk berbagi UX yang hebat. Semua itu akan berubah dengan upaya Pengambilan Konten Tab WebRTC, tetapi sampai menjadi kenyataan, kami diberikan plugin browser atau solusi terbatas seperti yang dibahas di sini.

Ada teknik lain? Posting komentar!