هل تريد مشاركة الشاشة مع علامة تبويب متصفّح باستخدام HTML5؟

خلال العامَين الماضيَين، ساعدتُ بضع شركات مختلفة في الحصول على وظائف مشابهة لمشاركة الشاشة باستخدام تكنولوجيات المتصفّحات فقط. من تجربتي، إنّ تنفيذ VNC في تكنولوجيات منصة الويب فقط (أي بدون مكونات إضافية) يشكّل مشكلة صعبة. هناك الكثير من الأمور التي يجب أخذها في الاعتبار والكثير من التحديات التي يجب التغلب عليها. إنّ إعادة توجيه موضع مؤشر الماوس وإعادة توجيه ضغطات المفاتيح وإعادة طلاء الألوان بالكامل بدقة 24 بت بمعدّل 60 لقطة في الثانية هي بعض المشاكل التي تواجهها.

تسجيل محتوى علامة التبويب

إذا أزلنا تعقيدات ميزة مشاركة الشاشة التقليدية وركّزنا على مشاركة محتوى علامة تبويب المتصفّح، تتبسّط المشكلة إلى حد كبير لتصبح: أ) التقاط علامة التبويب المرئية في حالتها الحالية، و(ب) إرسال هذا "الإطار" عبر الإنترنت. نحتاج في الأساس إلى طريقة لالتقاط لقطة شاشة لـ DOM ومشاركتها.

إنّ عملية المشاركة سهلة. يمكن لواجهات WebSocket إرسال البيانات بتنسيقات مختلفة (سلسلة أو JSON أو ثنائي). يُعدّ جزء لقطات الشاشة مشكلة أكثر صعوبة. عالجت مشاريع مثل html2canvas مشكلة التقاط لقطات شاشة من صفحات HTML من خلال إعادة تنفيذ محرّك عرض المتصفّح…باستخدام JavaScript. ومن الأمثلة الأخرى على ذلك ملاحظات Google، على الرغم من أنّه ليس مفتوح المصدر. هذه الأنواع من المشاريع رائعة جدًا، ولكنها أيضًا بطيئة جدًا. سيكون من حسن حظك الحصول على معدل نقل بيانات يبلغ إطارًا واحدًا في الثانية، ناهيك عن معدل النقل المطلوب الذي يبلغ 60 إطارًا في الثانية.

تتناول هذه المقالة بعض الحلول المفضّلة لديّ لإثبات إمكانية "مشاركة الشاشة" لإحدى علامات التبويب.

الطريقة 1: مراقبو الطفرات وWebSocket

في وقت سابق من هذا العام، شرح رافائيل وينشتاين إحدى الطرق لعرض علامة تبويب على شاشة ثانية. يستخدم أسلوبه مراقبي الطفرات وWebSocket.

في الأساس، ترصد علامة التبويب التي يشاركها المُقدّم التغييرات في الصفحة وتُرسِل الاختلافات إلى المشاهد باستخدام WebSocket. وعندما ينتقل المستخدم للأعلى أو للأسفل في الصفحة أو يتفاعل معها، يرصد المراقبون هذه التغييرات ويُبلغون المشاهد بها باستخدام مكتبة ملخّص الطفرات في Rafael. ويحافظ ذلك على مستوى الأداء. لا يتم إرسال الصفحة بأكملها لكل إطار.

كما يشير "رافائيل" في الفيديو، هذا مجرد دليل على إمكانية تنفيذ الفكرة. مع ذلك، أعتقد أنّها طريقة رائعة لدمج ميزة منصة أحدث، مثل Mutation Observers، مع ميزة قديمة، مثل Websockets.

الطريقة 2: رمز Blob من HTMLDocument + WebSocket ثنائي

هذه الطريقة التالية هي طريقة حديثة اكتشفتها مؤخرًا. يشبه هذا الأسلوب نهج Mutation Observers، ولكن بدلاً من إرسال اختلافات موجزة، يتم إنشاء نسخة طبق الأصل من HTMLDocument بأكملها وإرسالها عبر WebSocket ثنائي. في ما يلي الخطوات التي يجب اتّباعها لكل عملية إعداد:

  1. أعد كتابة جميع عناوين URL على الصفحة لتكون مطلقة. ويمنع ذلك أن تحتوي ملفات CSS وملفات الصور الثابتة على روابط معطّلة.
  2. استنسِخ عنصر مستند الصفحة: document.documentElement.cloneNode(true);
  3. يمكنك جعل النسخة المكرّرة للعنصر للقراءة فقط وغير قابلة للاختيار ومنع الانتقال للأعلى أو للأسفل باستخدام CSS. pointer-events: 'none';user-select:'none';overflow:hidden;
  4. سجِّل موضع التمرير الحالي للصفحة وأضِفه كسمات data-* في الصفحة المكرّرة.
  5. أنشئ 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 للملفّات المصغّرة (مثلاً من مصدر مختلف).

لقد أجريت تعديلاً أخيرًا على التطبيق يتيح التمرير. عندما ينتقل المُقدّم للأسفل أو للأعلى في الصفحة، يجب أن يتبعه المشاهد. لإجراء ذلك، أُخفي مواضع scrollX وscrollY الحالية كسمات data-* في العنصر المكرّر HTMLDocument. قبل إنشاء العنصر النهائي من النوع Blob، يتمّ إدراج جزء من JavaScript يتمّ تشغيله عند تحميل الصفحة:

// 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 + WebSocket الثنائي

في مؤتمر Google I/O لعام 2012، عرضتُ أسلوبًا آخر لمشاركة الشاشة وعرض محتوى علامة تبويب في المتصفّح. ومع ذلك، هذا الإجراء هو عملية خداع. وتتطلّب هذه الميزة استخدام واجهة برمجة تطبيقات إضافات Chrome، وليس سحر HTML5 الخالص.

يمكن العثور على مصدر هذه الميزة أيضًا على GitHub، ولكن في ما يلي أهم التفاصيل:

  1. التقاط علامة التبويب الحالية كعنوان URL لملف بتنسيق ‎.png تتوفّر واجهة برمجة تطبيقات في إضافات Chrome لهذا الغرض chrome.tabs.captureVisibleTab().
  2. حوِّل dataURL إلى Blob. يُرجى الاطّلاع على مساعدة convertDataURIToBlob().
  3. أرسِل كل 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، اقترح الفريق واجهة برمجة تطبيقات WebRTC Tab Content Capture لمشاركة محتوى علامات التبويب:

إلى أن يصبح هذا الخيار متاحًا، تتوفّر لنا الطرق من 1 إلى 3.

الخاتمة

وبالتالي، يمكن مشاركة علامات التبويب في المتصفّح باستخدام تكنولوجيا الويب الحالية.

ولكن يجب أخذ هذا البيان بحذر. على الرغم من أنّ التقنيات الواردة في هذه المقالة رائعة، إلا أنّها لا توفّر تجربة مشاركة رائعة بطريقة أو بأخرى. سيتغيّر كل ذلك بفضل جهود WebRTC لالتقاط محتوى علامات التبويب، ولكن إلى أن يصبح ذلك حقيقة، لا يتوفّر لدينا سوى الإضافات في المتصفّح أو الحلول المحدودة مثل تلك التي تم تناولها هنا.

هل لديك المزيد من التقنيات؟ يُرجى نشر تعليق.