เริ่มต้นใช้งานการเชื่อมต่อเพียร์

การเชื่อมต่อเพียร์เป็นส่วนหนึ่งของข้อกำหนด WebRTC ที่เกี่ยวข้องกับการเชื่อมต่อแอปพลิเคชัน 2 รายการในคอมพิวเตอร์ที่ต่างกันเพื่อสื่อสารโดยใช้โปรโตคอลแบบเพียร์ทูเพียร์ การสื่อสารระหว่างเพียร์อาจเป็นวิดีโอ เสียง หรือ ข้อมูลไบนารีที่กำหนดเอง (สำหรับไคลเอ็นต์ที่รองรับ RTCDataChannel API) หากต้องการทราบวิธีที่เพียร์ 2 รายเชื่อมต่อกันได้ ไคลเอ็นต์ทั้ง 2 รายจะต้องระบุการกำหนดค่าเซิร์ฟเวอร์ ICE ซึ่งอาจเป็นเซิร์ฟเวอร์ STUN หรือ TURN และมีบทบาทในการ ระบุผู้สมัคร ICE ให้กับไคลเอ็นต์แต่ละราย จากนั้นจะโอนไปยังเพียร์ระยะไกล การโอน ICE Candidate นี้มักเรียกว่าการส่งสัญญาณ

การส่งสัญญาณ

ข้อกำหนด WebRTC มี API สำหรับการสื่อสารกับเซิร์ฟเวอร์ ICE (Internet Connectivity Establishment) แต่คอมโพเนนต์การส่งสัญญาณไม่ได้เป็นส่วนหนึ่งของ ข้อกำหนดดังกล่าว การส่งสัญญาณเป็นสิ่งจำเป็นเพื่อให้เพียร์ 2 รายแชร์วิธีเชื่อมต่อ โดยปกติแล้วปัญหานี้จะได้รับการแก้ไขผ่าน 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() จากนั้นจะส่งผ่านช่องการส่งสัญญาณไปยังฝั่งที่รับ นอกจากนี้ เรายังตั้งค่า Listener สำหรับช่องสัญญาณ เมื่อได้รับคำตอบสำหรับคำอธิบายเซสชันที่เราเสนอจาก ฝั่งที่รับ

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});
    }
});

เมื่อเพียร์ทั้ง 2 รายตั้งค่าทั้งคำอธิบายเซสชันในเครื่องและเซสชันระยะไกลแล้ว เพียร์ทั้ง 2 รายจะทราบความสามารถของเพียร์ระยะไกล ซึ่งไม่ได้หมายความว่าการเชื่อมต่อ ระหว่างเพียร์พร้อมใช้งานแล้ว เราต้องรวบรวม ICE candidates ที่แต่ละเพียร์และโอน (ผ่านช่องทางการส่งสัญญาณ) ไปยังเพียร์อื่น เพื่อให้ทำงานได้

ผู้สมัคร ICE

ก่อนที่เพียร์ 2 เครื่องจะสื่อสารกันโดยใช้ WebRTC ได้ เพียร์ทั้ง 2 เครื่องต้องแลกเปลี่ยน ข้อมูลการเชื่อมต่อกันก่อน เนื่องจากสภาพเครือข่ายอาจแตกต่างกันไปตามปัจจัยหลายประการ จึงมักใช้บริการภายนอกเพื่อค้นหาผู้สมัครที่เป็นไปได้ในการเชื่อมต่อกับเพียร์ บริการนี้เรียกว่า ICE และใช้เซิร์ฟเวอร์ STUN หรือ TURN STUN ย่อมาจาก Session Traversal Utilities for NAT และมักใช้โดยอ้อมในแอปพลิเคชัน WebRTC ส่วนใหญ่

TURN (Traversal Using Relay NAT) เป็นโซลูชันขั้นสูงกว่าที่รวมโปรโตคอล STUN และบริการส่วนใหญ่ที่ใช้ WebRTC เชิงพาณิชย์จะใช้เซิร์ฟเวอร์ TURN เพื่อสร้างการเชื่อมต่อระหว่างเพียร์ WebRTC API รองรับทั้ง STUN และ TURN โดยตรง และจะรวบรวมไว้ภายใต้คำที่สมบูรณ์กว่าอย่าง Internet Connectivity Establishment เมื่อสร้างการเชื่อมต่อ WebRTC โดยปกติเราจะระบุเซิร์ฟเวอร์ ICE อย่างน้อย 1 รายการในการกำหนดค่าสำหรับออบเจ็กต์ RTCPeerConnection

Trickle ICE

เมื่อสร้างออบเจ็กต์ RTCPeerConnection แล้ว เฟรมเวิร์กพื้นฐานจะใช้เซิร์ฟเวอร์ ICE ที่ระบุเพื่อรวบรวมแคนดิเดตสำหรับการสร้างการเชื่อมต่อ (แคนดิเดต ICE) เหตุการณ์ icegatheringstatechange ใน RTCPeerConnection จะส่งสัญญาณ สถานะของการรวบรวม ICE (new, gathering หรือ complete)

แม้ว่าเพียร์จะรอจนกว่าการรวบรวม ICE จะเสร็จสมบูรณ์ได้ แต่โดยปกติแล้วการใช้เทคนิค "trickle ice" และการส่งผู้สมัคร ICE แต่ละรายไปยังเพียร์ระยะไกลเมื่อพบจะมีประสิทธิภาพมากกว่ามาก ซึ่งจะช่วย ลดเวลาในการตั้งค่าการเชื่อมต่อแบบเพียร์อย่างมาก และช่วยให้วิดีโอ คอลเริ่มทำงานได้โดยมีความล่าช้าน้อยลง

หากต้องการรวบรวม ICE Candidate เพียงเพิ่ม Listener สำหรับเหตุการณ์ icecandidate RTCPeerConnectionIceEvent ที่ปล่อยออกมาใน Listener นั้นจะมีพร็อพเพอร์ตี้ 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 Candidate แล้ว เราควรคาดหวังว่าสถานะของการเชื่อมต่อเพียร์จะเปลี่ยนเป็นสถานะที่เชื่อมต่อในที่สุด หากต้องการตรวจหาเหตุการณ์นี้ เราจะเพิ่ม Listener ไปยัง RTCPeerConnection ซึ่งเราจะรอรับเหตุการณ์ connectionstatechange

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

เอกสารประกอบเกี่ยวกับ RTCPeerConnection API