Building Real-time Communication App with Pion WebRTC with Golang(Go)

Learn how to build real-time communication applications using Pion WebRTC and Go. This guide covers setup, implementation, and troubleshooting for seamless audio and video communication.

What is Pion WebRTC?

Pion WebRTC is an open-source implementation of WebRTC, written in Go(Golang), that allows developers to create real-time communication applications with ease. WebRTC (

Web Real-Time Communication

) is a technology that enables peer-to-peer communication directly between browsers and mobile applications. This technology is crucial for building applications like video conferencing, live streaming, and other interactive media applications without the need for intermediary servers.
Pion WebRTC stands out for several reasons. First, it leverages the power of Go, a statically typed, compiled programming language known for its simplicity and performance. This makes Pion WebRTC not only fast but also easy to use and integrate into existing Go projects. Furthermore, Pion WebRTC provides a flexible and modular framework, allowing developers to customize and extend functionalities as needed.

What is WebRTC?

The importance of WebRTC in real-time communication cannot be overstated. It has revolutionized how we interact online, making it possible to have high-quality, low-latency audio and video communication directly within web browsers. This technology eliminates the need for plugins or additional software, making it accessible and user-friendly.
Choosing Pion WebRTC for your project can offer several advantages. It provides a robust framework for building scalable and efficient real-time communication solutions. Whether you're developing a video conferencing tool, a live streaming service, or an IoT application that requires real-time data transmission, Pion WebRTC offers the capabilities you need.
In this article, we will guide you through the process of getting started with Pion WebRTC, from setting up your development environment to implementing key features such as video controls and participant views. By the end of this guide, you will have a fully functional real-time communication application built with Go and Pion WebRTC.

Getting Started with Pion WebRTC

Creating real-time communication applications with Pion WebRTC involves several steps, starting with setting up your development environment, installing Pion WebRTC, understanding the project structure, and grasping the overall architecture of your application. Let's dive into each step in detail.

Create a New Pion WebRTC App

Initial Setup

Before you start, ensure that you have the Go programming language installed on your machine. You can download and install Go from the official

Go website

. Additionally, make sure you have a basic understanding of WebRTC concepts and how peer-to-peer communication works.

Prerequisites

  • Go Environment: Ensure Go is properly installed and configured on your machine.
  • WebRTC Knowledge: Familiarize yourself with WebRTC basics, including concepts like peer connections, signaling, and media streams.

Install Pion WebRTC: Step-by-Step Installation Guide

[a] Create a New Go Project

Open your terminal and create a new directory for your project. Navigate into this directory and initialize a new Go module.

sh

1   mkdir pion-webrtc-app
2   cd pion-webrtc-app
3   go mod init pion-webrtc-app

[b] Install Pion WebRTC Package

Use the go get command to install the Pion WebRTC package.

sh

1   go get github.com/pion/webrtc/v3

[c] Verify Installation

Ensure that Pion WebRTC is successfully installed by listing your project dependencies.

sh

1   go list -m all
You should see github.com/pion/webrtc/v3 listed among the dependencies.

Structure of the Project

Directory and File Structure

Organize your project files and directories for better maintainability. Here’s a suggested structure:
1pion-webrtc-app/
2├── main.go
3├── signaling/
4│   └── signaling.go
5├── web/
6│   ├── index.html
7│   ├── app.js
8│   └── style.css
9└── go.mod

Explanation of Each Component

  • main.go: The main entry point of your Go application.
  • signaling/: Directory containing signaling-related code to establish peer connections.
  • web/: Directory for frontend files including HTML, JavaScript, and CSS.

App Architecture

pion-webrtc

Overview of the Architecture

The architecture of a Pion WebRTC application typically involves the following components:
  • Signaling Server: Manages the exchange of signaling messages between peers to establish a connection.
  • Peer Connections: Direct connections between peers for media and data exchange.
  • Media Streams: Handles the capture and rendering of audio and video streams.

How Components Interact

  • Signaling Server: Facilitates the initial handshake between peers. This can be done using WebSockets, HTTP, or any other communication protocol.
  • Peer Connections: Once signaling is complete, peers establish a direct connection for real-time communication.
  • Media Streams: Capture media from user devices and send it over peer connections.

Real-time Data Flow

  • Client A initiates a connection request.
  • Signaling server exchanges offer/answer messages between Client A and Client B.
  • Peer connection is established.
  • Media streams (audio/video) are transmitted directly between clients.
By following this structure and understanding the basic components, you will be well on your way to creating a robust real-time communication application using Pion WebRTC and Go. In the next sections, we will go through each step of building this application, starting with setting up the main Go file and writing the initial code.

Step 1: Get Started with Main.go

In this step, we will focus on setting up the main entry point of our Pion WebRTC application. This involves writing the initial code, importing necessary packages, and configuring basic settings for our application.

Setting Up Main.go

[a] Writing the Initial Code

Create a main.go file in the root directory of your project. This file will serve as the starting point of your application. Open main.go and start by defining the package and importing the necessary packages.

Go

1package main
2
3import (
4    "fmt"
5    "log"
6    "net/http"
7    "github.com/pion/webrtc/v3"
8    "github.com/gorilla/websocket"
9)
10
11// Define constants and variables
12const (
13    webPort = ":8080"
14)
15
16var upgrader = websocket.Upgrader{
17    ReadBufferSize:  1024,
18    WriteBufferSize: 1024,
19}
20
21func main() {
22    // Setup HTTP server
23    http.HandleFunc("/ws", handleWebSocket)
24    fs := http.FileServer(http.Dir("./web"))
25    http.Handle("/", fs)
26
27    fmt.Printf("Starting server at http://localhost%s\n", webPort)
28    log.Fatal(http.ListenAndServe(webPort, nil))
29}
30
31func handleWebSocket(w http.ResponseWriter, r *http.Request) {
32    conn, err := upgrader.Upgrade(w, r, nil)
33    if err != nil {
34        log.Println(err)
35        return
36    }
37    defer conn.Close()
38
39    // Handle WebSocket connection and signaling
40}
This basic setup includes:
  • Importing required packages such as net/http, github.com/pion/webrtc/v3, and github.com/gorilla/websocket.
  • Defining a WebSocket upgrader to handle WebSocket connections.
  • Setting up an HTTP server to serve static files and handle WebSocket connections.
  • A placeholder function handleWebSocket to manage signaling.

[b] Importing Necessary Packages

We import the necessary packages to enable WebRTC functionality and handle WebSocket connections:
  • net/http: To create and handle the HTTP server.
  • github.com/pion/webrtc/v3: The Pion WebRTC package.
  • github.com/gorilla/websocket: To handle WebSocket connections.

[c] Basic Configuration

The basic configuration involves setting up a simple HTTP server that listens on port 8080 and handles WebSocket connections for signaling.

Initialization

[d] Setting Up Signaling

Signaling is crucial for establishing peer connections between clients. In this step, we will set up the signaling server to handle WebSocket messages, which include WebRTC offers and answers.
Update the handleWebSocket function to handle incoming WebSocket messages:

Go

1func handleWebSocket(w http.ResponseWriter, r *http.Request) {
2    conn, err := upgrader.Upgrade(w, r, nil)
3    if err != nil {
4        log.Println(err)
5        return
6    }
7    defer conn.Close()
8
9    // Handle incoming messages
10    for {
11        _, message, err := conn.ReadMessage()
12        if err != nil {
13            log.Println(err)
14            break
15        }
16
17        // Process signaling messages
18        log.Printf("Received message: %s", message)
19        // Add logic to handle signaling messages (e.g., offer, answer, ICE candidates)
20    }
21}

[e] Creating Peer Connections

Next, we will create a function to initialize peer connections. This includes setting up ICE servers, creating peer connection objects, and handling incoming signaling messages.
Add the following function to main.go:

