पिछले कुछ सालों में, मैंने कुछ अलग-अलग कंपनियों को सिर्फ़ ब्राउज़र टेक्नोलॉजी का इस्तेमाल करके, स्क्रीन शेयर करने जैसी सुविधा देने में मदद की है. मेरे अनुभव के मुताबिक, सिर्फ़ वेब प्लैटफ़ॉर्म टेक्नोलॉजी (यानी कोई प्लग इन नहीं) में VNC लागू करना मुश्किल है. इसमें कई बातों का ध्यान रखना होता है और कई चुनौतियों का सामना करना पड़ता है. माउस पॉइंटर की पोज़िशन को रिले करना, कीस्ट्रोक को फ़ॉरवर्ड करना, और 60fps पर 24-बिट कलर रीपेंट करना, इनमें से कुछ समस्याएं हैं.
टैब का कॉन्टेंट कैप्चर करना
अगर हम स्क्रीन शेयर करने के पारंपरिक तरीके की जटिलताओं को हटाकर, ब्राउज़र टैब के कॉन्टेंट को शेयर करने पर फ़ोकस करें, तो समस्या का हल बहुत आसान हो जाता है. इसके लिए, आपको सिर्फ़ ये दो काम करने होंगे: a.) दिख रहे टैब को उसकी मौजूदा स्थिति में कैप्चर करना और b.) उस "फ़्रेम" को वायर पर भेजना. असल में, हमें DOM का स्नैपशॉट लेने और उसे शेयर करने का तरीका चाहिए.
शेयर करना आसान है. वेबसोकेट, डेटा को अलग-अलग फ़ॉर्मैट (स्ट्रिंग, JSON, बाइनरी) में भेजने में काफ़ी सक्षम हैं. स्नैपशॉट लेने की प्रोसेस बहुत मुश्किल है. html2canvas जैसे प्रोजेक्ट ने JavaScript में ब्राउज़र के रेंडरिंग इंजन को फिर से लागू करके, एचटीएमएल की स्क्रीनशॉट लेने की समस्या को हल किया है! Google Feedback भी एक उदाहरण है. हालांकि, यह ओपन-सोर्स नहीं है. इस तरह के प्रोजेक्ट बहुत शानदार होते हैं, लेकिन ये बहुत धीमे होते हैं. आपको 1 फ़्रेम प्रति सेकंड (एफ़पीएस) का थ्रूपुट मिलना ही मुश्किल है, 60 एफ़पीएस तो बहुत दूर की बात है.
इस लेख में, किसी टैब को "स्क्रीन शेयर करने" के लिए, मेरे पसंदीदा प्रूफ़-ऑफ़-कांसेप्ट के कुछ समाधानों के बारे में बताया गया है.
पहला तरीका: म्यूटेशन ऑब्ज़र्वर + वेबसोकेट
टैब को मिरर करने के एक तरीके के बारे में, +राफ़ेल वाइंस्टीन ने इस साल की शुरुआत में बताया था. इस तकनीक में, म्यूटेशन ऑब्ज़र्वर और वेबसोकेट का इस्तेमाल किया जाता है.
असल में, प्रज़ेंटर जिस टैब को शेयर कर रहा है वह पेज में होने वाले बदलावों को देखता है और वेबसोकेट का इस्तेमाल करके दर्शकों को बदलावों की जानकारी भेजता है. जब उपयोगकर्ता पेज को स्क्रोल करता है या उससे इंटरैक्ट करता है, तो ऑब्ज़र्वर इन बदलावों को पिक अप करते हैं और Rafael की म्यूटेशन की खास जानकारी वाली लाइब्रेरी का इस्तेमाल करके, दर्शक को इनकी रिपोर्ट देते हैं. इससे चीज़ें बेहतर तरीके से काम करती हैं. हर फ़्रेम के लिए पूरा पेज नहीं भेजा जाता.
वीडियो में राफ़ेल ने बताया है कि यह सिर्फ़ एक कॉन्सेप्ट है. हालांकि, मुझे लगता है कि म्यूटेशन ऑब्ज़र्वर जैसी नई प्लैटफ़ॉर्म सुविधा को वेबसोकेट जैसी पुरानी सुविधा के साथ जोड़ने का यह एक अच्छा तरीका है.
दूसरा तरीका: एचटीएमएल दस्तावेज़ से ब्लॉब + बाइनरी WebSocket
यह अगला तरीका, मुझे हाल ही में पता चला है. यह म्यूटेशन ऑब्ज़र्वर के तरीके से मिलता-जुलता है. हालांकि, इसमें बदलावों की खास जानकारी भेजने के बजाय, पूरे 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()
में, रिलेटिव/बिना स्कीम वाले यूआरएल को एब्सोलूट यूआरएल में फिर से लिखने के लिए, आसान रेगुलर एक्सप्रेशन शामिल होते हैं. ऐसा इसलिए ज़रूरी है, ताकि इमेज, सीएसएस, फ़ॉन्ट, और स्क्रिप्ट को ब्लॉब यूआरएल (उदाहरण के लिए, किसी दूसरे ऑरिजिन से) के कॉन्टेक्स्ट में देखते समय, वे काम करना बंद न कर दें.
मैंने एक और बदलाव किया है. मैंने स्क्रोल करने की सुविधा जोड़ी है. जब प्रज़ेंटर पेज को स्क्रोल करता है, तो दर्शक को भी पेज को स्क्रोल करना चाहिए. ऐसा करने के लिए, मैं डुप्लीकेट HTMLDocument
में मौजूदा scrollX
और scrollY
पोज़िशन को 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 वेबसोकेट सर्वर, ऐप्लिकेशन, और बुकमार्कलेट लिखा है, जो इस प्रोसेस को दिखाता है. अगर आपको कोड में दिलचस्पी नहीं है, तो यहां इस बारे में एक छोटा वीडियो दिया गया है:
आने वाले समय में होने वाले सुधार
ऑप्टिमाइज़ेशन का एक तरीका यह है कि हर फ़्रेम में पूरे दस्तावेज़ का डुप्लीकेट न बनाया जाए. यह बेकार है और म्यूटेशन ऑब्ज़र्वर के उदाहरण में यह अच्छी तरह से काम करता है. 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 को ब्लॉब में बदलने से जुड़ा ओवरहेड हटा दिया जाएगा. माफ़ करें, chrome.tabs.captureVisibleTab()
हमें सिर्फ़ dataURL देता है. अगर यह Blob या टाइप किया गया कलेक्शन दिखाता है, तो हम उसे Blob में बदलने के बजाय, सीधे वेबसोकेट के ज़रिए भेज सकते हैं. ऐसा करने के लिए, कृपया crbug.com/32498 पर स्टार का निशान लगाएं!
चौथा तरीका: WebRTC - आने वाले समय का सबसे सही तरीका
आखिर में,
आने वाले समय में, ब्राउज़र में स्क्रीन शेयर करने की सुविधा WebRTC की मदद से उपलब्ध होगी. 14 अगस्त, 2012 को, टीम ने टैब का कॉन्टेंट शेयर करने के लिए, WebRTC टैब कॉन्टेंट कैप्चर एपीआई का प्रस्ताव दिया था:
जब तक यह व्यक्ति तैयार नहीं हो जाता, तब तक हमारे पास पहला से तीसरा तरीका ही है.
नतीजा
इसलिए, आज की वेब टेक्नोलॉजी की मदद से ब्राउज़र टैब शेयर करना मुमकिन है!
हालांकि, इस बात पर भरोसा नहीं किया जा सकता. इस लेख में बताई गई तकनीकें अच्छी हैं, लेकिन शेयर करने के बेहतर यूज़र एक्सपीरियंस के लिए, इनमें कुछ और चीज़ें जोड़ी जा सकती हैं. WebRTC टैब कॉन्टेंट कैप्चर की सुविधा के आने के बाद, यह सब बदल जाएगा. हालांकि, जब तक यह सुविधा उपलब्ध नहीं हो जाती, तब तक हमारे पास ब्राउज़र प्लग इन या यहां बताए गए सीमित समाधान ही रहेंगे.
क्या आपके पास और तकनीकें हैं? कोई टिप्पणी पोस्ट करें!