جاوا اسکریپترایانه

WebRTC چیست

ارتباط بلادرنگ برای وب

با WebRTC، می‌توانید قابلیت‌های ارتباطی بی‌درنگ را به برنامه خود اضافه کنید که بر روی یک استاندارد باز کار می‌کند. از ویدئو، صدا و داده های عمومی برای ارسال بین همتایان پشتیبانی می کند و به توسعه دهندگان این امکان را می دهد تا راه حل های قدرتمند ارتباط صوتی و تصویری بسازند. این فناوری در تمام مرورگرهای مدرن و همچنین در مشتریان بومی برای همه پلتفرم های اصلی در دسترس است. فن آوری های پشت WebRTC به عنوان یک استاندارد وب باز پیاده سازی شده و به عنوان API های جاوا اسکریپت معمولی در همه مرورگرهای اصلی موجود است. برای مشتریان بومی، مانند برنامه‌های Android و iOS، کتابخانه‌ای در دسترس است که عملکرد مشابهی را ارائه می‌کند. پروژه WebRTC منبع باز است و توسط اپل، گوگل، مایکروسافت و موزیلا و غیره پشتیبانی می شود. این صفحه توسط تیم Google WebRTC نگهداری می شود.

WebRTC چه کاری می تواند انجام دهد؟

موارد استفاده مختلفی برای WebRTC وجود دارد، از برنامه‌های وب اولیه که از دوربین یا میکروفون استفاده می‌کنند تا برنامه‌های پیشرفته‌تر تماس ویدیویی و اشتراک‌گذاری صفحه. ما تعدادی نمونه کد جمع آوری کرده ایم تا بهتر نشان دهیم این فناوری چگونه کار می کند و برای چه کاری می توانید از آن استفاده کنید.

نمونه ها

جریان برنامه

یک برنامه WebRTC معمولاً از یک جریان برنامه مشترک عبور می کند. دسترسی به دستگاه‌های رسانه، باز کردن اتصالات همتا، کشف همتایان و شروع پخش جریانی. ما به توسعه دهندگان جدید توصیه می کنیم قبل از شروع توسعه، مقدمه ما در WebRTC را مطالعه کنند.

شروع با WebRTC

اگر با API ها آشنا نباشید، ایجاد یک برنامه جدید بر اساس فناوری های WebRTC می تواند بسیار دشوار باشد. در این بخش نحوه شروع کار با API های مختلف در استاندارد WebRTC را با توضیح تعدادی از موارد استفاده رایج و قطعه کد برای حل آن ها نشان خواهیم داد.

API های WebRTC

استاندارد WebRTC در سطح بالایی دو فناوری مختلف را پوشش می‌دهد: دستگاه‌های ضبط رسانه و اتصال همتا به همتا.

دستگاه‌های ضبط رسانه شامل دوربین‌های فیلمبرداری و میکروفون‌ها، اما همچنین «دستگاه‌های» تصویربرداری از صفحه است. برای دوربین‌ها و میکروفون‌ها، از navigator.mediaDevices.getUserMedia() برای ضبط MediaStreams استفاده می‌کنیم. برای ضبط صفحه، به جای آن از navigator.mediaDevices.getDisplayMedia() استفاده می کنیم.

اتصال همتا به همتا توسط رابط RTCPeerConnection می شود. این نقطه مرکزی برای ایجاد و کنترل ارتباط بین دو همشروع کار با دستگاه های رسانه ای

هنگام توسعه برای وب، استاندارد WebRTC APIهایی را برای دسترسی به دوربین ها و میکروفون های متصل به رایانه یا تلفن هوشمند ارائه می دهد. به این دستگاه‌ها معمولاً Media Devices گفته می‌شود و می‌توان با جاوا اسکریپت از طریق شی navigator.mediaDevices که رابط MediaDevices را پیاده‌سازی می‌کند، به آن‌ها دسترسی داشت. از این شی ما می‌توانیم همه دستگاه‌های متصل را برشماریم، به تغییرات دستگاه گوش دهیم (هنگامی که دستگاهی متصل یا قطع شود)، و دستگاهی را برای بازیابی جریان رسانه باز کنیم (به زیر مراجعه کنید).

رایج‌ترین روش استفاده از این تابع از طریق تابع getUserMedia() است که یک وعده به MediaStream برای دستگاه‌های رسانه منطبق برمی‌گرداند. این تابع یک شئ MediaStreamConstraints را می گیرد که نیازمندی های ما را مشخص می کند. به عنوان مثال، برای باز کردن میکروفون و دوربین پیش فرض، موارد زیر را انجام می دهیم.تا در WebRTC است.

استفاده از وعده

<!DOCTYPE html>
<html>
<head>
<head><title>Local video playback</video></head>
</head>
<body>
<video id="localVideo" autoplay playsinline controls="false"/>
<script>
const constraints = {
    'video': true,
    'audio': true
}
navigator.mediaDevices.getUserMedia(constraints)
    .then(stream => {
        console.log('Got MediaStream:', stream);
    })
    .catch(error => {
        console.error('Error accessing media devices.', error);
    });
</script>
</body>
</html> 

استفاده-از-async/wait

const openMediaDevices = async (constraints) => {
    return await navigator.mediaDevices.getUserMedia(constraints);
}

try {
    const stream = openMediaDevices({'video':true,'audio':true});
    console.log('Got MediaStream:', stream);
} catch(error) {
    console.error('Error accessing media devices.', error);
}

فراخوانی getUserMedia() یک درخواست مجوز را راه اندازی می کند. اگر کاربر مجوز را بپذیرد، وعده با MediaStream شامل یک ویدیو و یک آهنگ صوتی حل می شود. اگر مجوز رد شود، یک PermissionDeniedError پرتاب می شود. در صورتی که دستگاه منطبقی متصل نباشد، NotFoundError پرتاب می شود.

مرجع کامل API برای رابط MediaDevices در MDN web docs موجود است.

پرس و جو از دستگاه های رسانه ای

در یک برنامه پیچیده تر، ما به احتمال زیاد می خواهیم تمام دوربین ها و میکروفون های متصل را بررسی کنیم و بازخورد مناسب را به کاربر ارائه دهیم. این کار را می توان با فراخوانی تابع enumerateDevices() انجام داد. این یک وعده به آرایه ای از MediaDevicesInfo که هر دستگاه رسانه شناخته شده را توصیف می کند، باز می گرداند. ما می‌توانیم از این برای ارائه یک رابط کاربری به کاربر استفاده کنیم که به او اجازه می‌دهیم رابطی را که ترجیح می‌دهد انتخاب کند. هر MediaDevicesInfo حاوی یک ویژگی به نام kind با مقدار audioinput ، audiooutput یا videoinput است که نشان می دهد نوع دستگاه رسانه ای آن چیست.

استفاده از وعده

function getConnectedDevices(type, callback) {
    navigator.mediaDevices.enumerateDevices()
        .then(devices => {
            const filtered = devices.filter(device => device.kind === type);
            callback(filtered);
        });
}

getConnectedDevices('videoinput', cameras => console.log('Cameras found', cameras));

استفاده-از-async/wait

async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

const videoCameras = getConnectedDevices('videoinput');
console.log('Cameras found:', videoCameras);

گوش دادن به دستگاه ها تغییر می کند

اکثر رایانه ها از اتصال دستگاه های مختلف در زمان اجرا پشتیبانی می کنند. این می تواند یک وب کم متصل به USB، یک هدست بلوتوث یا مجموعه ای از بلندگوهای خارجی باشد. به منظور پشتیبانی مناسب از این، یک برنامه وب باید به تغییرات دستگاه های رسانه گوش دهد. این را می توان با افزودن شنونده به navigator.mediaDevices برای رویداد devicechange داد.

// Updates the select element with the provided set of cameras
function updateCameraList(cameras) {
    const listElement = document.querySelector('select#availableCameras');
    listElement.innerHTML = '';
    cameras.map(camera => {
        const cameraOption = document.createElement('option');
        cameraOption.label = camera.label;
        cameraOption.value = camera.deviceId;
    }).forEach(cameraOption => listElement.add(cameraOption));
}

// Fetch an array of devices of a certain type
async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

// Get the initial set of cameras connected
const videoCameras = getConnectedDevices('videoinput');
updateCameraList(videoCameras);

// Listen for changes to media devices and update the list accordingly
navigator.mediaDevices.addEventListener('devicechange', event => {
    const newCameraList = getConnectedDevices('video');
    updateCameraList(newCameraList);
});

محدودیت های رسانه ای

شیء محدودیت، که باید رابط MediaStreamConstraints را پیاده‌سازی کند، که به‌عنوان پارامتر به getUserMedia() می‌دهیم، به ما امکان می‌دهد یک دستگاه رسانه‌ای را باز کنیم که با یک نیاز خاص مطابقت دارد. این نیاز می تواند بسیار ضعیف تعریف شود (صوتی و/یا تصویری)، یا بسیار خاص (حداقل وضوح دوربین یا شناسه دقیق دستگاه). توصیه می‌شود برنامه‌هایی که از getUserMedia() API استفاده می‌کنند، ابتدا دستگاه‌های موجود را بررسی کنند و سپس با استفاده از محدودیت deviceId ، محدودیتی را مشخص کنند که دقیقاً با دستگاه مطابقت دارد. دستگاه ها نیز در صورت امکان، با توجه به محدودیت ها پیکربندی خواهند شد. می‌توانیم لغو اکو را در میکروفون‌ها فعال کنیم یا یک عرض و ارتفاع خاص یا حداقل ویدیو را از دوربین تنظیم کنیم.

async function getConnectedDevices(type) {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices.filter(device => device.kind === type)
}

// Open camera with at least minWidth and minHeight capabilities
async function openCamera(cameraId, minWidth, minHeight) {
    const constraints = {
        'audio': {'echoCancellation': true},
        'video': {
            'deviceId': cameraId,
            'width': {'min': minWidth},
            'height': {'min': minHeight}
            }
        }

    return await navigator.mediaDevices.getUserMedia(constraints);
}

const cameras = getConnectedDevices('videoinput');
if (cameras && cameras.length > 0) {
    // Open first available video camera with a resolution of 1280x720 pixels
    const stream = openCamera(cameras[0].deviceId, 1280, 720);
}

اسناد کامل برای رابط MediaStreamConstraints را می توان در اسناد وب MDN یافت.

پخش محلی

هنگامی که یک دستگاه رسانه باز شد و یک MediaStream در دسترس داریم، می‌توانیم آن را به یک عنصر ویدیویی یا صوتی اختصاص دهیم تا جریان را به صورت محلی پخش کند.

async function playVideoFromCamera() {
    try {
        const constraints = {'video': true, 'audio': true};
        const stream = await navigator.mediaDevices.getUserMedia(constraints);
        const videoElement = document.querySelector('video#localVideo');
        videoElement.srcObject = stream;
    } catch(error) {
        console.error('Error opening video camera.', error);
    }
}

HTML مورد نیاز برای یک عنصر ویدیویی معمولی که با getUserMedia() استفاده می‌شود، معمولاً دارای ویژگی‌های autoplay و playsinline است. ویژگی autoplay باعث می شود که جریان های جدید اختصاص داده شده به عنصر به طور خودکار پخش شوند. ویژگی playsinline به ویدیوها اجازه می‌دهد تا به‌جای نمایش تمام‌صفحه، در برخی از مرورگرهای تلفن همراه، به صورت درون خطی پخش شوند. همچنین توصیه می شود از controls="false" برای پخش زنده استفاده کنید، مگر اینکه کاربر بتواند آنها را متوقف کند.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Local video playback</title>
</head>
<body>
    <video id="localVideo" autoplay playsinline controls="false" />
    <script language="javascript">
        async function playVideoFromCamera() {
            try {
                const constraints = {
                    'video': true,
                    'audio': true
                };
                const stream = await navigator.mediaDevices.getUserMedia(constraints);
                const videoElement = document.querySelector('video#localVideo');
                videoElement.srcObject = stream;
            } catch (error) {
                console.error('Error opening video camera.', error);
            }
        }

        playVideoFromCamera()
    </script>
</body>
</html>

جذب رسانه ها و محدودیت ها

