مدونة د.خلدون عرفة للروبوتيك
WebRTC Protocol
WebRTC (Web Real-Time Communication) هو بروتوكول مفتوح المصدر يوفر لمتصفحات الويب والتطبيقات إمكانية إجراء اتصالات في الزمن الحقيقي بالصوت والفيديو والبيانات مباشرة بين المستخدمين دون الحاجة إلى إضافات خارجية، حيث تم تطوير WebRTC لتبسيط بناء تطبيقات الاتصالات الفورية على الويب، حيث يسمح بنقل الصوت والفيديو والبث المباشر عبر اتصال نظير-إلى-نظير (Peer-to-Peer) داخل صفحات الويب، مما يلغى الحاجة لتثبيت ملحقات أو برامج وسيطة، بالطبع المواصفات مدعومة من شركات كبرى (مثل Google وMozilla وMicrosoft) وهي متاحة مجانًا ضمن معايير مفتوحة مدعومة في جميع المتصفحات الحديثة.
في هذا المقال سنتحدث بشكل عميق عن WebRTC Protocol ونجرب مشروعا بسيطا في node js يوضح البروتوكول بشكل جيد.
كيف يعمل WebRTC؟
- الإشارة (Signaling): تبدأ عملية الاتصال بتبادل معلومات التفاوض بين الطرفين عبر خادم وسيط خاص بالتطبيق (مثل WebSocket أو HTTP). هذه المعلومات تشمل عروض SDP وICE candidates. المواصفات لا تحدد بروتوكولًا موحدًا للإشارة، بل يقع على عاتق التطبيق تبادل هذه البيانات على قناة خارجية قبل البدء في إنشاء الاتصال.
- إطار ICE وSTUN وTURN: يستخدم WebRTC إطار عمل ICE (تأسيس الاتصال التفاعلي) لاكتشاف أفضل مسار اتصال بين الجهازين عبر الإنترنت. أولاً يرسل الجهاز طلبًا إلى خادم STUN ليكتشف عنوانه العام على الإنترنت ومدى وجود قيود NAT في الشبكة. إذا تعذّر إنشاء الاتصال المباشر بسبب قيود NAT متماثلة (مثل Symmetric NAT)، يتم حينئذٍ اللجوء إلى خادم TURN كنقطة عبور وسيطة لإعادة توجيه كل البيانات بين الطرفين. بهذه الطريقة ينجح إثبات الاتصال حتى ضمن الشبكات المعقدة، وإن كانت الازدحامات أو الكمون قد تزداد عند استخدام خوادم TURN.
- وصف الجلسة (SDP): بعد أن تجمع كل طرف معلومات ICE، يتم التفاوض على خصائص الوسائط عبر بروتوكول *Session Description Protocol (SDP)*. يرسل الطرف المبادر طلب اتصال (offer) يشتمل على وصف SDP يحتوي على تفاصيل الترميزات والجودة والطُرُق الممكنة لنقل الوسائط، ثم يرد الطرف الآخر برد (answer) يحدد موافقته على هذه التفاصيل. هذا التبادل يضمن توافق الطرفين على شكل الاتصال (الصوت والفيديو وترميزاتهما) قبل بدء نقل البيانات.
- إنشاء الاتصال وتبادل البيانات: بعد أن يتفق الطرفان على أوصاف SDP ويكمل كل منهما جمع مرشحي ICE، يُنشأ RTCPeerConnection لإطلاق الاتصال الفعلي. تبدأ بعدها المتصفحات في تبادل المسارات الإعلامية (MediaStream) وبيانات القنوات (RTCDataChannel) مباشرة فيما بينها بدون خادم وسيط. في هذه المرحلة يصبح الاتصال جاهزًا وتبدأ نقل بيانات الصوت والفيديو أو أي بيانات ثنائية أخرى بشكل مباشر بين الطرفين.
مكونات WebRTC
- واجهة RTCPeerConnection: تمثل نقطة الاتصال الرئيسية بين نظيرين في WebRTC. تتيح هذه الواجهة إنشاء الاتصال الصوتي/المرئي بين الجهاز المحلي والنظير البعيد، وإدارة جلسة الاتصال مثل إضافة المسارات الإعلامية (Audio/Video)، والتحكم بالترميزات والأمان، ومراقبة حالة الاتصال وإغلاقه عند الحاجة.
- واجهة MediaStream: تمثل تيارًا وسائطيًا من مسارات متعددة (صوت وفيديو). يمكن الحصول على كائن MediaStream عبر الدالة getUserMedia() لالتقاط الفيديو والصوت من كاميرا وميكروفون الجهاز، أو عبر getDisplayMedia() لمشاركة الشاشة. يحتوي كل تيار إعلامي على مسارات (MediaStreamTrack) يمكن إضافتها أو إزالتها من الاتصال، وتُرسل تلقائيًا عند ربطها بـ RTCPeerConnection.
- واجهة RTCDataChannel: تمثل قناة شبكة ثنائية الاتجاه تتيح تبادل البيانات العامة بين النظيرين بشكل مباشر. تُنشئ عبر RTCPeerConnection.createDataChannel()، وتدعم إرسال رسائل نصية أو ثنائية (مثل الملفات أو تحديثات الألعاب) بكمون منخفض يشبه بروتوكول WebSocket. يمكن لكل اتصال WebRTC إنشاء آلاف قنوات بيانات حسب الحاجة.
- getUserMedia (Media Capture): دالة في واجهة الـ WebRTC تتيح للتطبيق طلب الوصول إلى كاميرا وميكروفون المستخدم. عند المناداة، يطلب المتصفح إذن المستخدم ثم يعيد Promise يحتوي على كائن MediaStream جاهز ليُرسل إلى النظير أو يُعرض في صفحة الويب.
- ICE: إطار عمل يجمع مرشحي الاتصال (ICE Candidates) من الجهاز، وتضم معلومات عن العناوين المحتملة (العنوان الخاص والعام) وأية قيود شبكة. يُمكن هذه المرشحين من محاولة إنشاء اتصال عبر مسارات مختلفة وتجربة STUN أو TURN حسب الحاجة.
- STUN (Session Traversal Utilities for NAT): بروتوكول يُستخدم ضمن ICE لمعرفة عنوان الـ IP العام للجهاز واختبار قيود NAT على الشبكة المحلية. يرسل العميل طلبات إلى خادم STUN على الإنترنت فيُعيد له عنوانه العام وما إذا كان يمكن الوصول إليه من الخارج.
- TURN (Traversal Using Relays around NAT): بروتوكول يستخدم في الحالات التي يكون فيها NAT معقدًا يمنع الاتصال المباشر (مثل NAT متماثل)، فيقوم حينها بإعادة توجيه البيانات عبر خادم وسيط. على الرغم من أنه يزيد الكمون ويستهلك موارد إضافية، إلا أنه يضمن استمرارية الاتصال في بيئات الشبكات الصارمة.
- SDP (Session Description Protocol): معيار لوصف خصائص جلسة الوسائط (مثل تنسيقات الفيديو، الصوت، الترميزات، التشفير، الدقة، الإطارات). يتم تبادل كائنات SDP عند تقديم العرض (offer) والرد (answer) بين الطرفين لضمان فهم متبادل لمواصفات الاتصال الإعلامي قبل نقل البيانات.
- الإشارة (Signaling): عملية تبادل البيانات الوصفية (SDP وICE candidates) بين النظيرين عبر خادم وسيط. WebRTC نفسه لا يحدد كيفية الإشارة؛ لذا فإن التطبيقات تستخدم أي قناة اتصال ثنائية (مثل WebSocket أو بروتوكول HTTP) لنقل هذه المعلومات. دور خادم الإشارة هو توصيل الرسائل بين الطرفين دون الحاجة لفهم محتوياتها، وهو يظل شاغلاً بإرسال/استقبال العروض والإجابات ومعلومات ICE.
حالات الاستخدام العملية
بالطبع لكل تقنية او بروتوكول حالات استخدام يكون مبنيا لاجلها ومن هذه الحالات لبروتوكول WebRTC:
- مكالمات الفيديو والصوت المباشرة: يعتبر WebRTC مناسبًا تمامًا لبناء تطبيقات مؤتمرات الفيديو والصوت ثنائية أو جماعية صغيرة داخل المتصفح. يستخدمه العديد من المنتجات الشهيرة (مثل Microsoft Teams، Slack، Google Meet) لتوفير دردشة فيديو ونقل صوت بدقة عالية في الوقت الحقيقي.
- مشاركة الشاشة والعروض التفاعلية: يتيح WebRTC تبادل بث شاشة الجهاز مع النظير في الوقت الحقيقي، مما يدعم تطبيقات الدعم الفني والعروض التقديمية والتدريب عن بعد دون الحاجة لتثبيت إضافات.
- نقل الملفات (File Sharing): يمكن استخدام RTCDataChannel لنقل الملفات والبيانات عبر اتصال WebRTC دون خادم وسيط. مثال شهير هو تطبيق WebTorrent الذي يعتمد على WebRTC لمشاركة الملفات بين المتصفحات مباشرة.
- الإنترنت الأشياء (IoT) والمراقبة: يُستخدم WebRTC في سيناريوهات إنترنت الأشياء لنقل الفيديو أو البيانات من أجهزة الاستشعار ذكية في الوقت الحقيقي. على سبيل المثال، يمكن تشغيل بث مباشر من كاميرات المراقبة أو الطائرات بدون طيار إلى المتصفحات أو الهواتف الذكية باستخدام WebRTC.
- الترفيه والتجارب التفاعلية: يدخل WebRTC في تطبيقات الواقع الافتراضي والمعزز والألعاب الجماعية لإرسال الفيديو والصوت بسرعة عالية بين اللاعبين.
- معالجة اللغة والنصوص الحية: يمكن دمج واجهات WebRTC مع واجهات التعرف على الصوت لترجمة المحادثات في الوقت الحقيقي أو عرض ترجمات مباشرة داخل تطبيقات مثل الندوات والعروض المباشرة.
فوائد ومميزات WebRTC
اذا من ما كتبناه نستطيع الان استنتاج فوائد وميزات WebRTC والتي يمكن تلخيصها في:
- الاتصال المباشر بين المتصفحات دون إضافات: يعمل WebRTC في المتصفح بشكل أصلي بدون الحاجة إلى أي ملحقات أو تنزيلات. هذا يسهل نشر التطبيقات ويقلل حاجز الدخول للمستخدمين.
- زمن انتقال منخفض (Low Latency): بفضل الاتصال النظير-إلى-نظير (P2P)، يُوفر WebRTC نقلًا فوريًا للوسائط تقريبًا، ما يقلل الكمون مقارنة بالوسائل التقليدية التي تمر عبر خوادم بعيدة.
- مفتوح المصدر ومتوافق عبر المنصات: المواصفات مفتوحة ويحصل عليها جميع المتصفحات الحديثة (Chrome وFirefox وEdge وSafari وغيرها) مما يجعل التطبيق يعمل على الحواسب والهواتف دون مشاكل.
- أمان مدمج: يستخدم WebRTC بروتوكولات تشفير قوية (DTLS-SRTP) لضمان سلامة بيانات الصوت والفيديو أثناء النقل. هذا يعني أن الاتصالات صامتة مشفرة من الطرف إلى الطرف (في حالة عدم وجود وسيط فك تشفير)، مما يزيد من خصوصية المستخدم.
- تقليل الاعتماد على الخوادم الوسطى: نظريًا يمكن إجراء الاتصال باستخدام أقل عدد من الخوادم (خادم إشارة وربما خادم TURN واحد)، وبالتالي تقليل تكاليف البنية التحتية وتوزيع الحمل مقارنة بالحلول التقليدية.
سلبيات وتحديات WebRTC
لكل تقنية او بروتوكول سلبيات او تحديات يواجهها، وهنا يأتي دور المطور في حلها او تفاديها في برنامجه، لكن ليستطيع ذلك عليه معرفتها ويمكننا تلخيصها في:
- مشكلات NAT وجدران الحماية: قد يواجه الاتصالات صعوبة عند وجود NAT معقد أو جدار ناري صارم. رغم استخدام STUN/TURN، فإن بعض الشبكات قد تمنع الاتصال المطلق أو تؤدي إلى تأخيرات عند اللجوء إلى خادم TURN.
- تعقيد الإشارة: يضطر المطورون لإنشاء خادم وسيط (على سبيل المثال عبر WebSocket أو HTTP) للتفاوض وتبادل الرسائل الوصفية، بما أن بروتوكول WebRTC نفسه لا يتضمن آلية إشارة موحدة. بناء وإدارة خادم الإشارة يزيد من التعقيد في عملية التطوير.
- تفاوت دعم المتصفحات: رغم المعيارية العامة، قد تختلف النسخ المختلفة من المتصفحات في مدى دعم ميزات WebRTC أو ترميزاتها. قد تظهر سلوكيات مختلفة بين المتصفحات أو تحتاج لإضافات (polyfills) للتوافق. هذا يفرض على المطورين إجراء اختبارات مكثفة وضبط التوافق باستمرار.
- التوسع في المكالمات الجماعية: النموذج P2P في WebRTC يجعله ملائمًا لمكالمات صغيرة، ولكن عند زيادة عدد المشاركين يزيد عدد الاتصالات المباشرة تفاديًا للحلقات، مما يضخم استهلاك النطاق الترددي والأداء على الأجهزة. عادةً يُستعان حينها ببروكسي وسيط (SFU أو MCU) لإعادة توزيع الوسائط، ما يزيد التعقيد وممكن أن يؤثر على التشفير الطرفي الكامل.
- التكلفة والأداء عند استخدام TURN: إذا احتاج التطبيق إلى خوادم TURN متعددة لتجاوز القيود العالمية في الشبكات، فإن ذلك يؤدي إلى ازدحام مروري وتكاليف إضافية بالنطاق الترددي على الخوادم. الاعتماد المفرط على TURN يقلل من مزايا الاتصال المباشر الأصلي.
متى وأين يُفضل استخدام WebRTC
- الاتصالات التفاعلية المباشرة: يُفضل WebRTC للمكالمات الثنائية والصغيرة مثل دردشة الفيديو أو دعم العملاء المرئي، حيث يوفر زمن استجابة منخفض وتفاعلية عالية.
- التعليم والتدريب عن بعد والرعاية الصحية: في السيناريوهات التي تتطلب خصوصية (مثل المؤتمرات الطبية أو الصفوف الافتراضية)، يتيح WebRTC تنفيذ تشفير مضمن وإرسال بيانات مباشرة بين الأجهزة.
- تطبيقات إنترنت الأشياء (IoT): عند الحاجة لإرسال فيديو/صور أو بيانات حسية في الوقت الحقيقي من أجهزة ميدانية إلى خوادم أو هواتف، يعتبر WebRTC خيارًا مناسبًا بسبب دعمه الصوتي/الفيديو المدمج.
الحالات التي لا تناسب فيها WebRTC:
لا يُعد WebRTC الخيار الأمثل للبث الجماعي واسع النطاق (مثل نقل فيديو لمئات أو آلاف المشاهدين) دون بناء بنية تحتية خاصة (مثل SFU/MCU). كما قد يحتاج التطبيقات التي تتطلب تحكمًا عميقًا للغاية في الترميزات أو واجهات مخصصة خارج ما يوفره WebRTC حلولًا أخرى أو إضافات.
مشروع بسيط لتجربة بروتوكول WebRTC
سيتكون المشروع خاصتنا من عدة ملفات ستجد كود كل ملف منفصلا وموضحا ادناه وبالنهاية طريقة التشغيل:
كود ملف server.js
const fs = require('fs');
const https = require('https')
const express = require('express');
const app = express();
const socketio = require('socket.io');
app.use(express.static(__dirname))
//we need a key and cert to run https
//we generated them with mkcert
// $ mkcert create-ca
// $ mkcert create-cert
const key = fs.readFileSync('cert.key');
const cert = fs.readFileSync('cert.crt');
//we changed our express setup so we can use https
//pass the key and cert to createServer on https
const expressServer = https.createServer({key, cert}, app);
//create our socket.io server... it will listen to our express port
const io = socketio(expressServer,{
cors: {
origin: [
"https://localhost",
'https://192.168.1.5' //if using a phone or another computer
],
methods: ["GET", "POST"]
}
});
expressServer.listen(8181);
//offers will contain {}
const offers = [
// offererUserName
// offer
// offerIceCandidates
// answererUserName
// answer
// answererIceCandidates
];
const connectedSockets = [
//username, socketId
]
io.on('connection',(socket)=>{
// console.log("Someone has connected");
const userName = socket.handshake.auth.userName;
const password = socket.handshake.auth.password;
if(password !== "x"){
socket.disconnect(true);
return;
}
connectedSockets.push({
socketId: socket.id,
userName
})
//a new client has joined. If there are any offers available,
//emit them out
if(offers.length){
socket.emit('availableOffers',offers);
}
socket.on('newOffer',newOffer=>{
offers.push({
offererUserName: userName,
offer: newOffer,
offerIceCandidates: [],
answererUserName: null,
answer: null,
answererIceCandidates: []
})
// console.log(newOffer.sdp.slice(50))
//send out to all connected sockets EXCEPT the caller
socket.broadcast.emit('newOfferAwaiting',offers.slice(-1))
})
socket.on('newAnswer',(offerObj,ackFunction)=>{
console.log(offerObj);
//emit this answer (offerObj) back to CLIENT1
//in order to do that, we need CLIENT1's socketid
const socketToAnswer = connectedSockets.find(s=>s.userName === offerObj.offererUserName)
if(!socketToAnswer){
console.log("No matching socket")
return;
}
//we found the matching socket, so we can emit to it!
const socketIdToAnswer = socketToAnswer.socketId;
//we find the offer to update so we can emit it
const offerToUpdate = offers.find(o=>o.offererUserName === offerObj.offererUserName)
if(!offerToUpdate){
console.log("No OfferToUpdate")
return;
}
//send back to the answerer all the iceCandidates we have already collected
ackFunction(offerToUpdate.offerIceCandidates);
offerToUpdate.answer = offerObj.answer
offerToUpdate.answererUserName = userName
//socket has a .to() which allows emiting to a "room"
//every socket has it's own room
socket.to(socketIdToAnswer).emit('answerResponse',offerToUpdate)
})
socket.on('sendIceCandidateToSignalingServer',iceCandidateObj=>{
const { didIOffer, iceUserName, iceCandidate } = iceCandidateObj;
// console.log(iceCandidate);
if(didIOffer){
//this ice is coming from the offerer. Send to the answerer
const offerInOffers = offers.find(o=>o.offererUserName === iceUserName);
if(offerInOffers){
offerInOffers.offerIceCandidates.push(iceCandidate)
// 1. When the answerer answers, all existing ice candidates are sent
// 2. Any candidates that come in after the offer has been answered, will be passed through
if(offerInOffers.answererUserName){
//pass it through to the other socket
const socketToSendTo = connectedSockets.find(s=>s.userName === offerInOffers.answererUserName);
if(socketToSendTo){
socket.to(socketToSendTo.socketId).emit('receivedIceCandidateFromServer',iceCandidate)
}else{
console.log("Ice candidate recieved but could not find answere")
}
}
}
}else{
//this ice is coming from the answerer. Send to the offerer
//pass it through to the other socket
const offerInOffers = offers.find(o=>o.answererUserName === iceUserName);
const socketToSendTo = connectedSockets.find(s=>s.userName === offerInOffers.offererUserName);
if(socketToSendTo){
socket.to(socketToSendTo.socketId).emit('receivedIceCandidateFromServer',iceCandidate)
}else{
console.log("Ice candidate recieved but could not find offerer")
}
}
// console.log(offers)
})
})
كود ملف socketListeners.js
//on connection get all available offers and call createOfferEls
socket.on('availableOffers',offers=>{
console.log(offers)
createOfferEls(offers)
})
//someone just made a new offer and we're already here - call createOfferEls
socket.on('newOfferAwaiting',offers=>{
createOfferEls(offers)
})
socket.on('answerResponse',offerObj=>{
console.log(offerObj)
addAnswer(offerObj)
})
socket.on('receivedIceCandidateFromServer',iceCandidate=>{
addNewIceCandidate(iceCandidate)
console.log(iceCandidate)
})
function createOfferEls(offers){
//make green answer button for this new offer
const answerEl = document.querySelector('#answer');
offers.forEach(o=>{
console.log(o);
const newOfferEl = document.createElement('div');
newOfferEl.innerHTML = `<button class="btn btn-success col-1">Answer ${o.offererUserName}</button>`
newOfferEl.addEventListener('click',()=>answerOffer(o))
answerEl.appendChild(newOfferEl);
})
}
كود ملف scripts.js
const userName = "user-"+Math.floor(Math.random() * 1000)
const password = "x";
document.querySelector('#user-name').innerHTML = userName;
//if trying it on a phone, use this instead...
const socket = io.connect('https://192.168.1.5:8181/',{
// const socket = io.connect('https://localhost:8181/',{
auth: {
userName,password
}
})
const localVideoEl = document.querySelector('#local-video');
const remoteVideoEl = document.querySelector('#remote-video');
let localStream; //a var to hold the local video stream
let remoteStream; //a var to hold the remote video stream
let peerConnection; //the peerConnection that the two clients use to talk
let didIOffer = false;
let peerConfiguration = {
iceServers:[
{
urls:[
'stun:stun.l.google.com:19302',
'stun:stun1.l.google.com:19302'
]
}
]
}
//when a client initiates a call
const call = async e=>{
await fetchUserMedia();
//peerConnection is all set with our STUN servers sent over
await createPeerConnection();
//create offer time!
try{
console.log("Creating offer...")
const offer = await peerConnection.createOffer();
console.log(offer);
peerConnection.setLocalDescription(offer);
didIOffer = true;
socket.emit('newOffer',offer); //send offer to signalingServer
}catch(err){
console.log(err)
}
}
const answerOffer = async(offerObj)=>{
await fetchUserMedia()
await createPeerConnection(offerObj);
const answer = await peerConnection.createAnswer({}); //just to make the docs happy
await peerConnection.setLocalDescription(answer); //this is CLIENT2, and CLIENT2 uses the answer as the localDesc
console.log(offerObj)
console.log(answer)
// console.log(peerConnection.signalingState) //should be have-local-pranswer because CLIENT2 has set its local desc to it's answer (but it won't be)
//add the answer to the offerObj so the server knows which offer this is related to
offerObj.answer = answer
//emit the answer to the signaling server, so it can emit to CLIENT1
//expect a response from the server with the already existing ICE candidates
const offerIceCandidates = await socket.emitWithAck('newAnswer',offerObj)
offerIceCandidates.forEach(c=>{
peerConnection.addIceCandidate(c);
console.log("======Added Ice Candidate======")
})
console.log(offerIceCandidates)
}
const addAnswer = async(offerObj)=>{
//addAnswer is called in socketListeners when an answerResponse is emitted.
//at this point, the offer and answer have been exchanged!
//now CLIENT1 needs to set the remote
await peerConnection.setRemoteDescription(offerObj.answer)
// console.log(peerConnection.signalingState)
}
const fetchUserMedia = ()=>{
return new Promise(async(resolve, reject)=>{
try{
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
localVideoEl.srcObject = stream;
localStream = stream;
resolve();
}catch(err){
console.log(err);
reject()
}
})
}
const createPeerConnection = (offerObj)=>{
return new Promise(async(resolve, reject)=>{
//RTCPeerConnection is the thing that creates the connection
//we can pass a config object, and that config object can contain stun servers
//which will fetch us ICE candidates
peerConnection = await new RTCPeerConnection(peerConfiguration)
remoteStream = new MediaStream()
remoteVideoEl.srcObject = remoteStream;
localStream.getTracks().forEach(track=>{
//add localtracks so that they can be sent once the connection is established
peerConnection.addTrack(track,localStream);
})
peerConnection.addEventListener("signalingstatechange", (event) => {
console.log(event);
console.log(peerConnection.signalingState)
});
peerConnection.addEventListener('icecandidate',e=>{
console.log('........Ice candidate found!......')
console.log(e)
if(e.candidate){
socket.emit('sendIceCandidateToSignalingServer',{
iceCandidate: e.candidate,
iceUserName: userName,
didIOffer,
})
}
})
peerConnection.addEventListener('track',e=>{
console.log("Got a track from the other peer!! How excting")
console.log(e)
e.streams[0].getTracks().forEach(track=>{
remoteStream.addTrack(track,remoteStream);
console.log("Here's an exciting moment... fingers cross")
})
})
if(offerObj){
//this won't be set when called from call();
//will be set when we call from answerOffer()
// console.log(peerConnection.signalingState) //should be stable because no setDesc has been run yet
await peerConnection.setRemoteDescription(offerObj.offer)
// console.log(peerConnection.signalingState) //should be have-remote-offer, because client2 has setRemoteDesc on the offer
}
resolve();
})
}
const addNewIceCandidate = iceCandidate=>{
peerConnection.addIceCandidate(iceCandidate)
console.log("======Added Ice Candidate======")
}
document.querySelector('#call').addEventListener('click',call)
طريقة تشغيل المشروع
1.ثبت المكتبات اللازمة:
npm init –y
npm install express socket.io mkcert
npm install mkcert -g
2.تحقق من الIP Address الخاص بجهازك وقم بانشاء ملفات الcert لانشاء اتصال https:
ipconfig
mkcert create-ca
mkcert create-cert
3.ضع IP جهازك في ملف scripts.js في السطر 6 وفي ملف server.js في السطر 20
4.شغل المشروع:
node server.js
5.افتح الرابط التالي في المتصفح خاصتك (مع وضع الip الخاص بجهازك في الرابط) في جهازين او صفحتين مختلفتين:
https://<your-IPv4-Address>:8181
هكذا يكون اصبح لديك معلومات جيدة وعميقة عن بروتوكول WebRTC مع مشروع بسيط يمكنك البدء منه للابحار في مشروع اضخم !
WebRTC ليس البروتوكول الوحيد في مجاله، لكنه من الافضل والاكثر انتشارا ويمكنك البدء منه لتتطور لاحقا لبروتوكولات وطرق اعقد واضخم، حيث انه يفتح بابا جديدا تماما في التطوير !
https://www.facebook.com/groups/arabicyoungtalentrobotics/
https://www.youtube.com/@YoungTalentRobotics
حول المقالة
-
متوسط
عندما تشترك في المدونة سنرسل لك بريدا إلكترونيا عندما تكون هناك مقالات جديدة على الموقع حتى لا تفوتها.
تعليقات