DEV Community

PiterDev
PiterDev

Posted on

JavaScript WebRTC. WebRTC example JavaScript 🌐Remote Controller

As we have seen on the first post of this serie we have setup a Pion WebRTC "client". Now we are going to explore the "server" example built for the browser with JS(TS) and a little of WASM.

💭 Remember that this is modified and simplified example of "Remote Controller" my software product that tries to be an alternative to Steam Remote Play. If you are interested to learn more about how it works I will continue this serie, but for the moment you will have the codebase to check it out

Lets start with the example:

First we will define the default STUN servers

const defaultStunServers = [
    'stun:stun.l.google.com:19302',
    'stun:stun.l.google.com:19305'
];
Enter fullscreen mode Exit fullscreen mode

The next step will be the peerconnection creation and handle


const peerConnection = new RTCPeerConnection({
        iceServers: [
            {
                urls: defaultStunServers
            }
        ]
});

/* fn will be an optional callback function to execute 
 when the close succeded */
function ClosePeerConnection(fn?: () => void) {
    if (fn) fn();
    peerConnection.close();
}

async function CreateServerWebRTC() {

    peerConnection.onconnectionstatechange = handleConnectionState;

    const numbersChannel = peerConnection.createDataChannel("numbers");

    const otherChannel = peerConnection.createDataChannel("other");

    numbersChannel.onmessage = (e) => {
        console.log("Number: ", e.data);
    }

    othersChannel.onopen = () => {
        othersChannel.send(JSON.stringify({'message': "Hello World from WebRTC"}))
    };

    let copiedCode: string = '';


        const offer = await peerConnection.createOffer();

        await peerConnection.setLocalDescription(offer);

        const candidates: RTCIceCandidateInit[] = [];

        peerConnection.onicecandidate = (ev) => {
            if (ev.candidate === null) {

                copiedCode =
                    signalEncode(peerConnection?.localDescription) + ';' + signalEncode(candidates);

                // Server code logged on console
                console.log(copiedCode);

                return;
            }

            candidates.push(ev.candidate.toJSON());
        };

}

// Handle connection with the PionWebRTC client
/*hostAndCandidatesCode argument is the code provided by our client after submiting the server code in it */
async function ConnectToClientWebRTC(hostAndCandidatesCode: string) {
    try {
        const [hostCode, candidatesCode] = hostAndCandidatesCode.split(';');

        const answer: RTCSessionDescription = signalDecode(hostCode);

        const candidates: RTCIceCandidateInit[] = signalDecode(candidatesCode);

        await peerConnection.setRemoteDescription(answer);

        candidates.forEach(async (candidate) => {
            await peerConnection.addIceCandidate(candidate);
        });
    } catch (e) {
        console.error(e);
    }
}

// Execute code in base of the peerconection state change
function handleConnectionState() {

    const connectionState = peerConnection.connectionState;

    switch (connectionState) {
        case 'connected':
            console.log("connected");
            break;
        case 'disconnected':
            console.log("disconnected");
            ClosePeerConnection();
            break;
        case 'failed':
            console.log("failed");
            ClosePeerConnection();
            break;
        case 'closed':
            console.log("closed");
            ClosePeerConnection();
            break;
    }
}
Enter fullscreen mode Exit fullscreen mode

And now we will export our main functions to use in your own codebase


export { CreateServerWebRTC, ConnectToClientWebRTC, ClosePeerConnection };

Enter fullscreen mode Exit fullscreen mode

But now you may have thinked about signalEncode and signalDecode functions, these are some WASM functions generated from the Go codebase to compress the resulting codes and exchange more info like candidates.

You will have all the files on this gist, now let's create the helper functions to use it out of the box

// Function WASM (GOLANG)
function signalEncode<T>(signal: T): string {
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    //@ts-ignore
    return window.signalEncode(JSON.stringify(signal));
}

// Function WASM (GOLANG)
function signalDecode<T>(signal: string): T {
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    //@ts-ignore
    return JSON.parse(window.signalDecode(signal)) as T;
}

Enter fullscreen mode Exit fullscreen mode

Now with all our setup we will have the tooling to make an APP were a server (js in the browser) can exchange some numbers and objects with a client (golang with Pion) using a P2P (WebRTC connection) obviously passing the codes manually. But making a service to avoid this it is an easy task, but not what i wanted for my use case (avoid using third party servers/services).

I hope this little tutorial/showcase was helpfull for all the devs who want to experiment with the WebRTC APIs without shooting himself on the foot like i did during the proccess of building "Remote Controller" 😉

If you want me to continue this serie of articles talking about how i am building my personal project (like Gamepad emulation) leave one comment on the comments box below 👇

Top comments (0)