पिछले कुछ सालों में, मैंने कुछ कंपनियों को सिर्फ़ ब्राउज़र टेक्नोलॉजी का इस्तेमाल करके, स्क्रीन शेयर करने जैसी सुविधाएं उपलब्ध कराने में मदद की है. मेरे अनुभव के मुताबिक, सिर्फ़ वेब प्लैटफ़ॉर्म टेक्नोलॉजी (यानी कि बिना प्लग इन) में VNC को लागू करना एक मुश्किल काम है. इस दौरान, कई बातों का ध्यान रखना पड़ता है और कई चुनौतियों का सामना करना पड़ता है. माउस पॉइंटर की पोज़िशन को रिले करना, कीस्ट्रोक को फ़ॉरवर्ड करना, और 60fps पर पूरे 24-बिट कलर रीपेंट हासिल करना, कुछ ऐसी समस्याएं हैं जो इस दौरान आती हैं.
टैब का कॉन्टेंट कैप्चर करना
अगर हम स्क्रीन शेयर करने के पारंपरिक तरीके की जटिलताओं को हटाकर, ब्राउज़र टैब के कॉन्टेंट को शेयर करने पर फ़ोकस करें, तो समस्या काफ़ी हद तक हल हो जाती है. इसके लिए, हमें a.) विज़िबल टैब को उसकी मौजूदा स्थिति में कैप्चर करना होगा और b.) उस "फ़्रेम" को वायर के ज़रिए भेजना होगा. हमें डीओएम का स्नैपशॉट लेने और उसे शेयर करने का तरीका चाहिए.
शेयर करना आसान है. वेबसॉकेट, अलग-अलग फ़ॉर्मैट (स्ट्रिंग, JSON, बाइनरी) में डेटा भेजने में काफ़ी सक्षम होते हैं. स्नैपशॉट लेने वाला हिस्सा ज़्यादा मुश्किल है. html2canvas जैसे प्रोजेक्ट ने एचटीएमएल की स्क्रीन कैप्चर करने की समस्या को हल किया है. इसके लिए, JavaScript में ब्राउज़र के रेंडरिंग इंजन को फिर से लागू किया गया है! इसका एक और उदाहरण Google Feedback है. हालांकि, यह ओपन-सोर्स नहीं है. इस तरह के प्रोजेक्ट बहुत अच्छे होते हैं, लेकिन इन्हें पूरा होने में काफ़ी समय लगता है. आपको 1fps थ्रूपुट मिल जाए, तो यह आपकी खुशकिस्मती होगी. 60fps थ्रूपुट मिलना तो बहुत दूर की बात है.
इस लेख में, किसी टैब की "स्क्रीन शेयर करने" के लिए, सिद्धांत के प्रमाण से जुड़े मेरे कुछ पसंदीदा समाधानों के बारे में बताया गया है.
पहला तरीका: Mutation Observers + WebSocket
इस साल की शुरुआत में, +रफ़ाएल वाइनस्टीन ने टैब को मिरर करने का एक तरीका बताया था. इस तकनीक में म्यूटेशन ऑब्ज़र्वर और WebSocket का इस्तेमाल किया जाता है.
प्रज़ेंटर जिस टैब को शेयर कर रहा है वह पेज में होने वाले बदलावों पर नज़र रखता है. साथ ही, वेबसॉकेट का इस्तेमाल करके, व्यूअर को बदलावों की जानकारी भेजता है. जब उपयोगकर्ता पेज को स्क्रोल करता है या उससे इंटरैक्ट करता है, तो ऑब्ज़र्वर इन बदलावों को कैप्चर करते हैं. इसके बाद, वे राफ़ेल की म्यूटेशन समरी लाइब्रेरी का इस्तेमाल करके, व्यूअर को इन बदलावों की जानकारी देते हैं. इससे चीज़ें बेहतर तरीके से काम करती हैं. हर फ़्रेम के लिए पूरा पेज नहीं भेजा जाता.
जैसा कि रफ़ाएल ने वीडियो में बताया है, यह सिर्फ़ सिद्धांत का प्रमाण है. फिर भी, मुझे लगता है कि म्यूटेशन ऑब्ज़र्वर जैसी नई प्लैटफ़ॉर्म सुविधा को वेबसॉकेट जैसी पुरानी सुविधा के साथ जोड़ने का यह एक अच्छा तरीका है.
दूसरा तरीका: HTMLDocument + बाइनरी WebSocket से Blob
अगला तरीका वह है जो मुझे हाल ही में पता चला है. यह Mutation Observers के तरीके जैसा ही है. हालांकि, यह खास जानकारी के अंतर भेजने के बजाय, पूरे HTMLDocument का एक ब्लॉब क्लोन बनाता है और उसे बाइनरी वेबसॉकेट पर भेजता है. इसे सेट अप करने का तरीका यहां बताया गया है:
- पेज पर मौजूद सभी यूआरएल को ऐब्सलूट यूआरएल में बदलें. इससे स्टैटिक इमेज और सीएसएस ऐसेट में काम न करने वाले लिंक शामिल नहीं होते.
- पेज के दस्तावेज़ एलिमेंट को क्लोन करें:
document.documentElement.cloneNode(true); - क्लोन को रीड-ओनली और नॉन-सिलेक्टेबल बनाएं. साथ ही, सीएसएस
pointer-events: 'none';user-select:'none';overflow:hidden;का इस्तेमाल करके स्क्रोल करने से रोकें - पेज की मौजूदा स्क्रोल पोज़िशन को कैप्चर करें और उन्हें डुप्लीकेट पेज पर
data-*एट्रिब्यूट के तौर पर जोड़ें. - डुप्लीकेट
.outerHTMLसेnew Blob()बनाएं.
कोड कुछ ऐसा दिखता है (मैंने पूरे सोर्स को आसान बना दिया है):
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() में मिलते-जुलते/बिना स्कीम वाले यूआरएल को संपूर्ण यूआरएल में बदलने के लिए, सामान्य रेगुलर एक्सप्रेशन होते हैं. यह ज़रूरी है, ताकि ब्लॉब यूआरएल के कॉन्टेक्स्ट में देखे जाने पर इमेज, सीएसएस, फ़ॉन्ट, और स्क्रिप्ट काम करना बंद न करें. उदाहरण के लिए, किसी दूसरे ऑरिजिन से.
मैंने एक आखिरी बदलाव किया. वह था स्क्रोल करने की सुविधा जोड़ना. जब प्रज़ेंटर पेज को स्क्रोल करे, तो दर्शक को भी उसी के साथ आगे बढ़ना चाहिए. इसके लिए, मैं मौजूदा scrollX और scrollY पोज़िशन को डुप्लीकेट HTMLDocument पर data-* एट्रिब्यूट के तौर पर सेव करता हूं. फ़ाइनल ब्लॉब बनाने से पहले, थोड़ा सा 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
डेमो
हालांकि, टैब शेयर करने के लिए, हमें टैब को लगातार कैप्चर करना होगा और उसे दर्शकों को भेजना होगा. इसके लिए, मैंने एक छोटा Node वेबसॉकेट सर्वर, ऐप्लिकेशन, और बुकमार्कलेट लिखा है, जो इस फ़्लो को दिखाता है. अगर आपको कोड में दिलचस्पी नहीं है, तो यहां एक छोटा वीडियो दिया गया है. इसमें इस सुविधा को इस्तेमाल करने का तरीका बताया गया है:
आने वाले समय में होने वाले सुधार
एक ऑप्टिमाइज़ेशन यह है कि हर फ़्रेम पर पूरे दस्तावेज़ को डुप्लीकेट न किया जाए. इससे समय और संसाधन बर्बाद होते हैं. Mutation Observer का उदाहरण इस समस्या को हल करने में मदद करता है. एक और सुधार यह है कि urlsToAbsolute() में रिलेटिव सीएसएस बैकग्राउंड इमेज को हैंडल किया जा सकता है. यह ऐसी चीज़ है जिस पर मौजूदा स्क्रिप्ट ध्यान नहीं देती.
तीसरा तरीका: Chrome एक्सटेंशन एपीआई + बाइनरी वेबसॉकेट
Google I/O 2012 में, मैंने ब्राउज़र टैब के कॉन्टेंट की स्क्रीन शेयर करने का एक और तरीका दिखाया था. हालांकि, यह एक चीट कोड है. इसके लिए, Chrome एक्सटेंशन एपीआई की ज़रूरत होती है, न कि सिर्फ़ HTML5 की.
इस टूल का सोर्स भी GitHub पर उपलब्ध है. हालांकि, इसकी खास बातें ये हैं:
- मौजूदा टैब को .png dataURL के तौर पर कैप्चर करें. Chrome एक्सटेंशन में इसके लिए एक एपीआई होता है
chrome.tabs.captureVisibleTab(). - dataURL को
Blobमें बदलें.convertDataURIToBlob()हेल्पर देखें. 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 देता है. अगर यह ब्लॉब या टाइप किया गया कलेक्शन दिखाता, तो हम ब्लॉब में खुद कन्वर्ज़न करने के बजाय, सीधे वेबसॉकेट के ज़रिए भेज सकते थे. ऐसा करने के लिए, कृपया crbug.com/32498 पर स्टार का निशान लगाएं!
चौथा तरीका: WebRTC - आने वाले समय की टेक्नोलॉजी
आखिर में, सबसे ज़रूरी बात!
ब्राउज़र में स्क्रीन शेयर करने की सुविधा को बेहतर बनाने के लिए, WebRTC का इस्तेमाल किया जाएगा. टीम ने 14 अगस्त, 2012 को टैब का कॉन्टेंट शेयर करने के लिए, WebRTC Tab Content Capture एपीआई का सुझाव दिया:
जब तक यह सुविधा उपलब्ध नहीं हो जाती, तब तक हमारे पास पहले से तीसरे तरीके ही उपलब्ध हैं.
नतीजा
इसलिए, आज की वेब टेक्नोलॉजी की मदद से, ब्राउज़र टैब को शेयर किया जा सकता है!
हालांकि, इस बात पर पूरी तरह से भरोसा नहीं किया जा सकता. इस लेख में बताई गई तकनीकें अच्छी हैं, लेकिन ये शेयर करने के बेहतरीन उपयोगकर्ता अनुभव (UX) के लिए काफ़ी नहीं हैं. WebRTC टैब कॉन्टेंट कैप्चर की सुविधा से, यह सब बदल जाएगा. हालांकि, जब तक यह सुविधा उपलब्ध नहीं हो जाती, तब तक हमें ब्राउज़र प्लग इन या सीमित सुविधाओं का इस्तेमाल करना होगा. जैसे, यहां बताई गई सुविधाएं.
क्या आपके पास और भी तकनीकें हैं? कोई टिप्पणी पोस्ट करें!