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

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

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

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

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

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

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

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

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

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

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

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

  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, แบบอักษร และสคริปต์ไม่ทำงานเมื่อดูในบริบทของ URL ของ Blob (เช่น มาจากต้นทางอื่น)

การปรับเปลี่ยนครั้งสุดท้ายที่ผมทำคือเพิ่มการรองรับการเลื่อน เมื่อผู้นำเสนอเลื่อนหน้าเว็บ ผู้ชมควรอ่านตามไปด้วย ฉันจึงได้ซ่อนตำแหน่ง 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

ข้อมูลประชากร

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

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

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

วิธีที่ 3: API ส่วนขยายของ Chrome + WebSocket แบบไบนารี

ในงาน Google I/O 2012 ผมได้แสดงให้เห็นถึงอีกแนวทางในการแชร์หน้าจอเนื้อหาของแท็บเบราว์เซอร์ แต่ข้อนี้โกงจริงๆ ต้องใช้ API ส่วนขยายของ Chrome: ไม่ใช่เวทมนตร์ของ HTML5 เพียงอย่างเดียว

แหล่งที่มาสำหรับเรื่องนี้อยู่ใน GitHub เช่นกัน แต่สาระสำคัญคือ

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

ตัวอย่าง

นี่คือโค้ดสำหรับภาพหน้าจอของแท็บปัจจุบันเป็นรูป png และส่งเฟรมผ่านเว็บซ็อกเก็ต:

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 เราอาจส่งผ่านเว็บซ็อกเก็ตโดยตรงแทนการแปลงไปยัง Blob ที่เราเอง โปรดติดดาว crbug.com/32498 เพื่อให้บรรลุเป้าหมายนี้

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

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

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

วิธีที่ 1-3 จนกว่าเขาจะพร้อม

บทสรุป

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

แต่... ข้อความที่กล่าวไปนั้นควรใช้ร่วมกับเม็ดของเกลือ แม้ว่าจะดูดีมาก แต่เทคนิคในบทความนี้ไม่เหมือนกับ UX ในการแชร์ที่ยอดเยี่ยมไม่ว่าในทางใดทางหนึ่ง การเปลี่ยนแปลงทั้งหมดนี้จะเปลี่ยนแปลงไปโดยใช้การจับภาพเนื้อหาแท็บ WebRTC แต่ก่อนที่จะเกิดขึ้นจริง เรายังใช้ปลั๊กอินเบราว์เซอร์หรือโซลูชันที่จำกัดเช่นเดียวกับที่เราอธิบายไว้ที่นี่

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