ในช่วง 2-3 ปีที่ผ่านมา เราได้ช่วยให้บริษัทหลายแห่งบรรลุฟังก์ชันการทำงานที่คล้ายกับการแชร์หน้าจอโดยใช้เทคโนโลยีเบราว์เซอร์เท่านั้น จากประสบการณ์ของเรา การใช้ VNC ในเทคโนโลยีแพลตฟอร์มเว็บเพียงอย่างเดียว (ไม่มีปลั๊กอิน) เป็นปัญหาที่แก้ยาก มีหลายสิ่งที่ต้องพิจารณาและอุปสรรคมากมายที่ต้องเอาชนะ การส่งต่อตำแหน่งเคอร์เซอร์ของเมาส์ การส่งต่อการกดแป้นพิมพ์ และการทาสีใหม่ด้วยสี 24 บิตที่ 60 fps เป็นเพียงปัญหาบางส่วนเท่านั้น
การจับภาพเนื้อหาแท็บ
หากเรานำความซับซ้อนของการแชร์หน้าจอแบบดั้งเดิมออกและมุ่งเน้นที่การแชร์เนื้อหาของแท็บเบราว์เซอร์ ปัญหาจะง่ายขึ้นมาก โดยแบ่งออกเป็น 2 ส่วน ได้แก่ ก) การจับภาพแท็บที่มองเห็นได้ในสถานะปัจจุบัน และ ข) การส่ง "เฟรม" นั้นผ่านเครือข่าย โดยพื้นฐานแล้ว เราต้องการวิธีจับภาพ DOM และแชร์
ส่วนการแชร์นั้นง่ายมาก WebSocket สามารถส่งข้อมูลในรูปแบบต่างๆ (สตริง, JSON, ไบนารี) ส่วนการจับภาพเป็นปัญหาที่ยากกว่ามาก โปรเจ็กต์อย่าง html2canvas ได้แก้ปัญหาการจับภาพหน้าจอ HTML ด้วยการนำเครื่องมือแสดงผลของเบราว์เซอร์กลับมาใช้ใหม่…ใน JavaScript อีกตัวอย่างหนึ่งคือ Google Feedback แต่ไม่ใช่โอเพนซอร์ส โปรเจ็กต์ประเภทนี้เจ๋งมาก แต่ก็ช้ามากเช่นกัน คุณคงโชคดีแล้วหากได้อัตราข้อมูล 1 fps ยิ่งไม่ต้องพูดถึง 60 fps ที่ทุกคนต้องการ
บทความนี้กล่าวถึงโซลูชันการพิสูจน์แนวคิดที่ฉันชอบ 2-3 รายการสําหรับ "การแชร์หน้าจอ" แท็บ
วิธีที่ 1: Mutation Observer + WebSocket
+Rafael Weinstein ได้สาธิตวิธีมิเรอร์แท็บไปเมื่อต้นปีนี้ เทคนิคของเขาใช้ Mutation Observer และ WebSocket
โดยพื้นฐานแล้ว แท็บที่ผู้นำเสนอแชร์จะคอยตรวจสอบการเปลี่ยนแปลงในหน้าเว็บและส่งความแตกต่างไปยังผู้ดูโดยใช้ WebSocket เมื่อผู้ใช้เลื่อนหรือโต้ตอบกับหน้าเว็บ ผู้สังเกตการณ์จะจับการเปลี่ยนแปลงเหล่านี้และรายงานกลับไปให้ผู้ดูทราบโดยใช้คลังสรุปข้อมูลการกลายพันธุ์ของ Rafael วิธีนี้จะช่วยให้ระบบมีประสิทธิภาพ ระบบจะไม่ส่งทั้งหน้าสำหรับทุกเฟรม
ดังที่ Rafael ชี้ให้เห็นในวิดีโอ นี่เป็นเพียงแค่การพิสูจน์แนวคิด อย่างไรก็ตาม เราคิดว่านี่ก็เป็นวิธีที่ดีในการรวมฟีเจอร์แพลตฟอร์มใหม่อย่าง Mutation Observer เข้ากับฟีเจอร์เก่าอย่าง Websockets
วิธีที่ 2: Blob จาก HTMLDocument + 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, แบบอักษร และสคริปต์ไม่เสียหายเมื่อดูในบริบทของ 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
สาธิต
แต่สำหรับการแชร์แท็บ เราจำเป็นต้องจับภาพแท็บอย่างต่อเนื่องและส่งไปยังผู้ชม ด้วยเหตุนี้ เราจึงได้เขียนเซิร์ฟเวอร์ WebSocket ขนาดเล็กของแอป Node, แอป และบุ๊กมาร์กเล็ตเพื่อสาธิตขั้นตอน หากไม่สนใจโค้ด เรามีวิดีโอสั้นๆ ที่แสดงการทำงานของฟีเจอร์นี้ให้ดู
การปรับปรุงในอนาคต
การเพิ่มประสิทธิภาพอย่างหนึ่งคือไม่ทำซ้ำทั้งเอกสารในทุกเฟรม ซึ่งสิ้นเปลืองและไม่เหมาะกับตัวอย่าง Mutation Observer การปรับปรุงอีกอย่างหนึ่งคือการรองรับภาพพื้นหลัง CSS แบบสัมพัทธ์ใน urlsToAbsolute()
ซึ่งสคริปต์ปัจจุบันไม่ได้พิจารณา
วิธีที่ 3: Chrome Extension API + WebSocket แบบไบนารี
ในงาน Google I/O 2012 เราได้สาธิตวิธีอื่นในการแชร์หน้าจอเนื้อหาของแท็บเบราว์เซอร์ แต่อันนี้เป็นการโกง ต้องใช้ Chrome Extension API ไม่ใช่ HTML5 ล้วนๆ
แหล่งที่มาของโปรเจ็กต์นี้ก็มีอยู่ใน GitHub ด้วย แต่สรุปสั้นๆ คือ
- จับภาพแท็บปัจจุบันเป็น URL ข้อมูล .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 หรืออาร์เรย์ที่กําหนดประเภทแล้ว เราจะส่งผ่าน WebSocket ได้โดยตรงแทนที่จะแปลงเป็น Blob ด้วยตนเอง โปรดติดดาว crbug.com/32498 เพื่อให้เราดำเนินการดังกล่าว
วิธีที่ 4: WebRTC - อนาคตที่แท้จริง
สุดท้ายแต่ไม่ท้ายสุด
อนาคตของการแชร์หน้าจอในเบราว์เซอร์จะกลายเป็นจริงด้วย WebRTC ในวันที่ 14 สิงหาคม 2012 ทีมได้เสนอ API การจับภาพเนื้อหาแท็บ WebRTC สำหรับการแชร์เนื้อหาแท็บ
ในระหว่างนี้ เรามีวิธี 1-3 ให้เลือก
บทสรุป
ดังนั้นการแชร์แท็บเบราว์เซอร์จึงเป็นไปได้ด้วยเทคโนโลยีเว็บในปัจจุบัน
แต่…คุณควรพิจารณาข้อความนี้อย่างถี่ถ้วน แม้ว่าจะดูดี แต่เทคนิคในบทความนี้ยังขาด UX การแชร์ที่ยอดเยี่ยมในบางแง่มุม ทุกอย่างจะเปลี่ยนไปเมื่อเราใช้ WebRTC Tab Content Capture แต่ในระหว่างนี้ เราต้องใช้ปลั๊กอินของเบราว์เซอร์หรือโซลูชันแบบจำกัดดังที่อธิบายไว้ที่นี่
หากมีเทคนิคเพิ่มเติม โพสต์ความคิดเห็น