Go

1func createPeerConnection() (*webrtc.PeerConnection, error) {
2    // Define ICE servers
3    iceServers := []webrtc.ICEServer{
4        {
5            URLs: []string{"stun:stun.l.google.com:19302"},
6        },
7    }
8
9    // Create a new RTCPeerConnection
10    config := webrtc.Configuration{
11        ICEServers: iceServers,
12    }
13    peerConnection, err := webrtc.NewPeerConnection(config)
14    if err != nil {
15        return nil, err
16    }
17
18    // Handle ICE connection state changes
19    peerConnection.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
20        fmt.Printf("ICE Connection State has changed: %s\n", state.String())
21    })
22
23    return peerConnection, nil
24}
This function:
  • Configures ICE servers for NAT traversal.
  • Creates a new RTCPeerConnection object.
  • Sets up a handler for ICE connection state changes.
With these initial steps, we have set up the main file of our Pion WebRTC application. We've created a basic HTTP server, initialized WebSocket handling for signaling, and set up functions to create peer connections. In the next part, we will focus on wireframing all components, planning the UI, and integrating the frontend with our WebRTC backend.

Step 2: Wireframe All Components

In this part, we will focus on wireframing the entire application. This includes planning the UI components, designing the user flow, and integrating the frontend with the Pion WebRTC backend. Proper wireframing is crucial as it sets the foundation for a seamless user experience and ensures that all components interact efficiently.

Wireframing the App

Planning UI Components

Before diving into the code, it's essential to plan the user interface components that will form the building blocks of our real-time communication application. The key components include:
  • Join Screen: A screen where users can enter their names and join the communication session.
  • Controls Panel: A panel that provides controls for video and audio (e.g., mute/unmute, start/stop video).
  • Participant View: A section that displays the video streams of all participants.

Designing the User Flow

The user flow outlines how users will interact with the application:
  1. Join Screen: Users start by entering their name and clicking the join button.
  2. Participant View: Upon joining, users see their video stream along with the streams of other participants.
  3. Controls Panel: Users can control their audio and video using the controls panel.
By mapping out these components and user flow, we can ensure a cohesive and intuitive user experience.

Frontend Integration

To bring our wireframe to life, we need to set up the frontend using HTML, CSS, and JavaScript. We'll serve these files from our Go server.

[a] Creating the Join Screen UI

Create an index.html file in the web/ directory with the following content:

HTML

1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Pion WebRTC App</title>
7    <link rel="stylesheet" href="style.css">
8</head>
9<body>
10    <div id="join-screen">
11        <input type="text" id="name" placeholder="Enter your name">
12        <button id="join-btn">Join</button>
13    </div>
14    <div id="participant-view" style="display: none;">
15        <div id="videos"></div>
16        <div id="controls">
17            <button id="mute-btn">Mute</button>
18            <button id="video-btn">Stop Video</button>
19        </div>
20    </div>
21    <script src="app.js"></script>
22</body>
23</html>

[b] Designing with CSS

Create a style.css file in the web/ directory to style the UI components:

CSS

1body {
2    font-family: Arial, sans-serif;
3    text-align: center;
4    margin: 0;
5    padding: 0;
6}
7
8#join-screen {
9    margin-top: 100px;
10}
11
12#participant-view {
13    margin-top: 20px;
14}
15
16#videos {
17    display: flex;
18    justify-content: center;
19    flex-wrap: wrap;
20}
21
22#controls {
23    margin-top: 20px;
24}

[c] Connecting Frontend with Pion WebRTC

Create an app.js file in the web/ directory to handle the interaction between the frontend and the WebRTC backend:

JavaScript

1document.getElementById('join-btn').addEventListener('click', joinSession);
2
3let localStream;
4
5async function joinSession() {
6    const name = document.getElementById('name').value;
7    if (!name) {
8        alert('Please enter your name');
9        return;
10    }
11
12    document.getElementById('join-screen').style.display = 'none';
13    document.getElementById('participant-view').style.display = 'block';
14
15    // Initialize WebRTC connection and join the session
16    const peerConnection = new RTCPeerConnection();
17    
18    localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
19    localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
20
21    const localVideo = document.createElement('video');
22    localVideo.srcObject = localStream;
23    localVideo.autoplay = true;
24    localVideo.muted = true;
25    document.getElementById('videos').appendChild(localVideo);
26
27    // Handle signaling and peer connections here
28    // This will include WebSocket communication with the backend
29}
This JavaScript code handles:
  • The user joining the session by clicking the join button.
  • Initializing a WebRTC connection.
  • Capturing the user's video and audio streams.
  • Displaying the local video stream.
With the wireframe in place, we have a clear roadmap for building our Pion WebRTC application. We've planned the UI components, designed the user flow, and started integrating the frontend with the backend. In the next steps, we will implement the join screen functionality, handle signaling, and manage peer connections to enable real-time communication between participants.

Step 3: Implement Join Screen

In this step, we will focus on implementing the join screen functionality, which includes creating the join screen UI, handling user inputs, and managing the backend logic for user join requests and peer connections.

Creating the Join Screen UI

The join screen is where users will enter their name and join the communication session. We've already set up the basic HTML structure in the previous part. Now, let's enhance it and add the necessary JavaScript code to handle the join functionality.

[a] HTML Structure

Ensure your index.html file in the web/ directory looks like this:

HTML

1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Pion WebRTC App</title>
7    <link rel="stylesheet" href="style.css">
8</head>
9<body>
10    <div id="join-screen">
11        <input type="text" id="name" placeholder="Enter your name">
12        <button id="join-btn">Join</button>
13    </div>
14    <div id="participant-view" style="display: none;">
15        <div id="videos"></div>
16        <div id="controls">
17            <button id="mute-btn">Mute</button>
18            <button id="video-btn">Stop Video</button>
19        </div>
20    </div>
21    <script src="app.js"></script>
22</body>
23</html>

[b] JavaScript Code

Next, let's add the logic to handle user inputs and join requests. Update your app.js file in the web/ directory with the following code:

JavaScript

1document.getElementById('join-btn').addEventListener('click', joinSession);
2
3let localStream;
4let peerConnection;
5
6async function joinSession() {
7    const name = document.getElementById('name').value;
8    if (!name) {
9        alert('Please enter your name');
10        return;
11    }
12
13    document.getElementById('join-screen').style.display = 'none';
14    document.getElementById('participant-view').style.display = 'block';
15
16    // Initialize WebRTC connection and join the session
17    peerConnection = new RTCPeerConnection({
18        iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
19    });
20    
21    localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
22    localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
23
24    const localVideo = document.createElement('video');
25    localVideo.srcObject = localStream;
26    localVideo.autoplay = true;
27    localVideo.muted = true;
28    document.getElementById('videos').appendChild(localVideo);
29
30    // Connect to the signaling server
31    const ws = new WebSocket(`ws://${window.location.host}/ws`);
32
33    ws.onopen = () => {
34        console.log('Connected to the signaling server');
35        ws.send(JSON.stringify({ type: 'join', name: name }));
36    };
37
38    ws.onmessage = async (message) => {
39        const data = JSON.parse(message.data);
40        switch (data.type) {
41            case 'offer':
42                await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer));
43                const answer = await peerConnection.createAnswer();
44                await peerConnection.setLocalDescription(answer);
45                ws.send(JSON.stringify({ type: 'answer', answer: answer }));
46                break;
47            case 'answer':
48                await peerConnection.setRemoteDescription(new RTCSessionDescription(data.answer));
49                break;
50            case 'candidate':
51                await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
52                break;
53            default:
54                break;
55        }
56    };
57
58    peerConnection.onicecandidate = (event) => {
59        if (event.candidate) {
60            ws.send(JSON.stringify({ type: 'candidate', candidate: event.candidate }));
61        }
62    };
63
64    peerConnection.ontrack = (event) => {
65        const remoteVideo = document.createElement('video');
66        remoteVideo.srcObject = event.streams[0];
67        remoteVideo.autoplay = true;
68        document.getElementById('videos').appendChild(remoteVideo);
69    };
70}
This JavaScript code handles:
  • User Input: Captures the name entered by the user and handles the join button click event.
  • Media Stream Initialization: Accesses the user's camera and microphone and displays the local video stream.
  • WebSocket Connection: Establishes a WebSocket connection to the signaling server to manage signaling messages.
  • WebRTC Peer Connection: Manages the peer connection, handling ICE candidates and media streams.

Backend Logic

Next, we need to update the backend to handle WebSocket connections and signaling messages. This includes managing user join requests and peer connections.

Updating main.go

Modify the handleWebSocket function in main.go to process signaling messages:

Go

1func handleWebSocket(w http.ResponseWriter, r *http.Request) {
2    conn, err := upgrader.Upgrade(w, r, nil)
3    if err != nil {
4        log.Println(err)
5        return
6    }
7    defer conn.Close()
8
9    for {
10        _, message, err := conn.ReadMessage()
11        if err != nil {
12            log.Println(err)
13            break
14        }
15
16        var msg map[string]interface{}
17        if err := json.Unmarshal(message, &msg); err != nil {
18            log.Println(err)
19            continue
20        }
21
22        switch msg["type"] {
23        case "join":
24            log.Printf("%s joined the session", msg["name"])
25        case "offer":
26            // Handle offer message
27        case "answer":
28            // Handle answer message
29        case "candidate":
30            // Handle ICE candidate message
31        }
32    }
33}
This function:
  • Reads and Parses Messages: Reads incoming WebSocket messages and parses them as JSON.
  • Handles Different Message Types: Logs user join messages and sets up placeholders for handling offer, answer, and ICE candidate messages.
In this part, we have implemented the join screen functionality, including creating the UI, handling user inputs, and setting up the backend logic for user join requests and peer connections. This lays the groundwork for establishing real-time communication between participants. In the next steps, we will implement the controls panel and participant view to enable users to manage their audio and video streams and view other participants.

Step 4: Implement Controls

In this step, we will implement the controls panel for managing audio and video streams. This includes adding buttons to mute/unmute the microphone and start/stop the video, and ensuring these controls are synchronized with the WebRTC peer connection.

Building Control Components

The controls panel will provide users with the ability to manage their audio and video streams. We will add two buttons: one for muting/unmuting the microphone and another for starting/stopping the video.

[a] Updating HTML Structure

Update your index.html file to include IDs for the control buttons:

HTML

1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Pion WebRTC App</title>
7    <link rel="stylesheet" href="style.css">
8</head>
9<body>
10    <div id="join-screen">
11        <input type="text" id="name" placeholder="Enter your name">
12        <button id="join-btn">Join</button>
13    </div>
14    <div id="participant-view" style="display: none;">
15        <div id="videos"></div>
16        <div id="controls">
17            <button id="mute-btn">Mute</button>
18            <button id="video-btn">Stop Video</button>
19        </div>
20    </div>
21    <script src="app.js"></script>
22</body>
23</html>

[b] JavaScript Code for Controls

Next, add the logic to handle the mute/unmute and start/stop video functionalities in your app.js file:

JavaScript

1document.getElementById('join-btn').addEventListener('click', joinSession);
2document.getElementById('mute-btn').addEventListener('click', toggleMute);
3document.getElementById('video-btn').addEventListener('click', toggleVideo);
4
5let localStream;
6let peerConnection;
7let isMuted = false;
8let isVideoStopped = false;
9
10async function joinSession() {
11    const name = document.getElementById('name').value;
12    if (!name) {
13        alert('Please enter your name');
14        return;
15    }
16
17    document.getElementById('join-screen').style.display = 'none';
18    document.getElementById('participant-view').style.display = 'block';
19
20    peerConnection = new RTCPeerConnection({
21        iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
22    });
23    
24    localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
25    localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
26
27    const localVideo = document.createElement('video');
28    localVideo.srcObject = localStream;
29    localVideo.autoplay = true;
30    localVideo.muted = true;
31    document.getElementById('videos').appendChild(localVideo);
32
33    const ws = new WebSocket(`ws://${window.location.host}/ws`);
34
35    ws.onopen = () => {
36        console.log('Connected to the signaling server');
37        ws.send(JSON.stringify({ type: 'join', name: name }));
38    };
39
40    ws.onmessage = async (message) => {
41        const data = JSON.parse(message.data);
42        switch (data.type) {
43            case 'offer':
44                await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer));
45                const answer = await peerConnection.createAnswer();
46                await peerConnection.setLocalDescription(answer);
47                ws.send(JSON.stringify({ type: 'answer', answer: answer }));
48                break;
49            case 'answer':
50                await peerConnection.setRemoteDescription(new RTCSessionDescription(data.answer));
51                break;
52            case 'candidate':
53                await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
54                break;
55            default:
56                break;
57        }
58    };
59
60    peerConnection.onicecandidate = (event) => {
61        if (event.candidate) {
62            ws.send(JSON.stringify({ type: 'candidate', candidate: event.candidate }));
63        }
64    };
65
66    peerConnection.ontrack = (event) => {
67        const remoteVideo = document.createElement('video');
68        remoteVideo.srcObject = event.streams[0];
69        remoteVideo.autoplay = true;
70        document.getElementById('videos').appendChild(remoteVideo);
71    };
72}
73
74function toggleMute() {
75    localStream.getAudioTracks().forEach(track => track.enabled = !track.enabled);
76    isMuted = !isMuted;
77    document.getElementById('mute-btn').textContent = isMuted ? 'Unmute' : 'Mute';
78}
79
80function toggleVideo() {
81    localStream.getVideoTracks().forEach(track => track.enabled = !track.enabled);
82    isVideoStopped = !isVideoStopped;
83    document.getElementById('video-btn').textContent = isVideoStopped ? 'Start Video' : 'Stop Video';
84}
This JavaScript code handles:
  • Mute/Unmute Functionality: Toggles the audio track of the local stream and updates the button text.
  • Start/Stop Video Functionality: Toggles the video track of the local stream and updates the button text.

Synchronizing Controls

To ensure real-time updates and synchronization of controls, we need to handle the media track states appropriately. This has been addressed in the toggleMute and toggleVideo functions where the tracks' enabled properties are toggled.

Handling User Interactions

Ensure that the controls respond to user interactions in real-time by updating the UI and managing the media tracks.
  • Mute/Unmute: The toggleMute function toggles the enabled state of the audio tracks in the local stream and updates the button text to reflect the current state.
  • Start/Stop Video: The toggleVideo function toggles the enabled state of the video tracks in the local stream and updates the button text to reflect the current state.
In this part, we have implemented the controls panel, allowing users to manage their audio and video streams. We added buttons for mute/unmute and start/stop video functionalities and ensured these controls are synchronized with the WebRTC peer connection. In the next step, we will implement the participant view to display the video streams of all participants, enabling a complete real-time communication experience.

Get Free 10,000 Minutes Every Months

No credit card required to start | Let’s build together with VideoSDK

Step 5: Implement Participant View

In this step, we will focus on implementing the participant view, which will display the video streams of all participants in the communication session. This involves updating the UI to dynamically handle multiple video streams and ensuring that the participant view is synchronized with the WebRTC peer connections.

Displaying Participants

The participant view will show the local video stream and the video streams of remote participants. We'll need to handle the addition and removal of video elements dynamically based on the peer connections.

[a] JavaScript Code for Participant View

Update your app.js file to handle the display of participant video streams:

JavaScript

1document.getElementById('join-btn').addEventListener('click', joinSession);
2document.getElementById('mute-btn').addEventListener('click', toggleMute);
3document.getElementById('video-btn').addEventListener('click', toggleVideo);
4
5let localStream;
6let peerConnection;
7let isMuted = false;
8let isVideoStopped = false;
9
10async function joinSession() {
11    const name = document.getElementById('name').value;
12    if (!name) {
13        alert('Please enter your name');
14        return;
15    }
16
17    document.getElementById('join-screen').style.display = 'none';
18    document.getElementById('participant-view').style.display = 'block';
19
20    peerConnection = new RTCPeerConnection({
21        iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
22    });
23    
24    localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
25    localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
26
27    const localVideo = document.createElement('video');
28    localVideo.srcObject = localStream;
29    localVideo.autoplay = true;
30    localVideo.muted = true;
31    document.getElementById('videos').appendChild(localVideo);
32
33    const ws = new WebSocket(`ws://${window.location.host}/ws`);
34
35    ws.onopen = () => {
36        console.log('Connected to the signaling server');
37        ws.send(JSON.stringify({ type: 'join', name: name }));
38    };
39
40    ws.onmessage = async (message) => {
41        const data = JSON.parse(message.data);
42        switch (data.type) {
43            case 'offer':
44                await peerConnection.setRemoteDescription(new RTCSessionDescription(data.offer));
45                const answer = await peerConnection.createAnswer();
46                await peerConnection.setLocalDescription(answer);
47                ws.send(JSON.stringify({ type: 'answer', answer: answer }));
48                break;
49            case 'answer':
50                await peerConnection.setRemoteDescription(new RTCSessionDescription(data.answer));
51                break;
52            case 'candidate':
53                await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
54                break;
55            default:
56                break;
57        }
58    };
59
60    peerConnection.onicecandidate = (event) => {
61        if (event.candidate) {
62            ws.send(JSON.stringify({ type: 'candidate', candidate: event.candidate }));
63        }
64    };
65
66    peerConnection.ontrack = (event) => {
67        addRemoteStream(event.streams[0]);
68    };
69}
70
71function toggleMute() {
72    localStream.getAudioTracks().forEach(track => track.enabled = !track.enabled);
73    isMuted = !isMuted;
74    document.getElementById('mute-btn').textContent = isMuted ? 'Unmute' : 'Mute';
75}
76
77function toggleVideo() {
78    localStream.getVideoTracks().forEach(track => track.enabled = !track.enabled);
79    isVideoStopped = !isVideoStopped;
80    document.getElementById('video-btn').textContent = isVideoStopped ? 'Start Video' : 'Stop Video';
81}
82
83function addRemoteStream(stream) {
84    const remoteVideo = document.createElement('video');
85    remoteVideo.srcObject = stream;
86    remoteVideo.autoplay = true;
87    document.getElementById('videos').appendChild(remoteVideo);
88}

[b] Functionality of addRemoteStream

The addRemoteStream function dynamically creates a new video element for each incoming remote stream and appends it to the videos container. This ensures that all participants' video streams are displayed in the participant view.

Managing Streams

To handle the addition and removal of streams, ensure that the application correctly manages peer connections and updates the UI accordingly.

[a] Handling Remote Streams

The peerConnection.ontrack event is triggered whenever a new track is added to the connection. This is where we call the addRemoteStream function to add the new stream to the participant view.

[b] Removing Streams

To manage the removal of streams, you can add additional logic to handle the peerConnection.onremovetrack event and update the UI to remove the corresponding video element. Here’s a basic example of how to handle this:

JavaScript

1peerConnection.onremovetrack = (event) => {
2    const videoElements = document.getElementById('videos').getElementsByTagName('video');
3    for (let video of videoElements) {
4        if (video.srcObject === event.streams[0]) {
5            video.remove();
6            break;
7        }
8    }
9};

[c] Ensuring Synchronization

Ensure that the participant view is synchronized with the state of the WebRTC peer connections by:
  • Handling connection state changes.
  • Updating the UI based on connection status (e.g., adding/removing video elements).
  • Managing media tracks appropriately to reflect the current state of each peer connection.
In this part, we have implemented the participant view to display the video streams of all participants. We added the necessary JavaScript code to dynamically handle multiple video streams and ensure that the participant view is synchronized with the WebRTC peer connections. In the final step, we will run the application, test it, and troubleshoot any issues that arise to ensure a smooth and functional real-time communication experience.

Step 6: Run Your Code Now

In this final step, we will focus on running the application, testing it to ensure everything works correctly, and troubleshooting common issues. By the end of this step, you should have a fully functional real-time communication application using Pion WebRTC and Go.

Running the Application

Starting the Server

To run your Pion WebRTC application, navigate to the root directory of your project in the terminal and start the Go server:

sh

1go run main.go
This command will compile and run your Go application, starting the HTTP server on the specified port (e.g., :8080). You should see output indicating that the server has started:
1Starting server at http://localhost:8080

Accessing the Application

Open your web browser and navigate to http://localhost:8080. You should see the join screen where you can enter your name and join the session.

Testing the Application

Joining the Session

  1. Enter your name in the input field on the join screen.
  2. Click the "Join" button to enter the communication session.
  3. You should see your local video stream appear in the participant view.

Adding More Participants

To test with multiple participants, open additional browser tabs or windows and repeat the join process with different names. Each new participant should see their local video stream as well as the video streams of other participants.

Testing Controls

  • Mute/Unmute: Click the "Mute" button to mute your microphone. The button text should change to "Unmute." Click it again to unmute.
  • Start/Stop Video: Click the "Stop Video" button to stop your video stream. The button text should change to "Start Video." Click it again to start the video.

Verifying Peer Connections

Check the browser's console (usually accessible by pressing F12 or right-clicking and selecting "Inspect") to verify that WebRTC peer connections are being established correctly. You should see logs indicating the connection state and ICE candidate exchanges.

Troubleshooting (Common Issues and Solutions)

WebSocket Connection Fails

  • Ensure the WebSocket URL matches the server's address.
  • Check for any network or firewall restrictions.

No Video or Audio

  • Verify that your browser has permission to access the camera and microphone.
  • Check if the media devices are being correctly accessed using navigator.mediaDevices.getUserMedia.

ICE Candidate Errors

  • Ensure that the ICE server configuration is correct.
  • Check the network connectivity and firewall settings.

Signaling Issues

  • Verify that the signaling server is correctly handling offer, answer, and candidate messages.
  • Check the WebSocket message handling in both the client and server code.

Debugging Tips

  • Use browser developer tools to inspect network requests, WebSocket messages, and console logs.
  • Add additional logging in your Go and JavaScript code to trace the flow of signaling messages and peer connection states.
  • Test in different browsers to ensure compatibility and identify browser-specific issues.

Conclusion

Congratulations! You have successfully built a real-time communication application using Pion WebRTC and Go. Here's a recap of what we've covered:
  • Introduction to Pion WebRTC: Understanding the technology and its benefits.
  • Getting Started: Setting up the project structure, installing Pion WebRTC, and configuring the initial setup.
  • Join Screen Implementation: Creating the join screen UI and handling user inputs.
  • Controls Implementation: Adding mute/unmute and start/stop video functionalities.
  • Participant View Implementation: Displaying the video streams of all participants.
  • Running and Testing: Running the server, testing the application, and troubleshooting common issues.
With this foundation, you can further enhance your application by adding features such as screen sharing, chat functionality, and advanced media processing. The possibilities are vast, and Pion WebRTC provides a powerful platform to build innovative real-time communication solutions.

Want to level-up your learning? Subscribe now

Subscribe to our newsletter for more tech based insights

FAQ