Dalam beberapa tahun terakhir, saya telah membantu beberapa perusahaan berbeda untuk mendapatkan fungsi seperti berbagi layar hanya dengan menggunakan teknologi browser. Dari 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. Meneruskan posisi pointer mouse, meneruskan tombol, dan mencapai proses repaint warna 24-bit penuh pada 60 fps hanyalah beberapa masalah.
Merekam konten tab
Jika kita menghilangkan kompleksitas berbagi layar tradisional dan berfokus pada berbagi konten tab browser, masalahnya akan sangat disederhanakan menjadi a.) mengambil 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 sangatlah 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 mengatasi pengambilan screenshot HTML dengan menerapkan ulang mesin rendering browser…dalam JavaScript. Contoh lainnya adalah Google Feedback, meskipun bukan 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 Observers + WebSocket
Salah satu pendekatan untuk mencerminkan tab ditunjukkan oleh +Rafael Weinstein awal tahun ini. Tekniknya menggunakan Mutation Observer dan WebSocket.
Pada dasarnya, tab yang dibagikan presenter akan memantau perubahan pada halaman dan mengirim perbedaan ke penampil menggunakan websocket. Saat pengguna men-scroll atau berinteraksi dengan halaman, observer akan mengambil perubahan ini dan melaporkannya kembali ke penampil menggunakan library ringkasan mutasi Rafael. Hal ini akan menjaga performa. Seluruh halaman tidak dikirim untuk setiap frame.
Seperti yang ditunjukkan Rafael dalam video, ini hanyalah bukti konsep. Namun, saya pikir ini adalah cara yang rapi untuk menggabungkan fitur platform yang lebih baru seperti Mutation Observers dengan fitur yang lebih lama seperti Websocket.
Metode 2: Blob dari HTMLDocument + WebSocket Biner
Metode berikutnya adalah metode yang baru-baru ini saya temukan. Ini mirip dengan pendekatan Mutation Observers, tetapi bukannya mengirim perbedaan ringkasan, pendekatan ini membuat clone Blob dari seluruh HTMLDocument
dan mengirimkannya melalui websocket biner. Berikut adalah penyiapan menurut penyiapan:
- Tulis ulang semua URL di halaman agar bersifat absolut. Hal ini mencegah aset CSS dan gambar statis berisi link yang rusak.
- Membuat clone elemen dokumen halaman:
document.documentElement.cloneNode(true);
- Membuat clone hanya baca, tidak dapat dipilih, dan mencegah scroll 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.outerHTML
duplikat.
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, penonton harus mengikutinya. Untuk melakukannya, saya menyembunyikan posisi scrollX
dan scrollY
saat ini sebagai atribut data-*
pada HTMLDocument
duplikat. Sebelum Blob akhir dibuat, sedikit JS akan 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);
});
Menyembunyikan scroll akan memberikan kesan bahwa kita telah mengambil screenshot sebagian halaman asli, padahal sebenarnya kita telah menduplikasi semuanya dan hanya memosisikan ulang. #clever
Demo
Namun, untuk berbagi tab, kita harus terus mengambil tab dan mengirimkannya kepada penonton. Untuk itu, saya telah menulis server, aplikasi, dan bookmarklet websocket Node kecil yang menunjukkan alur. Jika Anda tidak tertarik dengan kode, berikut video singkat tentang cara kerjanya:
Peningkatan di Masa Mendatang
Salah satu pengoptimalan adalah tidak menduplikasi seluruh dokumen di setiap frame. Hal ini akan membuang-buang waktu dan merupakan hal yang dilakukan dengan baik oleh contoh Mutation Observer. Peningkatan lainnya adalah
untuk menangani gambar latar CSS relatif di urlsToAbsolute()
. Hal ini tidak dipertimbangkan oleh skrip saat ini.
Metode 3: Chrome Extension API + WebSocket Biner
Di Google I/O 2012, saya mendemonstrasikan pendekatan lain untuk berbagi layar konten tab browser. Namun, ini adalah cheat. Ekstensi ini memerlukan Chrome Extension API: bukan keajaiban HTML5 murni.
Sumber untuk contoh ini juga tersedia di GitHub, tetapi intinya adalah:
- Ambil tab saat ini sebagai dataURL .png. Ekstensi Chrome memiliki API untuk
chrome.tabs.captureVisibleTab()
tersebut. - Konversikan dataURL menjadi
Blob
. Lihat helperconvertDataURIToBlob()
. - Kirim setiap Blob (frame) ke penampil menggunakan websocket biner dengan menetapkan
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 di Masa Mendatang
Framerate-nya ternyata bagus untuk game ini, tetapi bisa lebih baik lagi. Salah satu peningkatannya adalah menghapus overhead konversi dataURL menjadi Blob. Sayangnya, chrome.tabs.captureVisibleTab()
hanya memberi kita dataURL. Jika menampilkan Blob atau Array Berjenis, 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 sebenarnya
Terakhir,
Masa depan berbagi layar di browser akan diwujudkan oleh WebRTC. Pada 14 Agustus 2012, tim mengusulkan API WebRTC Tab Content Capture untuk berbagi konten tab:
Sampai metode ini siap, kita hanya memiliki metode 1-3.
Kesimpulan
Jadi, berbagi tab browser dapat dilakukan dengan teknologi web saat ini.
Namun…pernyataan tersebut harus ditanggapi dengan hati-hati. Meskipun rapi, teknik dalam artikel ini kurang memberikan UX berbagi yang baik. Semuanya akan berubah dengan upaya WebRTC Tab Content Capture, tetapi hingga hal itu menjadi kenyataan, kita hanya memiliki plugin browser atau solusi terbatas seperti yang dibahas di sini.
Punya teknik lainnya? Posting komentar.