بدء استخدام اتصالات الزملاء

الاتصالات بين التطبيقات المشابهة هي جزء من مواصفات WebRTC التي تتعامل مع ربط تطبيقين على أجهزة كمبيوتر مختلفة للتواصل باستخدام بروتوكول نظير إلى نظير. يمكن أن يكون الاتصال بين التطبيقات المشابهة فيديو أو صوتًا أو بيانات ثنائية عشوائية (للعملاء الذين يدعمون واجهة برمجة تطبيقات RTCDataChannel). لاكتشاف إمكانية اتصال اثنين من التطبيقات المشابهة، يحتاج كلا العميلين إلى توفير إعداد ICE Server. إما أن يكون خادم STUN أو خادم TURN، ودوره هو توفير مرشحي ICE لكل عميل ثم يتم نقله إلى نظيره عن بُعد. ويُطلق على عملية نقل المرشحين من ICE عادةً اسم الإشارات.

إرسال الإشارات

تتضمن مواصفات WebRTC واجهات برمجة التطبيقات للاتصال بخادم ICE (إنترنت الاتصال بالإنترنت)، ولكن مكون الإشارة ليس جزءًا منه. ويجب إرسال الإشارات حتى يتمكّن زملاؤك من مشاركة كيفية تواصلهم مع الآخرين. ويتم عادةً حلّ هذه المشكلة من خلال واجهة برمجة تطبيقات عادية مستندة إلى HTTP (أي خدمة REST أو آلية أخرى لاستدعاء الإجراء عن بُعد (RPC)) حيث يمكن لتطبيقات الويب إرسال المعلومات اللازمة قبل بدء اتصال الأقران.

يعرض مقتطف الرمز التالي طريقة استخدام خدمة الإشارة الزائفة هذه لإرسال الرسائل واستلامها بشكل غير متزامن. وسيتم استخدامه في الأمثلة المتبقية في هذا الدليل عند الضرورة.

// Set up an asynchronous communication channel that will be
// used during the peer connection setup
const signalingChannel = new SignalingChannel(remoteClientId);
signalingChannel.addEventListener('message', message => {
    // New message from remote client received
});

// Send an asynchronous message to the remote client
signalingChannel.send('Hello!');

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

بدء اتصالات الأقران

يتم التعامل مع كل اتصال نظير من خلال عنصر RTCPeerConnection. تستخدم دالة إنشاء هذه الفئة كائن RTCConfiguration واحد كمعلَمة. يحدد هذا الكائن كيفية إعداد اتصال النظير ويجب أن يحتوي على معلومات عن خوادم ICE لاستخدامها.

بعد إنشاء RTCPeerConnection، نحتاج إلى إنشاء عرض أو بطاقة SDP، وذلك بناءً على ما إذا كنا نتصل من نظير لنظير أو مستلِم. بعد إنشاء عرض SDP أو الإجابة، يجب إرساله إلى النظير البعيد من خلال قناة مختلفة. يُطلق على تمرير كائنات SDP إلى التطبيقات المشابهة عن بُعد الإشارة وهي لا تشملها مواصفات WebRTC.

لبدء إعداد اتصال النظير من جهة الاستدعاء، ننشئ كائن RTCPeerConnection ثم ناستدعاء createOffer() لإنشاء كائن RTCSessionDescription. يتم ضبط وصف الجلسة هذا كوصف محلي باستخدام setLocalDescription() ثم يتم إرساله عبر قناة الإشارة الخاصة بنا إلى جانب الاستلام. وأعددنا أيضًا مستمعًا إلى قناتنا للإشارة عندما يتم تلقّي إجابة عن وصف الجلسة الذي يقدّمه المستخدم.

async function makeCall() {
    const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
    const peerConnection = new RTCPeerConnection(configuration);
    signalingChannel.addEventListener('message', async message => {
        if (message.answer) {
            const remoteDesc = new RTCSessionDescription(message.answer);
            await peerConnection.setRemoteDescription(remoteDesc);
        }
    });
    const offer = await peerConnection.createOffer();
    await peerConnection.setLocalDescription(offer);
    signalingChannel.send({'offer': offer});
}

