Build a Livestreaming Application with Twilio Live and Express

October 20, 2021
Written by
Mia Adjei
Twilion
Reviewed by

Build a Livestreaming Application with Twilio Live and Express

This article is for reference only. We're not onboarding new customers to Programmable Video. Existing customers can continue to use the product until December 5, 2024.


We recommend migrating your application to the API provided by our preferred video partner, Zoom. We've prepared this migration guide to assist you in minimizing any service disruption.

Twilio Live is finally here! If you have ever wanted to build your own livestreaming application, now is your chance.

In this tutorial, you will learn how to build a livestreaming application and share your live video feed with your friends, followers, and maybe even the world. For this project, you'll be using Node.js and Express to build the server side of the application, and vanilla JavaScript on the client side.

Let's get started!

Prerequisites

  • A free Twilio account. (If you register here, you'll receive $10 in Twilio credit when you upgrade to a paid account!)
  • Node.js v14+ and npm installed on your machine.
  • ngrok

What you will build

In this tutorial, you will be creating an application that allows a person to livestream video and audio from their device to people who have the link to the stream. The Express server you'll build will handle both the streamer and audience experiences. The client side will have one experience for the streamer and one experience for the audience members.

For the streamer experience, you will:

  • Create a Twilio Video Room, where the streamer will share their video and audio.
    • There are three different types of Video Rooms, but for this project, you'll be using a WebRTC Go room, which is limited to two participants. Since this application will just be for one user to stream their own video, a WebRTC Go room is the perfect size. When you finish this tutorial, if you decide you want to include more streaming participants in one room, you can upgrade to a Group room very easily!
  • Create the livestream itself — the captured audio and video, formatted into a livestream by a Video Composer running on a MediaProcessor.
  • Create a PlayerStreamer, which sends the livestreamed content to a web or mobile application running a version of the Player SDK.
  • Build a UI where the streamer can start and end the livestream.

For the audience experience, you will:

  • Build a UI where the audience can watch a livestream.
  • Use the Player SDK to receive and display the livestreamed media in the browser.

Create the project directory and install dependencies

From your terminal or command prompt, navigate to where you would like to set up your project. Create a new directory called express-live-stream and change into that directory by running the following commands in your terminal:

mkdir express-live-stream
cd express-live-stream

Next, set up a new Node.js project with a default package.json file by running the following command:

npm init -y

Once you have your package.json file, you're ready to install the needed dependencies:

Run the following command in your terminal to install the packages listed above:

npm install express twilio dotenv nodemon @twilio/live-player-sdk

Open the package.json file in your code editor. You will see that the packages listed above have been installed as dependencies.

Now that you have a package.json file set up, it's time to add your environment variables to the project.

Save your Twilio credentials safely as environment variables

Create a new file named .env at the root of your project and open it in your code editor. The .env file is where you will keep your Twilio account credentials. Add the following variables to your new file:

TWILIO_ACCOUNT_SID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TWILIO_API_KEY_SID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TWILIO_API_KEY_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

You’ll need to replace the placeholder text above with your actual Twilio credentials, which can be found in the Twilio Console. Log in to the Twilio Console and find your Account SID:

Twilio console, showing location of Account SID

Copy and paste the value for Account SID to replace the placeholder text for TWILIO_ACCOUNT_SID.

Then, navigate to the API Keys section of the console and generate a new API Key. Copy the API Key's values for SID and Secret to replace the placeholder text for TWILIO_API_KEY_SID and TWILIO_API_KEY_SECRET.

It’s important to keep these private credentials secure and out of version control. If you’re using GitHub, create a .gitignore file at the root of your project. In this file, you can list the files and directories that you want Git to not track or commit. Open .gitignore in your code editor and add the .env file and the node_modules directory:

.env
node_modules

Now that you have your Twilio credentials set up, it's time to create your Express server.

Create a new Express server

Create a new file called server.js at the root of your project. This is the place where you will write your server-side code.

Open server.js in your code editor and add the following lines of code to import the necessary packages and create your Express application, which will run on port 5000:

import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import express from 'express';
import crypto from 'crypto';
import twilio from 'twilio';

dotenv.config();

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
const port = 5000;

// Start the Express server
app.listen(port, async () => {
  console.log(`Express server running on port ${port}`);
});

Now, open package.json in your code editor. Here, you will see that the main file is set to index.js. Remove this line and the scripts section below it. Then add the following lines in their place:

  "main": "server.js",
  "type": "module",
  "scripts": {
    "start": "nodemon server.js"
  },

The code above changes the main file to server.js to match the server file you just created. Then, just below that, "type": "module" allows you to use ES6 modules in your project. The start script on the lines after that is the command to use nodemon to run your Node.js server. Your updated package.json file should look like the one below:


{
  "name": "express-live-stream",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "start": "nodemon server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@twilio/live-player-sdk": "1.0.0",
    "dotenv": "10.0.0",
    "express": "4.17.1",
    "nodemon": "2.0.13",
    "twilio": "3.70.0"
  }
}