بخش رسانه WebRTC نحوه دسترسی به سخت افزارهایی که قادر به ضبط ویدیو و صدا هستند، مانند دوربین ها و میکروفون ها، و همچنین نحوه عملکرد جریان های رسانه را پوشش می دهد. همچنین رسانه های نمایشگر را پوشش می دهد، به این ترتیب یک برنامه می تواند تصویربرداری از صفحه را انجام دهد.

دستگاه های رسانه ای

همه دوربین ها و میکروفون هایی که توسط مرورگر پشتیبانی می شوند از طریق شی navigator.mediaDevices قابل دسترسی و مدیریت هستند. برنامه‌ها می‌توانند لیست فعلی دستگاه‌های متصل را بازیابی کنند و همچنین به تغییرات گوش دهند، زیرا بسیاری از دوربین‌ها و میکروهپون‌ها از طریق USB متصل می‌شوند و می‌توانند در طول چرخه عمر برنامه متصل و جدا شوند. از آنجایی که وضعیت یک دستگاه رسانه می‌تواند در هر زمان تغییر کند، توصیه می‌شود برنامه‌ها برای تغییرات دستگاه ثبت نام کنند تا به درستی تغییرات را مدیریت کنند.

محدودیت ها

هنگام دسترسی به دستگاه های رسانه ای، ارائه محدودیت های تا حد امکان دقیق عمل خوبی است. در حالی که باز کردن دوربین و میکروفون پیش‌فرض با یک محدودیت ساده امکان‌پذیر است، اما ممکن است جریان رسانه‌ای را ارائه دهد که بهینه‌ترین حالت برای برنامه کاربردی نیست.

محدودیت‌های خاص در یک شی MediaTrackConstraint تعریف شده‌اند، یکی برای صدا و دیگری برای ویدیو. ویژگی های این شی از نوع ConstraintLong ، ConstraintBoolean ، ConstraintDouble یا ConstraintDOMString هستند. اینها می توانند یک مقدار خاص (به عنوان مثال، یک عدد، یک عدد بولی یا رشته)، یک محدوده ( LongRange یا DoubleRange با مقدار حداقل و حداکثر) یا یک شی با یک تعریف ideal یا exact باشند. برای یک مقدار خاص، مرورگر سعی می کند چیزی را تا حد امکان نزدیک تر انتخاب کند. برای یک محدوده، بهترین مقدار در آن محدوده استفاده خواهد شد. وقتی exact مشخص شود، فقط جریان‌های رسانه‌ای که دقیقاً با آن محدودیت مطابقت دارند بازگردانده می‌شوند.

نزدیک

// Camera with a resolution as close to 640x480 as possible
{
    "video": {
        "width": 640,
        "height": 480
    }
}

دامنه

// Camera with a resolution in the range 640x480 to 1024x768
{
    "video": {
        "width": {
            "min": 640,
            "max": 1024
        },
        "height": {
            "min": 480,
            "max": 768
        }
    }
}

دقیق

// Camera with the exact resolution of 1024x768
{
    "video": {
        "width": {
            "exact": 1024
        },
        "height": {
            "exact": 768
        }
    }
}

برای تعیین پیکربندی واقعی یک آهنگ خاص از یک جریان رسانه، می‌توانیم MediaStreamTrack.getSettings() را فراخوانی کنیم که MediaTrackSettings اعمال شده فعلی را برمی‌گرداند.

همچنین می‌توان محدودیت‌های یک آهنگ را از دستگاه رسانه‌ای که باز کرده‌ایم، با فراخوانی applyConstraints() در مسیر به‌روزرسانی کرد. این به برنامه اجازه می‌دهد بدون نیاز به بستن جریان موجود، دستگاه رسانه را مجدداً پیکربندی کند.

نمایش رسانه

برنامه‌ای که می‌خواهد تصویربرداری و ضبط صفحه را انجام دهد باید از Display Media API استفاده کند. تابع getDisplayMedia() (که بخشی از navigator.mediaDevices است مشابه getUserMedia() است و برای باز کردن محتوای نمایشگر (یا بخشی از آن مانند یک پنجره) استفاده می شود. MediaStream برگشتی با مانند هنگام استفاده از getUserMedia() .

محدودیت‌های getDisplayMedia() با محدودیت‌هایی که برای ورودی ویدیو یا صوتی معمولی استفاده می‌شوند متفاوت است.

{
    video: {
        cursor: 'always' | 'motion' | 'never',
        displaySurface: 'application' | 'browser' | 'monitor' | 'window'
    }
}

قطعه کد بالا نحوه عملکرد محدودیت های ویژه برای ضبط صفحه را نشان می دهد. توجه داشته باشید که ممکن است همه مرورگرهایی که از رسانه نمایشگر پشتیبانی می کنند، پشتیبانی نشوند.

جریان ها و آهنگ ها

MediaStream جریانی از محتوای رسانه ای را نشان می دهد که از آهنگ های صوتی و تصویری ( MediaStreamTrack ) تشکیل شده است. می‌توانید با فراخوانی MediaStream.getTracks() که آرایه‌ای از اشیاء MediaStreamTrack را برمی‌گرداند، همه آهنگ‌ها را از MediaStream بازیابی کنید.

MediaStreamTrack

MediaStreamTrack یک ویژگی kind دارد که audio یا video است، که نشان دهنده نوع رسانه ای است که آن را نشان می دهد. هر آهنگ را می توان با تغییر ویژگی enabled شده آن بی صدا کرد. یک آهنگ دارای یک remote ویژگی Boolean است که نشان می دهد آیا منبع آن توسط یک RTCPeerConnection و از یک همتای راه دور آمده است یا خیر.

شروع کار با ارتباطات همتا

اتصالات همتا بخشی از مشخصات WebRTC است که با اتصال دو برنامه در رایانه های مختلف برای برقراری ارتباط با استفاده از پروتکل همتا به همتا سروکار دارد. ارتباط بین همتایان می تواند ویدئویی، صوتی یا داده های باینری دلخواه باشد (برای مشتریانی که از RTCDataChannel API پشتیبانی می کنند). برای کشف نحوه اتصال دو همتا، هر دو سرویس گیرنده باید یک پیکربندی ICE Server ارائه دهند. این یا یک STUN یا یک سرور TURN است، و نقش آنها ارائه کاندیدهای ICE به هر مشتری است که سپس به همتای راه دور منتقل می شود. این انتقال نامزدهای ICE معمولاً سیگنالینگ نامیده می شود.

سیگنالینگ

مشخصات WebRTC شامل APIهایی برای ارتباط با یک سرور ICE (ایجاد اتصال به اینترنت) است، اما جزء سیگنالینگ بخشی از آن نیست. سیگنالینگ مورد نیاز است تا دو همتا بتوانند نحوه اتصال خود را به اشتراک بگذارند. معمولاً این مشکل از طریق یک Web API معمولی مبتنی بر 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() به عنوان توضیحات محلی تنظیم می شود و سپس از طریق کانال سیگنالینگ ما به سمت گیرنده ارسال می شود. ما همچنین یک شنونده برای کانال سیگنالینگ خود راه اندازی کردیم تا زمانی که پاسخی به توضیحات جلسه پیشنهادی ما از طرف گیرنده دریافت شود.

در سمت دریافت کننده، قبل از ایجاد نمونه RTCPeerConnection ، منتظر یک پیشنهاد دریافتی هستیم. پس از انجام این کار، پیشنهاد دریافتی را با استفاده از setRemoteDescription() تنظیم می کنیم. در مرحله بعد، ما createAnswer() را فراخوانی می کنیم تا پاسخی برای پیشنهاد دریافت شده ایجاد کنیم. این پاسخ با استفاده از 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});
}

