All Products
Search
Document Center

ApsaraVideo Live:Getting started with the ARTC Web SDK

Last Updated:Feb 26, 2026

Add real-time audio and video communication to your web application with the Alibaba Cloud ARTC Web SDK. This guide walks you through creating an ARTC application, integrating the SDK, and building a working video call.

Step 1: Create an application

  1. Log on to the ApsaraVideo Live console.

  2. In the navigation pane on the left, click Live + > ApsaraVideo Real-time Communication > Applications.

  3. Click Create Application.

  4. Enter a custom Instance Name, read the Terms of Service, and then click Buy Now.

  5. After a success message appears, refresh the Applications page to view your new ARTC application.

    Note

    Creating an application is free. You are charged on a pay-as-you-go basis for actual usage. For billing information, see Billing for audio/video communication.

Step 2: Get the AppID and AppKey

After you create the application, find it in the list. In the Actions column, click Manage to go to the Basic Information page. On this page, you can find the Application ID and AppKey. Save these values for later steps.

image

Step 3: Integrate the SDK

  1. Integrate the SDK.

    Use a script

    Add the following script to your HTML file:

    <script src="https://g.alicdn.com/apsara-media-box/imp-web-rtc/7.1.9/aliyun-rtc-sdk.js"></script>

    Use npm

    Use npm to install the SDK in your project. 

    npm install aliyun-rtc-sdk --save
  2. Initialize the engine.

    // Use one of the following methods.
    // Run the following command if you installed via npm.
    import AliRtcEngine from 'aliyun-rtc-sdk';
    // Run the following command if you used a script.
    const AliRtcEngine = window.AliRtcEngine;
    
    // Check browser compatibility.
    const checkResult = await AliRtcEngine.isSupported();
    if (!checkResult.support) {
      // The browser does not support WebRTC. Change or upgrade the browser.
    }
    
    // Create an AliRtcEngine instance, which can be saved as a global variable.
    const aliRtcEngine = AliRtcEngine.getInstance();
    
  3. Listen for and handle related events.

    // Triggered when the current user leaves the channel.
    aliRtcEngine.on('bye', (code) => {
      // code indicates the reason for leaving the channel. For more information, see the API reference.
      console.log(`bye, code=${code}`);
      // Configure your handling logic, such as exiting the calling page.
    });
    
    // Triggered when a remote user joins the channel.
    aliRtcEngine.on('remoteUserOnLineNotify', (userId, elapsed) => {
      console.log(`The user ${userId} has joined the channel. Time consumed: ${elapsed} seconds.`);
      // Configure your handling logic, such as displaying the module of the remote user.
    });
    
    // Triggered when a remote user leaves the channel.
    aliRtcEngine.on('remoteUserOffLineNotify', (userId, reason) => {
      // reason indicates the reason why the remote user gets offline. For more information, see the API reference.
      console.log(`The user ${userId} has left the channel. Reason: ${reason}.`);
      // Configure your handling logic, such as destroying the module of the remote user.
    });
    
    // Triggered when the subscription state of a remote video stream changes.
    aliRtcEngine.on('videoSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => {
      // oldState and newState are parameters of AliRtcSubscribeState. The valid values are 0 (initialized), 1 (unsubscribed), 2 (subscribing), and 3 (subscribed).
      // interval indicates the time elapsed for the change between two states, in milliseconds.
      console.log(`The state of subscription to the remote user ${userId} in the channel ${channelId} is changed from ${oldState} to ${newState}.`);
      // Configure the handling logic for the remote stream.
      // Call setRemoteViewConfig to play the remote stream if the value of newState changes to 3.
      // Stop the playback if the value of newState changes to 1.
    });
    
    // Triggered when authentication information expires.
    aliRtcEngine.on('authInfoExpired', () => {
      // The authentication information has expired.
      // Obtain a new token from your server and then call refreshAuthInfo to update the authentication information.
      aliRtcEngine.refreshAuthInfo({
        userId,
        token,
        timestamp
      });
    });
    
    // Triggered 30 seconds before authentication information expires.
    aliRtcEngine.on('authInfoWillExpire', () => {
      // After receiving the notification, update the authentication information in a timely manner.
      // To stay in the session, obtain a new token from your server and then call joinChannel to join the channel again.
    });
    
  4. (Optional) Set the channel mode. The default mode is communication mode. For more information, see Set the channel mode and user role.

    // Set the channel mode. Valid arguments: communication (the communication mode) and interactive_live (the interactive mode).
    aliRtcEngine.setChannelProfile('interactive_live');
    // Set the user role. The method is valid only in the interactive mode.
    // Valid arguments: interactive (can publish and subscribe) and live (subscribe only).
    aliRtcEngine.setClientRole('interactive');
  5. Generate a token and call joinChannel to join a channel. For details about token calculation, see Implement token-based authentication.

    • Join with a single parameter (recommended for production):

      In this approach, your server generates a Base64-encoded token. The client retrieves it through an authenticated API call.

      const userName = 'User 1'; // Custom user name. The name supports Chinese characters.
      
      try {
        // Implement fetchToken() to retrieve the Base64-encoded token from your server.
        const base64Token = await fetchToken();
        await aliRtcEngine.joinChannel(base64Token, userName);
        // Continue to perform subsequent operations after joining the channel.
      } catch (error) {
        // Handle potential errors if you fail to join the channel.
      }
    • Join with multiple parameters (for development and testing):

      // Generate a token on the server side or on the on-premises machine. 
      // Note: For your data security, never expose the token generation logic and AppKey in client-side code. 
      const appId = 'yourAppId'; // Enter the application ID obtained from Step 2.
      const appKey = 'yourAppKey'; // Enter the AppKey obtained from Step 2. Note: Do not expose your AppKey in the production environments.  
      const channelId = 'AliRtcDemo'; // Specify the channel ID. The ID can contain only letters and digits.
      const userId = 'test1'; // Specify the user ID. The ID can contain only letters and digits.
      const userName = 'User 1'; // Specify the username. The name supports Chinese characters.
      const timestamp = Math.floor(Date.now() / 1000) + 3600; // Specify the expiration time of the token. For example, a value of 3600 specifies that the token expires an hour after it is generated.
      
      try {
        const token = await generateToken(appId, appKey, channelId, userId, timestamp);
        // Join the channel. The parameters, token, and nonce are generally returned from the server.
        // The channelId, userId, appId, and timestamp parameters must be the same as those used to generate the token.
        await aliRtcEngine.joinChannel({
          channelId,
          userId,
          appId,
          token,
          timestamp,
        }, userName);
        // Continue to perform subsequent operations after joining the channel.
      } catch (error) {
        // Handle potential errors if you fail to join the channel.
      }
  6. By default, after joining a channel, the SDK automatically captures local audio and video and publishes the stream to Global Realtime Transport Network (GRTN). To display a preview of the local video feed, follow these steps:

    1. Create a <video> element in your HTML file to serve as the container for the local preview. Its id is localPreviewer.

      <video
        id="localPreviewer"
        muted
        style="display: block;width: 320px;height: 180px;background-color: black;"
      ></video>
    2. Call setLocalViewConfig and pass in the element ID to start preview. 

      // For the first parameter, pass in HTMLVideoElement or specify the element ID. If you set the value to null, the preview is stopped.
      // Set the value of the second parameter to 1 or 2. 1: the camera stream. 2: the screen sharing stream.
      aliRtcEngine.setLocalViewConfig('localPreviewer', 1);
  7. By default, the SDK automatically subscribes to the audio and video streams of other host users in the channel. While remote audio will begin playing automatically, you must explicitly render the video streams (both camera and screen sharing) using the setRemoteViewConfig method.

    1. In your HTML, create a <div> element to act as a container. This container will hold the dynamically created <video> elements for each remote user.

      <div id="remoteVideoContainer"></div>
    2. The core logic for managing remote videos involves listening for the videoSubscribeStateChanged event. This event fires whenever the subscription status of a remote user's video changes. The following code demonstrates how to dynamically create and remove video elements based on these events.

      // Map to store remote user IDs to their corresponding <video> elements
      const remoteVideoElMap = {};
      // Get the container element for remote videos
      const remoteVideoContainer = document.querySelector('#remoteVideoContainer');
      
      function removeRemoteVideo(userId) {
        const el = remoteVideoElMap[userId];
        if (el) {
          aliRtcEngine.setRemoteViewConfig(null, userId, 1);
          el.pause();
          remoteVideoContainer.removeChild(el);
          delete remoteVideoElMap[userId];
        }
      }
      
      // Listen for changes in remote video subscription status
      aliRtcEngine.on('videoSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => {
        // oldState and newState are of type AliRtcSubscribeState. 0: initializing, 1: unsubscribed, 2: subscribing, 3: subscribed
        // interval specifies the time elapsed for the change between two states, in milliseconds.
        console.log(`The state of subscription to the remote user ${userId} in the channel ${channelId} is changed from ${oldState} to ${newState}.`);
        // The following sample code provides a handling example.
        if (newState === 3) {
          const video = document.createElement('video');
          video.autoplay = true;
          video.setAttribute('style', 'display: block;width: 320px;height: 180px;background-color: black;');
          remoteVideoElMap[userId] = video;
          remoteVideoContainer.appendChild(video);
          // For the first parameter, pass in HTMLVideoElement.
          // For the second parameter, specify the ID of the remote user.
          // Set the value of the third parameter to 1 or 2. 1: the camera stream. 2: the screen sharing stream.
          aliRtcEngine.setRemoteViewConfig(video, userId, 1);
        } else if (newState === 1) {
          removeRemoteVideo(userId);
        }
      });
  8. Clean up.

    // Stop the local preview.
    await aliRtcEngine.stopPreview();
    // Leave the channel.
    await aliRtcEngine.leaveChannel();
    // Destroy the instance.
    aliRtcEngine.destroy();