To run the start script, return to your terminal window and run the following command:

npm start

Once you have done this, you should see the following log statement in your terminal window, letting you know that the Express server is running:

Express server running on port 5000

You can leave this server running while you work through the tutorial. Nodemon will watch for changes in the server.js file and reload the server to keep it up to date with your latest additions.

Now that your server is running, it's time to lay out the client side of your application. 

Build the application layout and serve static files

Create a new directory called public at the root of your project. This directory will hold your static files:

mkdir public

Inside the public directory, create 5 new files:

  • index.html, a simple entry point for your application 
  • streamer.html, containing the UI for the streamer
  • streamer.js, containing the logic for starting and ending a livestream
  • audience.html, containing the UI for the audience member
  • audience.js, containing the logic for watching a livestream

Now that you have the public files, you need to set up your server to serve these files when a user navigates to http://localhost:5000/ in their browser.

Return to server.js in your code editor. Add the following code to the file, just below your variable for port:

app.use(express.json());

// Serve static files from the public directory
app.use(express.static('public'));

app.get('/', (req, res) => {
  res.sendFile('public/index.html', { root: __dirname });
});

app.get('/stream', (req, res) => {
  res.sendFile('public/streamer.html', { root: __dirname });
});

app.get('/watch', (req, res) => {
  res.sendFile('public/audience.html', { root: __dirname });
});

This code will allow you to display the HTML files in your browser when users navigate to the endpoints you have set up above.

Let's work on the HTML files first to get the layout of the application set up. Open public/index.html in your code editor and add the following code to the file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8'>
    <meta http-equiv='X-UA-Compatible'>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <title>Livestream Demo</title>
    <link href='https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css' rel='stylesheet'>
  </head>
  <body>
    <div class='container mx-auto mt-20 text-center'>
      <div class='mx-auto'>
          <a href='/stream' class='hover:no-underline hover:text-blue-500 text-xl'>start a livestream</a>
      </div>
      <div class='mx-auto mt-10'>
        <a href='/watch' class='hover:no-underline hover:text-blue-500 text-xl'>watch a livestream</a>
      </div>
    </div>
  </body>
</html>

The HTML code above creates a simple layout with two links — one for the streamer and one for the audience members. This layout also includes a CDN link to TailwindCSS, which will allow you to quickly add CSS styles.

If you navigate to http://localhost:5000/ in your browser, you will see the links as shown below:

UI showing two links: "start a live stream" and "watch a live stream"

Next, open public/streamer.html in your editor and add the following code to the file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8'>
    <meta http-equiv='X-UA-Compatible'>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <title>Livestream Demo | Streamer</title>
    <link href='https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css' rel='stylesheet'>
    <script defer src='https://sdk.twilio.com/js/video/releases/2.18.0/twilio-video.min.js'></script>
    <script defer src='streamer.js' type='text/javascript'></script>
  </head>
  <body>
    <div id='container' class='container mx-auto mt-10 justify-center items-center text-center'>
      <div id='stream' class='flex items-center justify-center w-full'>
        <!-- video will be added here -->
      </div>
      <div id='controls' class='mt-10'>

        <input class='bg-gray-200 appearance-none border-2 border-gray-200 rounded  py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-purple-500'
                id='identity'
                type='text'
                placeholder='Your name'
                required>

        <input class='bg-gray-200 appearance-none border-2 border-gray-200 rounded  py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-purple-500'
                id='streamName'
                type='text'
                placeholder='Livestream name'
                required>

        <button class='bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-6 mr-2 rounded' id='streamStartEnd'>
          start stream
        </button>

      </div>
    </div>
  </body>
</html>

In this HTML file for the streamer, you will see that there is a div reserved for the video stream, as well as a controls section with inputs for the streamer's name and the livestream's name, as well as a button that lets the user start the stream. Navigating to http://localhost:5000/stream in your browser window will show you what the layout looks like so far:

Two inputs, one for your name and one for the live stream name, and a "start stream" button

Now open public/audience.html and add the following code to the file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8'>
    <meta http-equiv='X-UA-Compatible'>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <title>Livestream Demo | Audience</title>
    <link href='https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css' rel='stylesheet'>
    <script defer src='../livePlayer/twilio-live-player.min.js'></script>
    <script defer src='audience.js' type='text/javascript'></script>
  </head>
  <body>
    <div class='container mx-auto mt-10 justify-center items-center text-center'>
      <div id='player' class='mx-auto bg-gray-200 h-96 max-w-2xl'>
        <!-- livestream will appear here -->
      </div>

      <button class='bg-green-500 hover:bg-green-700 text-white font-bold mt-10 py-2 px-6 mr-2 rounded' id='streamStartEnd'>
        watch stream
      </button>

    </div>
  </body>
</html>

In this HTML file for the audience members, you will see that there is a div for the livestream to appear, as well as a button that allows viewers to start watching the stream. If you navigate to http://localhost:5000/watch in your browser, you will see the following layout:

Gray outline where stream will appear, and a "watch stream" button

Now that the layout is set up, the next step is to build out the streamer experience.

Build the streamer experience

Allow the streamer to preview their video feed

Open public/streamer.js. This is where you will add the logic that allows the person livestreaming to preview their own video feed and control the livestream.

To start, add the following code to the file:

const stream = document.getElementById('stream');
const identityInput = document.getElementById('identity');
const streamNameInput = document.getElementById('streamName');
const startEndButton = document.getElementById('streamStartEnd');
const video = document.getElementsByTagName('video')[0];

let streaming = false;
let room;
let streamDetails;

let liveNotification = document.createElement('div');
liveNotification.innerHTML = 'LIVE';
liveNotification.id = 'liveNotification';
liveNotification.classList.add('absolute', 'top-10', 'left-48', 'p-2', 'bg-red-500', 'text-white');

const addLocalVideo = async () => {
  const videoTrack = await Twilio.Video.createLocalVideoTrack();
  const trackElement = videoTrack.attach();
  stream.appendChild(trackElement);
};

addLocalVideo();

The first section of this code sets up the variables you will need for this part of the project. There is a list of HTML elements that you will refer to later in the code, as well as variables for whether a user is streaming, for the room being streamed, and for the details of that stream. After that, you create a liveNotification, which is a div that will appear for the user to let them know their video is being shared live.

Below that, the function addLocalVideo() creates a video track and attaches it to the DOM. This allows the streamer to preview their video feed before they start the stream. If you navigate to http://localhost:5000/stream in your browser, you will be able to preview your video feed.

If you don't see your video preview, you may need to refresh the page.

Add logic to control the livestream

It's time to add the functions that will allow the streamer to control the livestream. In this section, you will be adding three functions — one to start the livestream, one to end the livestream, and one to toggle which of these actions is selected.

Add the following startStream() function to public/streamer.js, just below the addLocalVideo() function:

const startStream = async (streamName, identity) => {
  // Create the livestream
  const startStreamResponse = await fetch('/start', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      'streamName': streamName
    })
  });

  streamDetails = await startStreamResponse.json();
  const roomId = streamDetails.roomId;

  // Get an Access Token
  const tokenResponse = await fetch('/streamerToken', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      'identity': identity,
      'room': roomId
    })
  });

  const tokenData = await tokenResponse.json();

  // Connect to the Video Room
  room = await Twilio.Video.connect(tokenData.token);
  streaming = true;

  stream.insertBefore(liveNotification, video);

  startEndButton.disabled = false;
  startEndButton.classList.replace('bg-green-500', 'bg-red-500');
  startEndButton.classList.replace('hover:bg-green-500', 'hover:bg-red-700');
}

