क्या आपको HTML5 में ब्राउज़र टैब स्क्रीन शेयर करना है?

पिछले कुछ सालों में, मैंने कुछ अलग-अलग कंपनियों को सिर्फ़ ब्राउज़र टेक्नोलॉजी का इस्तेमाल करके स्क्रीन शेयरिंग जैसी सुविधा पाने में मदद की है. मेरे अनुभव से, VNC को पूरी तरह से वेब प्लैटफ़ॉर्म टेक्नोलॉजी (यानी कि कोई प्लगिन नहीं) में लागू करना एक मुश्किल समस्या है. हमें कई बातों पर विचार करना चाहिए और कई चुनौतियों का सामना करना पड़ता है. माउस पॉइंटर की पोज़िशन बदलना, कीस्ट्रोक फ़ॉरवर्ड करना, और 60fps पर 24-बिट कलर रीपेंट करना, इनमें से कुछ समस्याएं हैं.

टैब का कॉन्टेंट कैप्चर किया जा रहा है

अगर हम स्क्रीन शेयर करने की सामान्य मुश्किलों को दूर करते हैं और ब्राउज़र टैब के कॉन्टेंट को शेयर करने पर ध्यान देते हैं, तो यह समस्या बहुत आसान हो जाती है. a.) दिखने वाले टैब को उसकी मौजूदा स्थिति में कैप्चर कर लेती है और b.) उस "फ़्रेम" को पूरे वायर के साथ भेज देती है. ज़रूरी है कि हमें डीओएम का स्नैपशॉट लेने और उसे शेयर करने के लिए एक तरीका चाहिए.

शेयर करना आसान है. Websockets अलग-अलग फ़ॉर्मैट (स्ट्रिंग, JSON, बाइनरी) में डेटा भेज सकते हैं. स्नैपशॉट लेने का हिस्सा ज़्यादा मुश्किल काम है. html2canvas जैसे प्रोजेक्ट ने JavaScript में ब्राउज़र के रेंडरिंग इंजन को फिर से लागू करके, एचटीएमएल को कैप्चर करने की प्रोसेस में समस्या डाल दी है! Google Feedback का एक और उदाहरण यह है कि यह ओपन सोर्स नहीं है. इस तरह के प्रोजेक्ट बहुत अच्छे होते हैं, लेकिन बहुत धीमे काम करते हैं. आप खुशकिस्मत होंगे कि आपको 1fps थ्रूपुट मिलेगा, जो 60fps की तुलना में बहुत कम था.

इस लेख में टैब "स्क्रीन शेयर करने" के लिए मेरे कुछ पसंदीदा कॉन्सेप्ट-ऑफ़-कॉन्सेप्ट समाधानों पर चर्चा की गई है.

पहला तरीका: म्यूटेशन ऑब्ज़र्वर + WebSocket

किसी टैब का डुप्लीकेट वर्शन बनाने का एक तरीका इस साल की शुरुआत में +राफ़ेल वेनस्टेन ने बताया था. उनकी तकनीक में म्यूटेशन ऑब्ज़र्वर और WebSocket का इस्तेमाल किया जाता है.

असल में, प्रज़ेंटर जिस टैब को शेयर कर रहा है वह पेज में हुए बदलावों को देखता है और दर्शक को वेबसॉकेट का इस्तेमाल करके, अलग-अलग जानकारी भेजता है. जब कोई उपयोगकर्ता किसी पेज को स्क्रोल करता है या उससे इंटरैक्ट करता है, तब ऑब्ज़र्वर इन बदलावों को अपनाते हैं और राफ़ेल की म्यूटेशन की खास जानकारी वाली लाइब्रेरी का इस्तेमाल करके, इन बदलावों को दर्शक को रिपोर्ट करते हैं. इससे चीज़ें बेहतर परफ़ॉर्म करती हैं. पूरा पेज हर फ़्रेम के लिए नहीं भेजा जाता है.

जैसा कि राफ़ेल ने वीडियो में बताया है, यह सिर्फ़ एक सबूत है. फिर भी, मुझे लगता है कि यह म्यूटेशन ऑब्ज़र्वर जैसी नई प्लैटफ़ॉर्म सुविधा को Websockets जैसी पुरानी सुविधाओं के साथ मिलाने का एक अच्छा तरीका है.

दूसरा तरीका: HTMLDocument + बाइनरी WebSocket से Blob

