How to Build a Video Calling App with Angular, Node.js, and Wowza: A Comprehensive Guide

Creating a video calling app using Angular, Node.js, and Wowza can be a complex task, but with the right tools and resources, it is possible to build a functional and user-friendly app. In this blog post, we will go over the steps required to create a video calling app using these technologies.

Before we begin, it's important to note that the steps outlined in this blog post are just one way to build a video calling app. There may be other approaches that work better for your specific needs, so feel free to adjust the steps as needed to suit your project.

Prerequisites

Before you can start building your video calling app, you will need to have the following tools and technologies installed on your computer:

  • Node.js: Node.js is a JavaScript runtime that allows you to run JavaScript on the server side. It is used to build the backend of web applications.
  • Angular: Angular is a frontend JavaScript framework that is used to build web applications.
  • Wowza: Wowza is a media server that allows you to stream video and audio to multiple users in real-time.

You will also need to have a basic understanding of JavaScript and web development concepts such as HTML, CSS, and HTTP.

Step 1: Set up the backend

The first step in creating your video calling app is to set up the backend using Node.js. Start by creating a new Node.js project using the following command:

npm init

This will create a package.json file that contains information about your project, including the dependencies that you will need to install.

Next, you will need to install the necessary dependencies for your project. For this video calling app, you will need to install the following packages:

  • express: A web framework for Node.js that makes it easy to build web applications.
  • socket.io: A library that allows you to create real-time connections between the backend and the frontend of your app.
  • wowza-streaming-engine-sdk: A library that provides a set of APIs for interacting with Wowza Streaming Engine.

To install these dependencies, run the following command:

npm install express socket.io wowza-streaming-engine-sdk

With the dependencies installed, you can now start building the backend of your app. Begin by creating an index.js file in the root of your project. This file will contain the code for your backend server.

Here is an example of how your index.js file might look:

const express = require('express');
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);

const port = 3000;

app.get('/', (req, res) => {
  res.send('Hello World!');
});

server.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

This code creates an express server and listens for incoming HTTP requests on port 3000. When a request is received, the server responds with a message that says "Hello World!".

Next, you will need to create a route for your video call. This route will be responsible for handling the signaling process, which is the process of exchanging information between the two users who are trying to connect for a video call.

To create the signaling route, add the following code to your index.js file:

io.on('connection', (socket) => {
  socket.on('offer', (id, message) => {
    socket.to(id).emit('offer', socket.id, message);
  });

  socket.on('answer', (id, message) => {
    socket.to(id).emit('answer', socket.id, message);
  });

  socket.on('candidate', (id, message) => {
    socket.to(id).emit('candidate', socket.id, message);
  });
});

This code creates three event listeners for the 'offer', 'answer', and 'candidate' events. These events are used to exchange information between the two users who are trying to connect for a video call.

The offer event is sent by the user who initiates the call, and it contains information about the session that the other user will need to join. The answer event is sent by the user who receives the call, and it contains information about the session that the initiator will need to join. The candidate event is sent by both users and contains information about the network connection between them.

Step 2: Set up the frontend

With the backend set up, you can now move on to setting up the frontend of your app using Angular. Start by creating a new Angular project using the following command:

ng new my-app

This will create a new directory called my-app that contains the necessary files and directories for your Angular project.

Next, you will need to install the necessary dependencies for your project. For this video calling app, you will need to install the following packages:

  • @angular/material: A library of Angular components that implement the material design specification.
  • @angular/flex-layout: A library that provides a set of tools for building responsive layouts in Angular.
  • socket.io-client: A library that allows you to create real-time connections between the backend and the frontend of your app.

To install these dependencies, run the following command:

npm install @angular/material @angular/flex-layout socket.io-client

With the dependencies installed, you can now start building the frontend of your app. Begin by creating a new component for your video call. You can do this using the following command:

ng generate component video-call

This will create a new video-call component in the src/app directory of your project.

Next, you will need to create the HTML template and the TypeScript code for your component. In the HTML template, you can add the necessary elements for displaying the video streams, as well as buttons for initiating and ending a call.

Here is an example of how your HTML template might look:

<div class="video-call">
  <div class="local-video">
    <video #localVideo></video>
  </div>
  <div class="remote-video">
    <video #remoteVideo></video>
  </div>
  <div class="controls">
    <button (click)="startCall()">Start Call</button>
    <button (click)="endCall()">End Call</button>
  </div>
</div>

in the TypeScript code for your component, you can add the necessary logic for handling the video streams, as well as the signaling process.

Here is an example of how your TypeScript code might look:

import { Component, ViewChild, ElementRef } from '@angular/core';
import * as io from 'socket.io-client';

@Component({
  selector: 'app-video-call',
  templateUrl: './video-call.component.html',
  styleUrls: ['./video-call.component.css']
})
export class VideoCallComponent {
  @ViewChild('localVideo') localVideo: ElementRef;
  @ViewChild('remoteVideo') remoteVideo: ElementRef;