This function takes the stream name that the user enters and calls a /start endpoint on the server side to create a new Video Room and a livestream. Once the stream is created, the function then calls the /streamerToken endpoint to retrieve an Access Token, then uses that token to join the Video Room and begin streaming. Once the stream has started, a red LIVE message is added to the top left corner of the video feed, and the start stream button changes to say end stream instead. If the streamer clicks this button, they can end the livestream.

Next, add the following endStream() function just below startStream():

const endStream = async () => {
  // If streaming, end the stream
  if (streaming) {
    try {
      const response = await fetch('/end', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          streamDetails: streamDetails
        })
      });

      const data = await response.json();
      room.disconnect();
      streaming = false;
      liveNotification.remove();

      startEndButton.innerHTML = 'start stream';
      startEndButton.classList.replace('bg-red-500', 'bg-green-500');
      startEndButton.classList.replace('hover:bg-red-500', 'hover:bg-green-700');
      identityInput.disabled = false;
      streamNameInput.disabled = false;

    } catch (error) {
      console.log(error)
    }
  }
}

If there is an ongoing livestream, this function will call the server-side /end endpoint to stop the stream and close the video room. The LIVE message disappears, and the button returns to saying start stream again.

Now, add the following startOrEndStream() function and event listeners to the file, just below endStream():

const startOrEndStream = async (event) => {
  event.preventDefault();
  if (!streaming) {
    const streamName = streamNameInput.value;
    const identity = identityInput.value;

    startEndButton.innerHTML = 'end stream';
    startEndButton.disabled = true;
    identityInput.disabled = true;
    streamNameInput.disabled = true;

    try {
      await startStream(streamName, identity);

    } catch (error) {
      console.log(error);
      alert('Unable to start livestream.');
      startEndButton.innerHTML = 'start stream';
      startEndButton.disabled = false;
      identityInput.disabled = false;
      streamNameInput.disabled = false;
    }

  }
  else {
    endStream();
  }
};

startEndButton.addEventListener('click', startOrEndStream);

window.addEventListener('beforeunload', async (event) => {
  event.preventDefault();
  await endStream();
  e.returnValue = '';
});

The startOrEndStream() function will be called when the startEndButton is clicked. If there is no livestream ongoing, this function will call startStream(). Otherwise, if a stream is in progress, the function will call endStream() to stop it. There is also an event listener for when a user closes the browser tab or window —  this way, the video room and livestream can be cleaned up even if the user does not click the stop stream button before navigating away.

Now that you have the client-side logic to handle controlling the livestream, it's time to add the server-side endpoints being called in the JavaScript code above.

Create endpoints for controlling the livestream and getting access tokens

In server.js, you will need to add the /start, /end, and /streamerToken endpoints referred to in the previous step.

First, just below const port = 5000;, add variables for your Twilio credentials and the access tokens you'll need for this project:

const AccessToken = twilio.jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;
const PlaybackGrant = AccessToken.PlaybackGrant;

const accountSid = process.env.TWILIO_ACCOUNT_SID;
const apiKey = process.env.TWILIO_API_KEY_SID;
const apiKeySecret = process.env.TWILIO_API_KEY_SECRET;

const twilioClient = twilio(apiKey, apiKeySecret, { accountSid: accountSid });

Then, add the following /start endpoint to the file, just below the /watch endpoint:

/**
 * Start a new livestream with a Video Room, PlayerStreamer, and MediaProcessor
 */
app.post('/start', async (req, res) => {
  const streamName  = req.body.streamName;

  try {
    // Create the WebRTC Go video room, PlayerStreamer, and MediaProcessors
    const room = await twilioClient.video.rooms.create({
      uniqueName: streamName,
      type: 'go'
    });

    const playerStreamer = await twilioClient.media.playerStreamer.create();

    const mediaProcessor = await twilioClient.media.mediaProcessor.create({
      extension: 'video-composer-v1',
      extensionContext: JSON.stringify({
        identity: 'video-composer-v1',
        room: {
          name: room.sid
        },
        outputs: [
          playerStreamer.sid
        ],
      })
    })

    return res.status(200).send({
      roomId: room.sid,
      streamName: streamName,
      playerStreamerId: playerStreamer.sid,
      mediaProcessorId: mediaProcessor.sid
    });

  } catch(error) {
    return res.status(400).send({
      message: `Unable to create livestream`,
      error
    });
  }
})

This endpoint uses the Twilio Node Helper Library to create a WebRTC Go Video Room using the stream name chosen by the user. Then it creates the PlayerStreamer and MediaProcessor. The PlayerStreamer is what will allow the livestreamed content to be sent to the audience. The MediaProcessor uses the Video Composer extension to compose the video feeds into a grid layout and send the content to the PlayerStreamer. (In this project, since there is only one streamer, the audience will only see one video feed.) Once the MediaProcessor is created, the Video Composer extension will join the video room as a second participant and begin to stream out the video and audio tracks.

By default, Media Processor resources are created with a maximum duration time of 5 minutes. The maxDuration option can be added to the create() call above to specify a different maximum duration in seconds.

Now that you can start the livestream, add the following /end endpoint under the /start endpoint to stop it:

/**
 * End a livestream
 */
app.post('/end', async (req, res) => {
  const streamDetails = req.body.streamDetails;

  // End the player streamer, media processor, and video room
  const streamName  = streamDetails.streamName;
  const roomId  = streamDetails.roomId;
  const playerStreamerId = streamDetails.playerStreamerId;
  const mediaProcessorId = streamDetails.mediaProcessorId;

  try {
    await twilioClient.media.mediaProcessor(mediaProcessorId).update({status: 'ended'});
    await twilioClient.media.playerStreamer(playerStreamerId).update({status: 'ended'});
    await twilioClient.video.rooms(roomId).update({status: 'completed'});

    return res.status(200).send({
      message: `Successfully ended stream ${streamName}`
    });

  } catch (error) {
    return res.status(400).send({
      message: `Unable to end stream`,
      error
    });
  }
});

This endpoint takes the streamDetails from the request and sets the PlayerStreamer and MediaProcessor's statuses to ended. After this is complete, the endpoint also updates the video room's status to completed.

To avoid unnecessary charges to your Twilio account, it's important to always end the MediaProcessor and end the PlayerStreamer resources when the livestream is finished.

These resources will continue to run until you explicitly end them via the REST API. See the Billing and Resource Management documentation page for more information.

Next, add the following /streamerToken endpoint to the file, just below /end:

/**
 * Get an Access Token for a streamer
 */
app.post('/streamerToken', async (req, res) => {
  if (!req.body.identity || !req.body.room) {
    return res.status(400).send({ message: `Missing identity or stream name` });
  }

  // Get the user's identity and the room name from the request
  const identity  = req.body.identity;
  const roomName  = req.body.room;

  try {
    // Create a video grant for this specific room
    const videoGrant = new VideoGrant({
      room: roomName,
    });

    // Create an access token
    const token = new AccessToken(accountSid, apiKey, apiKeySecret);

    // Add the video grant and the user's identity to the token
    token.addGrant(videoGrant);
    token.identity = identity;

    // Serialize the token to a JWT and return it to the client side
    return res.send({
      token: token.toJwt()
    });

  } catch (error) {
    return res.status(400).send({error});
  }
});

This endpoint creates a new access token for the streamer and adds a VideoGrant, which will allow this person to join the video room and begin streaming. Now that you have all the endpoints you need for the streamer, it's time to build out the experience for the audience members.

Build the audience experience

Add the Twilio Player SDK

To play a livestream, your application will need the following three static files from the Twilio JavaScript Player SDK:

  • twilio-live-player.min.js
  • twilio-live-player-wasmworker-1-5-0.min.js
  • twilio-live-player-wasmworker-1-5-0.min.wasm

You can find these files in node_modules/@twilio/live-player-sdk/dist/build from when you ran npm install earlier in the tutorial.

