Today, We will learn the how to create the live streaming with Twilio and Node.js
Live Streaming with Twilio:
Step 1: Create node project:
npm init
Step 2: Install the following packages:
npm i dotenv url path express crypto twilio
Step 3: Create .env file and add the following in it:
TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXX TWILIO_API_KEY_SID=SKXXXXXXXXXXXXXXXXXXXXXXXXXXXX TWILIO_API_KEY_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Step 4: Create server.js file and add the following in it:
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; 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 }); const WHITE_LIST = ["http://localhost:8080", "http://localhost:8081"]; 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 }); }); /** * 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: "group", }); 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) { console.log(error); return res.status(400).send({ message: `Unable to create livestream`, error, }); } }); /** * 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, }); } }); /** * 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 }); } }); /** * 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 var playerStreamerId = ""; if (!req.query.id) { const playerStreamerList = await twilioClient.media.playerStreamer.list({ status: "started", }); const playerStreamer = playerStreamerList.length ? playerStreamerList[0] : null; playerStreamerId = playerStreamer.sid; // If no one is streaming, return a message if (!playerStreamer) { return res.status(200).send({ message: `No one is streaming right now`, }); } } else{ playerStreamerId = req.query.id; } // 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(playerStreamerId) .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, }); } }); // Start the Express server app.listen(port, async () => { console.log(`Express server running on port ${port}`); });
Step 5: Create the public/livePlayer folder and add the following files in it:
- twilio-live-player-wasmworker-1-5-0.min.js
- twilio-live-player-wasmworker-1-5-0.min.wasm
- twilio-live-player.js
- twilio-live-player.min.js
Step 6: Open index.html file and add the following in it:
<!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>
Step 7: Open stremer.html file and add the following in it:
<!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>
Step 8: Open streamer.js file and add the following in it:
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); }; 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'); } 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) } } } 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 = ''; }); addLocalVideo();
Step 9: Open audience.html file and add following in it:
<!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>
Step 10: Open audience.html file and following in it:
const streamPlayer = document.getElementById('player'); const startEndButton = document.getElementById('streamStartEnd'); let player; let watchingStream = false; 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'); } } 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'); } const watchOrLeaveStream = async (event) => { event.preventDefault(); if (!watchingStream) { await watchStream(); } else { leaveStream(); } }; startEndButton.addEventListener('click', watchOrLeaveStream);
Code in action: