W ciągu ostatnich kilku lat udało mi się pomóc kilku różnym firmom osiągnąć funkcję udostępniania ekranu, korzystając wyłącznie z technologii przeglądarek. Z mojego doświadczenia wynika, że implementacja VNC wyłącznie w technologii platform internetowych (tj. bez wtyczek) to trudny problem. Jest wiele kwestii, które trzeba przemyśleć, i wiele wyzwań do przezwyciężenia. Na przykład przekazywanie pozycji wskaźnika myszy, naciśnięcia klawiszy do przekazywania dalej i pełne 24-bitowe ponowne renderowanie kolorów przy 60 kl./s to tylko niektóre z problemów.
Przechwytywanie zawartości karty
Jeśli usuniemy złożoność tradycyjnego udostępniania ekranu i skupimy się na udostępnianiu zawartości karty przeglądarki, problem znacznie się uprości: a.) przechwycenie widocznej karty w jej obecnym stanie i b) przesłanie tej „ramki” przez sieć. Zasadniczo potrzebujemy sposobu na zapisanie DOM i udostępnienie go.
Etap udostępniania jest prosty. Protokół Websockets może wysyłać dane w różnych formatach (ciąg znaków, JSON, plik binarny). Znacznie trudniej jest zrobić zdjęcia. Projekty takie jak html2canvas poradziły sobie z przechwytywaniem ekranu HTML przez zastosowanie mechanizmu renderowania w przeglądarce (...) w języku JavaScript. Innym przykładem jest Google Feedback, choć nie jest to oprogramowanie typu open source. Projekty tego typu są bardzo fajne, ale za to strasznie powolne. Prawdopodobnie uzyskasz przepustowość 1 kl./s, a to znacznie mniej, niż w przypadku wartości 60 kl./s.
W tym artykule omawiam kilka moich ulubionych rozwiązań koncepcyjnych dotyczących „udostępniania ekranu” kart.
Metoda 1. Obserwatorzy mutacji + WebSocket
Jednym ze sposobów na odbicie lustrzane karty jest +Rafael Weinstein w tym roku. W swojej metodzie stosuje obserwacje zmian i WebSocket.
Zasadniczo karta udostępniana przez prowadzącego monitoruje zmiany na stronie i wysyła różnice do widza za pomocą czytnika internetowego. Gdy użytkownik przewija stronę lub wchodzi z nią w interakcję, obserwatorzy wychwytują te zmiany i przesyłają je z powrotem do użytkownika, używając biblioteki podsumowania mutacji Rafaela. Zapewnia to dużą wydajność. W przypadku każdej klatki nie jest wysyłana cała strona.
Jak zaznacza Rafael w filmie, jest to jedynie dowód koncepcyjny. Myślę jednak, że to świetny sposób na połączenie nowszej funkcji platformy, takiej jak Obserwatorzy mutacji, ze starszą, taką jak Websockets.
Metoda 2. Blob z dokumentu HTML + Binary WebSocket
To kolejna metoda, którą zauważyłam niedawno. Ta metoda jest podobna do metody Obserwatorzy mutacji, ale zamiast wysyłania różnic podsumowujących tworzy klon całego obiektu blob (HTMLDocument
) i wysyła go na binarny websocket. Oto sposób konfiguracji w zależności od konfiguracji:
- Przepisz wszystkie adresy URL na stronie na bezwzględne. Dzięki temu obrazy statyczne i zasoby CSS nie będą zawierać uszkodzonych linków.
- Skopiuj element dokumentu strony:
document.documentElement.cloneNode(true);
- Ustaw klonowanie w trybie tylko do odczytu, nie wybieraj opcji wyboru i zapobiegaj przewijaniu za pomocą CSS
pointer-events: 'none';user-select:'none';overflow:hidden;
- Przechwyć obecną pozycję przewijania strony i dodaj ją jako atrybuty
data-*
w duplikacie. - Utwórz
new Blob()
z.outerHTML
duplikatu.
Kod wygląda mniej więcej tak (w pełnym kodzie zostały wprowadzone uproszczenia):
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()
zawiera proste wyrażenia regularne, które pozwalają zastępować względne/bezschematowe adresy URL na bezwzględne. Jest to konieczne, aby obrazy, pliki CSS, czcionki i skrypty nie działały w kontekście adresu URL obiektu blob (np. z innego źródła).
Ostatnia poprawka to dodanie obsługi przewijania. Gdy prowadzący przewija stronę, widz powinien podążać za nim. Aby to zrobić, zapisuję bieżące pozycje scrollX
i scrollY
jako atrybuty data-*
w duplikacie HTMLDocument
. Przed utworzeniem ostatniego obiektu blob wstrzyknięty jest fragment kodu JS, który uruchamia się podczas wczytywania strony:
// 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);
});
Fałszywe przewijanie daje wrażenie, że zrobiliśmy zrzut ekranu tylko dla części oryginalnej strony, podczas gdy w rzeczywistości zduplikowaliśmy cały element i zmieniliśmy jego położenie. #clever
Pokaz
Jednak aby udostępniać kartę, musimy ją stale zarejestrować i wysyłać do widzów. W tym celu stworzyłem mały serwer WWW, aplikację i zakładki do zakładek, które ilustrują ten przepływ. Jeśli nie interesuje Cię kod, obejrzyj krótki film pokazujący działanie:
Planowane ulepszenia
Jedną z optymalizacji jest nie duplikowanie całego dokumentu w każdej klatce. To marnotrawstwo, a przykład Mutation Observer sprawdza się. Kolejną ulepszoną funkcją jest obsługa względnych obrazów tła CSS w komponencie urlsToAbsolute()
. Tego nie uwzględnia
obecny skrypt.
Metoda 3. Chrome Extension API + Binary WebSocket
Na Google I/O 2012 zaprezentowałem inny sposób udostępniania ekranu zawartości karty przeglądarki. To jednak oszustwo. Wymaga interfejsu API rozszerzeń Chrome, a nie samej magii HTML5.
Źródło tego artykułu jest też dostępne w GitHubie, ale sedno jest:
- Przechwyć bieżącą kartę jako adres URL danych w formacie .png. Rozszerzenia do Chrome mają interfejs API odpowiadający temu
chrome.tabs.captureVisibleTab()
. - Przekonwertuj parametr dataURL na typ
Blob
. Zobacz pomocnikconvertDataURIToBlob()
. - Wyślij każdy obiekt blob (ramkę) do przeglądarki za pomocą binarnego webhooka, ustawiając wartość
socket.responseType='blob'
.
Przykład
Oto kod umożliwiający zrzut ekranu z bieżącą kartą w postaci PNG i wysłanie ramki w programie 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);
Planowane ulepszenia
Liczba klatek na sekundę jest zaskakująca dobra, ale może być jeszcze lepsza. Jednym z poprawek jest ograniczenie nakładu pracy związanego z konwertowaniem obiektu dataURL na obiekt Blob. chrome.tabs.captureVisibleTab()
podaje tylko adres URL. Jeśli zwraca on tablicę blob lub typową, można wysłać ją bezpośrednio z websocketu, zamiast samodzielnie konwertować te obiekty. Aby tak się stało, oznacz to gwiazdką crbug.com/32498.
Metoda 4. WebRTC – rzeczywista przyszłość
Na koniec jeszcze jedna uwaga.
Przyszłość udostępniania ekranu w przeglądarce urzeczywistni się dzięki WebRTC. 14 sierpnia 2012 roku zespół zaproponował interfejs API WebRTC Tab Content Capture do udostępniania zawartości kart:
Dopóki ten gość nie będzie gotowy, pozostajemy z metodami 1-3.
Podsumowanie
Dlatego udostępnianie kart przeglądarki jest możliwe dzięki dzisiejszej technologii internetowej.
Jednak... to twierdzenie należy podchodzić z ziarnem ostrożności. Techniki omówione w tym artykule są utrzymane w porządku, jednak w jakiś sposób nie dają dobrych wrażeń w związku z udostępnianiem. Wszystko to się zmieni dzięki zastosowaniu narzędzia WebRTC Tab Content Capture, ale dopóki nie urzeczywistnimy się to w Google, będziemy pozostawiać wtyczki do przeglądarki lub ograniczone rozwiązania, takie jak te omówione w tym artykule.
Masz więcej metod? Opublikuj komentarz