การแชร์แท็บเบราว์เซอร์ใน HTML5 ใช่ไหม

ในช่วง 2-3 ปีที่ผ่านมา ฉันได้ช่วยบริษัทต่างๆ 2-3 แห่งให้ใช้ฟังก์ชันการทำงานที่คล้ายกับการแชร์หน้าจอได้โดยใช้เพียงเทคโนโลยีเบราว์เซอร์ จากประสบการณ์ของฉัน การใช้ VNC ในเทคโนโลยีแพลตฟอร์มเว็บเพียงอย่างเดียว (เช่น ไม่มีปลั๊กอิน) เป็นปัญหาที่ยาก มีหลายสิ่งหลายอย่างที่ต้องพิจารณาและอุปสรรคมากมายที่ต้องเอาชนะ การส่งต่อตำแหน่งเคอร์เซอร์เมาส์ การส่งต่อการกดแป้นพิมพ์ และการแสดงสี 24 บิตแบบเต็มที่ 60 FPS เป็นเพียงปัญหาบางส่วนเท่านั้น

กำลังจับภาพเนื้อหาแท็บ

หากเราตัดความซับซ้อนของการแชร์หน้าจอแบบเดิมออกและมุ่งเน้นที่การแชร์เนื้อหาของแท็บเบราว์เซอร์ ปัญหาจะลดลงอย่างมากเหลือเพียง ก.) การจับภาพแท็บที่มองเห็นได้ในสถานะปัจจุบัน และ ข.) การส่ง "เฟรม" นั้นผ่านสาย โดยพื้นฐานแล้ว เราต้องการวิธีสร้างภาพรวมของ DOM และแชร์ออกไป

การแชร์นั้นทำได้ง่ายๆ Websocket มีความสามารถสูงในการส่งข้อมูลในรูปแบบต่างๆ (สตริง, JSON, ไบนารี) ส่วนการจับภาพเป็นปัญหาที่ยากกว่ามาก โปรเจ็กต์อย่าง html2canvas ได้จัดการกับการจับภาพหน้าจอ HTML โดยการติดตั้งใช้งานเครื่องมือแสดงผลของเบราว์เซอร์อีกครั้ง…ใน JavaScript อีกตัวอย่างหนึ่งคือ Google ฟีดแบ็ก แม้ว่าจะไม่ใช่โอเพนซอร์สก็ตาม โปรเจ็กต์ประเภทนี้เจ๋งมาก แต่ก็ช้ามากเช่นกัน คุณโชคดีมากที่ได้อัตราการส่งข้อมูล 1 FPS ซึ่งน้อยกว่า 60 FPS ที่ต้องการมาก

บทความนี้จะพูดถึงโซลูชันการพิสูจน์แนวคิดที่ฉันชอบสำหรับการ "แชร์หน้าจอ" ของแท็บ

วิธีที่ 1: Mutation Observers + WebSocket

+Rafael Weinstein ได้สาธิตวิธีหนึ่งในการมิเรอร์แท็บเมื่อต้นปีนี้ เทคนิคของเขาใช้ Mutation Observer และ WebSocket

โดยพื้นฐานแล้ว แท็บที่ผู้นำเสนอกำลังแชร์จะคอยตรวจสอบการเปลี่ยนแปลงในหน้าเว็บและส่งความแตกต่างไปยังผู้ดูโดยใช้ WebSocket เมื่อผู้ใช้เลื่อนหรือโต้ตอบกับหน้าเว็บ ผู้สังเกตการณ์จะตรวจจับการเปลี่ยนแปลงเหล่านี้และรายงานกลับไปยังผู้ดูโดยใช้ไลบรารีสรุปการเปลี่ยนแปลงของ Rafael ซึ่งจะช่วยให้ระบบทำงานได้อย่างมีประสิทธิภาพ ระบบจะไม่ส่งทั้งหน้าสำหรับทุกเฟรม