Inside the public directory, create a new directory called livePlayer. This is where you will store the Player SDK files:

mkdir public/livePlayer

From your terminal window, run the following command at the root of the project to copy the three Player SDK files into the livePlayer directory:

cp node_modules/@twilio/live-player-sdk/dist/build/twilio-live-player-wasmworker* public/livePlayer/
cp node_modules/@twilio/live-player-sdk/dist/build/twilio-live-player.min.js public/livePlayer/

You can learn more about the JavaScript Player SDK by visiting its documentation page here.

Now you are ready to add the logic to show the player in the UI.

Allow the audience member to start watching the stream

For the audience member's experience, you will be adding logic to public/audience.js. Open this file in your code editor and start by adding the following variables to the top of the file:

const streamPlayer = document.getElementById('player');
const startEndButton = document.getElementById('streamStartEnd');

let player;
let watchingStream = false;

Now you are able to refer to the player element, the button that will control it, and whether the audience member is currently watching a livestream.

Next, add the following watchStream() function to the file, just below the variables:

const watchStream = async () => {
  try {
    const response = await fetch('/audienceToken', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      }
    });

    const data = await response.json();

    if (data.message) {
      alert(data.message);
      return;
    }

    player = await Twilio.Live.Player.connect(data.token, {playerWasmAssetsPath: '../livePlayer'});
    player.play();
    streamPlayer.appendChild(player.videoElement);

    watchingStream = true;
    startEndButton.innerHTML = 'leave stream';
    startEndButton.classList.replace('bg-green-500', 'bg-red-500');
    startEndButton.classList.replace('hover:bg-green-500', 'hover:bg-red-700');

  } catch (error) {
    console.log(error);
    alert('Unable to connect to livestream');
  }
}

Similar to the person streaming their content, an audience member who wants to view a livestream also needs an access token. The watchStream() function calls the /audienceToken endpoint you will create in a later step to get the token for the user. After that, the function uses the token to connect to the Player SDK and begin playing the livestreamed content in the browser window. Once the user is watching the livestream, the watch stream button changes to say leave stream.

Now, add the following leaveStream() function, just below where you added watchStream():

const leaveStream = () => {
  player.disconnect();
  watchingStream = false;
  startEndButton.innerHTML = 'watch stream';
  startEndButton.classList.replace('bg-red-500', 'bg-green-500');
  startEndButton.classList.replace('hover:bg-red-500', 'hover:bg-green-700');
}

This function disconnects the player and changes the button's text and color back to its original state.

Finally, similar to on the streamer side of the application, add the following watchOrLeaveStream() function and event listener:

const watchOrLeaveStream = async (event) => {
  event.preventDefault();
  if (!watchingStream) {
    await watchStream();
  }
  else {
    leaveStream();
  }
};

startEndButton.addEventListener('click', watchOrLeaveStream);

On a button click, if the audience member is already watching the livestream, this function will call leaveStream(). If they are not watching, then watchStream() will be called.

You may be wondering why we don't just let the livestream start playing when the audience member navigates to the audience experience page at the /watch endpoint. Well, there is a browser policy that makes autoplay videos start muted — to start playing the audio, an explicit user interaction is required. In this application, clicking the watch stream button is the explicit user interaction that will start playing the livestream's audio along with its video.

Now that you have the logic to let the audience member watch or leave the stream, you just need to fill in the /audienceToken endpoint on the server side to allow these users to get the access tokens they need.

Create endpoints for controlling the livestream and getting access tokens

Return to server.js and add the following /audienceToken endpoint just below the  /streamerToken endpoint:

/**
 * Get an Access Token for an audience member
 */
