Dalam beberapa tahun terakhir, saya telah membantu beberapa perusahaan berbeda mencapai fungsi seperti berbagi layar hanya menggunakan teknologi browser. Berdasarkan pengalaman saya, menerapkan VNC hanya di teknologi platform web (yaitu tanpa plugin) adalah masalah yang sulit. Ada banyak hal yang perlu dipertimbangkan dan banyak tantangan yang harus diatasi. Menyampaikan posisi kursor mouse, meneruskan penekanan tombol, dan mencapai penggambaran ulang warna 24-bit penuh pada 60 fps hanyalah beberapa masalahnya.
Merekam konten tab
Jika kita menghilangkan kompleksitas berbagi layar tradisional dan berfokus pada berbagi konten tab browser, masalahnya akan sangat disederhanakan menjadi a.) merekam tab yang terlihat dalam statusnya saat ini, dan b.) mengirim "frame" tersebut melalui jaringan. Pada dasarnya, kita memerlukan cara untuk mengambil snapshot DOM dan membagikannya.
Bagian berbagi ini mudah. Websocket sangat mampu mengirim data dalam berbagai format (string, JSON, biner). Bagian pembuatan snapshot adalah masalah yang jauh lebih sulit. Project seperti html2canvas telah menangani screenshot HTML dengan menerapkan kembali mesin rendering browser…di JavaScript! Contoh lainnya adalah Google Masukan, meskipun tidak bersifat open source. Jenis project ini sangat keren, tetapi juga sangat lambat. Anda akan beruntung jika mendapatkan throughput 1 fps, apalagi 60 fps yang didambakan.
Artikel ini membahas beberapa solusi bukti konsep favorit saya untuk "berbagi layar" tab.
Metode 1: Mutation Observer + WebSocket
Salah satu pendekatan untuk mencerminkan tab telah ditunjukkan oleh +Rafael Weinstein pada awal tahun ini. Tekniknya menggunakan Mutation Observer dan WebSocket.
Pada dasarnya, tab yang dibagikan oleh presenter akan memantau perubahan pada halaman dan mengirimkan perbedaan ke penampil menggunakan websocket. Saat pengguna men-scroll atau berinteraksi dengan halaman, observer akan mendeteksi perubahan ini dan melaporkannya kembali ke penampil menggunakan library ringkasan mutasi Rafael. Hal ini membuat performa tetap baik. Seluruh halaman tidak dikirim untuk setiap frame.
Seperti yang ditunjukkan Rafael dalam video, ini hanyalah bukti konsep. Namun, menurut saya ini adalah cara yang bagus untuk menggabungkan fitur platform yang lebih baru seperti Mutation Observer dengan fitur yang lebih lama seperti Websocket.
Metode 2: Blob dari HTMLDocument + WebSocket Biner
Metode berikutnya adalah metode yang baru-baru ini saya sadari. Pendekatan ini mirip dengan pendekatan Mutation Observer, tetapi alih-alih mengirimkan perbedaan ringkasan, pendekatan ini membuat clone Blob dari seluruh HTMLDocument dan mengirimkannya melalui websocket biner. Berikut langkah-langkah penyiapannya:
- Tulis ulang semua URL di halaman agar menjadi absolut. Tindakan ini mencegah aset gambar statis dan CSS berisi link rusak.
- Meng-clone elemen dokumen halaman:
document.documentElement.cloneNode(true); - Buat clone menjadi hanya baca, tidak dapat dipilih, dan cegah scrolling menggunakan CSS
pointer-events: 'none';user-select:'none';overflow:hidden; - Ambil posisi scroll halaman saat ini dan tambahkan sebagai atribut
data-*pada duplikat. - Buat
new Blob()dari.outerHTMLduplikat.
Kodenya terlihat seperti ini (saya telah menyederhanakan dari sumber lengkap):
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 origin yang berbeda).
Penyesuaian terakhir yang saya lakukan adalah menambahkan dukungan scroll. Saat presenter men-scroll halaman, audiens akan 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 scroll akan memberikan kesan bahwa kita telah mengambil screenshot sebagian halaman asli, padahal sebenarnya kita telah menduplikasi seluruh halaman dan hanya mengubah posisinya. #clever
Demo
Namun, untuk berbagi tab, kita harus terus merekam tab dan mengirimkannya ke penonton. Untuk itu, kami telah menulis server, aplikasi, dan bookmarklet websocket Node kecil yang menunjukkan alur tersebut. Jika Anda tidak tertarik dengan kode, berikut video singkat tentang cara kerjanya:
Peningkatan mendatang
Salah satu pengoptimalan adalah tidak menduplikasi seluruh dokumen di setiap frame. Hal ini tidak efisien dan merupakan sesuatu yang dapat dilakukan dengan baik oleh contoh Mutation Observer. Peningkatan lainnya adalah
menangani gambar latar CSS relatif di urlsToAbsolute(). Hal itu tidak dipertimbangkan oleh skrip saat ini.
Metode 3: API ekstensi Chrome + WebSocket Biner
Di Google I/O 2012, saya mendemonstrasikan pendekatan lain untuk berbagi layar konten tab browser. Namun, yang satu ini adalah cheat. Fitur ini memerlukan Chrome Extension API, bukan keajaiban HTML5 murni.
Sumber untuk yang satu ini juga ada di GitHub, tetapi intinya adalah:
- Mengambil tab saat ini sebagai dataURL .png. Ekstensi Chrome memiliki API untuk itu
chrome.tabs.captureVisibleTab(). - Konversikan dataURL menjadi
Blob. Lihat helperconvertDataURIToBlob(). - Kirim setiap Blob (frame) ke penampil menggunakan websocket biner dengan menetapkan
socket.responseType='blob'.
Contoh
Berikut 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
Framerate-nya lumayan bagus untuk game ini, tetapi bisa lebih baik lagi. Salah satu peningkatan adalah menghapus overhead konversi dataURL menjadi Blob. Sayangnya, chrome.tabs.captureVisibleTab() hanya memberi kita dataURL. Jika menampilkan Blob atau Typed Array, kita dapat mengirimkannya langsung melalui websocket, bukan melakukan konversi ke Blob sendiri. Beri bintang pada crbug.com/32498 untuk mewujudkannya.
Metode 4: WebRTC - Masa depan yang sesungguhnya
Terakhir, tetapi tidak kalah penting.
Masa depan berbagi layar di browser akan diwujudkan oleh WebRTC. Pada 14 Agustus 2012, tim mengusulkan API WebRTC Tab Content Capture untuk membagikan konten tab:
Sebelum fitur ini siap, kita hanya bisa menggunakan metode 1-3.
Kesimpulan
Jadi, berbagi tab browser dapat dilakukan dengan teknologi web saat ini.
Namun…pernyataan itu tidak boleh ditelan mentah-mentah. Meskipun rapi, teknik dalam artikel ini tidak memberikan UX berbagi yang bagus dalam satu atau lain hal. Semua itu akan berubah dengan upaya WebRTC Tab Content Capture, tetapi hingga hal itu menjadi kenyataan, kita harus menggunakan plugin browser atau solusi terbatas seperti yang dibahas di sini.
Punya teknik lain? Posting komentar.