Demo

Important

The demo generates tokens (generateToken) on the client side using your AppKey. This is for local testing only. Never expose your AppKey in client-side code in a production environment. We recommend that you generate tokens on your server and retrieve them on the frontend by making an authenticated API call.

Prerequisite

Install the http-server package if you haven't already:

npm install --global http-server

Step 1: Create a directory

Create the following file structure:

- demo
  - quick.html
  - quick.js

Step 2: Edit quick.html

Copy the following code to the quick.html file:

Sample code

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>ARTC Web SDK - Quick Start</title>
    <link rel="stylesheet" href="https://g.alicdn.com/code/lib/bootstrap/5.3.0/css/bootstrap.min.css" />
    <style>
      .video {
        display: inline-block;
        width: 320px;
        height: 180px;
        margin-right: 8px;
        margin-bottom: 8px;
        background-color: black;
      }
    </style>
  </head>
  <body class="container p-2">
    <h1>ARTC Web SDK - Quick Start</h1>

    <div class="toast-container position-fixed top-0 end-0 p-3">
      <div id="loginToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
        <div class="toast-header">
          <strong class="me-auto">Logon message</strong>
          <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
        </div>
        <div class="toast-body" id="loginToastBody"></div>
      </div>

      <div id="onlineToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
        <div class="toast-header">
          <strong class="me-auto">User gets online</strong>
          <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
        </div>
        <div class="toast-body" id="onlineToastBody"></div>
      </div>

      <div id="offlineToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
        <div class="toast-header">
          <strong class="me-auto">User gets offline</strong>
          <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
        </div>
        <div class="toast-body" id="offlineToastBody"></div>
      </div>
    </div>

    <div class="row mt-3">
      <div class="col-6">
        <form id="loginForm">
          <div class="form-group mb-2">
            <label for="channelId" class="form-label">Channel ID</label>
            <input class="form-control" id="channelId" />
          </div>
          <div class="form-group mb-2">
            <label for="userId" class="form-label">User ID</label>
            <input class="form-control" id="userId" />
          </div>
          <button id="joinBtn" type="submit" class="btn btn-primary mb-2">Join Channel</button>
          <button id="leaveBtn" type="button" class="btn btn-secondary mb-2" disabled>Leave Channel</button>
        </form>
    
        <div class="mt-3">
          <h4>Local preview</h4>
          <video
            id="localPreviewer"
            muted
            class="video"
          ></video>
        </div>
      </div>
      <div class="col-6">
        <h4>Remote user</h4>
        <div id="remoteVideoContainer"></div>
      </div>
    </div>

    <script src="https://g.alicdn.com/code/lib/jquery/3.7.1/jquery.min.js"></script>
    <script src="https://g.alicdn.com/code/lib/bootstrap/5.3.0/js/bootstrap.min.js"></script>
<script src="https://g.alicdn.com/apsara-media-box/imp-web-rtc/7.1.9/aliyun-rtc-sdk.js"></script>
    <script src="./quick.js"></script>
  </body>
</html>

Step 3: Edit quick.js

Copy the following code to the quick.js file, specify the appId and appKey parameters in the code, and save the file. 

Sample code

function hex(buffer) {
  const hexCodes = [];
  const view = new DataView(buffer);
  for (let i = 0; i < view.byteLength; i += 4) {
    const value = view.getUint32(i);
    const stringValue = value.toString(16);
    const padding = '00000000';
    const paddedValue = (padding + stringValue).slice(-padding.length);
    hexCodes.push(paddedValue);
  }
  return hexCodes.join('');
}
async function generateToken(appId, appKey, channelId, userId, timestamp) {
  const encoder = new TextEncoder();
  const data = encoder.encode(`${appId}${appKey}${channelId}${userId}${timestamp}`);

  const hash = await crypto.subtle.digest('SHA-256', data);
  return hex(hash);
}

function showToast(baseId, message) {
  $(`#${baseId}Body`).text(message);
  const toast = new bootstrap.Toast($(`#${baseId}`));

  toast.show();
}

// Specify your application ID and AppKey.
const appId = '';
const appKey = '';
AliRtcEngine.setLogLevel(0);
let aliRtcEngine;
const remoteVideoElMap = {};
const remoteVideoContainer = document.querySelector('#remoteVideoContainer');