app.post('/audienceToken', async (req, res) => {
  // Generate a random string for the identity
  const identity = crypto.randomBytes(20).toString('hex');

  try {
    // Get the first player streamer
    const playerStreamerList = await twilioClient.media.playerStreamer.list({status: 'started'});
    const playerStreamer = playerStreamerList.length ? playerStreamerList[0] : null;

    // If no one is streaming, return a message
    if (!playerStreamer){
      return res.status(200).send({
        message: `No one is streaming right now`,
      })
    }

    // Otherwise create an access token with a PlaybackGrant for the livestream
    const token = new AccessToken(accountSid, apiKey, apiKeySecret);

    // Create a playback grant and attach it to the access token
    const playbackGrant = await twilioClient.media.playerStreamer(playerStreamer.sid).playbackGrant().create({ttl: 60});

    const wrappedPlaybackGrant = new PlaybackGrant({
      grant: playbackGrant.grant
    });

    token.addGrant(wrappedPlaybackGrant);
    token.identity = identity;

    // Serialize the token to a JWT and return it to the client side
    return res.send({
      token: token.toJwt()
    });

  } catch (error) {
    res.status(400).send({
      message: `Unable to view livestream`,
      error
    });
  }
});

This endpoint generates a random string for the audience member's identity. Then, it queries for a list of PlayerStreamers with a status of started. In this application, you can assume that there is only one PlayerStreamer, so the endpoint takes the first one from the list. If the list comes back empty, the endpoint returns a message to the client side to let them know that no one is streaming right now.

Once you have the PlayerStreamer, you can create a PlaybackGrant which will allow the audience member to start viewing the livestream. This grant is attached to the access token, and the token is returned to the client side.

You have all the code you need to begin streaming! There is just one more step before you can test it all out.

Run your application on ngrok

In this project, you'll be using ngrok to connect the Express application running locally on your machine to a temporary public URL. To start a new ngrok tunnel, open up a new terminal window and run the following command:

ngrok http 5000

Once ngrok is running, you will see text similar to the below in your terminal window:


ngrok by @inconshreveable                                                                                                             (Ctrl+C to quit)

Version                       2.3.40
Region                        <YOUR_REGION>
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://XXXXXXXXXXX.ngrok.io -> http://localhost:5000
Forwarding                    https://XXXXXXXXXXX.ngrok.io -> http://localhost:5000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

Copy the https:// URL next to Forwarding. Any requests that are made to this ngrok URL will be forwarded to your local server. You now have a temporary link to your application that you can share with a friend or colleague!

Try livestreaming!

Now that everything is set up, it's time to try out a livestream. You will be trying out both the streamer and audience member experiences.

In order to avoid audio feedback between the Video Room and the livestream, you may want to wear headphones for this step.

Open the ngrok forwarding URL in your browser window. Click the link to start a livestream. Enter your name and your livestream's name in the input boxes, then click the start stream button.

You may see an alert dialog asking if this browser tab can use your camera and microphone. Once you allow this permission, you will be able to share your video and audio feeds.

Streamer view, with a sleeping dog in the video feed

For my livestream, I've decided to share a cute stream of my dog, Nova! 🐶

Once you've started the stream, the LIVE marker will appear and the button will change to say end stream. Congratulations, you're livestreaming!

Stream is LIVE, showing a dog who has just woken up

Now it's time to check out the audience experience. In a second browser tab, navigate to the ngrok forwarding URL again. This time, click watch a livestream. You will see a layout like the following appear:

Gray box, with "watch stream" button just below it

Click the watch stream button to connect to the stream and begin watching. The live video feed will replace the gray box in the UI:

Audience view with livestream, showing sleeping pup. The "leave stream" button is just below the video.

If you want, you can send the /watch link to a friend so they can see your livestream too!

To avoid unnecessary charges to your Twilio account, don't forget to stop all of the livestream resources when you are finished trying out the livestream app.

You can do this by calling the /end endpoint you wrote earlier, connected to the end stream button in the streamer's side of the UI.

Learn more by visiting the Twilio Live documentation page for billing and resource management.

What's next for building a livestreaming application?

Now that you have had a taste of livestreaming by learning to stream your video feed, perhaps you'd like to try another Live tutorial and learn how to livestream your screen. Or, if you're curious about livestreaming in general, check out this blog post about livestreaming's use cases and benefits. Maybe you already have your own project idea in mind, in which case you can check out the Twilio Live documentation to learn even more.

I can't wait to see what you build!

Mia Adjei is a Software Developer on the Developer Voices team. They love to help developers build out new project ideas and discover aha moments. Mia can be reached at madjei [at] twilio.com.