ดังที่ Rafael ชี้ให้เห็นในวิดีโอ นี่เป็นเพียงการพิสูจน์แนวคิดเท่านั้น อย่างไรก็ตาม ฉันคิดว่านี่เป็นวิธีที่ยอดเยี่ยมในการรวมฟีเจอร์แพลตฟอร์มที่ใหม่กว่าอย่าง Mutation Observer เข้ากับฟีเจอร์ที่เก่ากว่าอย่าง Websocket

วิธีที่ 2: Blob จาก HTMLDocument + Binary WebSocket

วิธีถัดไปนี้เป็นวิธีที่ฉันเพิ่งนึกออกเมื่อไม่นานมานี้ ซึ่งคล้ายกับแนวทาง Mutation Observers แต่แทนที่จะส่งส่วนต่างสรุป ระบบจะสร้างโคลน Blob ของ HTMLDocument ทั้งหมดและส่งผ่าน WebSocket แบบไบนารี ขั้นตอนการตั้งค่ามีดังนี้

  1. เขียน URL ทั้งหมดในหน้าใหม่ให้เป็นแบบสัมบูรณ์ วิธีนี้จะช่วยป้องกันไม่ให้ชิ้นงานรูปภาพและ CSS แบบคงที่มีลิงก์เสีย
  2. โคลนองค์ประกอบเอกสารของหน้า: document.documentElement.cloneNode(true);
  3. ทำให้โคลนเป็นแบบอ่านอย่างเดียว เลือกไม่ได้ และป้องกันการเลื่อนโดยใช้ CSS pointer-events: 'none';user-select:'none';overflow:hidden;
  4. บันทึกตำแหน่งการเลื่อนปัจจุบันของหน้าและเพิ่มเป็นแอตทริบิวต์ data-* ในหน้าเว็บที่ซ้ำกัน
  5. สร้าง new Blob() จาก .outerHTML ของรายการที่ซ้ำกัน

โค้ดจะมีลักษณะดังนี้ (เราได้ปรับให้เข้าใจง่ายขึ้นจากแหล่งที่มาแบบเต็ม)

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() มีนิพจน์ทั่วไปแบบง่ายเพื่อเขียน URL สัมพัทธ์/ไม่มีรูปแบบใหม่เป็น URL แบบสัมบูรณ์ ซึ่งจำเป็นเพื่อให้รูปภาพ, CSS, แบบอักษร และสคริปต์ไม่เสียหายเมื่อดูในบริบทของ Blob URL (เช่น จากต้นทางอื่น)

การปรับแต่งสุดท้ายที่ฉันทำคือการเพิ่มการรองรับการเลื่อน เมื่อผู้นำเสนอเลื่อนหน้าเว็บ ผู้ชมควรเลื่อนตามไปด้วย โดยฉันจะเก็บตำแหน่ง scrollX และ scrollY ปัจจุบันไว้เป็นแอตทริบิวต์ data-* ใน HTMLDocument ที่ซ้ำกัน ก่อนที่จะสร้าง Blob สุดท้าย ระบบจะแทรก JS เล็กน้อยที่เริ่มทำงานเมื่อการโหลดหน้าเว็บ

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

การเลื่อนแบบปลอมๆ จะทำให้ดูเหมือนว่าเราได้จับภาพหน้าจอส่วนหนึ่งของหน้าต้นฉบับ แต่จริงๆ แล้วเราได้ทำซ้ำทั้งหน้าและเพียงแค่เปลี่ยนตำแหน่งเท่านั้น #clever

สาธิต

แต่สำหรับการแชร์แท็บ เราจำเป็นต้องบันทึกแท็บอย่างต่อเนื่องและส่งไปยังผู้ชม สำหรับเรื่องนี้ ฉันได้เขียนเซิร์ฟเวอร์ WebSocket, แอป และบุ๊กมาร์กเล็ตขนาดเล็กของ Node ที่สาธิตขั้นตอนดังกล่าว หากคุณไม่สนใจโค้ด โปรดดูวิดีโอสั้นๆ เกี่ยวกับการทำงานของสิ่งต่างๆ ต่อไปนี้

การปรับปรุงในอนาคต