نگامی که دو همتا توضیحات جلسه محلی و راه دور را تنظیم کردند، از قابلیت های همتای راه دور مطلع می شوند. این بدان معنا نیست که ارتباط بین همتایان آماده است. برای انجام این کار، باید نامزدهای ICE را در هر همتا جمع آوری کنیم و (از طریق کانال سیگنالینگ) به همتای دیگر منتقل کنیم.

نامزدهای ICE

قبل از اینکه دو همتا بتوانند با استفاده از WebRTC ارتباط برقرار کنند، نیاز به تبادل اطلاعات اتصال دارند. از آنجایی که شرایط شبکه می تواند بسته به عوامل متعددی متفاوت باشد، معمولاً از یک سرویس خارجی برای کشف نامزدهای احتمالی برای اتصال به همتا استفاده می شود. این سرویس ICE نام دارد و از سرور STUN یا TURN استفاده می کند. STUN مخفف Session Traversal Utilities برای NAT است و معمولاً به طور غیر مستقیم در اکثر برنامه های WebRTC استفاده می شود.

TURN (Traversal Using Relay NAT) راه حل پیشرفته تری است که پروتکل های STUN را در خود جای داده است و اکثر سرویس های تجاری مبتنی بر WebRTC از سرور TURN برای ایجاد ارتباط بین همتایان استفاده می کنند. WebRTC API از STUN و TURN به طور مستقیم پشتیبانی می کند و تحت عنوان کاملتر ایجاد اتصال به اینترنت جمع آوری شده است. هنگام ایجاد یک اتصال WebRTC، ما معمولا یک یا چند سرور ICE را در پیکربندی برای شی RTCPeerConnection می کنیم.

قطره یخ

هنگامی که یک شی RTCPeerConnection ایجاد می شود، چارچوب زیربنایی از سرورهای ICE ارائه شده برای جمع آوری نامزدها برای ایجاد اتصال (کاندیداهای ICE) استفاده می کند. رویداد 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 را گوش می دهیم.

شروع به کار با جریان های راه دور

هنگامی که یک RTCPeerConnection به یک همتای راه دور متصل می شود، امکان پخش صدا و تصویر بین آنها وجود دارد. این نقطه ای است که جریانی را که از getUserMedia() دریافت می کنیم به RTCPeerConnection می کنیم. یک جریان رسانه حداقل از یک آهنگ رسانه تشکیل شده است، و زمانی که می‌خواهیم رسانه را به همتای راه دور انتقال دهیم، اینها به صورت جداگانه به RTCPeerConnection اضافه می‌شوند.

const localStream = await getUserMedia({vide: true, audio: true});
const peerConnection = new RTCPeerConnection(iceConfig);
localStream.getTracks().forEach(track => {
    peerConnection.addTrack(track, localStream);
});

تراک‌ها را می‌توان قبل از اتصال به یک همتای راه دور به یک RTCPeerConnection اضافه کرد، بنابراین منطقی است که این راه‌اندازی را در اسرع وقت به جای منتظر ماندن برای تکمیل اتصال انجام دهید.

اضافه کردن آهنگ های راه دور

برای دریافت آهنگ های راه دور که توسط همتای دیگر اضافه شده است، شنونده ای را در RTCPeerConnection محلی که برای رویداد track گوش می دهد ثبت می کنیم. RTCTrackEvent حاوی آرایه‌ای از اشیاء MediaStream است که مقادیر MediaStream.id یکسانی با جریان‌های محلی مربوطه دارند. در مثال ما، هر آهنگ تنها با یک جریان مرتبط است.

توجه داشته باشید که در حالی که شناسه های MediaStream در هر دو طرف اتصال همتا مطابقت دارند، این موضوع معمولاً برای شناسه های MediaStreamTrack صادق نیست.

const remoteVideo = document.querySelector('#remoteVideo');

peerConnection.addEventListener('track', async (event) => {
    const [remoteStream] = event.streams;
    remoteVideo.srcObject = remoteStream;
});

کانال های داده

استاندارد WebRTC همچنین یک API برای ارسال داده های دلخواه از طریق RTCPeerConnection را پوشش می دهد. این کار با فراخوانی createDataChannel() createDataChannel روی یک شی RTCPeerConnection می شود که یک شی RTCDataChannel برمی گرداند.

const peerConnection = new RTCPeerConnection(configuration);
const dataChannel = peerConnection.createDataChannel();

همتای راه دور می تواند کانال های داده را با گوش دادن به رویداد کانال داده در شی datachannel RTCPeerConnection کند. رویداد دریافتی از نوع RTCDataChannelEvent است و حاوی یک ویژگی channel است که نشان دهنده RTCDataChannel متصل بین همتایان است.

const peerConnection = new RTCPeerConnection(configuration);
peerConnection.addEventListener('datachannel', event => {
    const dataChannel = event.channel;
});

باز و بسته شدن رویدادها

قبل از اینکه بتوان از یک کانال داده برای ارسال داده استفاده کرد، مشتری باید تا باز شدن آن صبر کند. این کار با گوش دادن به رویداد open انجام می شود. به همین ترتیب، برای زمانی که هر یک از طرفین کانال را close ، یک رویداد بسته وجود دارد.

const messageBox = document.querySelector('#messageBox');
const sendButton = document.querySelector('#sendButton');
const peerConnection = new RTCPeerConnection(configuration);
const dataChannel = peerConnection.createDataChannel();

// Enable textarea and button when opened
dataChannel.addEventListener('open', event => {
    messageBox.disabled = false;
    messageBox.focus();
    sendButton.disabled = false;
});

// Disable input when closed
dataChannel.addEventListener('close', event => {
    messageBox.disabled = false;
    sendButton.disabled = false;
});

پیام ها

ارسال پیام در RTCDataChannel با فراخوانی تابع send() با داده هایی که می خواهیم ارسال کنیم انجام می شود. پارامتر data برای این تابع می تواند یک رشته، یک Blob ، یک ArrayBuffer یا و ArrayBufferView باشد.

const messageBox = document.querySelector('#messageBox');
const sendButton = document.querySelector('#sendButton');

// Send a simple text message when we click the button
sendButton.addEventListener('click', event => {
    const message = messageBox.textContent;
    dataChannel.send(message);
})

همتای راه دور با گوش دادن به رویداد message ، پیام های ارسال شده در RTCDataChannel را دریافت می کند.

const incomingMessages = document.querySelector('#incomingMessages');

const peerConnection = new RTCPeerConnection(configuration);
const dataChannel = peerConnection.createDataChannel();

// Append new messages to the box of incoming messages
dataChannel.addEventListener('message', event => {
    const message = event.data;
    incomingMessages.textContent += message + '\n';
});

سرور را روشن کنید

برای عملکرد بیشتر برنامه های WebRTC، یک سرور برای انتقال ترافیک بین همتایان مورد نیاز است، زیرا یک سوکت مستقیم اغلب بین کلاینت ها امکان پذیر نیست (مگر اینکه آنها در همان شبکه محلی ساکن باشند). راه رایج برای حل این مشکل استفاده از سرور TURN است. این اصطلاح مخفف عبارت Traversal Using Relays around NAT است و پروتکلی برای انتقال ترافیک شبکه است.

در حال حاضر چندین گزینه برای سرورهای TURN به صورت آنلاین در دسترس است، هم به عنوان برنامه های کاربردی خود میزبان (مانند پروژه COTURN منبع باز) و هم به عنوان خدمات ارائه شده در ابر.

هنگامی که یک سرور TURN آنلاین در دسترس دارید، تنها چیزی که نیاز دارید پیکربندی صحیح RTCConfiguration برای برنامه مشتری شما برای استفاده از آن است. قطعه کد زیر یک پیکربندی نمونه برای یک RTCPeerConnection را نشان می دهد که در آن سرور TURN نام میزبان my-turn-server.mycompany.com دارد و روی پورت 19403 اجرا می شود. شی پیکربندی همچنین از ویژگی های username و credentials برای ایمن کردن دسترسی به سرور پشتیبانی می کند. این موارد هنگام اتصال به سرور TURN ضروری است.

const iceConfiguration = {
    iceServers: [
        {
            urls: 'turn:my-turn-server.mycompany.com:19403',
            username: 'optional-username',
            credentials: 'auth-token'
        }
    ]
}
const peerConnection = new RTCPeerConnection(iceConfiguration);

تست برنامه های WebRTC

هنگام نوشتن تست‌های خودکار برای برنامه‌های WebRTC، تنظیمات مفیدی وجود دارد که می‌توان آن‌ها را برای مرورگرها فعال کرد که توسعه و آزمایش را آسان‌تر می‌کنند.

کروم

هنگام اجرای آزمایش‌های خودکار در Chrome، آرگومان‌های زیر هنگام راه‌اندازی مفید هستند:

  • --allow-file-access-from-files – اجازه دسترسی به API را برای URL های file:// می دهد
  • --disable-translate – پنجره بازشو ترجمه را غیرفعال می کند
  • --use-fake-ui-for-media-stream – ارائه جریان های رسانه ای جعلی. هنگام اجرا بر روی سرورهای CI مفید است.
  • --use-file-for-fake-audio-capture=<filename> – فایلی را برای استفاده در هنگام ضبط صدا ارائه دهید.
  • --use-file-for-fake-video-capture=<filename> – فایلی را برای استفاده در هنگام فیلمبرداری ارائه دهید.
  • — بدون --headless – در حالت بدون سر اجرا شود. هنگام اجرا بر روی سرورهای CI مفید است.
  • --mute-audio – خروجی صدا را قطع کنید.

فایرفاکس

هنگام اجرای آزمایش‌های خودکار در فایرفاکس، باید مجموعه‌ای از کلیدهای ترجیحی را ارائه کنیم که در نمونه راه‌اندازی شده استفاده می‌شوند. در زیر پیکربندی مورد استفاده برای تست های خودکار نمونه WebRTC آمده است:

"prefs": {
    "browser.cache.disk.enable": false,
    "browser.cache.disk.capacity": 0,
    "browser.cache.disk.smart_size.enabled": false,
    "browser.cache.disk.smart_size.first_run": false,
    "browser.sessionstore.resume_from_crash": false,
    "browser.startup.page": 0,
    "media.navigator.streams.fake": true,
    "media.navigator.permission.disabled": true,
    "device.storage.enabled": false,
    "media.gstreamer.enabled": false,
    "browser.startup.homepage": "about:blank",
    "browser.startup.firstrunSkipsHomepage": false,
    "extensions.update.enabled": false,
    "app.update.enabled": false,
    "network.http.use-cache": false,
    "browser.shell.checkDefaultBrowser": false
}

قالب یکپارچه SDP – طرح انتقال

Google در حال برنامه ریزی برای انتقال پیاده سازی WebRTC کروم از قالب SDP فعلی (به نام “Plan B”) به یک قالب منطبق با استانداردها (“Unified Plan”، draft-ietf-rtcweb-jsep) در چند سه ماهه آینده است.

این طرح شامل ۵ مرحله و یک ویژگی API گذرا است.

چه کسانی تحت تاثیر قرار خواهند گرفت

افرادی که از چندین تراک صوتی یا چند آهنگ ویدیویی در یک PeerConnection استفاده می کنند، باید محصول خود را تحت Unified Plan آزمایش کنند و مطابق با آن سازگار شوند. در مواردی که تماسی از نقطه پایانی غیر از Chrome آغاز شده و کروم به آن پاسخ داده است، ممکن است شکل پیشنهاد تغییر کند. افرادی که تجزیه دقیق SDP را انجام می دهند و به ویژگی های msid اهمیت می دهند، باید بررسی کنند که کد تجزیه آنها فرمت جدید (a=msid) را انتخاب می کند. جزئیات در مورد نیاز به تغییرات و نحوه نیاز به تغییر برنامه ها به برنامه بستگی دارد. ما فکر می‌کنیم که تقریباً همه برنامه‌هایی که تنها از یک آهنگ صوتی و یک آهنگ ویدیویی در هر RTCPeerConnection استفاده می‌کنند تحت تأثیر این تغییر قرار نخواهند گرفت.

ویژگی API

ما یک ویژگی جدید را به پیکربندی RTCPeerConnection اضافه می کنیم:

enum SdpSemantics {
  "plan-b",
  "unified-plan"
};
partial dictionary RTCConfiguration {
   SdpSemantics sdpSemantics;
}

پیکربندی RTCC را می توان به سازنده یک RTCPeerConnection منتقل کرد و همه پیشنهادات و پاسخ های ساخته شده در قالب Unified Plan خواهند بود. فراخوانی به setLocalDescription و setRemoteDescription همچنین انتظار دارد که SDP در قالب Unified Plan باشد. اگر در قالب کروم قدیمی باشد، همه به جز اولین تراک صوتی و اولین تراک ویدیویی نادیده گرفته می شوند.

همچنین یک پرچم خط فرمان (–enable-features=RTCUnifiedPlanByDefault در کروم M71 و بالاتر، –enable-blink-features=RTCUnifiedPlanByDefault در نسخه‌های قبلی) وجود دارد که اجازه می‌دهد مقدار پیش‌فرض این پرچم را روی «Unified-plan» تنظیم کنید.

فازها

فاز ۱: اجرای طرح یکپارچه

در این مرحله، Unified Plan پشت پرچم آزمایشی که از M65 در دسترس بود، در حال توسعه بود. تا مرحله ۲، عاقلانه‌ترین کار این بود که با Chrome Canary با استفاده از «–enable-blink-features=RTCUnifiedPlan» آزمایش کنید.

فاز ۲: قابلیت API را به طور کلی در دسترس قرار دهید

منتشر شده در M69 (بتا اوت ۲۰۱۸، سپتامبر ۲۰۱۸ پایدار)

در این مرحله، مقدار پیش‌فرض پرچم sdpSemantics “plan-b” بود. در فاز ۲، از افرادی که پیاده‌سازی‌هایی داشتند که به فرمت SDP وابسته بودند، انتظار می‌رفت که آزمایش‌هایی را اجرا کنند تا ببینند که آیا برنامه‌هایشان در زمانی که Unified Plan در حال استفاده است کار می‌کنند یا خیر. برای برنامه‌هایی که از فایرفاکس پشتیبانی می‌کنند، انتظار داریم این یک تمرین بسیار ساده باشد: همان کاری را که برای فایرفاکس انجام می‌دهید انجام دهید.

مقدار پیش فرض پرچم sdpSemantics را می توان در “chrome://flags” تغییر داد. به دنبال ویژگی “WebRTC: استفاده از Unified Plan SDP Semantics به طور پیش فرض” باشید.

فاز ۳: پیش فرض را تغییر دهید

تاریخ سوئیچ M72 (بتا دسامبر ۲۰۱۸، ژانویه ۲۰۱۹ پایدار) بود.

در این مرحله، مقدار پیش‌فرض پرچم sdpSemantics را به “unified-plan” تغییر دادیم. برنامه هایی که متوجه شدند برای تبدیل به زمان بیشتری نیاز دارند، پرچم sdpSemantics را به صراحت روی “plan-b” تنظیم کردند تا رفتار قبلی را بازیابی کنند.

مرحله ۴: پرتاب “Plan B” را انجام دهید

در این مرحله، تنظیم پرچم sdpSemantics روی “plan-b” منجر به ایجاد یک استثنا می شود. از M93 در قناری پرتاب شده است. از M96، استثناء پرتاب در همه کانال ها، از جمله Stable بود.

در طول این مرحله، آزمایشی Deprecation در دسترس بود که اجازه استفاده از طرح B را بدون استثناء پرتاب می داد، اما آزمایش در ۲۵ مه ۲۰۲۲ کار خود را متوقف کرد.

فاز ۵: «Plan B» را از Chromium حذف کنید

پس از پایان دوره آزمایشی، طرح B از Chrome حذف خواهد شد. در این مرحله، پرچم sdpSemantics حذف خواهد شد. تلاش برای تنظیم آن بر روی “plan-b” استثنایی ایجاد نمی کند، اما دیگر هیچ تاثیری نخواهد داشت.

پلن B هنوز در پشت پرچم‌های خاص یا بیلدهای ویژه موجود است، اما حذف کامل کد در H2، ۲۰۲۲ اتفاق می‌افتد.

فاز ۶: منسوخ کردن و حذف “Plan B” از WebRTC

طرح B قبلاً در WebRTC به عنوان منسوخ علامت گذاری شده است، اما هنوز در دسترس است. حذف باید در سال ۲۰۲۳ اتفاق بیفتد.

آماده سازی درخواست شما برای طرح یکپارچه

برای اطلاعات دقیق درباره تفاوت‌های پلن B و طرح یکپارچه و اینکه چگونه برنامه شما ممکن است برای آماده‌سازی طرح یکپارچه نیاز به به‌روزرسانی داشته باشد، به راهنمای انتقال «طرح یکپارچه» (جاوا اسکریپت) مراجعه کنید.

برای برنامه‌های بومی (C++)، به سند «مهاجرت برنامه‌های بومی/موبایل به برنامه واحد» مراجعه کنید.

Firebase + WebRTC Codelab

۱٫ معرفی