function removeRemoteVideo(userId, type = 'camera') {
  const vid = `${type}_${userId}`;
  const el = remoteVideoElMap[vid];
  if (el) {
    aliRtcEngine.setRemoteViewConfig(null, userId, type === 'camera' ?  1: 2);
    el.pause();
    remoteVideoContainer.removeChild(el);
    delete remoteVideoElMap[vid];
  }
}

function listenEvents() {
  if (!aliRtcEngine) {
    return;
  }
  // Triggered when a remote user joins the channel.
  aliRtcEngine.on('remoteUserOnLineNotify', (userId, elapsed) => {
    console.log(`The user ${userId} has joined the channel. Time consumed: ${elapsed} seconds.`);
    // Configure your handling logic, such as displaying the module of the remote user.
    showToast('onlineToast', `The user ${userId} is online.`);
  });

  // Triggered when a remote user leaves the channel.
  aliRtcEngine.on('remoteUserOffLineNotify', (userId, reason) => {
    // reason indicates the reason why the remote user gets offline. For more information, see the API reference.
    console.log(`The user ${userId} has left the channel. Reason: ${reason}.`);
    // Configure your handling logic, such as destroying the module of the remote user.
    showToast('offlineToast', `The user ${userId} is offline.`);
    removeRemoteVideo(userId, 'camera');
    removeRemoteVideo(userId, 'screen');
  });

  aliRtcEngine.on('bye', code => {
    // code indicates the reason for leaving the channel. For more information, see the API reference.
    console.log(`bye, code=${code}`);
    // Configure your handling logic, such as exiting the calling page.
    showToast('loginToast', `You have left the channel. Reason: ${code}.`);
  });

  aliRtcEngine.on('videoSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => {
    // oldState and newState are parameters of AliRtcSubscribeState. The valid values are 0 (initializing), 1 (unsubscribed), 2 (subscribing), and 3 (subscribed).
    // interval specifies the time elapsed for the change between two states, in milliseconds.
    console.log(`The state of subscription to the remote user ${userId} in the channel ${channelId} is changed from ${oldState} to ${newState}.`);
    const vid = `camera_${userId}`;
    // The following sample code provides a handling example.
    if (newState === 3) {
      const video = document.createElement('video');
      video.autoplay = true;
      video.className = 'video';
      remoteVideoElMap[vid] = video;
      remoteVideoContainer.appendChild(video);
      // For the first parameter, pass in HTMLVideoElement.
      // For the second parameter, specify the ID of the remote user.
      // Set the value of the third parameter to 1 or 2. 1: the camera stream. 2: the screen sharing stream.
      aliRtcEngine.setRemoteViewConfig(video, userId, 1);
    } else if (newState === 1) {
      removeRemoteVideo(userId, 'camera');
    }
  });

  aliRtcEngine.on('screenShareSubscribeStateChanged', (userId, oldState, newState, interval, channelId) => {
    // oldState and newState are of type AliRtcSubscribeState. 0: initializing, 1: unsubscribed, 2: subscribing, 3: subscribed
    // interval specifies the time elapsed for the change between two states, in milliseconds.
    console.log(`The state of subscription to the screen-sharing stream of the remote user ${userId} in the channel ${channelId} is changed from ${oldState} to ${newState}`);
    const vid = `screen_${userId}`;
    // The following sample code provides a handling example.    
    if (newState === 3) {
      const video = document.createElement('video');
      video.autoplay = true;
      video.className = 'video';
      remoteVideoElMap[vid] = video;
      remoteVideoContainer.appendChild(video);
      // For the first parameter, pass in HTMLVideoElement.
      // For the second parameter, specify the ID of the remote user.
      // Set the value of the third parameter to 1 or 2. 1: the camera stream. 2: the screen sharing stream.
      aliRtcEngine.setRemoteViewConfig(video, userId, 2);
    } else if (newState === 1) {
      removeRemoteVideo(userId, 'screen');
    }
  });
}

