HTML5'te bir tarayıcı sekmesini ekran paylaşımı

Geçtiğimiz birkaç yıl içinde birkaç farklı şirketin yalnızca tarayıcı teknolojilerini kullanarak ekran paylaşımı benzeri işlevlere ulaşmalarına yardımcı oldum. Deneyimlerime göre, VNC'nin yalnızca web platformu teknolojilerinde (ör. eklenti olmadan) uygulanması zor bir sorun. Üzerinde düşünülecek çok şey ve üstesinden gelinmesi gereken pek çok zorluk var. Fare işaretçisi konumunun geçişini yapmak, tuş vuruşlarını yönlendirmek ve 60 fps'de tam olarak 24 bit renkli boyalar yapmak, sorunlardan yalnızca birkaçıdır.

Sekme içeriğini yakalama

Geleneksel ekran paylaşımının karmaşıklığını ortadan kaldırır ve bir tarayıcı sekmesinin içeriğini paylaşmaya odaklanırsak a) görünür sekmeyi mevcut haliyle yakalamayı ve b. o "çerçeveyi" kablonun her tarafına göndermemizi oldukça basit hale getirir. Esas olarak, DOM'nin anlık görüntüsünü alıp paylaşabileceğimiz bir yönteme ihtiyacımız vardır.

Paylaşım kısmı kolaydır. Websockets farklı biçimlerde (dize, JSON, ikili) veri gönderme yeteneğine sahiptir. Anlık görüntü oluşturma kısmı çok daha zor bir sorun. html2canvas gibi projeler, tarayıcının oluşturma motorunu JavaScript ile yeniden uygulayarak HTML ekran görüntüsü yakalamayı ele aldı. Başka bir örnek, açık kaynak olmasa da Google Geri Bildirim Aracı'dır. Bu tür projeler çok havalı olsa da çok yavaşlar. Özlediğiniz 60 fps'den çok daha düşük bir oranda 1 fps işleme hızı elde ederseniz şanslıydınız.

Bu makalede, sekmedeki "ekran paylaşımı" için en sevdiğim kavram kanıtlama çözümlerinden birkaçı anlatılmaktadır.

1. Yöntem: Mutasyon Gözlemcileri + WebSocket

Bir sekmeyi yansıtmaya ilişkin yaklaşımlardan biri, bu yılın başlarında +Rafael Weinstein tarafından gösterilmiştir. Kullandığı teknikte Dönüşüm Gözlemcileri ve bir WebSocket kullanılır.

Temel olarak, sunucunun paylaştığı sekme sayfada yapılan değişiklikleri izler ve bir websocket kullanarak farkları görüntüleyene gönderir. Kullanıcı sayfayı kaydırırken veya sayfada etkileşimde bulunurken, gözlemciler bu değişiklikleri alır ve Rafael'in mutasyon özeti kitaplığını kullanarak izleyiciye geri bildirir. Bu, öğelerin yüksek performanslı kalmasını sağlar. Her kare için sayfanın tamamı gönderilmez.

Rafael'in videoda belirttiği gibi bu, yalnızca bir kavramın kanıtı. Yine de Mutation Observers gibi daha yeni bir platform özelliğini Websockets gibi daha eski bir platformla birleştirmenin güzel bir yolu olduğunu düşünüyorum.

2. Yöntem: HTMLDocument + İkili WebSocket'den Blob

Bu yeni yöntem benim de başıma gelen bir yöntem. Bu yöntem Mutasyon Gözlemcileri yaklaşımına benzer, ancak özet farkları göndermek yerine tüm HTMLDocument için bir Blob klonu oluşturur ve bunu bir ikili web soketi üzerinden gönderir. Kurulum aşağıdaki şekilde yapılır:

  1. Sayfadaki tüm URL'leri mutlak olacak şekilde yeniden yazın. Bu, statik resim ve CSS öğelerinin bozuk bağlantılar içermesini önler.
  2. Sayfanın doküman öğesini klonla: document.documentElement.cloneNode(true);
  3. Klonu salt okunur, seçilemez hale getirin ve CSS pointer-events: 'none';user-select:'none';overflow:hidden; kullanarak kaydırmayı önleyin
  4. Sayfanın mevcut kaydırma konumunu yakalayın ve kopyaya data-* özellikleri olarak ekleyin.
  5. Kopya .outerHTML öğesinden bir new Blob() oluşturun.