في الجانب المتلقّي، ننتظر عرضًا واردًا قبل إنشاء مثيل RTCPeerConnection. وبعد الانتهاء، نضبط العرض الذي تم الحصول عليه باستخدام setRemoteDescription(). بعد ذلك، نتصل بـ createAnswer() لإنشاء إجابة على العرض الذي تم استلامه. تم ضبط هذه الإجابة على أنها وصف محلي باستخدام setLocalDescription()، ثم تم إرسالها إلى جانب الاتصال من خلال خادم الإشارات.

const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
    if (message.offer) {
        peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
        const answer = await peerConnection.createAnswer();
        await peerConnection.setLocalDescription(answer);
        signalingChannel.send({'answer': answer});
    }
});

وبعد أن يحدّد كلاكما أوصاف الجلسة المحلية والبعد عن بُعد، فإنهما يعرفان إمكانيات النظراء عن بُعد. ولا يعني هذا أن الاتصال بين الزملاء جاهز. ولإجراء ذلك، يجب جمع مرشحي ICE على مستوى كل زميل ونقله (عبر قناة الإشارة) إلى نظيره الآخر.

مرشحو ICE

قبل أن يتمكّن زميلان من التواصل باستخدام WebRTC، يجب تبادل معلومات الاتصال. نظرًا لاختلاف ظروف الشبكة بناءً على عدد من العوامل، يتم عادةً استخدام الخدمة الخارجية لاكتشاف المرشحين المحتملين للتواصل مع نظير. هذه الخدمة تُسمى ICE وتستخدم إما STUN أو خادم TURN. يشير الاختصار STUN إلى أداة "اجتياز الجلسة" Utilities for Utilities for NAT (نُسخ المساعدة) ويُستخدم عادة بشكل غير مباشر في معظم تطبيقات WebRTC.

TURN (Traversal Using Relay NAT) هو الحل الأكثر تقدّمًا الذي يتضمن بروتوكولات STUN ومعظم الخدمات المستندة إلى WebRTC، ويستخدم خادم TURN لإنشاء اتصالات بين التطبيقات المشابهة. تتوافق واجهة برمجة تطبيقات WebRTC مع كل من STUN وTURN مباشرةً، ويتم جمعها بموجب بنود اتصال الإنترنت الأكثر اكتمالاً. عند إنشاء اتصال WebRTC، نتيح عادةً خادمًا واحدًا أو عدة خوادم ICE في ضبط العنصر RTCPeerConnection.

Trickle ICE

بعد إنشاء عنصر RTCPeerConnection، يستخدم إطار العمل الأساسي خوادم ICE المقدَّمة لجمع المرشحين لمؤسسة الاتصال (ICEi). يشير الحدث icegatheringstatechange على RTCPeerConnection إلى الحالة التي يتم فيها جمع بيانات ICE (أي new أو gathering أو complete).

وعلى الرغم من أنه من الممكن أن ينتظر أحد الأقران حتى اكتمال تجمُّع اللجنة المهندسية الدولية (ICE)، فإنه يكون عادةً أكثر فعالية استخدام الأسلوب "خادع للثلج&نقل البيانات" ونقل كل مرشح ICE إلى نظيره عن بُعد عند اكتشافه. وسيؤدي ذلك إلى تقليل وقت الإعداد بشكل كبير لاتصال الزملاء ويتيح بدء مكالمة فيديو بتأخير أقل.

لجمع مرشحي ICE، ما عليك سوى إضافة مستمع إلى حدث icecandidate. سيحتوي الحقل RTCPeerConnectionIceEvent المنبّه على هذا المستمع على سمة candidate التي تمثل مرشّحًا جديدًا ويجب إرساله إلى النظير البعيد (راجِع الإشارة).

// Listen for local ICE candidates on the local RTCPeerConnection
peerConnection.addEventListener('icecandidate', event => {
    if (event.candidate) {
        signalingChannel.send({'new-ice-candidate': event.candidate});
    }
});

// Listen for remote ICE candidates and add them to the local RTCPeerConnection
signalingChannel.addEventListener('message', async message => {
    if (message.iceCandidate) {
        try {
            await peerConnection.addIceCandidate(message.iceCandidate);
        } catch (e) {
            console.error('Error adding received ice candidate', e);
        }
    }
});

تم إنشاء اتصال

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

// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
    if (peerConnection.connectionState === 'connected') {
        // Peers connected!
    }
});

وثائق واجهة برمجة تطبيقات RTCPeerConnection