$('#loginForm').submit(async e => {
  // Prevent the form from being submitted by default.
  e.preventDefault();
  const channelId = $('#channelId').val();
  const userId = $('#userId').val();
  const timestamp = Math.floor(Date.now() / 1000) + 3600 * 3;

  if (!channelId || !userId) {
    showToast('loginToast', 'The data is incomplete.');
    return;
  }

  aliRtcEngine = AliRtcEngine.getInstance();
  listenEvents();

  try {
    const token = await generateToken(appId, appKey, channelId, userId, timestamp);
    // Set the mode of the channel. Valid values: communication and interactive_live.
    aliRtcEngine.setChannelProfile('communication');
    // Specify the user role. This setting takes effect only in the interactive mode.
    // Valid values: interactive (can publish and subscribe) and live (subscribe only).
    // aliRtcEngine.setClientRole('interactive');
    // Join the channel. The token and nonce parameters are returned by the server.
    await aliRtcEngine.joinChannel(
      {
        channelId,
        userId,
        appId,
        token,
        timestamp,
      },
      userId
    );
    showToast('loginToast', 'You succeed in joining the channel.');
    $('#joinBtn').prop('disabled', true);
    $('#leaveBtn').prop('disabled', false);

    // Start preview.
    aliRtcEngine.setLocalViewConfig('localPreviewer', 1);
  } catch (error) {
    console.log('Failed to join the channel.', error);
    showToast('loginToast', 'You fail to join the channel.');
  }
});

$('#leaveBtn').click(async () => {
  Object.keys(remoteVideoElMap).forEach(vid => {
    const arr = vid.split('_');
    removeRemoteVideo(arr[1], arr[0]);
  });
  // Stop the local preview.
  await aliRtcEngine.stopPreview();
  // Leave the channel.
  await aliRtcEngine.leaveChannel();
  // Destroy the instance.
  aliRtcEngine.destroy();
  aliRtcEngine = undefined;
  $('#joinBtn').prop('disabled', false);
  $('#leaveBtn').prop('disabled', true);
  showToast('loginToast', 'You have left the channel.');
});

Step 4: Run the demo

  1. In the terminal, navigate to the demo folder and run http-server -p 8080 to start the HTTP service. 

  2. Open localhost:8080/quick.html in the browser, enter a channel ID and user ID, then click Join Channel

  3. Create another tab in the browser. Open localhost:8080/quick.html, enter the same channel ID and another user ID, and click Join Channel.

  4. Both tabs show the video feed from the other user. Toast notifications confirm when each user joins.