Bu kod şuna benzer: (Tam kaynaktan basit değişiklikler yaptık):

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(), göreli/şemasız URL'leri mutlak URL'lere yeniden yazmak için basit normal ifadeler içerir. Bir blob URL'si bağlamında (ör. farklı bir kaynaktan) görüntülendiğinde resimlerin, css'nin, yazı tiplerinin ve komut dosyalarının bozulmaması için bu gereklidir.

Yaptığım son ince ayar kaydırma desteği eklemekti. Sunucu sayfayı kaydırdığında, görüntüleyici onu takip etmelidir. Bunu yapmak için, mevcut scrollX ve scrollY konumlarını yinelenen HTMLDocument öğesinde data-* özellikleri olarak saklıyorum. Son Blob oluşturulmadan önce, sayfa yüklendiğinde tetiklenen bir JS eklenir:

// 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);
    });

Kaydırmada sahtecilik yapmak, orijinal sayfanın bir bölümünün ekran görüntüsünü aldığımız izlenimini yaratır. Aslında, tüm metni kopyalayıp yalnızca yeniden konumlandırmışızdır. #clever

Demografi

Ancak sekme paylaşımı için sürekli olarak sekmeyi yakalamamız ve izleyicilere göndermemiz gerekiyor. Bunun için akışı gösteren küçük bir Node websocket sunucusu, uygulaması ve yer işareti uygulaması yazdım. Kod ilginizi çekmiyorsa, işleyişi gösteren kısa bir videoyu aşağıda bulabilirsiniz:

Gelecekteki İyileştirmeler

Optimizasyonlardan biri belgenin tamamını her karede çoğaltmamaktır. Bu savurganlığa yol açıyor ve Mutation Observer örneğinde kullanılan bir iş. Diğer bir iyileştirme de urlsToAbsolute() ürününde göreli CSS arka plan resimlerini işlemektir. Bu, mevcut komut dosyasının düşünmediği bir şeydir.

3. Yöntem: Chrome Extension API + Binary WebSocket

Google I/O 2012'de, bir tarayıcı sekmesinin içeriğini ekran paylaşımı için farklı bir yaklaşım sergiledim. Ancak bu bir hiledir. Bir Chrome Extension API'si gerektirir: Sadece HTML5 sihrini değil.

Bunun kaynağı da GitHub'da mevcut ama özet şu:

  1. Geçerli sekmeyi .png veri URL'si olarak kaydedin. Chrome Uzantılarının bu chrome.tabs.captureVisibleTab() için bir API'si vardır.
  2. dataURL'yi Blob biçimine dönüştürün. convertDataURIToBlob() yardımcısına göz atın.
  3. socket.responseType='blob' ayarlayarak ikili websocket kullanarak her bir Blob'u (çerçeve) görüntüleyiciye gönderin.

Örnek

Geçerli sekmeyi png olarak ekran görüntüsünü almak ve çerçeveyi bir websocket üzerinden göndermek için aşağıdaki kodu kullanabilirsiniz:

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

Gelecekteki İyileştirmeler

Bu video için kare hızı şaşırtıcı derecede iyi ama daha da iyi olabilirdi. İyileştirmelerden biri, dataURL'yi Blob'a dönüştürmenin ek yükünü ortadan kaldırmaktır. Maalesef chrome.tabs.captureVisibleTab() bize yalnızca bir dataURL sağlar. Blob veya Typed Array döndürürse, dönüştürme işlemini kendimiz yapmak yerine websocket aracılığıyla doğrudan gönderebiliriz. Bunun için lütfen crbug.com/32498 adresine yıldız ekleyin.

4. Yöntem: WebRTC - gerçek gelecek

Son ama önemli bir not!

Tarayıcıdaki ekran paylaşımının geleceği WebRTC tarafından gerçekleştirilecektir. 14 Ağustos 2012'de ekip, sekme içeriklerinin paylaşılması için bir WebRTC Tab Content Capture API önerdi:

Adam hazır olana kadar 1-3. yöntem bırakacağız.

Sonuç

Bu nedenle bugünün web teknolojisiyle tarayıcı sekmesini paylaşmak mümkün.

Ancak bu ifade çok da hassas kabul edilmelidir. Bu makaledeki teknikler sade olsa da, bir şekilde mükemmel bir paylaşım kullanıcı deneyiminin gerisinde kalıyor. WebRTC Sekme İçerik Yakalama çalışmasıyla bu her şey değişecek, ancak bu gerçekleşene kadar tarayıcı eklentilerimiz veya burada ele alınanlar gibi sınırlı çözümler elimizde kalır.

Başka teknikleriniz mi var? Yorum gönderin!