यह अगला तरीका है जिसके बारे में मुझे हाल ही में पता चला. यह म्यूटेशन ऑब्ज़र्वर अप्रोच के जैसा है. हालांकि, जवाब में अंतर भेजने के बजाय, यह पूरे HTMLDocument का Blob क्लोन बनाता है और उसे एक बाइनरी वेबसॉकेट पर भेजता है. सेटअप के अनुसार सेटअप यहां दिया गया है:

  1. पेज पर मौजूद सभी यूआरएल को फिर से लिखें, ताकि उन्हें बेहतर बनाया जा सके. यह, स्टैटिक इमेज और सीएसएस ऐसेट में, काम न करने वाले लिंक शामिल होने से रोकता है.
  2. पेज के दस्तावेज़ एलिमेंट का क्लोन बनाएं: document.documentElement.cloneNode(true);
  3. सीएसएस pointer-events: 'none';user-select:'none';overflow:hidden; का इस्तेमाल करके क्लोन को सिर्फ़ पढ़ने के लिए, न चुनने लायक बनाएं, और स्क्रोल करने से रोकें
  4. पेज की स्क्रोल की मौजूदा जगह को कैप्चर करें और उन्हें डुप्लीकेट पर data-* एट्रिब्यूट के तौर पर जोड़ें.
  5. डुप्लीकेट के .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() में सामान्य रेगुलर एक्सप्रेशन मौजूद होते हैं, जो मिलते-जुलते/बिना प्लान वाले यूआरएल को सटीक यूआरएल में फिर से लिखने के लिए इस्तेमाल किए जाते हैं. यह इसलिए ज़रूरी है, ताकि blob यूआरएल (उदाहरण के लिए, किसी अलग ऑरिजिन से) के हिसाब से देखे जाने पर इमेज, सीएसएस, फ़ॉन्ट, और स्क्रिप्ट काम न करें.

मैंने आखिरी ट्वीट, स्क्रोल की सुविधा को शामिल करने के लिए किया. जब प्रज़ेंटर, पेज को स्क्रोल करता है, तब दर्शक को भी उस पेज पर जाना चाहिए. ऐसा करने के लिए, मैं डुप्लीकेट HTMLDocument पर scrollX और scrollY की मौजूदा पोज़िशन को data-* एट्रिब्यूट के तौर पर छिपा देता हूं. फ़ाइनल 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

डेमो

हालांकि, टैब शेयर करने की सुविधा के लिए, हमें लगातार टैब को कैप्चर करके उसे दर्शकों को भेजना होगा. इसके लिए, मैंने एक छोटा नोड वेबसॉकेट सर्वर, ऐप्लिकेशन, और बुकमार्कलेट लिखा है, जो फ़्लो दिखाता है. अगर आपको कोड में दिलचस्पी नहीं है, तो इस छोटे से वीडियो में इस बारे में बताया गया है कि किन तरीकों का इस्तेमाल किया जा सकता है:

आने वाले समय में किए जाने वाले सुधार

एक ऑप्टिमाइज़ेशन यह नहीं है कि हर फ़्रेम पर पूरे दस्तावेज़ का डुप्लीकेट बनाया जाए. यह बेकार है और म्यूटेशन ऑब्ज़र्वर के उदाहरण में इसे अच्छी तरह माना जाता है. दूसरा सुधार, urlsToAbsolute() में मिलती-जुलती सीएसएस बैकग्राउंड इमेज को मैनेज करना है. मौजूदा स्क्रिप्ट इस पर ध्यान नहीं देती है.

तीसरा तरीका: Chrome एक्सटेंशन एपीआई + बाइनरी WebSocket

Google I/O 2012 में, मैंने ब्राउज़र टैब के कॉन्टेंट को स्क्रीन शेयर करने का एक और तरीका बताया. हालांकि, यह एक चाल है. इसके लिए Chrome एक्सटेंशन API की ज़रूरत है: पूरी तरह से HTML5 मैजिक नहीं.

इसका सोर्स GitHub पर भी मौजूद है, लेकिन मुख्य बात यह है:

  1. मौजूदा टैब को .png dataURL के तौर पर कैप्चर करें. Chrome एक्सटेंशन में उस chrome.tabs.captureVisibleTab() के लिए एक एपीआई है.
  2. dataURL को Blob में बदलें. convertDataURIToBlob() हेल्पर देखें.
  3. socket.responseType='blob' सेट करके बाइनरी वेबसॉकेट का इस्तेमाल करके, दर्शक को हर 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 में बदलाव करने के बजाय उसे सीधे websocket के ज़रिए भेज सकते हैं. ऐसा करने के लिए, कृपया crbug.com/32498 पर स्टार का निशान लगाएं!

चौथा तरीका: WebRTC - आने वाले समय का सही डेटा

आखिरी लेकिन सबसे ज़रूरी बात!

ब्राउज़र में स्क्रीन शेयर करने की सुविधा को आने वाले समय में WebRTC पूरा करेगा. 14 अगस्त, 2012 में, टीम ने टैब का कॉन्टेंट शेयर करने के लिए, WebRTC टैब कॉन्टेंट कैप्चर एपीआई का प्रस्ताव दिया:

जब तक यह लड़का तैयार नहीं होता, तब तक हमारे पास 1-3 तरीकों का इस्तेमाल नहीं होता.

नतीजा

इसलिए, आज की वेब टेक्नोलॉजी के ज़रिए ब्राउज़र टैब शेयर किया जा सकता है!

हालांकि... इस बात को एक दाने के साथ निकालना चाहिए. यह ज़रूरी है कि इस लेख में दी गई तकनीकें, एक या दूसरे तरीके से शेयर करने के लिए बेहतरीन उपयोगकर्ता अनुभव न दें. WebRTC टैब कॉन्टेंट कैप्चर करने की प्रोसेस से यह सब बदल जाएगा. हालांकि, जब तक यह संभव नहीं हो जाता, तब तक हमारे पास ब्राउज़र प्लगिन या सीमित समाधान हैं. इनके बारे में यहां बताया गया है.

क्या आपके पास और तकनीकें हैं? टिप्पणी पोस्ट करें!