در این کد لبه، یاد خواهید گرفت که چگونه با استفاده از WebRTC API در مرورگر خود و Cloud Firestore برای سیگنالینگ، یک برنامه چت ویدیویی ساده بسازید. این برنامه FirebaseRTC نام دارد و به عنوان یک مثال ساده عمل می کند که اصول ساخت برنامه های کاربردی دارای WebRTC را به شما آموزش می دهد.

نکته: یکی دیگر از گزینه های سیگنالینگ می تواند Firebase Cloud Messaging باشد. با این حال، در حال حاضر فقط در کروم پشتیبانی می‌شود و در این کد لبه روی راه‌حلی تمرکز می‌کنیم که در همه مرورگرهای پشتیبانی‌کننده WebRTC کار می‌کند.

چیزی که یاد خواهید گرفت

  • برقراری تماس تصویری در یک برنامه وب با استفاده از WebRTC
  • با استفاده از Cloud Firestore به طرف راه دور سیگنال می دهید

آنچه شما نیاز دارید

قبل از شروع این کد لبه، مطمئن شوید که نصب کرده اید:

  • npm که معمولاً با Node.js ارائه می شود – Node LTS توصیه می شود

۲٫ یک پروژه Firebase ایجاد و راه اندازی کنید

یک پروژه Firebase ایجاد کنید

  1. در کنسول Firebase ، روی افزودن پروژه کلیک کنید، سپس نام پروژه Firebase را FirebaseRTC بگذارید.

شناسه پروژه برای پروژه Firebase خود را به خاطر بسپارید.

توجه: اگر به صفحه اصلی پروژه خود بروید، می توانید آن را در تنظیمات > تنظیمات پروژه (یا به URL نگاه کنید!)

  1. روی ایجاد پروژه کلیک کنید.

برنامه ای که می خواهید بسازید از دو سرویس Firebase موجود در وب استفاده می کند:

  • Cloud Firestore برای ذخیره داده های ساختار یافته در Cloud و دریافت اعلان فوری هنگام به روز رسانی داده ها
  • میزبانی Firebase برای میزبانی و سرویس دهی به دارایی های ثابت شما

برای این کد لبه خاص، شما قبلاً میزبانی Firebase را در پروژه ای که شبیه سازی خواهید کرد، پیکربندی کرده اید. با این حال، برای Cloud Firestore، پیکربندی و فعال‌سازی سرویس‌ها را با استفاده از کنسول Firebase راهنمایی می‌کنیم.

Cloud Firestore را فعال کنید

این برنامه از Cloud Firestore برای ذخیره پیام های چت و دریافت پیام های چت جدید استفاده می کند.

باید Cloud Firestore را فعال کنید:

  1. در بخش توسعه منوی کنسول Firebase، روی پایگاه داده کلیک کنید.
  2. روی ایجاد پایگاه داده در پنجره Cloud Firestore کلیک کنید.
  3. گزینه Start in test mode را انتخاب کنید، سپس پس از خواندن سلب مسئولیت در مورد قوانین امنیتی، روی Enable کلیک کنید.

حالت تست تضمین می کند که می توانید آزادانه در حین توسعه در پایگاه داده بنویسید. ما پایگاه داده خود را بعداً در این Codelab ایمن تر خواهیم کرد.

۳٫ کد نمونه را دریافت کنید

مخزن GitHub را از خط فرمان کلون کنید:

git clone https://github.com/webrtc/FirebaseRTC

کد نمونه باید در دایرکتوری FirebaseRTC کلون شده باشد. اطمینان حاصل کنید که خط فرمان شما از این پس از این دایرکتوری اجرا می شود:

cd FirebaseRTC

برنامه شروع را وارد کنید

فایل های FirebaseRTC را در ویرایشگر خود باز کنید و طبق دستورالعمل زیر آنها را تغییر دهید. این دایرکتوری حاوی کد شروع برای codelab است که شامل یک برنامه WebRTC هنوز کاربردی نیست. ما آن را در سراسر این Codelab کاربردی خواهیم کرد.

۴٫ رابط خط فرمان Firebase را نصب کنید

رابط خط فرمان Firebase (CLI) به شما امکان می دهد برنامه وب خود را به صورت محلی ارائه دهید و برنامه وب خود را در میزبانی Firebase مستقر کنید.

توجه: برای نصب CLI، باید npm را نصب کنید که معمولاً همراه با Node.js است.

  1. CLI را با اجرای دستور npm زیر نصب کنید: sh npm -g install firebase-tools

توجه: کار نمی کند؟ ممکن است لازم باشد دستور را با استفاده از sudo اجرا کنید.

  1. با اجرای دستور زیر بررسی کنید که CLI به درستی نصب شده باشد: sh firebase --version

مطمئن شوید که نسخه Firebase CLI نسخه ۶٫۷٫۱ یا بالاتر باشد.

  1. Firebase CLI را با اجرای دستور زیر مجاز کنید: sh firebase login

شما الگوی برنامه وب را به گونه ای تنظیم کرده اید که پیکربندی برنامه خود را برای میزبانی Firebase از فهرست و فایل های محلی برنامه خود خارج کند. اما برای انجام این کار، باید برنامه خود را با پروژه Firebase خود مرتبط کنید.

  1. برنامه خود را با پروژه Firebase خود با اجرای دستور زیر مرتبط کنید: sh firebase use --add
  2. وقتی از شما خواسته شد، شناسه پروژه خود را انتخاب کنید، سپس به پروژه Firebase خود یک نام مستعار بدهید.

اگر چندین محیط (تولید، صحنه سازی و غیره) دارید، نام مستعار مفید است. با این حال، برای این کد لبه، اجازه دهید فقط از نام مستعار default استفاده کنیم.

  1. دستورالعمل های باقی مانده را در خط فرمان خود دنبال کنید.

۵٫ سرور محلی را اجرا کنید

شما آماده هستید که در واقع کار را روی برنامه ما شروع کنید! بیایید برنامه را به صورت محلی اجرا کنیم!

  1. دستور Firebase CLI زیر را اجرا کنید: sh firebase serve --only hosting
  2. خط فرمان شما باید پاسخ زیر را نمایش دهد: hosting: Local server: http://localhost:5000

ما از شبیه ساز میزبانی Firebase برای ارائه برنامه خود به صورت محلی استفاده می کنیم. اکنون برنامه وب باید از http://localhost:5000 در دسترس باشد.

  1. برنامه خود را در http://localhost:5000 باز کنید.

باید کپی FirebaseRTC خود را که به پروژه Firebase شما متصل شده است ببینید.

برنامه به طور خودکار به پروژه Firebase شما متصل شده است.

۶٫ ایجاد یک اتاق جدید

در این اپلیکیشن به هر جلسه چت تصویری اتاق گفته می شود. کاربر می تواند با کلیک بر روی دکمه ای در برنامه خود یک اتاق جدید ایجاد کند. این یک شناسه ایجاد می کند که طرف راه دور می تواند از آن برای پیوستن به همان اتاق استفاده کند. شناسه به عنوان کلید در Cloud Firestore برای هر اتاق استفاده می شود.

هر اتاق شامل RTCSessionDescriptions برای پیشنهاد و پاسخ، و همچنین دو مجموعه جداگانه با نامزدهای ICE از هر حزب خواهد بود.

اولین وظیفه شما پیاده سازی کد گم شده برای ایجاد اتاق جدید با پیشنهاد اولیه تماس گیرنده است. public/app.js را باز کنید و نظر // Add code for creating a room here اضافه کنید و کد زیر را اضافه کنید:

const offer = await pe
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);

const roomWithOffer = {
    offer: {
        type: offer.type,
        sdp: offer.sdp
    }
}
const roomRef = await db.collection('rooms').add(roomWithOffer);
const roomId = roomRef.id;
document.querySelector('#currentRoom').innerText = `Current room is ${roomId} - You are the caller!`

خط اول یک RTCSessionDescription ایجاد می کند که پیشنهاد تماس گیرنده را نشان می دهد. سپس این به عنوان توضیحات محلی تنظیم می شود و در نهایت در شی اتاق جدید در Cloud Firestore نوشته می شود.

در مرحله بعد، به تغییرات در پایگاه داده گوش می دهیم و تشخیص می دهیم که چه زمانی پاسخی از طرف تماس گیرنده اضافه شده است.

roomRef.onSnapshot(async snapshot -> {
    console.log('Got updated room:', snapshot.data());
    const data = snapshot.data();
    if (!peerConnection.currentRemoteDescription && data.answer) {
        console.log('Set remote description: ', data.answer);
        const answer = new RTCSessionDescription(data.answer)
        await peerConnection.setRemoteDescription(answer);
    }
});

این کار منتظر می ماند تا تماس RTCSessionDescription را برای پاسخ بنویسد و آن را به عنوان توضیحات راه دور در تماس گیرنده RTCPeerConnection کند.

۷٫ پیوستن به یک اتاق

گام بعدی اجرای منطق برای پیوستن به یک اتاق موجود است. کاربر این کار را با کلیک روی دکمه Join room و وارد کردن شناسه اتاق برای عضویت انجام می دهد. وظیفه شما در اینجا پیاده سازی ایجاد RTCSessionDescription برای پاسخ و به روز رسانی اتاق در پایگاه داده بر اساس آن است.

const offer = roomSnapshot.data().offer;
await peerConnection.setRemoteDescription(offer);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);

const roomWithAnswer = {
    answer: {
        type: answer.type,
        sdp: answer.sdp
    }
}
await roomRef.update(roomWithAnswer);

در کد بالا، ما با استخراج پیشنهاد از تماس گیرنده و ایجاد یک RTCSessionDescription که به عنوان توضیحات راه دور تنظیم می کنیم، شروع می کنیم. بعد، پاسخ را ایجاد می کنیم، آن را به عنوان توضیحات محلی تنظیم می کنیم و پایگاه داده را به روز می کنیم. به روز رسانی پایگاه داده، پاسخ تماس onSnapshot را در سمت تماس گیرنده آغاز می کند، که به نوبه خود توضیحات راه دور را بر اساس پاسخ تماس گیرنده تنظیم می کند. این تبادل اشیاء RTCSessionDescription بین تماس گیرنده و تماس گیرنده را کامل می کند.

۸٫ نامزدهای ICE را جمع آوری کنید

قبل از اینکه تماس گیرنده و تماس گیرنده بتوانند به یکدیگر متصل شوند، همچنین باید نامزدهای ICE را مبادله کنند که به WebRTC نحوه اتصال به همتای راه دور را بگویند. وظیفه بعدی شما پیاده سازی کدی است که به نامزدهای ICE گوش می دهد و آنها را به مجموعه ای در پایگاه داده اضافه می کند. تابع collectIceCandidates را پیدا کنید و کد زیر را اضافه کنید:

async function collectIceCandidates(roomRef, peerConnection,
                                    localName, remoteName) {
    const candidatesCollection = roomRef.collection(localName);

    peerConnection.addEventListener('icecandidate', event -> {
        if (event.candidate) {
            const json = event.candidate.toJSON();
            candidatesCollection.add(json);
        }
    });

    roomRef.collection(remoteName).onSnapshot(snapshot -> {
        snapshot.docChanges().forEach(change -> {
            if (change.type === "added") {
                const candidate = new RTCIceCandidate(change.doc.data());
                peerConneciton.addIceCandidate(candidate);
            }
        });
    })
}

این تابع دو کار را انجام می دهد. نامزدهای ICE را از WebRTC API جمع‌آوری می‌کند و آنها را به پایگاه داده اضافه می‌کند، و به کاندیدهای ICE اضافه شده از همتای راه دور گوش می‌دهد و آنها را به نمونه RTCPeerConnection خود اضافه می‌کند. هنگام گوش دادن به تغییرات پایگاه داده مهم است که هر چیزی را که جدید نیست فیلتر کنید، زیرا در غیر این صورت می توانستیم همان مجموعه ای از نامزدهای ICE را بارها و بارها اضافه کنیم.

۹٫ نتیجه گیری

در این نرم افزار کد، نحوه پیاده سازی سیگنالینگ برای WebRTC با استفاده از Cloud Firestore و همچنین نحوه استفاده از آن برای ایجاد یک برنامه چت ویدیویی ساده را یاد گرفتید.

برای کسب اطلاعات بیشتر به منابع زیر مراجعه کنید:

  1. کد منبع FirebaseRTC
  2. نمونه های WebRTC
  3. Cloud Firestore
Share

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *