Check it on GitHub

A client-side only application built with JavaScript, HTML and CSS.

If it is the first time you use OpenVidu, it is higly recommended to start with openvidu-hello-world tutorial, as this app is no more than an extension of it with some new features and sytles.

Understanding this tutorial

OpenVidu is composed by the three modules displayed on the image above in its insecure version.

  • openvidu-browser: JavaScript library for the browser. It allows you to manage your video-calls straight away from your clients
  • openvidu-server: Java application that controls Kurento Media Server
  • Kurento Media Server: server that handles low level operations of media flows transmission
Tutorial's name includes "insecure" word because this application has no backend and therefore it has no control over the users. Typically you don't want such application in production environments. When you feel comfortable with the client-side of OpenVidu, add your own server or follow one of our super simple secure tutorials.

Running this tutorial

1) Clone the repo:

git clone

2) You will need an http web server installed in your development computer to execute the sample application. If you have node.js installed, you can use http-server to serve application files. It can be installed with:

npm install -g http-server

3) Run the tutorial:

http-server openvidu-tutorials/openvidu-insecure-js/web

4) openvidu-server and Kurento Media Server must be up and running in your development machine. The easiest way is running this Docker container which wraps both of them (you will need Docker CE):

docker run -p 8443:8443 --rm -e -e KMS_STUN_PORT=19302 -e openvidu.secret=MY_SECRET openvidu/openvidu-server-kms

5) Go to localhost:8080 to test the app once the server is running. The first time you use the docker container, an alert message will suggest you accept the self-signed certificate of openvidu-server when you first try to join a video-call.

To learn some tips to develop with OpenVidu, check this FAQ

If you are using Windows, read this FAQ to properly run the tutorial

Understanding the code

This application is very simple. It has only 4 files:

  • openvidu-browser-VERSION.js: openvidu-browser library. You don't have to manipulate this file.
  • app.js: sample application main JavaScritp file, which makes use of openvidu-browser-VERSION.js. You can manipulate this file to suit your needs.
  • style.css: some CSS classes to style index.html. You can manipulate this file to suit your needs.
  • index.html: HTML code for the form to connect to a video-call and for the video-call itself. You can manipulate this file to suit your needs. It has two links to both JavaScript files:
    <script src="openvidu-browser-VERSION.js"></script>
<script src="app.js"></script>

Let's see how app.js uses openvidu-browser-VERSION.js:

First lines declare the two variables that will be needed in different points along the code. OV will be our OpenVidu object and session the video-call we will connect to:

var OV;
var session;

Let's initialize a new session and configure our events:

// --- 1) Get an OpenVidu object and init a session with a sessionId ---

// Init OpenVidu object
OV = new OpenVidu();

// We will join the video-call "sessionId". As there's no server, this parameter must start with the URL of
// OpenVidu Server (with secure websocket protocol: "wss://") and must include the OpenVidu secret at the end
session = OV.initSession("wss://" + location.hostname + ":8443/" + sessionId + '?secret=MY_SECRET');

Session's identifiers must begin with the URL where openvidu-server listens, so they can connect through WebSocket to it. It is necessary to explicitly set this URL in the param when using a pure frontend web. Since we are in a local sample app, OV.initSession will finally receive wss://localhost:8443/ as its openvidu-server URL. sessionId is the distinctive portion of the session identifier and allows OpenVidu to differentiate sessions from each other. In this case, this parameter is retrieved from HTML input <input class="form-control" type="text" id="sessionId" required>, which may be filled by the user. Finally, '?secret=MY_SECRET' string allows us to connect to OpenVidu directly from the browser, without a server side.

WARNING: this is only for demos and developing environments. Do NOT include your secret in production. Check this FAQ to learn more.
// --- 2) Specify the actions when events take place ---

// On every new Stream received...
session.on('streamCreated', function (event) {

    // Subscribe to the Stream to receive it. HTML video will be appended to element with 'video-container' id
    var subscriber = session.subscribe(, 'video-container');

    // When the HTML video has been appended to DOM...
    subscriber.on('videoElementCreated', function (event) {

        // Add a new <p> element for the user's nickname just below its video

// On every Stream destroyed...
session.on('streamDestroyed', function (event) {

    // Delete the HTML element with the user's nickname. HTML videos are automatically removed from DOM

Here we subscribe to the events that interest us. In this case, we want to receive all videos published to the video-call, as well as displaying every user's nickname next to its video. To achieve this:

  • streamCreated: for each new Stream received by OpenVidu, we immediately subscribe to it so we can see its video. A new HTML video element will be appended to element with id 'video-container'.

  • videoElementCreated: event triggered by Subscriber object (returned by the previous Session.subscribe method). This allows us to add the participant nickname to the new video previously added in streamCreated event. Auxiliary method appendUserData is responsible for appending a new paragraph element just below the event.element video, containing field. In this case, this field contains the user's nickName. You can see how to feed this property from the client in the next step.

  • streamDestroyed: for each Stream that has been destroyed (which means a user has left the video-call), we remove the element with the user's nickname that we added in the previous event with the auxiliary method removeUserData (appendUserData method created the element with an id containing unique value, so we can now identify the right element to be removed). OpenVidu automatically deletes the proper video element by default, so we don't need to do anything else.

Check Application specific methods section to see all the auxiliary methods used in this app

Finally connect to the session and publish your webcam:

// --- 3) Connect to the session ---

    // First param irrelevant if your app has no server-side. Second param will be received by every user
    // in property, which will be appended to DOM as the user's nickname
    session.connect(null, '{"clientData": "' + userName + '"}', function (error) {

        // If the connection is successful, initialize a publisher and publish to the session
        if (!error) {

            // --- 4) Get your own camera stream with the desired resolution ---

            var publisher = OV.initPublisher('video-container', {
                audio: true,
                video: true,
                quality: 'MEDIUM'

            // When our HTML video has been added to DOM...
            publisher.on('videoElementCreated', function (event) {
                initMainVideo(event.element, userName);
                appendUserData(event.element, userName);
                event.element['muted']  = true;

            // --- 5) Publish your stream ---


        } else {
            console.log('There was an error connecting to the session:', error.code, error.message);

In session.connect method: first param is irrelevant when you don't have a backend (it is the user's token). Remember videoElementCreated event, when we added the user's nickname to the HTML? Well, second parameter is the actual value you will receive in property. So in this case it is a JSON formatted string with a "clientData" tag with "token" value, which is retrieved from HTML input <input type="text" id="participantId" required> (filled by the user and also reused for the first token param).

In the callback of Session.connect method, we check the connection has been succesful (error value must be null) and right after that we get a Publisher object with both audio and video activated and MEDIUM quality. This process will end with the addition of a new HTML video element showing your camera, as a child of element with id 'video-container'. Event videoElementCreated will be fired by the Publisher object just after this video is added to DOM, so we can subscribe to it and do whatever we want with it. In this case, we init a big video element with our video and append our nickname to it, by using auxiliary methods initMainVideo and appendUserData.

Finally we just have to publish publisher object through Session.publish method, and the rest of users will begin receiving our webcam.

Leaving the session:

Whenever we want a user to leave the session, we just need to call session.disconnect method:

function leaveSession() {

    // --- 6) Leave the session by calling 'disconnect' method over the Session object ---


    // Removing all HTML elements with the user's nicknames. 
    // HTML videos are automatically removed when leaving a Session

    // Back to 'Join session' page
    document.getElementById('join').style.display = 'block';
    document.getElementById('session').style.display = 'none';

Application specific methods

Here you have all the auxiliary methods used in this app, which are not directly related to OpenVidu:


window.addEventListener('load', function () {

window.onbeforeunload = function () {
    if (session) session.disconnect();

function generateParticipantInfo() {
    document.getElementById("sessionId").value = "SessionA";
    document.getElementById("userName").value = "Participant" + Math.floor(Math.random() * 100);

function appendUserData(videoElement, connection) {
    var userData;
    var nodeId;
    if (typeof connection === "string") {
        userData = connection;
        nodeId = connection;
    } else {
        userData = JSON.parse(;
        nodeId = connection.connectionId;
    var dataNode = document.createElement('div');
    dataNode.className = "data-node"; = "data-" + nodeId;
    dataNode.innerHTML = "<p>" + userData + "</p>";
    videoElement.parentNode.insertBefore(dataNode, videoElement.nextSibling);
    addClickListener(videoElement, userData);

function removeUserData(connection) {
    var dataNode = document.getElementById("data-" + connection.connectionId);

function removeAllUserData() {
    var nicknameElements = document.getElementsByClassName('data-node');
    while (nicknameElements[0]) {

function addClickListener(videoElement, userData) {
    videoElement.addEventListener('click', function () {
        var mainVideo = document.querySelector('#main-video video');
        var mainUserData = document.querySelector('#main-video p');
        if (mainVideo.srcObject !== videoElement.srcObject) {
            mainUserData.innerHTML = userData;
            mainVideo.srcObject = videoElement.srcObject;

function initMainVideo(videoElement, userData) {
    document.querySelector('#main-video video').srcObject = videoElement.srcObject;
    document.querySelector('#main-video p').innerHTML = userData;
    document.querySelector('#main-video video')['muted'] = true;