การเพิ่มประสิทธิภาพอย่างหนึ่งคือการไม่ทำซ้ำเอกสารทั้งหมดในทุกเฟรม ซึ่งเป็นการสิ้นเปลืองและเป็นสิ่งที่ตัวอย่าง Mutation Observer ทำได้ดี การปรับปรุงอีกอย่างหนึ่งคือการจัดการภาพพื้นหลัง CSS แบบสัมพัทธ์ใน urlsToAbsolute() ซึ่งเป็นสิ่งที่สคริปต์ปัจจุบันไม่ได้พิจารณา

วิธีที่ 3: ส่วนขยาย Chrome API + Binary WebSocket

ในงาน Google I/O 2012 ฉันได้สาธิตอีกวิธีหนึ่งในการแชร์หน้าจอเนื้อหาของแท็บเบราว์เซอร์ อย่างไรก็ตาม อีเมลนี้เป็นการโกง โดยต้องใช้ Chrome Extension API ไม่ใช่แค่ HTML5 เท่านั้น

ซอร์สโค้ดของตัวอย่างนี้ก็อยู่ใน GitHub เช่นกัน แต่สรุปได้ดังนี้

  1. จับภาพแท็บปัจจุบันเป็น dataURL ของ .png ส่วนขยาย Chrome มี API สำหรับการดำเนินการดังกล่าว chrome.tabs.captureVisibleTab()
  2. แปลง dataURL เป็น Blob ดูตัวช่วย convertDataURIToBlob()
  3. ส่ง Blob (เฟรม) แต่ละรายการไปยังผู้ดูโดยใช้ WebSocket แบบไบนารีด้วยการตั้งค่า socket.responseType='blob'

ตัวอย่าง

โค้ดสำหรับจับภาพหน้าจอแท็บปัจจุบันเป็น PNG และส่งเฟรมผ่าน 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);

การปรับปรุงในอนาคต

อัตราเฟรมของเกมนี้ดีอย่างน่าประหลาดใจ แต่ก็ยังดีกว่านี้ได้อีก การปรับปรุงอย่างหนึ่งคือการนำค่าใช้จ่ายในการแปลง dataURL เป็น Blob ออก ขออภัย chrome.tabs.captureVisibleTab() ให้เฉพาะ dataURL แก่เราเท่านั้น หากแสดงผลเป็น Blob หรือ Typed Array เราจะส่งผ่าน WebSocket ได้โดยตรงแทนที่จะทำการแปลงเป็น Blob ด้วยตนเอง โปรดติดดาว crbug.com/32498 เพื่อให้ฟีเจอร์นี้เกิดขึ้นจริง

วิธีที่ 4: WebRTC - อนาคตที่แท้จริง

สุดท้ายแต่ไม่ท้ายสุด

อนาคตของการแชร์หน้าจอในเบราว์เซอร์จะเกิดขึ้นได้ด้วย WebRTC เมื่อวันที่ 14 สิงหาคม 2012 ทีมได้เสนอ API WebRTC Tab Content Capture สำหรับการแชร์เนื้อหาแท็บ ดังนี้

ดังนั้นเราจึงต้องใช้วิธีที่ 1-3 ไปก่อนจนกว่าวิธีนี้จะพร้อมใช้งาน

บทสรุป

ดังนั้นการแชร์แท็บเบราว์เซอร์จึงเป็นไปได้ด้วยเทคโนโลยีเว็บในปัจจุบัน

แต่…คุณควรฟังหูไว้หู แม้ว่าเทคนิคในบทความนี้จะดูดี แต่ก็ยังไม่ถึงขั้นเป็น UX การแชร์ที่ยอดเยี่ยมไม่ทางใดก็ทางหนึ่ง ทั้งหมดนี้จะเปลี่ยนไปเมื่อมีการใช้ WebRTC Tab Content Capture แต่จนกว่าจะถึงตอนนั้น เราก็ยังต้องใช้ปลั๊กอินเบราว์เซอร์หรือโซลูชันที่จำกัด เช่น โซลูชันที่กล่าวถึงในที่นี้

หากมีเทคนิคเพิ่มเติม โพสต์ความคิดเห็น