Skip to content

openvidu-js#

Source code

This tutorial is a simple video-call application built with plain JavaScript, HTML and CSS that allows:

  • Joining a video call room by requesting a token from any application server.
  • Publishing your camera and microphone.
  • Subscribing to all other participants' video and audio tracks automatically.
  • Leaving the video call room at any time.

It uses the LiveKit JS SDK to connect to the LiveKit server and interact with the video call room.

Running this tutorial#

1. Run OpenVidu Server#

  1. Download OpenVidu

    git clone https://github.com/OpenVidu/openvidu-local-deployment
    
  2. Configure the local deployment

    cd openvidu-local-deployment/community
    .\configure_lan_private_ip_windows.bat
    
    cd openvidu-local-deployment/community
    ./configure_lan_private_ip_macos.sh
    
    cd openvidu-local-deployment/community
    ./configure_lan_private_ip_linux.sh
    
  3. Run OpenVidu

    docker compose up
    

To use a production-ready OpenVidu deployment, visit the official deployment guide.

2. Download the tutorial code#

git clone https://github.com/OpenVidu/openvidu-livekit-tutorials.git

3. Run a server application#

To run this server application, you need Node installed on your device.

  1. Navigate into the server directory
    cd openvidu-livekit-tutorials/application-server/node
    
  2. Install dependencies
    npm install
    
  3. Run the application
    npm start
    

To run this server application, you need Go installed on your device.

  1. Navigate into the server directory
    cd openvidu-livekit-tutorials/application-server/go
    
  2. Run the application
    go run main.go
    

To run this server application, you need Ruby installed on your device.

  1. Navigate into the server directory
    cd openvidu-livekit-tutorials/application-server/ruby
    
  2. Install dependencies
    bundle install
    
  3. Run the application
    ruby app.rb
    

To run this server application, you need Java and Maven installed on your device.

  1. Navigate into the server directory
    cd openvidu-livekit-tutorials/application-server/java
    
  2. Run the application
    mvn spring-boot:run
    

To run this server application, you need Python 3 installed on your device.

  1. Navigate into the server directory

    cd openvidu-livekit-tutorials/application-server/python
    
  2. Create a python virtual environment

    python -m venv venv
    
  3. Activate the virtual environment

    .\venv\Scripts\activate
    
    . ./venv/bin/activate
    
    . ./venv/bin/activate
    
  4. Install dependencies

    pip install -r requirements.txt
    
  5. Run the application

    python app.py
    

To run this server application, you need Rust installed on your device.

  1. Navigate into the server directory
    cd openvidu-livekit-tutorials/application-server/rust
    
  2. Run the application
    cargo run
    

To run this server application, you need PHP and Composer installed on your device.

  1. Navigate into the server directory
    cd openvidu-livekit-tutorials/application-server/php
    
  2. Install dependencies
    composer install
    
  3. Run the application
    composer start
    

To run this server application, you need .NET installed on your device.

  1. Navigate into the server directory
    cd openvidu-livekit-tutorials/application-server/dotnet
    
  2. Run the application
    dotnet run
    

Warning

This .NET server application needs the LIVEKIT_API_SECRET env variable to be at least 32 characters long. Make sure to update it here and in your OpenVidu Server.

4. Run the client application#

To run the client application tutorial, you need an HTTP web server installed on your development computer. A great option is http-server. You can install it via NPM:

npm install -g http-server
  1. Navigate into the application client directory:

    cd openvidu-livekit-tutorials/application-client/openvidu-js
    
  2. Serve the application:

    http-server -p 5080 ./src
    

Once the server is up and running, you can test the application by visiting http://localhost:5080. You should see a screen like this:

Accessing your application client from other devices in your local network

One advantage of running OpenVidu locally is that you can test your application client with other devices in your local network very easily without worrying about SSL certificates.

Access your application client through https://xxx-yyy-zzz-www.openvidu-local.dev:5443, where xxx-yyy-zzz-www part of the domain is your LAN private IP address with dashes (-) instead of dots (.). For more information, see section Accessing your app from other devices in your network.

Understanding the code#

This application is designed to be beginner-friendly and consists of only three essential files that are located in the src directory:

  • app.js: This is the main JavaScript file for the sample application. It uses the LiveKit JS SDK to connect to the LiveKit server and interact with the video call room.
  • index.html: This HTML file is responsible for creating the user interface. It contains the form to connect to a video call and the video call layout.
  • styles.css: This file contains CSS classes that are used to style the index.html page.

To use the LiveKit JS SDK in your application, you need to include the library in your HTML file. You can do this by adding the following script tag to the <head> section of your HTML file:

index.html
<script src="https://cdn.jsdelivr.net/npm/livekit-client@2.1.5/dist/livekit-client.umd.js"></script>

Then, you can use the LivekitClient object in your JavaScript code by referencing it from the window object under LivekitClient. When accessing symbols from the class, you will need to prefix them with LivekitClient.. For example, Room becomes LivekitClient.Room.

Now let's see the code of the app.js file:

app.js
// For local development, leave these variables empty
// For production, configure them with correct URLs depending on your deployment
var APPLICATION_SERVER_URL = ""; // (1)!
var LIVEKIT_URL = ""; // (2)!
configureUrls();

const LivekitClient = window.LivekitClient; // (3)!
var room; // (4)!

function configureUrls() {
    // If APPLICATION_SERVER_URL is not configured, use default value from local development
    if (!APPLICATION_SERVER_URL) {
        if (window.location.hostname === "localhost") {
            APPLICATION_SERVER_URL = "http://localhost:6080/";
        } else {
            APPLICATION_SERVER_URL = "https://" + window.location.hostname + ":6443/";
        }
    }

    // If LIVEKIT_URL is not configured, use default value from local development
    if (!LIVEKIT_URL) {
        if (window.location.hostname === "localhost") {
            LIVEKIT_URL = "ws://localhost:7880/";
        } else {
            LIVEKIT_URL = "wss://" + window.location.hostname + ":7443/";
        }
    }
}
  1. The URL of the application server.
  2. The URL of the LiveKit server.
  3. The LivekitClient object, which is the entry point to the LiveKit JS SDK.
  4. The room object, which represents the video call room.

The app.js file defines the following variables:

  • APPLICATION_SERVER_URL: The URL of the application server. This variable is used to make requests to the server to obtain a token for joining the video call room.
  • LIVEKIT_URL: The URL of the LiveKit server. This variable is used to connect to the LiveKit server and interact with the video call room.
  • LivekitClient: The LiveKit JS SDK object, which is the entry point to the LiveKit JS SDK.
  • room: The room object, which represents the video call room.

Configure the URLs

For local development, leave APPLICATION_SERVER_URL and LIVEKIT_URL variables empty. The function configureUrls() will automatically configure them with default values. However, for production, you should configure these variables with the correct URLs depending on your deployment.


Joining a Room#

After the user specifies their participant name and the name of the room they want to join, when they click the Join button, the joinRoom() function is called:

app.js
async function joinRoom() {
    // Disable 'Join' button
    document.getElementById("join-button").disabled = true;
    document.getElementById("join-button").innerText = "Joining...";

    // Initialize a new Room object
    room = new LivekitClient.Room(); // (1)!

    // Specify the actions when events take place in the room
    // On every new Track received...
    room.on(LivekitClient.RoomEvent.TrackSubscribed, (track, _publication, participant) => {
        // (2)!
        addTrack(track, participant.identity);
    });

    // On every new Track destroyed...
    room.on(LivekitClient.RoomEvent.TrackUnsubscribed, (track, _publication, participant) => {
        // (3)!
        track.detach();
        document.getElementById(track.sid)?.remove();

        if (track.kind === "video") {
            removeVideoContainer(participant.identity);
        }
    });

    try {
        // Get the room name and participant name from the form
        const roomName = document.getElementById("room-name").value; // (4)!
        const userName = document.getElementById("participant-name").value;

        // Get a token from your application server with the room name and participant name
        const token = await getToken(roomName, userName); // (5)!

        // Connect to the room with the LiveKit URL and the token
        await room.connect(LIVEKIT_URL, token); // (6)!

        // Hide the 'Join room' page and show the 'Room' page
        document.getElementById("room-title").innerText = roomName; // (7)!
        document.getElementById("join").hidden = true;
        document.getElementById("room").hidden = false;

        // Publish your camera and microphone
        await room.localParticipant.enableCameraAndMicrophone(); // (8)!
        const localVideoTrack = this.room.localParticipant.videoTrackPublications.values().next().value.track;
        addTrack(localVideoTrack, userName, true);
    } catch (error) {
        console.log("There was an error connecting to the room:", error.message);
    }
}
  1. Initialize a new Room object.
  2. Event handling for when a new track is received in the room.
  3. Event handling for when a track is destroyed.
  4. Get the room name and participant name from the form.
  5. Get a token from the application server with the room name and participant name.
  6. Connect to the room with the LiveKit URL and the token.
  7. Hide the "Join room" page and show the "Room" page.
  8. Publish your camera and microphone.

The joinRoom() function performs the following actions:

  1. It creates a new Room object using LivekitClient.Room(). This object represents the video call room.
  2. Event handling is configured for different scenarios within the room. These events are fired when new tracks are subscribed to and when existing tracks are unsubscribed.

    • LivekitClient.RoomEvent.TrackSubscribed: This event is triggered when a new track is received in the room. It handles the attachment of the track to the HTML page, assigning an ID, and appending it to the layout-container element. If the track is of kind video, a video-container is created and participant data is appended as well.
    app.js
    function addTrack(track, participantIdentity, local = false) {
        const element = track.attach(); // (1)!
        element.id = track.sid;
    
        /* If the track is a video track, we create a container and append the video element to it
        with the participant's identity */
        if (track.kind === "video") {
            const videoContainer = createVideoContainer(participantIdentity, local);
            videoContainer.append(element);
            appendParticipantData(videoContainer, participantIdentity + (local ? " (You)" : ""));
        } else {
            document.getElementById("layout-container").append(element);
        }
    }
    
    1. Attach the track to an HTML element.
    app.js
    function createVideoContainer(participantIdentity, local = false) {
        const videoContainer = document.createElement("div");
        videoContainer.id = `camera-${participantIdentity}`;
        videoContainer.className = "video-container";
        const layoutContainer = document.getElementById("layout-container");
    
        if (local) {
            layoutContainer.prepend(videoContainer);
        } else {
            layoutContainer.append(videoContainer);
        }
    
        return videoContainer;
    }
    
    function appendParticipantData(videoContainer, participantIdentity) {
        const dataElement = document.createElement("div");
        dataElement.className = "participant-data";
        dataElement.innerHTML = `<p>${participantIdentity}</p>`;
        videoContainer.prepend(dataElement);
    }
    
    • LivekitClient.RoomEvent.TrackUnsubscribed: This event occurs when a track is destroyed, and it takes care of detaching the track from the HTML page and removing it from the DOM. If the track is a video track, video-container with the participant's identity is removed as well.
    app.js
    function removeVideoContainer(participantIdentity) {
        const videoContainer = document.getElementById(`camera-${participantIdentity}`);
        videoContainer?.remove();
    }
    

    These event handlers are essential for managing the behavior of tracks within the video call.

    Take a look at all events

    You can take a look at all the events in the Livekit Documentation

  3. It retrieves the room name and participant name from the form.

  4. It requests a token from the application server using the room name and participant name. This is done by calling the getToken() function:

    app.js
    /**
     * --------------------------------------------
     * GETTING A TOKEN FROM YOUR APPLICATION SERVER
     * --------------------------------------------
     * The method below request the creation of a token to
     * your application server. This prevents the need to expose
     * your LiveKit API key and secret to the client side.
     *
     * In this sample code, there is no user control at all. Anybody could
     * access your application server endpoints. In a real production
     * environment, your application server must identify the user to allow
     * access to the endpoints.
     */
    async function getToken(roomName, participantName) {
        const response = await fetch(APPLICATION_SERVER_URL + "token", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify({
                roomName,
                participantName
            })
        });
    
        if (!response.ok) {
            const error = await response.json();
            throw new Error(`Failed to get token: ${error.errorMessage}`);
        }
    
        const token = await response.json();
        return token.token;
    }
    

    This function sends a POST request using fetch() to the application server's /token endpoint. The request body contains the room name and participant name. The server responds with a token that is used to connect to the room.

  5. It connects to the room using the LiveKit URL and the token.

  6. It updates the UI to hide the "Join room" page and show the "Room" layout.
  7. It publishes the camera and microphone tracks to the room using room.localParticipant.enableCameraAndMicrophone(), which asks the user for permission to access their camera and microphone at the same time. The local video track is then added to the layout.

Leaving the Room#

When the user wants to leave the room, they can click the Leave Room button. This action calls the leaveRoom() function:

app.js
async function leaveRoom() {
    // Leave the room by calling 'disconnect' method over the Room object
    await room.disconnect(); // (1)!

    // Remove all HTML elements inside the layout container
    removeAllLayoutElements(); // (2)!

    // Back to 'Join room' page
    document.getElementById("join").hidden = false; // (3)!
    document.getElementById("room").hidden = true;

    // Enable 'Join' button
    document.getElementById("join-button").disabled = false;
    document.getElementById("join-button").innerText = "Join!";
}

// (4)!
window.onbeforeunload = () => {
    room?.disconnect();
};
  1. Disconnect the user from the room.
  2. Remove all HTML elements inside the layout container.
  3. Show the "Join room" page and hide the "Room" layout.
  4. Call the disconnect() method on the room object when the user closes the tab or navigates to another page.

The leaveRoom() function performs the following actions:

  • It disconnects the user from the room by calling the disconnect() method on the room object.
  • It removes all HTML elements inside the layout container by calling the removeAllLayoutElements() function.
  • It shows the "Join room" page and hides the "Room" layout.

The window.onbeforeunload event is used to ensure that the user is disconnected from the room before the page is unloaded. This event is triggered when the user closes the tab or navigates to another page.