Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic Example Web Application #16

Open
JacobZwang opened this issue Jul 6, 2020 · 3 comments
Open

Basic Example Web Application #16

JacobZwang opened this issue Jul 6, 2020 · 3 comments

Comments

@JacobZwang
Copy link

Could you provide a basic web application that demonstrates how to use this? Something minimal like sending a message or media stream would be great to learn how this works. Thanks for the help. (Beginner developer here)

@rasviitanen
Copy link
Owner

rasviitanen commented Jul 8, 2020

@JacobZwang Sure! You will have to change wsaddr to the address of which your signaling server is running on.
This example exposes an app called WebRTCApp. You connect to the singaling server by running WebRTCApp.connect(). This will send a connection reqeust to everyone that's connected to the same room, in this case the room variable is set to test_room. Once connected, you are able to use WebRTCApp.sendDirect("Hello! Here is my message") in order to send messages to every peer that you have connected to.

To make the code more "clear", "actions" that come from peers (clients) are written in lower-case. And actions from/to the signaling server is in upper-case.

Let me know if you run into any issues! I am happy to help :)

export const WebRTCApp= (function() {

    // Define "global" variables
    var dataChannels = []
    var peerConfig = {"iceServers":[{"urls":"stun:stun.l.google.com:19302"}]};
    var sdpConstraints = { offerToReceiveAudio: false,  offerToReceiveVideo: false };
    var ws = null;
    var client = generateUUID();
    var room = "test_room"
    var wsaddr = "127.0.0.1:3003"

    // Functions

    // Generate a random user-id
    function generateUUID() {
        // http://www.ietf.org/rfc/rfc4122.txt
        var s = [];
        var hexDigits = "0123456789abcdef";
        for (var i = 0; i < 36; i++) {
            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
        }
        s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
        s[8] = s[13] = s[18] = s[23] = "-";

        return s.join("");
    }

    // Avoid responding to ourself, and only respond to messages in the same room
    function shouldRespond(jsonMsg) {
        return jsonMsg.from !== client && ((jsonMsg.endpoint === client) || (jsonMsg.room === room));
    }

    // Connect to the signaling server
    async function connect() {
        return new Promise((resolve, reject) => {
            ws = new WebSocket("ws://" + wsaddr + "?user=" + client);
            ws.onopen = (e) => {
                console.log("Websocket opened");
                sendNegotiation('HANDLE_CONNECTION', client)
            }
            ws.onclose = (e) => {
                console.log("Websocket closed");
            }
            ws.onmessage = async (e) => {
                var json = JSON.parse(e.data);
                if (shouldRespond(json)) {
                    if(json.action === "HANDLE_CONNECTION"){
                        console.log("NEW PEER WANTS TO CONNECT")
                        await connectPeers(json.data)
                        resolve(WebRTCApp)
                    } else if (json.action === "offer"){
                        console.log("GOT OFFER FROM A NODE WE WANT TO CONNECT TO")
                        console.log("THE NODE IS", json.from)
                        await processOffer(json.from, json.data)
                        resolve(WebRTCApp)
                    }
                }
            }
            ws.onerror = (e) => {
                console.log("Websocket error");
                reject(e);
            }
        })

    }

    function processMessage(e) {
        console.log("----- DC MESSAGE PROCESS -----");
        var message = JSON.parse(e.data);
        // Here is where we process direct messages from other peers
    }

    // Used when establishing a connection
    function processIce(localConnection, iceCandidate){
        localConnection.addIceCandidate(new RTCIceCandidate(iceCandidate)).catch(e => {
            console.log(e)
        })
    }

    // Used when establishing a connection with a peer
    function sendNegotiation(type, sdp){
        var jsonSend = { protocol: "one-to-all", room: room, from: client, endpoint: "any", action: type, data: sdp};
        ws.send(JSON.stringify(jsonSend));
    }

    // Send connection request to a specific endpoint-id
    function sendOneToOneNegotiation(type, endpoint, sdp){
        var jsonSend = { protocol: "one-to-one", room: room, from: client, endpoint: endpoint, action: type, data: sdp};
        ws.send(JSON.stringify(jsonSend));
    }

    function connectPeers(requestee) {
        return new Promise((resolve, reject) => {
            console.log("CONNECTING PEERS")
            // Create the local connection and its event listeners
            let localConnection = new RTCPeerConnection(peerConfig);
            let sendChannel = localConnection.createDataChannel("sendChannel");

            localConnection.onicecandidate = event => {
                if (event.candidate) {
                    sendOneToOneNegotiation("candidate", requestee, event.candidate)
                }
            }

            function messageListener (e) {
                var json = JSON.parse(e.data);
                if(shouldRespond(json)){
                    if(json.action === "candidate"){
                        processIce(localConnection, json.data);
                    } else if(json.action === "answer"){
                        console.log("--- GOT ANSWER IN CONNECT ---")
                        localConnection.setRemoteDescription(new RTCSessionDescription(json.data));
                    }
                }
            }

            ws.addEventListener('message', messageListener)

            // Create the data channel and establish its event listeners
            sendChannel.onopen = event => {
                console.log("Channel opened");
                resolve(WebRTCApp);
                ws.removeEventListener('message', messageListener)
            }
            sendChannel.onmessage = message => {
                processMessage(message)
            }
            sendChannel.onclose = event =>  {
                console.log("Channel closed");
                ws.removeEventListener('message', messageListener)
                reject();
            };

            dataChannels.push(sendChannel)

            // Now create an offer to connect; this starts the process
            localConnection.createOffer(sdpConstraints)
            .then(offer => {
                localConnection.setLocalDescription(offer)
                sendOneToOneNegotiation("offer", requestee, offer);
                console.log("------ SEND OFFER ------");

            })
            .catch(handleCreateDescriptionError);
        })
    }

    function processOffer(requestee, remoteOffer) {
        console.log("RUNNING PROCESS OFFER")
        return new Promise((resolve, reject) => {
            let localConnection = new RTCPeerConnection(peerConfig);
            
            localConnection.onicecandidate = event => {
                if (event.candidate) {
                    sendOneToOneNegotiation("candidate", requestee, event.candidate)
                }
            }

            localConnection.ondatachannel = event => {
                event.channel.onopen = () => {
                    console.log('Data channel is open and ready to be used.');
                    dataChannels.push(event.channel)
                    resolve(WebRTCApp)
                };
                event.channel.onmessage = message => {
                    processMessage(message)
                };
                event.channel.onerror = error => {
                    // Handle channel errors here!
                    reject(WebRTCApp)
                }
            };

            // SEND ANSWER
            localConnection.setRemoteDescription(new RTCSessionDescription(remoteOffer))
            localConnection.createAnswer().then(localDescription => {
                localConnection.setLocalDescription(localDescription)
                console.log("------ SEND ANSWER ------");
                sendOneToOneNegotiation('answer', requestee, localDescription)
            })
        })
    }

    function handleCreateDescriptionError(error) {
      console.log("Unable to create an offer: " + error.toString());
    }

    function sendDirect(message) {
        let sentMessages = 0
        dataChannels.forEach(function (datachannel) {
            if (datachannel.readyState === 'open') {
                datachannel.send(JSON.stringify({sender: client, payload: message}))
                sentMessages++
            }
        });
        console.log("Sent this many messages:", sentMessages)
    }
    
    return {
        connect: (connect),
        sendDirect: (sendDirect),
    };
})();

@JacobZwang
Copy link
Author

Thanks so much! I'll try this out.

@JacobZwang
Copy link
Author

So I half got it working. I'm not completely sure how this is supposed to bed run. I assumed it was ES6 and tried to use rollup which didn't work. So to make it partially work, I just deleted "export" from the beginning. Which allowed it to work enough to connect 2 peers to a room, however many errors were thrown. Maybe you can message me on discord? JacobZwang#5310

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants