ในช่วง 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 แบบไบนารี ขั้นตอนการตั้งค่ามีดังนี้
- เขียน URL ทั้งหมดในหน้าใหม่ให้เป็นแบบสัมบูรณ์ วิธีนี้จะช่วยป้องกันไม่ให้ชิ้นงานรูปภาพและ CSS แบบคงที่มีลิงก์เสีย
- โคลนองค์ประกอบเอกสารของหน้า:
document.documentElement.cloneNode(true); - ทำให้โคลนเป็นแบบอ่านอย่างเดียว เลือกไม่ได้ และป้องกันการเลื่อนโดยใช้ CSS
pointer-events: 'none';user-select:'none';overflow:hidden; - บันทึกตำแหน่งการเลื่อนปัจจุบันของหน้าและเพิ่มเป็นแอตทริบิวต์
data-*ในหน้าเว็บที่ซ้ำกัน - สร้าง
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 เช่นกัน แต่สรุปได้ดังนี้
- จับภาพแท็บปัจจุบันเป็น dataURL ของ .png ส่วนขยาย Chrome มี API สำหรับการดำเนินการดังกล่าว
chrome.tabs.captureVisibleTab() - แปลง dataURL เป็น
Blobดูตัวช่วยconvertDataURIToBlob() - ส่ง 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 แต่จนกว่าจะถึงตอนนั้น เราก็ยังต้องใช้ปลั๊กอินเบราว์เซอร์หรือโซลูชันที่จำกัด เช่น โซลูชันที่กล่าวถึงในที่นี้
หากมีเทคนิคเพิ่มเติม โพสต์ความคิดเห็น