  socket: any;
  localStream: any;
  remoteStream: any;

  constructor() {
    this.socket = io.connect('http://localhost:3000');
  }

  startCall() {
    // code for starting a call
  }

  endCall() {
    // code for ending a call
  }

  // other methods for handling the video streams and the signaling process
}

With the HTML template and TypeScript code for your component in place, you can now add it to your app's module and include it in your app's template.

To do this, open the src/app/app.module.ts file and import the VideoCallComponent component. Then, add it to the declarations array of the AppModule class.

Here is an example of how this might look:

import { Component, ViewChild, ElementRef } from '@angular/core';
import * as io from 'socket.io-client';

@Component({
  selector: 'app-video-call',
  templateUrl: './video-call.component.html',
  styleUrls: ['./video-call.component.css']
})
export class VideoCallComponent {
  @ViewChild('localVideo') localVideo: ElementRef;
  @ViewChild('remoteVideo') remoteVideo: ElementRef;

  socket: any;
  localStream: any;
  remoteStream: any;

  constructor() {
    this.socket = io.connect('http://localhost:3000');
  }

  startCall() {
    // code for starting a call
  }

  endCall() {
    // code for ending a call
  }

  // other methods for handling the video streams and the signaling process
}

Finally, you can add the VideoCallComponent component to your app's template by opening the src/app/app.component.html file and adding the following code:

<app-video-call></app-video-call>

Step 3: Connect to Wowza

With the frontend and backend of your app set up, you can now integrate your app with Wowza Streaming Engine. To do this, you will need to use the Wowza Streaming Engine SDK.

First, you will need to create a new application in Wowza Streaming Engine. To do this, log in to the Wowza Streaming Engine Manager and click on the "Applications" tab. Then, click the "Add Application" button and follow the prompts to create a new application.

Next, you will need to create a new stream in your application. To do this, click on the "Streams" tab and then click the "Add Stream" button. Follow the prompts to create a new stream.

With your application and stream set up, you can now use the Wowza Streaming Engine SDK to connect to Wowza Streaming Engine and start streaming video.

To do this, you will need to import the wowza-streaming-engine-sdk library in your `index.js` file and use the provided APIs to create a new connection to Wowza Streaming Engine.

Here is an example of how you might do this:

const wowza = require('wowza-streaming-engine-sdk');

const client = new wowza.Client(wowza.WSE_PROTOCOL.WSE_PROTOCOL_RTSP);

client.addConnection(wowza.WSE_PROTOCOL.WSE_PROTOCOL_RTSP, {
  host: 'localhost',
  app: 'my-app',
  streamName: 'my-stream',
  username: 'wowza',
  password: 'wowza'
});

client.on('connected', () => {
  console.log('Connected to Wowza Streaming Engine');
});

client.on('disconnected', () => {
  console.log('Disconnected from Wowza Streaming Engine');
});

client.connect();

With the connection to Wowza Streaming Engine established, you can now use the provided APIs to start streaming video. For example, you can use the publish method to start publishing a video stream to Wowza Streaming Engine.

Here is an example of how you might do this:

client.publish({
  audioBitrate: 128000,
  videoBitrate: 256000,
  audioCodec: 'AAC',
  videoCodec: 'H264'
}, (error) => {
  if (error) {
    console.error(error);
  } else {
    console.log('Streaming video to Wowza Streaming Engine');
  }
});

With the video streaming to Wowza Streaming Engine, you can now use the play method to start playing the stream on the frontend of your app.

Here is an example of how you might do this in your VideoCallComponent:

import { Component, ViewChild, ElementRef } from '@angular/core';
import * as io from 'socket.io-client';
import * as Wowza from 'wowza-streaming-engine-sdk';

@Component({
  selector: 'app-video-call',
  templateUrl: './video-call.component.html',
  styleUrls: ['./video-call.component.css']
})
export class VideoCallComponent {
  @ViewChild('localVideo') localVideo: ElementRef;
  @ViewChild('remoteVideo') remoteVideo: ElementRef;

  socket: any;
  localStream: any;
  remoteStream: any;
  client: any;

  constructor() {
    this.socket = io.connect('http://localhost:3000');
    this.client = new Wowza.Client(Wowza.WSE_PROTOCOL.WSE_PROTOCOL_RTSP);
  }

  startCall() {
    this.client.addConnection(Wowza.WSE_PROTOCOL.WSE_PROTOCOL_RTSP, {
      host: 'localhost',
      app: 'my-app',
      streamName: 'my-stream',
      username: 'wowza',
      password: 'wowza'
    });

    this.client.play((error) => {
      if (error) {
      	  console.error(error);
      } else {
          console.log('Playing video stream from Wowza Streaming Engine');
          this.remoteStream = this.client.getVideoElement();
          this.remoteVideo.nativeElement.srcObject = this.remoteStream;
      }
   });
 }

 endCall() {
    this.client.disconnect();
    this.remoteStream = null;
    this.remoteVideo.nativeElement.srcObject = null;
 }
}

With the integration with Wowza Streaming Engine complete, your video calling app is now ready to use. Users can initiate and end calls, and the video streams will be streamed through Wowza Streaming Engine to the other user.

One important aspect of building a video calling app is handling errors and exceptions that may occur during the process. There are several potential errors that you may need to handle when building your app, including:

  • MediaStreamError: This error occurs when there is a problem with the media stream, such as when the user denies access to their camera or microphone.
  • NetworkError: This error occurs when there is a problem with the network connection, such as when the user's internet connection is lost or when there is a problem with the server.
  • SignalingError: This error occurs when there is a problem with the signaling process, such as when the signaling messages are not received or when there is an issue with the data being exchanged.

To handle these errors, you can use the onerror event of the RTCPeerConnection object. This event is fired when an error occurs during the process of creating or managing an RTC connection.

Here is an example of how you might handle errors in your VideoCallComponent:

import { Component, ViewChild, ElementRef } from '@angular/core';
import * as io from 'socket.io-client';
import * as Wowza from 'wowza-streaming-engine-sdk';

@Component({
  selector: 'app-video-call',
  templateUrl: './video-call.component.html',
  styleUrls: ['./video-call.component.css']
})
export class VideoCallComponent {
  @ViewChild('localVideo') localVideo: ElementRef;
  @ViewChild('remoteVideo') remoteVideo: ElementRef;

  socket: any;
  localStream: any;
  remoteStream: any;
  client: any;

  constructor() {
    this.socket = io.connect('http://localhost:3000');
    this.client = new Wowza.Client(Wowza.WSE_PROTOCOL.WSE_PROTOCOL_RTSP);
  }

  startCall() {
    this.client.addConnection(Wowza.WSE_PROTOCOL.WSE_PROTOCOL_RTSP, {
      host: 'localhost',
      app: 'my-app',
      streamName: 'my-stream',
      username: 'wowza',
      password: 'wowza'
    });

    this.client.play((error) => {
      if (error) {
        console.error(error);
      } else {
        console.log('Playing video stream from Wowza Streaming Engine');
        this.remoteStream = this.client.getVideoElement();
        this.remoteVideo.nativeElement.srcObject = this.remoteStream;
      }
    });
  }

  endCall() {
    this.client.disconnect();
    this.remoteStream = null;
    this.remoteVideo.nativeElement.srcObject = null;
  }

  handleError(error) {
    console.error(error);
  }
}

In this example, the handleError method is called whenever an error occurs during the process of creating or managing an RTC connection. You can customize this method to handle the different types of errors that may occur, such as displaying an error message to the user or attempting to reconnect to the server.

It's also important to handle errors that may occur on the backend of your app, such as when there is a problem with the Socket.io connection or when there is an issue with the data being exchanged.

To handle these errors, you can use the error event of the Socket.io socket object. This event is fired when an error occurs during the process of creating or managing a Socket.io connection.

Here is an example of how you might handle errors in your index.js file:

io.on('connection', (socket) => {
  socket.on('offer', (id, message) => {
    socket.to(id).emit('offer', socket.id, message);
  });

  socket.on('answer', (id, message) => {
    socket.to(id).emit('answer', socket.id, message);
  });

  socket.on('candidate', (id, message) => {
    socket.to(id).emit('candidate', socket.id, message);
  });

  socket.on('error', (error) => {
    console.error(error);
  });
});

In this example, the error event is fired whenever an error occurs during the process of creating or managing a Socket.io connection. You can customize this event handler to handle the different types of errors that may occur, such as logging the error to the console or attempting to reconnect to the server.

By handling errors and exceptions properly, you can ensure that your video calling app is more robust and able to recover from unexpected issues.

Another important aspect of building a video calling app is ensuring that it is secure and that the data being exchanged between the users is protected. There are several ways you can do this, including:

  • Using Secure WebSockets (WSS) for the Socket.io connection: By using WSS instead of plain WebSockets (WS), you can ensure that the data being exchanged between the client and server is encrypted and secure.
  • Using secure HTTP (HTTPS) for the server: By using HTTPS instead of HTTP, you can ensure that the data being exchanged between the client and server is encrypted and secure.
  • Implementing user authentication: By implementing user authentication, you can ensure that only authorized users are able to use your video calling app. This can be done using a variety of methods, such as password-based authentication or OAuth.
  • Implementing access control: By implementing access control, you can ensure that users are only able to access the resources they are authorized to access. This can be done using a variety of methods, such as role-based access control or attribute-based access control.

By following these best practices, you can ensure that your video calling app is secure and that the data being exchanged between the users is protected.

I hope this blog post has helped you understand how to create a secure video calling app using Angular, Node.js, and Wowza. If you have any questions or need further assistance, feel free to ask.